import { LitElement, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; @customElement('arx-dialog') export class EveDialog extends LitElement { @property({ type: String }) override title = ''; @property({ type: Boolean }) open = false; @property({ type: Boolean }) closeOnOverlayClick = true; @property({ type: Boolean }) closeOnEscape = true; @property({ type: String }) width = '420px'; @property({ type: String }) maxWidth = '90%'; @state() private _previousFocus: HTMLElement | null = null; static override styles = css` :host { display: block; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999999; } .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: oklch(from var(--color-base-content) l c h / 0.6); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; backdrop-filter: blur(4px); } .overlay.active { opacity: 1; pointer-events: all; } .dialog-container { background-color: var(--color-base-100); border-radius: var(--radius-box); border: var(--border) solid var(--color-base-300); box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) calc(var(--depth) * 8px) oklch(from var(--color-base-content) l c h / 0.2), calc(var(--depth) * -1px) calc(var(--depth) * -1px) calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4); width: var(--dialog-width, 420px); max-width: var(--dialog-max-width, 90%); padding: 28px; transform: scale(0.95) translateY(10px); transition: transform 0.25s cubic-bezier(0.1, 1, 0.2, 1); color: var(--color-base-content); } .overlay.active .dialog-container { transform: scale(1) translateY(0); } .dialog-header { font-size: 18px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.4; color: var(--color-base-content); } .dialog-content { margin-bottom: 24px; } .dialog-footer { display: flex; justify-content: flex-end; gap: 12px; } .dialog-container:focus-within { box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) calc(var(--depth) * 8px) oklch(from var(--color-base-content) l c h / 0.2), calc(var(--depth) * -1px) calc(var(--depth) * -1px) calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4), 0 0 0 2px oklch(from var(--color-accent) l c h / 0.2); } `; constructor() { super(); this._handleKeyDown = this._handleKeyDown.bind(this); } override connectedCallback() { super.connectedCallback(); document.addEventListener('keydown', this._handleKeyDown); } override disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('keydown', this._handleKeyDown); } override updated(changedProps: Map) { if (changedProps.has('open') && this.open) { this._previousFocus = document.activeElement as HTMLElement; // Focus the dialog container after rendering setTimeout(() => { const container = this.shadowRoot?.querySelector('.dialog-container') as HTMLElement; if (container) container.focus(); }, 50); } else if (changedProps.has('open') && !this.open && this._previousFocus) { this._previousFocus.focus(); } } private _handleKeyDown(e: KeyboardEvent) { if (!this.open || !this.closeOnEscape) return; if (e.key === 'Escape') this.close(); } private _handleOverlayClick(e: MouseEvent) { if (this.closeOnOverlayClick && e.target === e.currentTarget) { this.close(); } } show() { this.open = true; } close() { this.open = false; this.dispatchEvent(new CustomEvent('close')); } override render() { return html` ${this.title ? html`${this.title}` : ''} `; } }