@vielzeug/refine 26.10.1 → 26.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/command-palette.cjs +9 -1
  2. package/dist/command-palette.cjs.map +1 -1
  3. package/dist/command-palette.js +9 -1
  4. package/dist/command-palette.js.map +1 -1
  5. package/dist/core/aria-reflection.cjs +1 -1
  6. package/dist/core/aria-reflection.cjs.map +1 -1
  7. package/dist/core/aria-reflection.d.ts +8 -8
  8. package/dist/core/aria-reflection.d.ts.map +1 -1
  9. package/dist/core/aria-reflection.js +1 -1
  10. package/dist/core/aria-reflection.js.map +1 -1
  11. package/dist/custom-elements.json +4881 -4569
  12. package/dist/frameworks/elements.d.ts +1 -1
  13. package/dist/inputs/index.d.ts +2 -0
  14. package/dist/inputs/index.d.ts.map +1 -1
  15. package/dist/inputs/query-bar/query-bar-grammar.cjs +2 -0
  16. package/dist/inputs/query-bar/query-bar-grammar.cjs.map +1 -0
  17. package/dist/inputs/query-bar/query-bar-grammar.d.ts +85 -0
  18. package/dist/inputs/query-bar/query-bar-grammar.d.ts.map +1 -0
  19. package/dist/inputs/query-bar/query-bar-grammar.js +2 -0
  20. package/dist/inputs/query-bar/query-bar-grammar.js.map +1 -0
  21. package/dist/inputs/query-bar/query-bar.css.cjs +2 -0
  22. package/dist/inputs/query-bar/query-bar.css.cjs.map +1 -0
  23. package/dist/inputs/query-bar/query-bar.css.js +2 -0
  24. package/dist/inputs/query-bar/query-bar.css.js.map +1 -0
  25. package/dist/inputs/query-bar/query-bar.d.ts +68 -0
  26. package/dist/inputs/query-bar/query-bar.d.ts.map +1 -0
  27. package/dist/inputs/query-bar/query-bar.types.d.ts +42 -0
  28. package/dist/inputs/query-bar/query-bar.types.d.ts.map +1 -0
  29. package/dist/navbar.cjs +2 -2
  30. package/dist/navbar.cjs.map +1 -1
  31. package/dist/navbar.js +2 -2
  32. package/dist/navbar.js.map +1 -1
  33. package/dist/overlay/command-palette/command-palette.css.cjs +1 -1
  34. package/dist/overlay/command-palette/command-palette.css.cjs.map +1 -1
  35. package/dist/overlay/command-palette/command-palette.css.js +1 -1
  36. package/dist/overlay/command-palette/command-palette.css.js.map +1 -1
  37. package/dist/overlay/command-palette/command-palette.d.ts +4 -0
  38. package/dist/overlay/command-palette/command-palette.d.ts.map +1 -1
  39. package/dist/overlay/command-palette/command-palette.types.d.ts +10 -0
  40. package/dist/overlay/command-palette/command-palette.types.d.ts.map +1 -1
  41. package/dist/query-bar.cjs +77 -0
  42. package/dist/query-bar.cjs.map +1 -0
  43. package/dist/query-bar.js +77 -0
  44. package/dist/query-bar.js.map +1 -0
  45. package/dist/refine.cjs +112 -29
  46. package/dist/refine.cjs.map +1 -1
  47. package/dist/refine.iife.js +112 -29
  48. package/dist/refine.iife.js.map +1 -1
  49. package/dist/refine.js +112 -29
  50. package/dist/refine.js.map +1 -1
  51. package/dist/tabs.cjs +1 -1
  52. package/dist/tabs.cjs.map +1 -1
  53. package/dist/tabs.js +1 -1
  54. package/dist/tabs.js.map +1 -1
  55. package/dist/types/elements.d.ts +2 -0
  56. package/package.json +25 -18
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./core/announcer.cjs");require("./_dev.cjs");const n=require("./core/ore.cjs"),r=require("./core/nav.cjs"),i=require("./core/parse.cjs"),a=require("./icon.cjs"),o=require("./overlay/shared/use-dialog.cjs"),s=require("./overlay/command-palette/command-palette.css.cjs"),c=require("./overlay/command-palette/command-palette-items.cjs"),l=require("./keyboard-key.cjs");let u=require("@vielzeug/ripple"),d=require("@vielzeug/ore"),f=require("@vielzeug/keymap");var p=`ore-command-palette-item`;(0,d.define)(p,{setup(){return d.html``}});var m=`ore-command-palette`;(0,d.define)(m,{props:{"default-open":d.prop.bool(!1),"empty-text":d.prop.string(`No results found.`),items:d.prop.data(),"keep-open-on-select":d.prop.bool(!1),label:d.prop.string(`Command palette`),loading:d.prop.bool(!1),"no-filter":d.prop.bool(!1),open:{default:void 0,parse:i.parseOptionalBool},placeholder:d.prop.string(`Type a command or search…`),shortcut:d.prop.string(`mod+k`)},setup(e){let i=(0,d.getHost)(),a=(0,d.useEmit)(),s=n.lifecycleSignal(d.onCleanup),l=(0,d.ref)(),p=(0,d.ref)(),m=`cmdk-listbox`,h=null,g=(0,u.signal)(``),_=(0,u.signal)([]),reparseSlottedItems=()=>{_.value=c.parseSlottedItems([...i.children])};reparseSlottedItems();let v=new MutationObserver(reparseSlottedItems);v.observe(i,{characterData:!0,childList:!0,subtree:!0}),(0,d.onCleanup)(()=>v.disconnect());let y=(0,u.computed)(()=>{let t=e.items.value;return Array.isArray(t)?t.map(c.normalizeItem):_.value}),b=(0,u.computed)(()=>c.filterItems(y.value,g.value,!!e[`no-filter`].value)),x=(0,u.computed)(()=>c.buildRows(b.value)),scrollFocusedIntoView=()=>{h?.querySelector(`[data-focused]`)?.scrollIntoView({block:`nearest`})},S=r.createListControl({getItems:()=>b.value,isItemDisabled:e=>e.disabled,onNavigate:scrollFocusedIntoView,signal:s}),{focusedIndex:C}=S,resetSearch=()=>{g.value=``,S.reset()},{closeWithAnimation:w,handleBackdropClick:T,overlay:E,requestClose:D,mount:O}=o.useDialogControl({defaultOpen:e[`default-open`],dialogRef:l,getPanelEl:()=>l.value?.querySelector(`.panel`),host:i,initialFocus:(0,u.computed)(()=>`.search-input`),isPersistent:()=>!1,onCleanup:d.onCleanup,onEvent:d.onEvent,onNativeClose:e=>{a(`open-change`,{open:!1,reason:e}),resetSearch()},onOpen:e=>{a(`open-change`,{open:!0,reason:e}),S.navigate(`first`)},openProp:e.open,performClose:()=>w(),returnFocus:(0,u.computed)(()=>!0)}),k=(0,f.createKeymap)();k.mount(window),(0,d.onCleanup)(()=>k.dispose());let A=null;(0,u.watch)(e.shortcut,e=>{A?.(),A=null;let t=(e??``).trim();if(t)try{A=k.bind({handler:()=>{E.toggle(`keyboard`,`trigger`)},id:`command-palette-toggle`,shortcut:t})}catch(e){`${t}${e instanceof Error?e.message:String(e)}`}},{immediate:!0}),(0,d.onMounted)(()=>{O(),l.value&&(0,d.onEvent)(l.value,`click`,T)});function selectItem(t){t.disabled||(a(`select`,{item:t,label:t.label,value:t.value}),e[`keep-open-on-select`].value?p.value?.focus():D(`trigger`))}function handleInput(n){let r=n.target.value;g.value=r,S.navigate(`first`),a(`search`,{query:r});let o=b.value.length;t.announce(o===0?e[`empty-text`].value:`${o} result${o===1?``:`s`} found`,{document:i.ownerDocument})}function handleKeydown(e){if(!S.handleKeydown(e)&&e.key===`Enter`){let t=S.getActiveItem()??b.value.find(e=>!e.disabled);t&&(e.preventDefault(),selectItem(t))}}return d.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./core/announcer.cjs");require("./_dev.cjs");const n=require("./core/ore.cjs"),r=require("./core/nav.cjs"),i=require("./core/parse.cjs"),a=require("./icon.cjs"),o=require("./overlay/shared/use-dialog.cjs"),s=require("./overlay/command-palette/command-palette.css.cjs"),c=require("./overlay/command-palette/command-palette-items.cjs"),l=require("./keyboard-key.cjs");let u=require("@vielzeug/ripple"),d=require("@vielzeug/ore"),f=require("@vielzeug/keymap");var p=`ore-command-palette-item`;(0,d.define)(p,{setup(){return d.html``}});var m=`ore-command-palette`;(0,d.define)(m,{props:{"default-open":d.prop.bool(!1),"empty-text":d.prop.string(`No results found.`),items:d.prop.data(),"keep-open-on-select":d.prop.bool(!1),label:d.prop.string(`Command palette`),loading:d.prop.bool(!1),"no-filter":d.prop.bool(!1),open:{default:void 0,parse:i.parseOptionalBool},placeholder:d.prop.string(`Type a command or search…`),shortcut:d.prop.string(`mod+k`)},setup(e){let i=(0,d.getHost)(),a=(0,d.useEmit)(),s=n.lifecycleSignal(d.onCleanup),l=(0,d.ref)(),p=(0,d.ref)(),m=`cmdk-listbox`,h=null,g=(0,u.signal)(``),_=(0,u.signal)([]),reparseSlottedItems=()=>{_.value=c.parseSlottedItems([...i.children])};reparseSlottedItems();let v=new MutationObserver(reparseSlottedItems);v.observe(i,{characterData:!0,childList:!0,subtree:!0}),(0,d.onCleanup)(()=>v.disconnect());let y=(0,u.computed)(()=>{let t=e.items.value;return Array.isArray(t)?t.map(c.normalizeItem):_.value}),b=(0,u.computed)(()=>c.filterItems(y.value,g.value,!!e[`no-filter`].value)),x=(0,u.computed)(()=>c.buildRows(b.value)),scrollFocusedIntoView=()=>{h?.querySelector(`[data-focused]`)?.scrollIntoView({block:`nearest`})},S=r.createListControl({getItems:()=>b.value,isItemDisabled:e=>e.disabled,onNavigate:scrollFocusedIntoView,signal:s}),{focusedIndex:C}=S;(0,u.watch)(()=>b.value[C.value],e=>{a(`active-change`,e?{item:e,label:e.label,value:e.value}:{label:``,value:``})});let w=(0,u.signal)(!1),resetSearch=()=>{g.value=``,S.reset()},{closeWithAnimation:T,handleBackdropClick:E,overlay:D,requestClose:O,mount:k}=o.useDialogControl({defaultOpen:e[`default-open`],dialogRef:l,getPanelEl:()=>l.value?.querySelector(`.panel`),host:i,initialFocus:(0,u.computed)(()=>`.search-input`),isPersistent:()=>!1,onCleanup:d.onCleanup,onEvent:d.onEvent,onNativeClose:e=>{a(`open-change`,{open:!1,reason:e}),resetSearch()},onOpen:e=>{a(`open-change`,{open:!0,reason:e}),S.navigate(`first`)},openProp:e.open,performClose:()=>T(),returnFocus:(0,u.computed)(()=>!0)}),A=(0,f.createKeymap)();A.mount(window),(0,d.onCleanup)(()=>A.dispose());let j=null;(0,u.watch)(e.shortcut,e=>{j?.(),j=null;let t=(e??``).trim();if(t)try{j=A.bind({handler:()=>{D.toggle(`keyboard`,`trigger`)},id:`command-palette-toggle`,shortcut:t})}catch(e){`${t}${e instanceof Error?e.message:String(e)}`}},{immediate:!0}),(0,d.onMounted)(()=>{k(),l.value&&(0,d.onEvent)(l.value,`click`,E)});function selectItem(t){t.disabled||(a(`select`,{item:t,label:t.label,value:t.value}),e[`keep-open-on-select`].value?p.value?.focus():O(`trigger`))}function handleInput(n){let r=n.target.value;g.value=r,S.navigate(`first`),a(`search`,{query:r});let o=b.value.length;t.announce(o===0?e[`empty-text`].value:`${o} result${o===1?``:`s`} found`,{document:i.ownerDocument})}function handleKeydown(e){if(!S.handleKeydown(e)&&e.key===`Enter`){let t=S.getActiveItem()??b.value.find(e=>!e.disabled);t&&(e.preventDefault(),selectItem(t))}}return d.html`
2
2
  <dialog ref="${l}" class="dialog" part="dialog" aria-label="${e.label}" aria-modal="true">
3
3
  <div class="panel" part="panel">
4
4
  <div class="search" part="search">
@@ -21,6 +21,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
21
21
  @keydown="${handleKeydown}" />
22
22
  <span class="search-loader" ?hidden="${()=>!e.loading.value}" aria-hidden="true"></span>
23
23
  </div>
24
+ <div class="body">
24
25
  <div
25
26
  class="listbox"
26
27
  part="listbox"
@@ -61,6 +62,13 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
61
62
  </div>
62
63
  `)}
63
64
  </div>
65
+ <div class="preview" part="preview" role="region" aria-label="Preview" ?hidden="${()=>!w.value}">
66
+ <slot
67
+ name="preview"
68
+ @slotchange="${e=>{w.value=e.target.assignedElements().length>0}}"
69
+ ></slot>
70
+ </div>
71
+ </div>
64
72
  <div class="footer" part="footer">
65
73
  <span class="footer-hint">
66
74
  <ore-keyboard-key>↑</ore-keyboard-key>
@@ -1 +1 @@
1
- {"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';\n\nimport { announce, createListControl, lifecycleSignal, parseOptionalBool } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport { useDialogControl } from '../shared/use-dialog';\nimport componentStyles from './command-palette.css?inline';\nimport type { CommandPaletteItem, OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\nimport { buildRows, filterItems, normalizeItem, parseSlottedItems, splitShortcutKeys } from './command-palette-items';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { KEYBOARD_SHORTCUT_TAG } from '../../content/keyboard-key/keyboard-key';\nexport type { OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\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, handleBackdropClick, overlay, requestClose, mount } = 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({\n handler: () => {\n overlay.toggle('keyboard', 'trigger');\n },\n id: 'command-palette-toggle',\n shortcut: trimmed,\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 mount();\n if (dialogRef.value) onEvent(dialogRef.value, 'click', handleBackdropClick);\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 document: el.ownerDocument,\n });\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 <ore-keyboard-key>${key}</ore-keyboard-key>\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 <ore-keyboard-key>↑</ore-keyboard-key>\n <ore-keyboard-key>↓</ore-keyboard-key>\n Navigate\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Enter</ore-keyboard-key>\n Select\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Esc</ore-keyboard-key>\n Close\n </span>\n </div>\n </div>\n </dialog>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"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';\n\nimport { announce, createListControl, lifecycleSignal, parseOptionalBool } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport { useDialogControl } from '../shared/use-dialog';\nimport componentStyles from './command-palette.css?inline';\nimport type { CommandPaletteItem, OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\nimport { buildRows, filterItems, normalizeItem, parseSlottedItems, splitShortcutKeys } from './command-palette-items';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { KEYBOARD_SHORTCUT_TAG } from '../../content/keyboard-key/keyboard-key';\nexport type { OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\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 active-change - Fired when the focused row changes (keyboard, hover, typing, open, close). detail: `{ value, label, item }`; `item` is `undefined` and `value` empty when no row is active\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 * @slot preview - Optional detail panel rendered beside the list, shown while it has content. Pair it with `active-change` to preview the focused row\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 * @cssprop --command-palette-preview-width - Preview panel width when the `preview` slot has content\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 * @part preview - Preview panel shown beside the list\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 // ── Active row: mirrors the focused row to the host so a `preview` slot can ──\n // follow keyboard, hover and typing without the host reaching into the listbox.\n // Watching the derived item (not just the index) also catches the row under a\n // stable index changing because the filtered list itself changed.\n watch(\n () => filteredItems.value[focusedIndex.value],\n (item) => {\n emit('active-change', item ? { item, label: item.label, value: item.value } : { label: '', value: '' });\n },\n );\n\n // The preview pane folds away while its slot is empty, so a palette that never\n // uses it keeps the plain single-column layout.\n const hasPreview = signal(false);\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, handleBackdropClick, overlay, requestClose, mount } = 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({\n handler: () => {\n overlay.toggle('keyboard', 'trigger');\n },\n id: 'command-palette-toggle',\n shortcut: trimmed,\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 mount();\n if (dialogRef.value) onEvent(dialogRef.value, 'click', handleBackdropClick);\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 document: el.ownerDocument,\n });\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 class=\"body\">\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 <ore-keyboard-key>${key}</ore-keyboard-key>\n `,\n )}\n </span>\n `\n : ''\n }\n </div>\n `,\n )}\n </div>\n <div class=\"preview\" part=\"preview\" role=\"region\" aria-label=\"Preview\" ?hidden=\"${() => !hasPreview.value}\">\n <slot\n name=\"preview\"\n @slotchange=\"${(e: Event) => {\n hasPreview.value = (e.target as HTMLSlotElement).assignedElements().length > 0;\n }}\"\n ></slot>\n </div>\n </div>\n <div class=\"footer\" part=\"footer\">\n <span class=\"footer-hint\">\n <ore-keyboard-key>↑</ore-keyboard-key>\n <ore-keyboard-key>↓</ore-keyboard-key>\n Navigate\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Enter</ore-keyboard-key>\n Select\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Esc</ore-keyboard-key>\n Close\n </span>\n </div>\n </div>\n </dialog>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{reducedMotionMixin as e}from"./styles/mixins/accessibility.css.js";import{announce as t}from"./core/announcer.js";import"./_dev.js";import{lifecycleSignal as n}from"./core/ore.js";import{createListControl as r}from"./core/nav.js";import{parseOptionalBool as i}from"./core/parse.js";import{ICON_TAG as a}from"./icon.js";import{useDialogControl as o}from"./overlay/shared/use-dialog.js";import s from"./overlay/command-palette/command-palette.css.js";import{buildRows as c,filterItems as l,normalizeItem as u,parseSlottedItems as d,splitShortcutKeys as f}from"./overlay/command-palette/command-palette-items.js";import{KEYBOARD_SHORTCUT_TAG as p}from"./keyboard-key.js";import{computed as m,signal as h,watch as g}from"@vielzeug/ripple";import{define as _,getHost as v,html as y,onCleanup as b,onEvent as x,onMounted as S,prop as C,ref as w,useEmit as T}from"@vielzeug/ore";import{createKeymap as E}from"@vielzeug/keymap";var D=`ore-command-palette-item`;_(D,{setup(){return y``}});var O=`ore-command-palette`;_(O,{props:{"default-open":C.bool(!1),"empty-text":C.string(`No results found.`),items:C.data(),"keep-open-on-select":C.bool(!1),label:C.string(`Command palette`),loading:C.bool(!1),"no-filter":C.bool(!1),open:{default:void 0,parse:i},placeholder:C.string(`Type a command or search…`),shortcut:C.string(`mod+k`)},setup(e){let i=v(),a=T(),s=n(b),p=w(),_=w(),C=`cmdk-listbox`,D=null,O=h(``),k=h([]),reparseSlottedItems=()=>{k.value=d([...i.children])};reparseSlottedItems();let A=new MutationObserver(reparseSlottedItems);A.observe(i,{characterData:!0,childList:!0,subtree:!0}),b(()=>A.disconnect());let j=m(()=>{let t=e.items.value;return Array.isArray(t)?t.map(u):k.value}),M=m(()=>l(j.value,O.value,!!e[`no-filter`].value)),N=m(()=>c(M.value)),scrollFocusedIntoView=()=>{D?.querySelector(`[data-focused]`)?.scrollIntoView({block:`nearest`})},P=r({getItems:()=>M.value,isItemDisabled:e=>e.disabled,onNavigate:scrollFocusedIntoView,signal:s}),{focusedIndex:F}=P,resetSearch=()=>{O.value=``,P.reset()},{closeWithAnimation:I,handleBackdropClick:L,overlay:R,requestClose:z,mount:B}=o({defaultOpen:e[`default-open`],dialogRef:p,getPanelEl:()=>p.value?.querySelector(`.panel`),host:i,initialFocus:m(()=>`.search-input`),isPersistent:()=>!1,onCleanup:b,onEvent:x,onNativeClose:e=>{a(`open-change`,{open:!1,reason:e}),resetSearch()},onOpen:e=>{a(`open-change`,{open:!0,reason:e}),P.navigate(`first`)},openProp:e.open,performClose:()=>I(),returnFocus:m(()=>!0)}),V=E();V.mount(window),b(()=>V.dispose());let H=null;g(e.shortcut,e=>{H?.(),H=null;let t=(e??``).trim();if(t)try{H=V.bind({handler:()=>{R.toggle(`keyboard`,`trigger`)},id:`command-palette-toggle`,shortcut:t})}catch(e){`${t}${e instanceof Error?e.message:String(e)}`}},{immediate:!0}),S(()=>{B(),p.value&&x(p.value,`click`,L)});function selectItem(t){t.disabled||(a(`select`,{item:t,label:t.label,value:t.value}),e[`keep-open-on-select`].value?_.value?.focus():z(`trigger`))}function handleInput(n){let r=n.target.value;O.value=r,P.navigate(`first`),a(`search`,{query:r});let o=M.value.length;t(o===0?e[`empty-text`].value:`${o} result${o===1?``:`s`} found`,{document:i.ownerDocument})}function handleKeydown(e){if(!P.handleKeydown(e)&&e.key===`Enter`){let t=P.getActiveItem()??M.value.find(e=>!e.disabled);t&&(e.preventDefault(),selectItem(t))}}return y`
1
+ import{reducedMotionMixin as e}from"./styles/mixins/accessibility.css.js";import{announce as t}from"./core/announcer.js";import"./_dev.js";import{lifecycleSignal as n}from"./core/ore.js";import{createListControl as r}from"./core/nav.js";import{parseOptionalBool as i}from"./core/parse.js";import{ICON_TAG as a}from"./icon.js";import{useDialogControl as o}from"./overlay/shared/use-dialog.js";import s from"./overlay/command-palette/command-palette.css.js";import{buildRows as c,filterItems as l,normalizeItem as u,parseSlottedItems as d,splitShortcutKeys as f}from"./overlay/command-palette/command-palette-items.js";import{KEYBOARD_SHORTCUT_TAG as p}from"./keyboard-key.js";import{computed as m,signal as h,watch as g}from"@vielzeug/ripple";import{define as _,getHost as v,html as y,onCleanup as b,onEvent as x,onMounted as S,prop as C,ref as w,useEmit as T}from"@vielzeug/ore";import{createKeymap as E}from"@vielzeug/keymap";var D=`ore-command-palette-item`;_(D,{setup(){return y``}});var O=`ore-command-palette`;_(O,{props:{"default-open":C.bool(!1),"empty-text":C.string(`No results found.`),items:C.data(),"keep-open-on-select":C.bool(!1),label:C.string(`Command palette`),loading:C.bool(!1),"no-filter":C.bool(!1),open:{default:void 0,parse:i},placeholder:C.string(`Type a command or search…`),shortcut:C.string(`mod+k`)},setup(e){let i=v(),a=T(),s=n(b),p=w(),_=w(),C=`cmdk-listbox`,D=null,O=h(``),k=h([]),reparseSlottedItems=()=>{k.value=d([...i.children])};reparseSlottedItems();let A=new MutationObserver(reparseSlottedItems);A.observe(i,{characterData:!0,childList:!0,subtree:!0}),b(()=>A.disconnect());let j=m(()=>{let t=e.items.value;return Array.isArray(t)?t.map(u):k.value}),M=m(()=>l(j.value,O.value,!!e[`no-filter`].value)),N=m(()=>c(M.value)),scrollFocusedIntoView=()=>{D?.querySelector(`[data-focused]`)?.scrollIntoView({block:`nearest`})},P=r({getItems:()=>M.value,isItemDisabled:e=>e.disabled,onNavigate:scrollFocusedIntoView,signal:s}),{focusedIndex:F}=P;g(()=>M.value[F.value],e=>{a(`active-change`,e?{item:e,label:e.label,value:e.value}:{label:``,value:``})});let I=h(!1),resetSearch=()=>{O.value=``,P.reset()},{closeWithAnimation:L,handleBackdropClick:R,overlay:z,requestClose:B,mount:V}=o({defaultOpen:e[`default-open`],dialogRef:p,getPanelEl:()=>p.value?.querySelector(`.panel`),host:i,initialFocus:m(()=>`.search-input`),isPersistent:()=>!1,onCleanup:b,onEvent:x,onNativeClose:e=>{a(`open-change`,{open:!1,reason:e}),resetSearch()},onOpen:e=>{a(`open-change`,{open:!0,reason:e}),P.navigate(`first`)},openProp:e.open,performClose:()=>L(),returnFocus:m(()=>!0)}),H=E();H.mount(window),b(()=>H.dispose());let U=null;g(e.shortcut,e=>{U?.(),U=null;let t=(e??``).trim();if(t)try{U=H.bind({handler:()=>{z.toggle(`keyboard`,`trigger`)},id:`command-palette-toggle`,shortcut:t})}catch(e){`${t}${e instanceof Error?e.message:String(e)}`}},{immediate:!0}),S(()=>{V(),p.value&&x(p.value,`click`,R)});function selectItem(t){t.disabled||(a(`select`,{item:t,label:t.label,value:t.value}),e[`keep-open-on-select`].value?_.value?.focus():B(`trigger`))}function handleInput(n){let r=n.target.value;O.value=r,P.navigate(`first`),a(`search`,{query:r});let o=M.value.length;t(o===0?e[`empty-text`].value:`${o} result${o===1?``:`s`} found`,{document:i.ownerDocument})}function handleKeydown(e){if(!P.handleKeydown(e)&&e.key===`Enter`){let t=P.getActiveItem()??M.value.find(e=>!e.disabled);t&&(e.preventDefault(),selectItem(t))}}return y`
2
2
  <dialog ref="${p}" class="dialog" part="dialog" aria-label="${e.label}" aria-modal="true">
3
3
  <div class="panel" part="panel">
4
4
  <div class="search" part="search">
@@ -21,6 +21,7 @@ import{reducedMotionMixin as e}from"./styles/mixins/accessibility.css.js";import
21
21
  @keydown="${handleKeydown}" />
22
22
  <span class="search-loader" ?hidden="${()=>!e.loading.value}" aria-hidden="true"></span>
23
23
  </div>
24
+ <div class="body">
24
25
  <div
25
26
  class="listbox"
26
27
  part="listbox"
@@ -61,6 +62,13 @@ import{reducedMotionMixin as e}from"./styles/mixins/accessibility.css.js";import
61
62
  </div>
62
63
  `)}
63
64
  </div>
65
+ <div class="preview" part="preview" role="region" aria-label="Preview" ?hidden="${()=>!I.value}">
66
+ <slot
67
+ name="preview"
68
+ @slotchange="${e=>{I.value=e.target.assignedElements().length>0}}"
69
+ ></slot>
70
+ </div>
71
+ </div>
64
72
  <div class="footer" part="footer">
65
73
  <span class="footer-hint">
66
74
  <ore-keyboard-key>↑</ore-keyboard-key>
@@ -1 +1 @@
1
- {"version":3,"file":"command-palette.js","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';\n\nimport { announce, createListControl, lifecycleSignal, parseOptionalBool } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport { useDialogControl } from '../shared/use-dialog';\nimport componentStyles from './command-palette.css?inline';\nimport type { CommandPaletteItem, OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\nimport { buildRows, filterItems, normalizeItem, parseSlottedItems, splitShortcutKeys } from './command-palette-items';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { KEYBOARD_SHORTCUT_TAG } from '../../content/keyboard-key/keyboard-key';\nexport type { OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\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, handleBackdropClick, overlay, requestClose, mount } = 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({\n handler: () => {\n overlay.toggle('keyboard', 'trigger');\n },\n id: 'command-palette-toggle',\n shortcut: trimmed,\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 mount();\n if (dialogRef.value) onEvent(dialogRef.value, 'click', handleBackdropClick);\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 document: el.ownerDocument,\n });\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 <ore-keyboard-key>${key}</ore-keyboard-key>\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 <ore-keyboard-key>↑</ore-keyboard-key>\n <ore-keyboard-key>↓</ore-keyboard-key>\n Navigate\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Enter</ore-keyboard-key>\n Select\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Esc</ore-keyboard-key>\n Close\n </span>\n </div>\n </div>\n </dialog>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-palette.js","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';\n\nimport { announce, createListControl, lifecycleSignal, parseOptionalBool } from '../../core';\nimport { reducedMotionMixin } from '../../styles';\nimport { useDialogControl } from '../shared/use-dialog';\nimport componentStyles from './command-palette.css?inline';\nimport type { CommandPaletteItem, OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\nimport { buildRows, filterItems, normalizeItem, parseSlottedItems, splitShortcutKeys } from './command-palette-items';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { KEYBOARD_SHORTCUT_TAG } from '../../content/keyboard-key/keyboard-key';\nexport type { OreCommandPaletteEvents, OreCommandPaletteProps } from './command-palette.types';\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 active-change - Fired when the focused row changes (keyboard, hover, typing, open, close). detail: `{ value, label, item }`; `item` is `undefined` and `value` empty when no row is active\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 * @slot preview - Optional detail panel rendered beside the list, shown while it has content. Pair it with `active-change` to preview the focused row\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 * @cssprop --command-palette-preview-width - Preview panel width when the `preview` slot has content\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 * @part preview - Preview panel shown beside the list\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 // ── Active row: mirrors the focused row to the host so a `preview` slot can ──\n // follow keyboard, hover and typing without the host reaching into the listbox.\n // Watching the derived item (not just the index) also catches the row under a\n // stable index changing because the filtered list itself changed.\n watch(\n () => filteredItems.value[focusedIndex.value],\n (item) => {\n emit('active-change', item ? { item, label: item.label, value: item.value } : { label: '', value: '' });\n },\n );\n\n // The preview pane folds away while its slot is empty, so a palette that never\n // uses it keeps the plain single-column layout.\n const hasPreview = signal(false);\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, handleBackdropClick, overlay, requestClose, mount } = 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({\n handler: () => {\n overlay.toggle('keyboard', 'trigger');\n },\n id: 'command-palette-toggle',\n shortcut: trimmed,\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 mount();\n if (dialogRef.value) onEvent(dialogRef.value, 'click', handleBackdropClick);\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 document: el.ownerDocument,\n });\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 class=\"body\">\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 <ore-keyboard-key>${key}</ore-keyboard-key>\n `,\n )}\n </span>\n `\n : ''\n }\n </div>\n `,\n )}\n </div>\n <div class=\"preview\" part=\"preview\" role=\"region\" aria-label=\"Preview\" ?hidden=\"${() => !hasPreview.value}\">\n <slot\n name=\"preview\"\n @slotchange=\"${(e: Event) => {\n hasPreview.value = (e.target as HTMLSlotElement).assignedElements().length > 0;\n }}\"\n ></slot>\n </div>\n </div>\n <div class=\"footer\" part=\"footer\">\n <span class=\"footer-hint\">\n <ore-keyboard-key>↑</ore-keyboard-key>\n <ore-keyboard-key>↓</ore-keyboard-key>\n Navigate\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Enter</ore-keyboard-key>\n Select\n </span>\n <span class=\"footer-hint\">\n <ore-keyboard-key>Esc</ore-keyboard-key>\n Close\n </span>\n </div>\n </div>\n </dialog>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var e={ariaControlsElements:`aria-controls`,ariaDescribedByElements:`aria-describedby`,ariaLabelledByElements:`aria-labelledby`};function setAriaReflection(t,n,r){if(n in t){t[n]=r.length>0?r:null;return}let i=e[n],a=r.map(e=>e.id).filter(Boolean);a.length===0?t.removeAttribute(i):t.setAttribute(i,a.join(` `))}exports.setAriaReflection=setAriaReflection;
1
+ var e={ariaActiveDescendantElement:`aria-activedescendant`,ariaControlsElements:`aria-controls`,ariaDescribedByElements:`aria-describedby`,ariaLabelledByElements:`aria-labelledby`};function setAriaReflection(t,n,r){if(n in t){if(n===`ariaActiveDescendantElement`){let e=t,n=r[0]??null;if(e.ariaActiveDescendantElement=n,e.ariaActiveDescendantElement===n)return}else{let e=t;e[n]=r.length>0?r:null;let i=e[n];if(r.length===0?!i||i.length===0:i?.length===r.length&&r.every((e,t)=>i[t]===e))return}}let i=e[n],a=r.map(e=>e.id).filter(Boolean);a.length===0?t.removeAttribute(i):t.setAttribute(i,a.join(` `))}exports.setAriaReflection=setAriaReflection;
2
2
  //# sourceMappingURL=aria-reflection.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"aria-reflection.cjs","names":[],"sources":["../../src/core/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot\n * resolve across shadow-tree boundaries: the browser only looks up an `id` within the same\n * root as the referencing element, so `<div id=\"x\">` inside one component's shadow root is\n * invisible to `aria-controls=\"x\"` set inside another component's shadow root, even when both\n * custom elements are siblings in the light DOM. This is a platform limitation, not a bug :\n * see the WICG \"reference target\" explainer for the full rationale.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely: these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n (el as unknown as Record<AriaReflectionProperty, readonly Element[] | null>)[property] =\n targets.length > 0 ? targets : null;\n\n return;\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":"AAoBA,IAAM,EAA2D,CAC/D,qBAAsB,gBACtB,wBAAyB,mBACzB,uBAAwB,iBAC1B,EASA,SAAgB,kBAAkB,EAAa,EAAkC,EAAmC,CAClH,GAAI,KAAY,EAAI,CAClB,EAA6E,GAC3E,EAAQ,OAAS,EAAI,EAAU,KAEjC,MACF,CAEA,IAAM,EAAO,EAAiB,GACxB,EAAM,EAAQ,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO,EAEzD,EAAI,SAAW,EAAG,EAAG,gBAAgB,CAAI,EACxC,EAAG,aAAa,EAAM,EAAI,KAAK,GAAG,CAAC,CAC1C"}
1
+ {"version":3,"file":"aria-reflection.cjs","names":[],"sources":["../../src/core/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-activedescendant`, `aria-describedby`,\n * `aria-labelledby`, …) cannot resolve across shadow-tree boundaries: the browser only looks\n * up an `id` within the same root as the referencing element, so a target inside one\n * component's shadow root is invisible to an attribute set inside another component's root.\n * This is a platform limitation, not a bug; see the WICG \"reference target\" explainer.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaActiveDescendantElement`, `ariaDescribedByElements`,\n * `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely: these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty =\n | 'ariaActiveDescendantElement'\n | 'ariaControlsElements'\n | 'ariaDescribedByElements'\n | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaActiveDescendantElement: 'aria-activedescendant',\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n if (property === 'ariaActiveDescendantElement') {\n const reflection = el as unknown as { ariaActiveDescendantElement: Element | null };\n const target = targets[0] ?? null;\n reflection.ariaActiveDescendantElement = target;\n if (reflection.ariaActiveDescendantElement === target) return;\n } else {\n const reflection = el as unknown as Record<\n Exclude<AriaReflectionProperty, 'ariaActiveDescendantElement'>,\n readonly Element[] | null\n >;\n const targetList = targets.length > 0 ? targets : null;\n reflection[property] = targetList;\n\n const current = reflection[property];\n if (\n targets.length === 0\n ? !current || current.length === 0\n : current?.length === targets.length && targets.every((target, index) => current[index] === target)\n ) {\n return;\n }\n }\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":"AAwBA,IAAM,EAA2D,CAC/D,4BAA6B,wBAC7B,qBAAsB,gBACtB,wBAAyB,mBACzB,uBAAwB,iBAC1B,EASA,SAAgB,kBAAkB,EAAa,EAAkC,EAAmC,CAClH,GAAI,KAAY,EAAI,CAClB,GAAI,IAAa,8BAA+B,CAC9C,IAAM,EAAa,EACb,EAAS,EAAQ,IAAM,KAE7B,GADA,EAAW,4BAA8B,EACrC,EAAW,8BAAgC,EAAQ,MACzD,KAAO,CACL,IAAM,EAAa,EAKnB,EAAW,GADQ,EAAQ,OAAS,EAAI,EAAU,KAGlD,IAAM,EAAU,EAAW,GAC3B,GACE,EAAQ,SAAW,EACf,CAAC,GAAW,EAAQ,SAAW,EAC/B,GAAS,SAAW,EAAQ,QAAU,EAAQ,OAAO,EAAQ,IAAU,EAAQ,KAAW,CAAM,EAEpG,MAEJ,CACF,CAEA,IAAM,EAAO,EAAiB,GACxB,EAAM,EAAQ,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO,EAEzD,EAAI,SAAW,EAAG,EAAG,gBAAgB,CAAI,EACxC,EAAG,aAAa,EAAM,EAAI,KAAK,GAAG,CAAC,CAC1C"}
@@ -1,21 +1,21 @@
1
1
  /**
2
2
  * Cross-shadow-root ARIA relationship helper.
3
3
  *
4
- * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot
5
- * resolve across shadow-tree boundaries: the browser only looks up an `id` within the same
6
- * root as the referencing element, so `<div id="x">` inside one component's shadow root is
7
- * invisible to `aria-controls="x"` set inside another component's shadow root, even when both
8
- * custom elements are siblings in the light DOM. This is a platform limitation, not a bug :
9
- * see the WICG "reference target" explainer for the full rationale.
4
+ * Plain IDREF attributes (`aria-controls`, `aria-activedescendant`, `aria-describedby`,
5
+ * `aria-labelledby`, …) cannot resolve across shadow-tree boundaries: the browser only looks
6
+ * up an `id` within the same root as the referencing element, so a target inside one
7
+ * component's shadow root is invisible to an attribute set inside another component's root.
8
+ * This is a platform limitation, not a bug; see the WICG "reference target" explainer.
10
9
  *
11
10
  * Modern browsers instead expose element-reflection IDL properties
12
- * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept
11
+ * (`ariaControlsElements`, `ariaActiveDescendantElement`, `ariaDescribedByElements`,
12
+ * `ariaLabelledByElements`, …) that accept
13
13
  * live `Element` references directly, sidestepping ID lookup entirely: these work across
14
14
  * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,
15
15
  * which is still correct for same-root pairings and degrades gracefully (no relationship)
16
16
  * for cross-root ones instead of throwing.
17
17
  */
18
- export type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';
18
+ export type AriaReflectionProperty = 'ariaActiveDescendantElement' | 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';
19
19
  /**
20
20
  * Associates `el` with `targets` via the given ARIA relationship. Prefers the
21
21
  * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute
@@ -1 +1 @@
1
- {"version":3,"file":"aria-reflection.d.ts","sourceRoot":"","sources":["../../src/core/aria-reflection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG,yBAAyB,GAAG,wBAAwB,CAAC;AAQnH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAalH"}
1
+ {"version":3,"file":"aria-reflection.d.ts","sourceRoot":"","sources":["../../src/core/aria-reflection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,sBAAsB,GAC9B,6BAA6B,GAC7B,sBAAsB,GACtB,yBAAyB,GACzB,wBAAwB,CAAC;AAS7B;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CA+BlH"}
@@ -1,2 +1,2 @@
1
- var e={ariaControlsElements:`aria-controls`,ariaDescribedByElements:`aria-describedby`,ariaLabelledByElements:`aria-labelledby`};function setAriaReflection(t,n,r){if(n in t){t[n]=r.length>0?r:null;return}let i=e[n],a=r.map(e=>e.id).filter(Boolean);a.length===0?t.removeAttribute(i):t.setAttribute(i,a.join(` `))}export{setAriaReflection};
1
+ var e={ariaActiveDescendantElement:`aria-activedescendant`,ariaControlsElements:`aria-controls`,ariaDescribedByElements:`aria-describedby`,ariaLabelledByElements:`aria-labelledby`};function setAriaReflection(t,n,r){if(n in t){if(n===`ariaActiveDescendantElement`){let e=t,n=r[0]??null;if(e.ariaActiveDescendantElement=n,e.ariaActiveDescendantElement===n)return}else{let e=t;e[n]=r.length>0?r:null;let i=e[n];if(r.length===0?!i||i.length===0:i?.length===r.length&&r.every((e,t)=>i[t]===e))return}}let i=e[n],a=r.map(e=>e.id).filter(Boolean);a.length===0?t.removeAttribute(i):t.setAttribute(i,a.join(` `))}export{setAriaReflection};
2
2
  //# sourceMappingURL=aria-reflection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aria-reflection.js","names":[],"sources":["../../src/core/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot\n * resolve across shadow-tree boundaries: the browser only looks up an `id` within the same\n * root as the referencing element, so `<div id=\"x\">` inside one component's shadow root is\n * invisible to `aria-controls=\"x\"` set inside another component's shadow root, even when both\n * custom elements are siblings in the light DOM. This is a platform limitation, not a bug :\n * see the WICG \"reference target\" explainer for the full rationale.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely: these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n (el as unknown as Record<AriaReflectionProperty, readonly Element[] | null>)[property] =\n targets.length > 0 ? targets : null;\n\n return;\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":"AAoBA,IAAM,EAA2D,CAC/D,qBAAsB,gBACtB,wBAAyB,mBACzB,uBAAwB,iBAC1B,EASA,SAAgB,kBAAkB,EAAa,EAAkC,EAAmC,CAClH,GAAI,KAAY,EAAI,CAClB,EAA6E,GAC3E,EAAQ,OAAS,EAAI,EAAU,KAEjC,MACF,CAEA,IAAM,EAAO,EAAiB,GACxB,EAAM,EAAQ,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO,EAEzD,EAAI,SAAW,EAAG,EAAG,gBAAgB,CAAI,EACxC,EAAG,aAAa,EAAM,EAAI,KAAK,GAAG,CAAC,CAC1C"}
1
+ {"version":3,"file":"aria-reflection.js","names":[],"sources":["../../src/core/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-activedescendant`, `aria-describedby`,\n * `aria-labelledby`, …) cannot resolve across shadow-tree boundaries: the browser only looks\n * up an `id` within the same root as the referencing element, so a target inside one\n * component's shadow root is invisible to an attribute set inside another component's root.\n * This is a platform limitation, not a bug; see the WICG \"reference target\" explainer.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaActiveDescendantElement`, `ariaDescribedByElements`,\n * `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely: these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty =\n | 'ariaActiveDescendantElement'\n | 'ariaControlsElements'\n | 'ariaDescribedByElements'\n | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaActiveDescendantElement: 'aria-activedescendant',\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n if (property === 'ariaActiveDescendantElement') {\n const reflection = el as unknown as { ariaActiveDescendantElement: Element | null };\n const target = targets[0] ?? null;\n reflection.ariaActiveDescendantElement = target;\n if (reflection.ariaActiveDescendantElement === target) return;\n } else {\n const reflection = el as unknown as Record<\n Exclude<AriaReflectionProperty, 'ariaActiveDescendantElement'>,\n readonly Element[] | null\n >;\n const targetList = targets.length > 0 ? targets : null;\n reflection[property] = targetList;\n\n const current = reflection[property];\n if (\n targets.length === 0\n ? !current || current.length === 0\n : current?.length === targets.length && targets.every((target, index) => current[index] === target)\n ) {\n return;\n }\n }\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":"AAwBA,IAAM,EAA2D,CAC/D,4BAA6B,wBAC7B,qBAAsB,gBACtB,wBAAyB,mBACzB,uBAAwB,iBAC1B,EASA,SAAgB,kBAAkB,EAAa,EAAkC,EAAmC,CAClH,GAAI,KAAY,EAAI,CAClB,GAAI,IAAa,8BAA+B,CAC9C,IAAM,EAAa,EACb,EAAS,EAAQ,IAAM,KAE7B,GADA,EAAW,4BAA8B,EACrC,EAAW,8BAAgC,EAAQ,MACzD,KAAO,CACL,IAAM,EAAa,EAKnB,EAAW,GADQ,EAAQ,OAAS,EAAI,EAAU,KAGlD,IAAM,EAAU,EAAW,GAC3B,GACE,EAAQ,SAAW,EACf,CAAC,GAAW,EAAQ,SAAW,EAC/B,GAAS,SAAW,EAAQ,QAAU,EAAQ,OAAO,EAAQ,IAAU,EAAQ,KAAW,CAAM,EAEpG,MAEJ,CACF,CAEA,IAAM,EAAO,EAAiB,GACxB,EAAM,EAAQ,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO,EAEzD,EAAI,SAAW,EAAG,EAAG,gBAAgB,CAAI,EACxC,EAAG,aAAa,EAAM,EAAI,KAAK,GAAG,CAAC,CAC1C"}