import { customElement, DeesElement, type TemplateResult, html, property, css, cssManager, } from '@design.estate/dees-element'; import { DeesIcon } from '@design.estate/dees-catalog'; import { demo } from './eco-applauncher-powermenu.demo.js'; // Ensure dees-icon is registered DeesIcon; declare global { interface HTMLElementTagNameMap { 'eco-applauncher-powermenu': EcoApplauncherPowermenu; } } export type TPowerAction = 'lock' | 'lock-sleep' | 'reboot'; @customElement('eco-applauncher-powermenu') export class EcoApplauncherPowermenu extends DeesElement { public static demo = demo; public static demoGroup = 'App Launcher'; public static styles = [ cssManager.defaultStyles, css` :host { display: block; position: relative; pointer-events: none; } :host([open]) { pointer-events: auto; } .menu-container { background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 10%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 20%)')}; border-radius: 12px; box-shadow: ${cssManager.bdTheme( '0 8px 32px rgba(0, 0, 0, 0.15)', '0 8px 32px rgba(0, 0, 0, 0.4)' )}; min-width: 200px; overflow: hidden; opacity: 0; transform: scale(0.95) translateY(-8px); transition: all 0.2s ease-out; pointer-events: none; } :host([open]) .menu-container { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; } .menu-header { padding: 12px 16px; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')}; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')}; } .menu-options { padding: 8px 0; } .menu-option { display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer; transition: background 0.15s ease; color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')}; } .menu-option:hover { background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(240 5% 15%)')}; } .menu-option:active { background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 18%)')}; } .menu-option.danger { color: ${cssManager.bdTheme('hsl(0 72% 45%)', 'hsl(0 72% 60%)')}; } .menu-option.danger:hover { background: ${cssManager.bdTheme('hsl(0 72% 97%)', 'hsl(0 50% 15%)')}; } .option-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 18%)')}; } .menu-option.danger .option-icon { background: ${cssManager.bdTheme('hsl(0 72% 94%)', 'hsl(0 50% 18%)')}; } .option-text { display: flex; flex-direction: column; gap: 2px; } .option-label { font-size: 14px; font-weight: 500; } .option-description { font-size: 11px; color: ${cssManager.bdTheme('hsl(0 0% 55%)', 'hsl(0 0% 50%)')}; } .menu-divider { height: 1px; background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')}; margin: 4px 0; } `, ]; @property({ type: Boolean, reflect: true }) accessor open = false; private boundHandleClickOutside = this.handleClickOutside.bind(this); private inactivityTimeout: ReturnType | null = null; private readonly INACTIVITY_TIMEOUT = 60000; // 1 minute private lastActivityTime = 0; public render(): TemplateResult { return html` `; } private handleAction(action: TPowerAction): void { this.dispatchEvent(new CustomEvent('power-action', { detail: { action }, bubbles: true, composed: true, })); this.closeMenu(); } private handleClickOutside(e: MouseEvent): void { if (this.open && !this.contains(e.target as Node)) { this.closeMenu(); } } private resetInactivityTimer(): void { const now = Date.now(); // Throttle: only reset if 5+ seconds since last reset if (now - this.lastActivityTime < 5000) { return; } this.lastActivityTime = now; this.clearInactivityTimer(); if (this.open) { this.inactivityTimeout = setTimeout(() => { this.closeMenu(); }, this.INACTIVITY_TIMEOUT); } } private clearInactivityTimer(): void { if (this.inactivityTimeout) { clearTimeout(this.inactivityTimeout); this.inactivityTimeout = null; } } private closeMenu(): void { this.open = false; this.dispatchEvent(new CustomEvent('menu-close', { bubbles: true, composed: true, })); } protected updated(changedProperties: Map): void { if (changedProperties.has('open')) { if (this.open) { this.resetInactivityTimer(); } else { this.clearInactivityTimer(); } } } async connectedCallback(): Promise { await super.connectedCallback(); setTimeout(() => { document.addEventListener('click', this.boundHandleClickOutside); }, 0); } async disconnectedCallback(): Promise { await super.disconnectedCallback(); document.removeEventListener('click', this.boundHandleClickOutside); this.clearInactivityTimer(); } }