{"version":3,"file":"command-palette.cjs","names":[],"sources":["../src/overlay/command-palette/command-palette.ts"],"sourcesContent":["import { createKeymap } from '@vielzeug/keymap';\nimport { define, getHost, html, onCleanup, onEvent, onMounted, prop, ref, useEmit } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { warn } from '../../_dev';\nimport { announce, createListControl, lifecycleSignal } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport { useDialogControl } from '../shared/use-dialog';\nimport type { CommandPaletteItem, OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\nimport { buildRows, filterItems, normalizeItem, parseSlottedItems, splitShortcutKeys } from './command-palette-items';\nimport '../../content/icon/icon';\nimport componentStyles from './command-palette.css?inline';\n\nexport type { OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\n\nconst parseOptionalBool = (value: string | null): boolean | undefined =>\n  value == null ? undefined : value === '' || value === 'true';\n\n/**\n * A pure data node describing one command. Never rendered directly — `ore-command-palette`\n * reads its attributes/text content and renders the visible row itself.\n *\n * @element ore-command-palette-item\n *\n * @attr {string} value - Value emitted by the `select` event and matched against the search query\n * @attr {string} label - Explicit label text; falls back to the element's text content\n * @attr {string} group - Group heading the item is clustered under\n * @attr {string} shortcut - Display-only keyboard hint rendered at the end of the row, one `<kbd>` per `+`-separated key (e.g. `\"⌘+S\"` renders two keycaps; a literal `+` key isn't representable — spell it out, e.g. `\"Ctrl+Plus\"`)\n * @attr {boolean} disabled - Excludes the item from keyboard navigation and selection\n *\n * @slot icon - Optional leading icon content\n *\n * @example\n * ```html\n * <ore-command-palette-item value=\"new-file\" group=\"File\" shortcut=\"⌘+N\">New File</ore-command-palette-item>\n * ```\n */\nexport const COMMAND_PALETTE_ITEM_TAG = 'ore-command-palette-item' as const;\ndefine(COMMAND_PALETTE_ITEM_TAG, {\n  setup() {\n    return html``;\n  },\n});\n\n/**\n * A searchable, keyboard-driven list of commands presented in a centered modal —\n * the \"⌘K\" pattern popularized by editors and productivity apps. Built on the\n * native `<dialog>` element (focus trap, top-layer stacking, `Escape`-to-close)\n * and `@vielzeug/keymap` for the global shortcut that opens it.\n *\n * @element ore-command-palette\n * @element ore-command-palette-item - Slotted command definition (place in default slot)\n *\n * @attr {boolean} open - Controls the open state of the palette\n * @attr {boolean} default-open - Initial uncontrolled open state\n * @attr {string} label - Accessible label for the dialog (screen-reader-only heading)\n * @attr {string} placeholder - Placeholder text for the search input\n * @attr {string} shortcut - Global keyboard shortcut (keymap syntax) that toggles the palette. Default `\"mod+k\"`; set to `\"\"` to disable\n * @attr {boolean} no-filter - Disable built-in client-side filtering (for server-driven search)\n * @attr {boolean} loading - Shows a loading row below the search input\n * @attr {string} empty-text - Message shown when no item matches the query\n * @attr {boolean} keep-open-on-select - Keep the palette open after an item is selected\n *\n * @fires open-change - Fired when the palette state changes. detail: `{ open, reason }`\n * @fires search - Fired on every keystroke in the search input. detail: `{ query }`\n * @fires select - Fired when a command is chosen (click or `Enter`). detail: `{ value, label, item }`\n *\n * @slot - `<ore-command-palette-item>` elements (alternative/supplement to the `items` prop)\n *\n * @cssprop --command-palette-bg - Panel background color\n * @cssprop --command-palette-border-color - Panel border color\n * @cssprop --command-palette-radius - Panel border radius\n * @cssprop --command-palette-shadow - Panel drop shadow\n * @cssprop --command-palette-max-width - Maximum panel width\n * @cssprop --command-palette-backdrop - Backdrop overlay color\n * @cssprop --command-palette-option-hover-bg - Item background on hover\n * @cssprop --command-palette-option-focus-bg - Item background when keyboard-focused\n *\n * @part dialog - Dialog root container\n * @part panel - Panel container\n * @part input - Search input\n * @part listbox - Listbox of matching commands\n *\n * @example\n * ```html\n * <ore-command-palette label=\"Command palette\" placeholder=\"Type a command…\">\n *   <ore-command-palette-item value=\"new-file\" group=\"File\" shortcut=\"⌘+N\">New File</ore-command-palette-item>\n *   <ore-command-palette-item value=\"open-file\" group=\"File\" shortcut=\"⌘+O\">Open File…</ore-command-palette-item>\n *   <ore-command-palette-item value=\"toggle-theme\" group=\"View\">Toggle Theme</ore-command-palette-item>\n * </ore-command-palette>\n * ```\n */\nexport const COMMAND_PALETTE_TAG = 'ore-command-palette' as const;\ndefine<OreCommandPaletteProps>(COMMAND_PALETTE_TAG, {\n  props: {\n    'default-open': prop.bool(false),\n    'empty-text': prop.string('No results found.'),\n    items: prop.data<OreCommandPaletteProps['items']>(),\n    'keep-open-on-select': prop.bool(false),\n    label: prop.string('Command palette'),\n    loading: prop.bool(false),\n    'no-filter': prop.bool(false),\n    open: { default: undefined as boolean | undefined, parse: parseOptionalBool },\n    placeholder: prop.string('Type a command or search…'),\n    shortcut: prop.string('mod+k'),\n  },\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreCommandPaletteEvents>();\n    const abortSignal = lifecycleSignal(onCleanup);\n\n    const dialogRef = ref<HTMLDialogElement>();\n    const searchInputRef = ref<HTMLInputElement>();\n    const listboxId = 'cmdk-listbox';\n\n    let listboxEl: HTMLElement | null = null;\n\n    const query = signal('');\n\n    // ── Items: slotted <ore-command-palette-item> elements, merged with the `items` prop ──\n    // Items are pure data nodes (see `ore-command-palette-item`'s own doc comment) — they're\n    // never projected through a `<slot>`, just read directly off the host's light DOM. That\n    // sidesteps `useSlots()`/`slotchange` entirely: a plain MutationObserver on the host\n    // already covers everything `slotchange` would (an item added or removed) *and* the case\n    // it can't — an already-assigned item's own text/attributes changing. For items written\n    // as static, inline HTML, the browser's parser can insert the last item before appending\n    // its text-node child; without watching `characterData`, that would permanently cache an\n    // empty label, since nothing else re-triggers once the element itself stops changing.\n    const slottedItems = signal<CommandPaletteItem[]>([]);\n\n    const reparseSlottedItems = (): void => {\n      slottedItems.value = parseSlottedItems([...el.children]);\n    };\n\n    reparseSlottedItems();\n\n    const lightDomObserver = new MutationObserver(reparseSlottedItems);\n\n    lightDomObserver.observe(el, { characterData: true, childList: true, subtree: true });\n    onCleanup(() => lightDomObserver.disconnect());\n\n    const allItems = computed<CommandPaletteItem[]>(() => {\n      const explicit = props.items.value;\n\n      return Array.isArray(explicit) ? explicit.map(normalizeItem) : slottedItems.value;\n    });\n\n    const filteredItems = computed(() => filterItems(allItems.value, query.value, Boolean(props['no-filter'].value)));\n    const rows = computed(() => buildRows(filteredItems.value));\n\n    // ── Keyboard navigation over the filtered list (Up/Down/Home/End) ──\n    const scrollFocusedIntoView = (): void => {\n      listboxEl?.querySelector<HTMLElement>('[data-focused]')?.scrollIntoView({ block: 'nearest' });\n    };\n\n    const list = createListControl<CommandPaletteItem>({\n      getItems: () => filteredItems.value,\n      isItemDisabled: (item) => item.disabled,\n      onNavigate: scrollFocusedIntoView,\n      signal: abortSignal,\n    });\n    const { focusedIndex } = list;\n\n    // ── Dialog chrome: native <dialog>, focus trap, Escape, backdrop click ──\n    const resetSearch = (): void => {\n      query.value = '';\n      list.reset();\n    };\n\n    const { closeWithAnimation, overlay, requestClose, setupNativeListeners } = useDialogControl({\n      defaultOpen: props['default-open'],\n      dialogRef,\n      getPanelEl: () => dialogRef.value?.querySelector<HTMLElement>('.panel'),\n      host: el,\n      initialFocus: computed(() => '.search-input'),\n      isPersistent: () => false,\n      onCleanup,\n      onEvent,\n      onNativeClose: (reason) => {\n        emit('open-change', { open: false, reason });\n        resetSearch();\n      },\n      onOpen: (reason) => {\n        emit('open-change', { open: true, reason });\n        list.navigate('first');\n      },\n      openProp: props.open,\n      performClose: () => closeWithAnimation(),\n      returnFocus: computed(() => true),\n    });\n\n    // ── Global keyboard shortcut (opens/closes the palette from anywhere) ──\n    const globalKeymap = createKeymap();\n\n    globalKeymap.mount(window);\n    onCleanup(() => globalKeymap.dispose());\n\n    let unbindShortcut: (() => void) | null = null;\n\n    watch(\n      props.shortcut,\n      (shortcutStr) => {\n        unbindShortcut?.();\n        unbindShortcut = null;\n\n        const trimmed = (shortcutStr ?? '').trim();\n\n        if (!trimmed) return;\n\n        try {\n          unbindShortcut = globalKeymap.bind(trimmed, () => {\n            overlay.toggle('keyboard', 'trigger');\n          });\n        } catch (error) {\n          warn(`invalid \"shortcut\" value \"${trimmed}\" — ${error instanceof Error ? error.message : String(error)}`);\n        }\n      },\n      { immediate: true },\n    );\n\n    onMounted(() => {\n      setupNativeListeners();\n    });\n\n    // ── Selection ────────────────────────────────────────────────────────────\n    function selectItem(item: CommandPaletteItem): void {\n      if (item.disabled) return;\n\n      emit('select', { item, label: item.label, value: item.value });\n\n      if (props['keep-open-on-select'].value) {\n        searchInputRef.value?.focus();\n      } else {\n        requestClose('trigger');\n      }\n    }\n\n    function handleInput(e: Event): void {\n      const value = (e.target as HTMLInputElement).value;\n\n      query.value = value;\n      list.navigate('first');\n      emit('search', { query: value });\n\n      const count = filteredItems.value.length;\n\n      announce(count === 0 ? props['empty-text'].value! : `${count} result${count === 1 ? '' : 's'} found`);\n    }\n\n    function handleKeydown(e: KeyboardEvent): void {\n      if (list.handleKeydown(e)) return;\n\n      if (e.key === 'Enter') {\n        // `list.navigate('first')` (called on open and on every keystroke) already keeps\n        // the focused row off a disabled item, so this fallback is only ever reached when\n        // nothing has been focused yet — but it mirrors `selectItem`'s own disabled guard\n        // rather than blindly grabbing index 0, in case that navigate-on-open wiring ever\n        // changes.\n        const active = list.getActiveItem() ?? filteredItems.value.find((item) => !item.disabled);\n\n        if (active) {\n          e.preventDefault();\n          selectItem(active);\n        }\n      }\n    }\n\n    return html`\n      <dialog ref=\"${dialogRef}\" class=\"dialog\" part=\"dialog\" aria-label=\"${props.label}\" aria-modal=\"true\">\n        <div class=\"panel\" part=\"panel\">\n          <div class=\"search\" part=\"search\">\n            <span class=\"search-icon\" aria-hidden=\"true\"><ore-icon name=\"search\" size=\"18\"></ore-icon></span>\n            <input\n              ref=\"${searchInputRef}\"\n              class=\"search-input\"\n              part=\"input\"\n              type=\"text\"\n              role=\"combobox\"\n              autocomplete=\"off\"\n              spellcheck=\"false\"\n              aria-expanded=\"true\"\n              aria-autocomplete=\"list\"\n              aria-controls=\"${listboxId}\"\n              aria-activedescendant=\"${() => (focusedIndex.value >= 0 ? `${listboxId}-opt-${focusedIndex.value}` : '')}\"\n              placeholder=\"${props.placeholder}\"\n              value=\"${query}\"\n              @input=\"${handleInput}\"\n              @keydown=\"${handleKeydown}\" />\n            <span class=\"search-loader\" ?hidden=\"${() => !props.loading.value}\" aria-hidden=\"true\"></span>\n          </div>\n          <div\n            class=\"listbox\"\n            part=\"listbox\"\n            role=\"listbox\"\n            id=\"${listboxId}\"\n            aria-label=\"Commands\"\n            ref=\"${(listEl: HTMLElement) => {\n              listboxEl = listEl;\n            }}\">\n            <div class=\"empty\" role=\"presentation\" ?hidden=\"${() => props.loading.value || rows.value.length > 0}\">\n              ${() => props['empty-text'].value}\n            </div>\n            ${() =>\n              rows.value.map((row) =>\n                row.type === 'group'\n                  ? html`\n                      <div class=\"group-heading\" role=\"presentation\">${row.group}</div>\n                    `\n                  : html`\n                      <div\n                        class=\"item\"\n                        part=\"item\"\n                        role=\"option\"\n                        id=\"${`${listboxId}-opt-${row.idx}`}\"\n                        aria-selected=\"${() => String(focusedIndex.value === row.idx)}\"\n                        aria-disabled=\"${() => String(row.item.disabled)}\"\n                        ?data-focused=\"${() => focusedIndex.value === row.idx}\"\n                        ?data-disabled=\"${() => row.item.disabled}\"\n                        @click=\"${(e: MouseEvent) => {\n                          e.stopPropagation();\n                          selectItem(row.item);\n                        }}\"\n                        @pointerenter=\"${() => {\n                          list.set(row.idx);\n                        }}\">\n                        ${\n                          row.item.icon\n                            ? html`\n                                <span class=\"item-icon\" aria-hidden=\"true\">\n                                  <ore-icon name=\"${row.item.icon}\" size=\"16\"></ore-icon>\n                                </span>\n                              `\n                            : ''\n                        }\n                        <span class=\"item-label\">${row.item.label}</span>\n                        ${\n                          row.item.shortcut\n                            ? html`\n                                <span class=\"item-shortcut\">\n                                  ${splitShortcutKeys(row.item.shortcut).map(\n                                    (key) => html`\n                                      <kbd>${key}</kbd>\n                                    `,\n                                  )}\n                                </span>\n                              `\n                            : ''\n                        }\n                      </div>\n                    `,\n              )}\n          </div>\n          <div class=\"footer\" part=\"footer\">\n            <span class=\"footer-hint\">\n              <kbd>↑</kbd>\n              <kbd>↓</kbd>\n              Navigate\n            </span>\n            <span class=\"footer-hint\">\n              <kbd>Enter</kbd>\n              Select\n            </span>\n            <span class=\"footer-hint\">\n              <kbd>Esc</kbd>\n              Close\n            </span>\n          </div>\n        </div>\n      </dialog>\n    `;\n  },\n  styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}