import { LitElement, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; @customElement('arx-prompt') export class EvePrompt extends LitElement { @property({ type: String }) promptText = 'Please provide input'; @property({ type: String }) cancelText = 'Cancel'; @property({ type: String }) saveText = 'Save'; @property({ type: Boolean }) open = false; @property({ type: Boolean }) showInput = false; @property({ type: String }) placeholder = 'Enter your response'; @property({ type: String }) defaultValue = ''; @state() private _inputValue = ''; private _previousFocus: HTMLElement | null = null; static override styles = css` :host { --prompt-primary: #3b82f6; --prompt-primary-hover: #2563eb; --prompt-bg: #ffffff; --prompt-text: #1f2937; --prompt-border: #e5e7eb; --prompt-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); --prompt-cancel-bg: #f3f4f6; --prompt-cancel-hover: #e5e7eb; --prompt-overlay: rgba(15, 23, 42, 0.6); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--prompt-overlay); 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; } .prompt-container { background-color: var(--prompt-bg); border-radius: 12px; box-shadow: var(--prompt-shadow); width: 90%; max-width: 420px; padding: 28px; transform: scale(0.95) translateY(10px); transition: transform 0.25s cubic-bezier(0.1, 1, 0.2, 1); color: var(--prompt-text); } .overlay.active .prompt-container { transform: scale(1) translateY(0); } .prompt-header { font-size: 18px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.4; } .input-container { margin: 20px 0; } .input-field { width: 100%; padding: 12px 14px; border-radius: 8px; border: 1px solid var(--prompt-border); font-size: 15px; transition: border-color 0.2s ease, box-shadow 0.2s ease; color: inherit; background-color: transparent; outline: none; box-sizing: border-box; } .input-field:focus { border-color: var(--prompt-primary); box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); } .buttons { display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px; } button { padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: none; outline: none; } button:focus-visible { box-shadow: 0 0 0 2px var(--prompt-bg), 0 0 0 4px var(--prompt-primary); } .cancel-btn { background-color: var(--prompt-cancel-bg); color: var(--prompt-text); } .cancel-btn:hover { background-color: var(--prompt-cancel-hover); } .save-btn { background-color: var(--prompt-primary); color: white; } .save-btn:hover { background-color: var(--prompt-primary-hover); } @media (prefers-color-scheme: dark) { :host { --prompt-bg: #1e1e1e; --prompt-text: #e5e7eb; --prompt-border: #374151; --prompt-cancel-bg: #374151; --prompt-cancel-hover: #4b5563; } } `; 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<string, unknown>) { if (changedProps.has('open') && this.open) { this._inputValue = this.defaultValue; this._previousFocus = document.activeElement as HTMLElement; // Focus the input or save button after rendering setTimeout(() => { if (this.showInput) { const input = this.shadowRoot?.querySelector('.input-field') as HTMLElement; if (input) input.focus(); } else { const saveBtn = this.shadowRoot?.querySelector('.save-btn') as HTMLElement; if (saveBtn) saveBtn.focus(); } }, 50); } else if (changedProps.has('open') && !this.open && this._previousFocus) { this._previousFocus.focus(); } } private _handleKeyDown(e: KeyboardEvent) { if (!this.open) return; if (e.key === 'Escape') this._handleCancel(); if (e.key === 'Enter' && !e.shiftKey) this._handleSave(); } private _handleInputChange(e: Event) { const target = e.target as HTMLInputElement; this._inputValue = target.value; } private _handleCancel() { this.open = false; this.dispatchEvent(new CustomEvent('cancel')); } private _handleSave() { this.open = false; this.dispatchEvent( new CustomEvent('save', { detail: { value: this._inputValue }, }), ); } override render() { return html` <div class="${classMap({ overlay: true, active: this.open })}" @click="${(e: MouseEvent) => e.target === e.currentTarget && this._handleCancel()}" > <div class="prompt-container"> <div class="prompt-header">${this.promptText}</div> ${ this.showInput ? html` <div class="input-container"> <input type="text" class="input-field" .value=${this._inputValue} @input=${this._handleInputChange} placeholder=${this.placeholder} /> </div> ` : '' } <div class="buttons"> <button @click=${this._handleCancel} class="cancel-btn"> ${this.cancelText} </button> <button @click=${this._handleSave} class="save-btn"> ${this.saveText} </button> </div> </div> </div> `; } show() { this.open = true; } hide() { this.open = false; } getValue(): string { return this._inputValue; } }