| 
									
										
										
										
											2024-01-15 19:42:15 +01:00
										 |  |  |  | import * as plugins from './00plugins.js'; | 
					
						
							| 
									
										
										
										
											2024-01-18 02:08:19 +01:00
										 |  |  |  | import { demoFunc } from './dees-contextmenu.demo.js'; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | import { | 
					
						
							|  |  |  |  |   customElement, | 
					
						
							|  |  |  |  |   html, | 
					
						
							|  |  |  |  |   DeesElement, | 
					
						
							|  |  |  |  |   property, | 
					
						
							| 
									
										
										
										
											2023-08-07 20:02:18 +02:00
										 |  |  |  |   type TemplateResult, | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |   cssManager, | 
					
						
							|  |  |  |  |   css, | 
					
						
							| 
									
										
										
										
											2023-08-08 01:10:02 +02:00
										 |  |  |  |   type CSSResult, | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |   unsafeCSS, | 
					
						
							| 
									
										
										
										
											2023-08-07 19:13:29 +02:00
										 |  |  |  | } from '@design.estate/dees-element'; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-07 19:13:29 +02:00
										 |  |  |  | import * as domtools from '@design.estate/dees-domtools'; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  | import { DeesWindowLayer } from './dees-windowlayer.js'; | 
					
						
							| 
									
										
										
										
											2025-06-26 15:46:44 +00:00
										 |  |  |  | import { zIndexLayers } from './00zindex.js'; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  | import './dees-icon.js'; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | declare global { | 
					
						
							|  |  |  |  |   interface HTMLElementTagNameMap { | 
					
						
							|  |  |  |  |     'dees-contextmenu': DeesContextmenu; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | @customElement('dees-contextmenu') | 
					
						
							|  |  |  |  | export class DeesContextmenu extends DeesElement { | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |   // DEMO
 | 
					
						
							| 
									
										
										
										
											2023-09-09 13:34:46 +02:00
										 |  |  |  |   public static demo = demoFunc | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |   // STATIC
 | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |   // This will store all the accumulated menu items
 | 
					
						
							|  |  |  |  |   public static contextMenuDeactivated = false; | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |   public static accumulatedMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[] } | { divider: true })[] = []; | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 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(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Clear previously accumulated items
 | 
					
						
							|  |  |  |  |       DeesContextmenu.accumulatedMenuItems = []; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |       // Use composedPath to properly traverse shadow DOM boundaries
 | 
					
						
							|  |  |  |  |       const path = event.composedPath(); | 
					
						
							|  |  |  |  |        | 
					
						
							|  |  |  |  |       // Traverse the composed path to accumulate menu items
 | 
					
						
							|  |  |  |  |       for (const element of path) { | 
					
						
							|  |  |  |  |         if ((element as any).getContextMenuItems) { | 
					
						
							|  |  |  |  |           const items = (element as any).getContextMenuItems(); | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |           if (items && items.length > 0) { | 
					
						
							|  |  |  |  |             if (DeesContextmenu.accumulatedMenuItems.length > 0) { | 
					
						
							|  |  |  |  |               DeesContextmenu.accumulatedMenuItems.push({ divider: true }); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |             DeesContextmenu.accumulatedMenuItems.push(...items); | 
					
						
							|  |  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Open the context menu with the accumulated items
 | 
					
						
							|  |  |  |  |       DeesContextmenu.openContextMenuWithOptions(event, DeesContextmenu.accumulatedMenuItems); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // allows opening of a contextmenu with options
 | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |   public static async openContextMenuWithOptions(eventArg: MouseEvent, menuItemsArg: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[] } | { divider: true })[]) { | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |     if (this.contextMenuDeactivated) { | 
					
						
							|  |  |  |  |       return; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     eventArg.preventDefault(); | 
					
						
							| 
									
										
										
										
											2023-09-08 11:44:03 +02:00
										 |  |  |  |     eventArg.stopPropagation(); | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |     const contextMenu = new DeesContextmenu(); | 
					
						
							| 
									
										
										
										
											2023-09-13 01:37:02 +02:00
										 |  |  |  |     contextMenu.style.position = 'fixed'; | 
					
						
							| 
									
										
										
										
											2025-06-26 15:46:44 +00:00
										 |  |  |  |     contextMenu.style.zIndex = String(zIndexLayers.overlay.contextMenu); | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     contextMenu.style.opacity = '0'; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     contextMenu.style.transform = 'scale(0.95) translateY(-10px)'; | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     contextMenu.menuItems = menuItemsArg; | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |  |     contextMenu.windowLayer = await DeesWindowLayer.createAndShow(); | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |     contextMenu.windowLayer.addEventListener('click', async (event) => { | 
					
						
							|  |  |  |  |       // Check if click is on the context menu or its submenus
 | 
					
						
							|  |  |  |  |       const clickedElement = event.target as HTMLElement; | 
					
						
							|  |  |  |  |       const isContextMenu = clickedElement.closest('dees-contextmenu'); | 
					
						
							|  |  |  |  |       if (!isContextMenu) { | 
					
						
							|  |  |  |  |         await contextMenu.destroy(); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |  |     }) | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     document.body.append(contextMenu); | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |      | 
					
						
							|  |  |  |  |     // Get dimensions after adding to DOM
 | 
					
						
							|  |  |  |  |     await domtools.plugins.smartdelay.delayFor(0); | 
					
						
							|  |  |  |  |     const rect = contextMenu.getBoundingClientRect(); | 
					
						
							|  |  |  |  |     const windowWidth = window.innerWidth; | 
					
						
							|  |  |  |  |     const windowHeight = window.innerHeight; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Calculate position
 | 
					
						
							|  |  |  |  |     let top = eventArg.clientY; | 
					
						
							|  |  |  |  |     let left = eventArg.clientX; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Adjust if menu would go off right edge
 | 
					
						
							|  |  |  |  |     if (left + rect.width > windowWidth) { | 
					
						
							|  |  |  |  |       left = windowWidth - rect.width - 10; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Adjust if menu would go off bottom edge
 | 
					
						
							|  |  |  |  |     if (top + rect.height > windowHeight) { | 
					
						
							|  |  |  |  |       top = windowHeight - rect.height - 10; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Ensure menu doesn't go off left or top edge
 | 
					
						
							|  |  |  |  |     if (left < 10) left = 10; | 
					
						
							|  |  |  |  |     if (top < 10) top = 10; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     contextMenu.style.top = `${top}px`; | 
					
						
							|  |  |  |  |     contextMenu.style.left = `${left}px`; | 
					
						
							|  |  |  |  |     contextMenu.style.transformOrigin = 'top left'; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Animate in
 | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     await domtools.plugins.smartdelay.delayFor(0); | 
					
						
							|  |  |  |  |     contextMenu.style.opacity = '1'; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     contextMenu.style.transform = 'scale(1) translateY(0)'; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |   // INSTANCE
 | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |   @property({ | 
					
						
							|  |  |  |  |     type: Array, | 
					
						
							|  |  |  |  |   }) | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |   public menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[]; divider?: never } | { divider: true })[] = []; | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |  |   windowLayer: DeesWindowLayer; | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |    | 
					
						
							|  |  |  |  |   private submenu: DeesContextmenu | null = null; | 
					
						
							|  |  |  |  |   private submenuTimeout: any = null; | 
					
						
							|  |  |  |  |   private parentMenu: DeesContextmenu | null = null; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   constructor() { | 
					
						
							|  |  |  |  |     super(); | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     this.tabIndex = 0; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |   /** | 
					
						
							|  |  |  |  |    * STATIC STYLES | 
					
						
							|  |  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |   public static styles = [ | 
					
						
							|  |  |  |  |     cssManager.defaultStyles, | 
					
						
							|  |  |  |  |     css`
 | 
					
						
							|  |  |  |  |       :host { | 
					
						
							|  |  |  |  |         display: block; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |         transition: opacity 0.2s, transform 0.2s; | 
					
						
							|  |  |  |  |         outline: none; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       .mainbox { | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |         min-width: 200px; | 
					
						
							|  |  |  |  |         max-width: 280px; | 
					
						
							|  |  |  |  |         background: ${cssManager.bdTheme('#ffffff', '#000000')}; | 
					
						
							|  |  |  |  |         border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; | 
					
						
							|  |  |  |  |         border-radius: 4px; | 
					
						
							|  |  |  |  |         box-shadow: ${cssManager.bdTheme( | 
					
						
							|  |  |  |  |           '0 4px 12px rgba(0, 0, 0, 0.15)', | 
					
						
							|  |  |  |  |           '0 4px 12px rgba(0, 0, 0, 0.3)' | 
					
						
							|  |  |  |  |         )}; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |         user-select: none; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |         padding: 4px 0; | 
					
						
							|  |  |  |  |         font-size: 12px; | 
					
						
							|  |  |  |  |         color: ${cssManager.bdTheme('#333', '#ccc')}; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |       .menuitem { | 
					
						
							|  |  |  |  |         display: flex; | 
					
						
							|  |  |  |  |         align-items: center; | 
					
						
							|  |  |  |  |         gap: 8px; | 
					
						
							|  |  |  |  |         padding: 8px 12px; | 
					
						
							|  |  |  |  |         cursor: default; | 
					
						
							|  |  |  |  |         transition: background 0.1s; | 
					
						
							|  |  |  |  |         line-height: 1; | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |         position: relative; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |       .menuitem:hover { | 
					
						
							|  |  |  |  |         background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |        | 
					
						
							|  |  |  |  |       .menuitem.has-submenu::after { | 
					
						
							|  |  |  |  |         content: '›'; | 
					
						
							|  |  |  |  |         position: absolute; | 
					
						
							|  |  |  |  |         right: 8px; | 
					
						
							|  |  |  |  |         font-size: 16px; | 
					
						
							|  |  |  |  |         opacity: 0.5; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |       .menuitem:active:not(.has-submenu) { | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |         background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |        | 
					
						
							|  |  |  |  |       .menuitem.disabled { | 
					
						
							|  |  |  |  |         opacity: 0.5; | 
					
						
							|  |  |  |  |         cursor: not-allowed; | 
					
						
							|  |  |  |  |         pointer-events: none; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       .menuitem dees-icon { | 
					
						
							|  |  |  |  |         font-size: 14px; | 
					
						
							|  |  |  |  |         opacity: 0.7; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       .menuitem-text { | 
					
						
							|  |  |  |  |         flex: 1; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |       .menuitem-shortcut { | 
					
						
							|  |  |  |  |         font-size: 11px; | 
					
						
							|  |  |  |  |         color: ${cssManager.bdTheme('#999', '#666')}; | 
					
						
							|  |  |  |  |         margin-left: auto; | 
					
						
							|  |  |  |  |         opacity: 0.7; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       .menu-divider { | 
					
						
							|  |  |  |  |         height: 1px; | 
					
						
							|  |  |  |  |         background: ${cssManager.bdTheme('#e0e0e0', '#202020')}; | 
					
						
							|  |  |  |  |         margin: 4px 0; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |     `,
 | 
					
						
							|  |  |  |  |   ]; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public render(): TemplateResult { | 
					
						
							|  |  |  |  |     return html`
 | 
					
						
							|  |  |  |  |       <div class="mainbox"> | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |         ${this.menuItems.map((menuItemArg) => { | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |           if ('divider' in menuItemArg && menuItemArg.divider) { | 
					
						
							|  |  |  |  |             return html`<div class="menu-divider"></div>`; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |            | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |           const menuItem = menuItemArg as plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: any }; | 
					
						
							|  |  |  |  |           const hasSubmenu = menuItem.submenu && menuItem.submenu.length > 0; | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |           return html`
 | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |             <div  | 
					
						
							|  |  |  |  |               class="menuitem ${menuItem.disabled ? 'disabled' : ''} ${hasSubmenu ? 'has-submenu' : ''}"  | 
					
						
							|  |  |  |  |               @click=${() => !menuItem.disabled && !hasSubmenu && this.handleClick(menuItem)} | 
					
						
							|  |  |  |  |               @mouseenter=${() => this.handleMenuItemHover(menuItem, hasSubmenu)} | 
					
						
							|  |  |  |  |               @mouseleave=${() => this.handleMenuItemLeave()} | 
					
						
							|  |  |  |  |             > | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |               ${menuItem.iconName ? html`
 | 
					
						
							|  |  |  |  |                 <dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon> | 
					
						
							|  |  |  |  |               ` : ''}
 | 
					
						
							|  |  |  |  |               <span class="menuitem-text">${menuItem.name}</span> | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |               ${menuItem.shortcut && !hasSubmenu ? html`
 | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |                 <span class="menuitem-shortcut">${menuItem.shortcut}</span> | 
					
						
							|  |  |  |  |               ` : ''}
 | 
					
						
							| 
									
										
										
										
											2023-01-13 00:30:56 +01:00
										 |  |  |  |             </div> | 
					
						
							|  |  |  |  |           `;
 | 
					
						
							|  |  |  |  |         })} | 
					
						
							| 
									
										
										
										
											2023-09-09 13:34:46 +02:00
										 |  |  |  |         ${this.menuItems.length === 0 ? html`
 | 
					
						
							|  |  |  |  |             <div class="menuitem" @click=${() => { | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  |               DeesContextmenu.contextMenuDeactivated = true; | 
					
						
							|  |  |  |  |               this.destroy(); | 
					
						
							| 
									
										
										
										
											2023-09-09 13:34:46 +02:00
										 |  |  |  |             }}> | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |               <dees-icon .icon="lucide:x"></dees-icon> | 
					
						
							|  |  |  |  |               <span class="menuitem-text">Allow native context</span> | 
					
						
							| 
									
										
										
										
											2023-09-09 13:34:46 +02:00
										 |  |  |  |             </div> | 
					
						
							|  |  |  |  |         ` : html``}
 | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |       </div> | 
					
						
							|  |  |  |  |     `;
 | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public async firstUpdated() { | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     // Focus on the menu for keyboard navigation
 | 
					
						
							|  |  |  |  |     this.focus(); | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Add keyboard event listeners
 | 
					
						
							|  |  |  |  |     this.addEventListener('keydown', this.handleKeydown); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   private handleKeydown = (event: KeyboardEvent) => { | 
					
						
							|  |  |  |  |     const menuItems = Array.from(this.shadowRoot.querySelectorAll('.menuitem:not(.disabled)')); | 
					
						
							|  |  |  |  |     const currentIndex = menuItems.findIndex(item => item.matches(':hover')); | 
					
						
							| 
									
										
										
										
											2023-09-09 13:34:46 +02:00
										 |  |  |  |      | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     switch (event.key) { | 
					
						
							|  |  |  |  |       case 'ArrowDown': | 
					
						
							|  |  |  |  |         event.preventDefault(); | 
					
						
							|  |  |  |  |         const nextIndex = currentIndex + 1 < menuItems.length ? currentIndex + 1 : 0; | 
					
						
							|  |  |  |  |         (menuItems[nextIndex] as HTMLElement).dispatchEvent(new MouseEvent('mouseenter')); | 
					
						
							|  |  |  |  |         break; | 
					
						
							|  |  |  |  |          | 
					
						
							|  |  |  |  |       case 'ArrowUp': | 
					
						
							|  |  |  |  |         event.preventDefault(); | 
					
						
							|  |  |  |  |         const prevIndex = currentIndex - 1 >= 0 ? currentIndex - 1 : menuItems.length - 1; | 
					
						
							|  |  |  |  |         (menuItems[prevIndex] as HTMLElement).dispatchEvent(new MouseEvent('mouseenter')); | 
					
						
							|  |  |  |  |         break; | 
					
						
							|  |  |  |  |          | 
					
						
							|  |  |  |  |       case 'Enter': | 
					
						
							|  |  |  |  |         event.preventDefault(); | 
					
						
							|  |  |  |  |         if (currentIndex >= 0) { | 
					
						
							|  |  |  |  |           (menuItems[currentIndex] as HTMLElement).click(); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |         break; | 
					
						
							|  |  |  |  |          | 
					
						
							|  |  |  |  |       case 'Escape': | 
					
						
							|  |  |  |  |         event.preventDefault(); | 
					
						
							|  |  |  |  |         this.destroy(); | 
					
						
							|  |  |  |  |         break; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |   public async handleClick(menuItem: plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean }) { | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     menuItem.action(); | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |      | 
					
						
							|  |  |  |  |     // Close all menus in the chain (this menu and all parent menus)
 | 
					
						
							|  |  |  |  |     await this.destroyAll(); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   private async handleMenuItemHover(menuItem: plugins.tsclass.website.IMenuItem & { submenu?: any }, hasSubmenu: boolean) { | 
					
						
							|  |  |  |  |     // Clear any existing timeout
 | 
					
						
							|  |  |  |  |     if (this.submenuTimeout) { | 
					
						
							|  |  |  |  |       clearTimeout(this.submenuTimeout); | 
					
						
							|  |  |  |  |       this.submenuTimeout = null; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Hide any existing submenu if hovering a different item
 | 
					
						
							|  |  |  |  |     if (this.submenu) { | 
					
						
							|  |  |  |  |       await this.hideSubmenu(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Show submenu if this item has one
 | 
					
						
							|  |  |  |  |     if (hasSubmenu && menuItem.submenu) { | 
					
						
							|  |  |  |  |       this.submenuTimeout = setTimeout(() => { | 
					
						
							|  |  |  |  |         this.showSubmenu(menuItem); | 
					
						
							|  |  |  |  |       }, 200); // Small delay to prevent accidental triggers
 | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   private handleMenuItemLeave() { | 
					
						
							|  |  |  |  |     // Add a delay before hiding to allow moving to submenu
 | 
					
						
							|  |  |  |  |     if (this.submenuTimeout) { | 
					
						
							|  |  |  |  |       clearTimeout(this.submenuTimeout); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     this.submenuTimeout = setTimeout(() => { | 
					
						
							|  |  |  |  |       if (this.submenu && !this.submenu.matches(':hover')) { | 
					
						
							|  |  |  |  |         this.hideSubmenu(); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     }, 300); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   private async showSubmenu(menuItem: plugins.tsclass.website.IMenuItem & { submenu?: any }) { | 
					
						
							|  |  |  |  |     if (!menuItem.submenu || menuItem.submenu.length === 0) return; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Find the menu item element
 | 
					
						
							|  |  |  |  |     const menuItems = Array.from(this.shadowRoot.querySelectorAll('.menuitem')); | 
					
						
							|  |  |  |  |     const menuItemElement = menuItems.find(el => el.querySelector('.menuitem-text')?.textContent === menuItem.name) as HTMLElement; | 
					
						
							|  |  |  |  |     if (!menuItemElement) return; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Create submenu
 | 
					
						
							|  |  |  |  |     this.submenu = new DeesContextmenu(); | 
					
						
							|  |  |  |  |     this.submenu.menuItems = menuItem.submenu; | 
					
						
							|  |  |  |  |     this.submenu.parentMenu = this; | 
					
						
							|  |  |  |  |     this.submenu.style.position = 'fixed'; | 
					
						
							|  |  |  |  |     this.submenu.style.zIndex = String(parseInt(this.style.zIndex) + 1); | 
					
						
							|  |  |  |  |     this.submenu.style.opacity = '0'; | 
					
						
							|  |  |  |  |     this.submenu.style.transform = 'scale(0.95)'; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Don't create a window layer for submenus
 | 
					
						
							|  |  |  |  |     document.body.append(this.submenu); | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Position submenu
 | 
					
						
							|  |  |  |  |     await domtools.plugins.smartdelay.delayFor(0); | 
					
						
							|  |  |  |  |     const itemRect = menuItemElement.getBoundingClientRect(); | 
					
						
							|  |  |  |  |     const menuRect = this.getBoundingClientRect(); | 
					
						
							|  |  |  |  |     const submenuRect = this.submenu.getBoundingClientRect(); | 
					
						
							|  |  |  |  |     const windowWidth = window.innerWidth; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     let left = menuRect.right - 4; // Slight overlap
 | 
					
						
							|  |  |  |  |     let top = itemRect.top; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Check if submenu would go off right edge
 | 
					
						
							|  |  |  |  |     if (left + submenuRect.width > windowWidth - 10) { | 
					
						
							|  |  |  |  |       // Show on left side instead
 | 
					
						
							|  |  |  |  |       left = menuRect.left - submenuRect.width + 4; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Adjust vertical position if needed
 | 
					
						
							|  |  |  |  |     if (top + submenuRect.height > window.innerHeight - 10) { | 
					
						
							|  |  |  |  |       top = window.innerHeight - submenuRect.height - 10; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     this.submenu.style.left = `${left}px`; | 
					
						
							|  |  |  |  |     this.submenu.style.top = `${top}px`; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Animate in
 | 
					
						
							|  |  |  |  |     await domtools.plugins.smartdelay.delayFor(0); | 
					
						
							|  |  |  |  |     this.submenu.style.opacity = '1'; | 
					
						
							|  |  |  |  |     this.submenu.style.transform = 'scale(1)'; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Handle submenu hover
 | 
					
						
							|  |  |  |  |     this.submenu.addEventListener('mouseenter', () => { | 
					
						
							|  |  |  |  |       if (this.submenuTimeout) { | 
					
						
							|  |  |  |  |         clearTimeout(this.submenuTimeout); | 
					
						
							|  |  |  |  |         this.submenuTimeout = null; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     this.submenu.addEventListener('mouseleave', () => { | 
					
						
							|  |  |  |  |       this.handleMenuItemLeave(); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   private async hideSubmenu() { | 
					
						
							|  |  |  |  |     if (!this.submenu) return; | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     await this.submenu.destroy(); | 
					
						
							|  |  |  |  |     this.submenu = null; | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public async destroy() { | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |     // Clear timeout
 | 
					
						
							|  |  |  |  |     if (this.submenuTimeout) { | 
					
						
							|  |  |  |  |       clearTimeout(this.submenuTimeout); | 
					
						
							|  |  |  |  |       this.submenuTimeout = null; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Destroy submenu first
 | 
					
						
							|  |  |  |  |     if (this.submenu) { | 
					
						
							|  |  |  |  |       await this.submenu.destroy(); | 
					
						
							|  |  |  |  |       this.submenu = null; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |      | 
					
						
							|  |  |  |  |     // Only destroy window layer if this is not a submenu
 | 
					
						
							|  |  |  |  |     if (this.windowLayer && !this.parentMenu) { | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |  |       this.windowLayer.destroy(); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |      | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     this.style.opacity = '0'; | 
					
						
							| 
									
										
										
										
											2025-06-17 11:39:16 +00:00
										 |  |  |  |     this.style.transform = 'scale(0.95) translateY(-10px)'; | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |     await domtools.plugins.smartdelay.delayFor(100); | 
					
						
							| 
									
										
										
										
											2025-06-27 19:25:34 +00:00
										 |  |  |  |      | 
					
						
							|  |  |  |  |     if (this.parentElement) { | 
					
						
							|  |  |  |  |       this.parentElement.removeChild(this); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |    | 
					
						
							|  |  |  |  |   /** | 
					
						
							|  |  |  |  |    * Destroys this menu and all parent menus in the chain | 
					
						
							|  |  |  |  |    */ | 
					
						
							|  |  |  |  |   public async destroyAll() { | 
					
						
							|  |  |  |  |     // First destroy parent menus if they exist
 | 
					
						
							|  |  |  |  |     if (this.parentMenu) { | 
					
						
							|  |  |  |  |       await this.parentMenu.destroyAll(); | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       // If we're at the top level, just destroy this menu
 | 
					
						
							|  |  |  |  |       await this.destroy(); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-01-13 02:15:30 +01:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-01-12 18:14:59 +01:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | DeesContextmenu.initializeGlobalListener(); |