import { LitElement, html, css } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { buildKeyString } from '../../services/keyboard-shortcut-service'; @customElement('shortcut-capture') export class ShortcutCapture extends LitElement { @property() action = ''; @property() currentKey = ''; @property() defaultKey = ''; /** * What the binding does, for the name. * * The button's text is the *key* — so the shortcuts list rendered * three buttons called "S", two called "Down" and one called "?", * beside a visible label that named none of them (it is a sibling, * in another shadow root, and nothing associated the two). The * label is what a control is for; the key is its value. */ @property() label = ''; @state() private recording = false; static override styles = css` :host { display: inline-block; } button { font-family: inherit; font-size: var(--yj-text-sm, 13px); padding: 4px 12px; border-radius: 4px; border: 1px solid var(--yj-border, #555); background: var(--yj-bg-input, #333); color: var(--yj-text-primary, #eee); cursor: pointer; min-width: 80px; text-align: center; transition: border-color 0.15s, background 0.15s; } button:hover { border-color: var(--yj-accent, #ffd43b); } button.recording { border-color: var(--yj-accent, #ffd43b); background: var(--yj-bg-active, #444); animation: pulse 1.2s ease-in-out infinite; } button.not-set { color: var(--yj-text-tertiary, #888); font-style: italic; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } .reset-btn { font-size: var(--yj-text-xs, 11px); padding: 2px 6px; margin-left: 4px; border: none; background: transparent; color: var(--yj-text-tertiary, #888); cursor: pointer; min-width: auto; opacity: 0; transition: opacity 0.15s; } :host(:hover) .reset-btn { opacity: 1; } .reset-btn:hover { color: var(--yj-accent-text, #ffd43b); } `; private handleClick = () => { this.recording = true; // Focus self so keydown events arrive this.shadowRoot?.querySelector('button')?.focus(); }; private handleKeydown = (e: KeyboardEvent) => { if (!this.recording) return; e.preventDefault(); e.stopPropagation(); const keyStr = buildKeyString(e); if (!keyStr) return; // bare modifier press — keep recording if (keyStr === 'Escape') { this.recording = false; return; } this.recording = false; this.dispatchEvent( new CustomEvent('shortcut-change', { detail: { action: this.action, key: keyStr }, bubbles: true, composed: true, }), ); }; private handleBlur = () => { // Cancel recording if focus leaves if (this.recording) { this.recording = false; } }; private handleReset = (e: Event) => { e.stopPropagation(); if (this.defaultKey && this.currentKey !== this.defaultKey) { this.dispatchEvent( new CustomEvent('shortcut-change', { detail: { action: this.action, key: this.defaultKey, }, bubbles: true, composed: true, }), ); } }; override render() { const showReset = this.defaultKey && this.currentKey !== this.defaultKey; return html` ${showReset ? html` ` : ''} `; } } declare global { interface HTMLElementTagNameMap { 'shortcut-capture': ShortcutCapture; } }