190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
import * as colors from './00colors.js';
|
|
import * as plugins from './00plugins.js';
|
|
import { demoFunc } from './dees-contextmenu.demo.js';
|
|
import {
|
|
customElement,
|
|
html,
|
|
DeesElement,
|
|
property,
|
|
type TemplateResult,
|
|
cssManager,
|
|
css,
|
|
type CSSResult,
|
|
unsafeCSS,
|
|
} from '@design.estate/dees-element';
|
|
|
|
import * as domtools from '@design.estate/dees-domtools';
|
|
import { DeesWindowLayer } from './dees-windowlayer.js';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'dees-contextmenu': DeesContextmenu;
|
|
}
|
|
}
|
|
|
|
@customElement('dees-contextmenu')
|
|
export class DeesContextmenu extends DeesElement {
|
|
// DEMO
|
|
public static demo = demoFunc
|
|
|
|
// STATIC
|
|
// This will store all the accumulated menu items
|
|
public static contextMenuDeactivated = false;
|
|
public static accumulatedMenuItems: plugins.tsclass.website.IMenuItem[] = [];
|
|
|
|
// Add a global event listener for the right-click context menu
|
|
public static initializeGlobalListener() {
|
|
document.addEventListener('contextmenu', (event: MouseEvent) => {
|
|
if (this.contextMenuDeactivated) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
|
|
// Get the target element of the right-click
|
|
let target: EventTarget | null = event.target;
|
|
|
|
// Clear previously accumulated items
|
|
DeesContextmenu.accumulatedMenuItems = [];
|
|
|
|
// Traverse up the DOM tree to accumulate menu items
|
|
while (target) {
|
|
if ((target as any).getContextMenuItems) {
|
|
DeesContextmenu.accumulatedMenuItems.push(...(target as any).getContextMenuItems());
|
|
}
|
|
target = (target as Node).parentNode;
|
|
}
|
|
|
|
// Open the context menu with the accumulated items
|
|
DeesContextmenu.openContextMenuWithOptions(event, DeesContextmenu.accumulatedMenuItems);
|
|
});
|
|
}
|
|
|
|
// allows opening of a contextmenu with options
|
|
public static async openContextMenuWithOptions(eventArg: MouseEvent, menuItemsArg: plugins.tsclass.website.IMenuItem[]) {
|
|
if (this.contextMenuDeactivated) {
|
|
return;
|
|
}
|
|
eventArg.preventDefault();
|
|
eventArg.stopPropagation();
|
|
const contextMenu = new DeesContextmenu();
|
|
contextMenu.style.position = 'fixed';
|
|
contextMenu.style.zIndex = '2000';
|
|
contextMenu.style.top = `${eventArg.clientY.toString()}px`;
|
|
contextMenu.style.left = `${eventArg.clientX.toString()}px`;
|
|
contextMenu.style.opacity = '0';
|
|
contextMenu.style.transform = 'scale(0.95,0.95)';
|
|
contextMenu.style.transformOrigin = 'top left';
|
|
contextMenu.menuItems = menuItemsArg;
|
|
contextMenu.windowLayer = await DeesWindowLayer.createAndShow();
|
|
contextMenu.windowLayer.addEventListener('click', async () => {
|
|
await contextMenu.destroy();
|
|
})
|
|
document.body.append(contextMenu);
|
|
await domtools.plugins.smartdelay.delayFor(0);
|
|
contextMenu.style.opacity = '1';
|
|
contextMenu.style.transform = 'scale(1,1)';
|
|
}
|
|
|
|
// INSTANCE
|
|
@property({
|
|
type: Array,
|
|
})
|
|
public menuItems: plugins.tsclass.website.IMenuItem[] = [];
|
|
windowLayer: DeesWindowLayer;
|
|
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* STATIC STYLES
|
|
*/
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
transition: all 0.1s;
|
|
}
|
|
|
|
.mainbox {
|
|
color: ${cssManager.bdTheme('#222', '#ccc')};
|
|
font-size: 14px;
|
|
width: 200px;
|
|
border: 1px solid ${cssManager.bdTheme('#fff', '#ffffff10')};
|
|
min-height: 34px;
|
|
border-radius: 3px;
|
|
background: ${cssManager.bdTheme('#fff', '#222')};
|
|
box-shadow: 0px 1px 4px ${cssManager.bdTheme('#00000020', '#000000')};
|
|
user-select: none;
|
|
padding: 4px;
|
|
}
|
|
|
|
.mainbox .menuitem {
|
|
padding: 4px 8px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.mainbox .menuitem dees-icon {
|
|
display: inline-block;
|
|
margin-right: 8px;
|
|
width: 14px;
|
|
transform: translateY(2px);
|
|
}
|
|
|
|
.mainbox .menuitem:hover {
|
|
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
|
|
}
|
|
|
|
.mainbox .menuitem:active {
|
|
background: #ffffff05;
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
return html`
|
|
<div class="mainbox">
|
|
${this.menuItems.map((menuItemArg) => {
|
|
return html`
|
|
<div class="menuitem" @click=${() => this.handleClick(menuItemArg)}>
|
|
<dees-icon .iconFA=${(menuItemArg.iconName as any) || 'minus'}></dees-icon
|
|
>${menuItemArg.name}
|
|
</div>
|
|
`;
|
|
})}
|
|
${this.menuItems.length === 0 ? html`
|
|
<div class="menuitem" @click=${() => {
|
|
DeesContextmenu.contextMenuDeactivated = true;
|
|
this.destroy();
|
|
}}>
|
|
<dees-icon .iconFA=${'xmark'}></dees-icon
|
|
>allow native context
|
|
</div>
|
|
` : html``}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
public async firstUpdated() {
|
|
|
|
}
|
|
|
|
public async handleClick(menuItem: plugins.tsclass.website.IMenuItem) {
|
|
menuItem.action();
|
|
await this.destroy();
|
|
}
|
|
|
|
public async destroy() {
|
|
if (this.windowLayer) {
|
|
this.windowLayer.destroy();
|
|
}
|
|
this.style.opacity = '0';
|
|
this.style.transform = 'scale(0.95,0,95)';
|
|
await domtools.plugins.smartdelay.delayFor(100);
|
|
this.parentElement.removeChild(this);
|
|
}
|
|
}
|
|
|
|
DeesContextmenu.initializeGlobalListener();
|