stimeo-ui 0.2.0 → 0.3.0

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 (227) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/cable/index.d.ts +16 -16
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.d.ts +3 -2
  5. package/dist/controllers/accordion_controller.js +10 -0
  6. package/dist/controllers/accordion_controller.js.map +1 -1
  7. package/dist/controllers/alert_dialog_controller.d.ts +64 -0
  8. package/dist/controllers/alert_dialog_controller.js +318 -0
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -0
  10. package/dist/controllers/breadcrumb_controller.d.ts +97 -10
  11. package/dist/controllers/breadcrumb_controller.js +225 -13
  12. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  13. package/dist/controllers/calendar_controller.d.ts +11 -1
  14. package/dist/controllers/calendar_controller.js +89 -22
  15. package/dist/controllers/calendar_controller.js.map +1 -1
  16. package/dist/controllers/carousel_controller.d.ts +115 -0
  17. package/dist/controllers/carousel_controller.js +313 -0
  18. package/dist/controllers/carousel_controller.js.map +1 -0
  19. package/dist/controllers/clipboard_controller.d.ts +72 -0
  20. package/dist/controllers/clipboard_controller.js +144 -0
  21. package/dist/controllers/clipboard_controller.js.map +1 -0
  22. package/dist/controllers/collapsible_controller.d.ts +70 -0
  23. package/dist/controllers/collapsible_controller.js +327 -0
  24. package/dist/controllers/collapsible_controller.js.map +1 -0
  25. package/dist/controllers/color_picker_controller.d.ts +87 -0
  26. package/dist/controllers/color_picker_controller.js +252 -0
  27. package/dist/controllers/color_picker_controller.js.map +1 -0
  28. package/dist/controllers/combobox_controller.d.ts +14 -1
  29. package/dist/controllers/combobox_controller.js +162 -23
  30. package/dist/controllers/combobox_controller.js.map +1 -1
  31. package/dist/controllers/command_palette_controller.d.ts +2 -0
  32. package/dist/controllers/command_palette_controller.js +194 -17
  33. package/dist/controllers/command_palette_controller.js.map +1 -1
  34. package/dist/controllers/confirm_controller.js.map +1 -1
  35. package/dist/controllers/context_menu_controller.d.ts +7 -6
  36. package/dist/controllers/context_menu_controller.js +32 -10
  37. package/dist/controllers/context_menu_controller.js.map +1 -1
  38. package/dist/controllers/count_up_controller.js +8 -1
  39. package/dist/controllers/count_up_controller.js.map +1 -1
  40. package/dist/controllers/currency_input_controller.d.ts +72 -0
  41. package/dist/controllers/currency_input_controller.js +147 -0
  42. package/dist/controllers/currency_input_controller.js.map +1 -0
  43. package/dist/controllers/data_grid_controller.d.ts +89 -0
  44. package/dist/controllers/data_grid_controller.js +246 -0
  45. package/dist/controllers/data_grid_controller.js.map +1 -0
  46. package/dist/controllers/date_range_picker_controller.d.ts +88 -0
  47. package/dist/controllers/date_range_picker_controller.js +441 -0
  48. package/dist/controllers/date_range_picker_controller.js.map +1 -0
  49. package/dist/controllers/dialog_controller.js.map +1 -1
  50. package/dist/controllers/dismissible_controller.d.ts +57 -0
  51. package/dist/controllers/dismissible_controller.js +117 -0
  52. package/dist/controllers/dismissible_controller.js.map +1 -0
  53. package/dist/controllers/drawer_controller.d.ts +96 -0
  54. package/dist/controllers/drawer_controller.js +630 -0
  55. package/dist/controllers/drawer_controller.js.map +1 -0
  56. package/dist/controllers/dropdown_controller.d.ts +1 -2
  57. package/dist/controllers/dropdown_controller.js.map +1 -1
  58. package/dist/controllers/editable_controller.d.ts +73 -0
  59. package/dist/controllers/editable_controller.js +169 -0
  60. package/dist/controllers/editable_controller.js.map +1 -0
  61. package/dist/controllers/file_dropzone_controller.d.ts +92 -0
  62. package/dist/controllers/file_dropzone_controller.js +165 -0
  63. package/dist/controllers/file_dropzone_controller.js.map +1 -0
  64. package/dist/controllers/filter_controller.d.ts +60 -0
  65. package/dist/controllers/filter_controller.js +86 -0
  66. package/dist/controllers/filter_controller.js.map +1 -0
  67. package/dist/controllers/flash_controller.js +36 -5
  68. package/dist/controllers/flash_controller.js.map +1 -1
  69. package/dist/controllers/focus_controller.d.ts +1 -1
  70. package/dist/controllers/focus_controller.js.map +1 -1
  71. package/dist/controllers/form_field_controller.js.map +1 -1
  72. package/dist/controllers/form_validation_controller.js +1 -1
  73. package/dist/controllers/form_validation_controller.js.map +1 -1
  74. package/dist/controllers/highlight_controller.js +6 -4
  75. package/dist/controllers/highlight_controller.js.map +1 -1
  76. package/dist/controllers/hover_card_controller.d.ts +1 -1
  77. package/dist/controllers/hover_card_controller.js.map +1 -1
  78. package/dist/controllers/intersection_controller.js +67 -19
  79. package/dist/controllers/intersection_controller.js.map +1 -1
  80. package/dist/controllers/lazy_frame_controller.js +54 -11
  81. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  82. package/dist/controllers/listbox_controller.d.ts +9 -2
  83. package/dist/controllers/listbox_controller.js +257 -53
  84. package/dist/controllers/listbox_controller.js.map +1 -1
  85. package/dist/controllers/local_time_controller.js +2 -2
  86. package/dist/controllers/local_time_controller.js.map +1 -1
  87. package/dist/controllers/masonry_controller.d.ts +54 -0
  88. package/dist/controllers/masonry_controller.js +142 -0
  89. package/dist/controllers/masonry_controller.js.map +1 -0
  90. package/dist/controllers/menu_controller.d.ts +29 -11
  91. package/dist/controllers/menu_controller.js +104 -17
  92. package/dist/controllers/menu_controller.js.map +1 -1
  93. package/dist/controllers/menubar_controller.d.ts +142 -0
  94. package/dist/controllers/menubar_controller.js +785 -0
  95. package/dist/controllers/menubar_controller.js.map +1 -0
  96. package/dist/controllers/meter_controller.js.map +1 -1
  97. package/dist/controllers/multi_select_controller.d.ts +141 -0
  98. package/dist/controllers/multi_select_controller.js +755 -0
  99. package/dist/controllers/multi_select_controller.js.map +1 -0
  100. package/dist/controllers/navigation_menu_controller.d.ts +146 -0
  101. package/dist/controllers/navigation_menu_controller.js +511 -0
  102. package/dist/controllers/navigation_menu_controller.js.map +1 -0
  103. package/dist/controllers/nested_form_controller.d.ts +4 -5
  104. package/dist/controllers/nested_form_controller.js.map +1 -1
  105. package/dist/controllers/number_input_controller.d.ts +1 -1
  106. package/dist/controllers/number_input_controller.js +7 -0
  107. package/dist/controllers/number_input_controller.js.map +1 -1
  108. package/dist/controllers/otp_controller.js +18 -1
  109. package/dist/controllers/otp_controller.js.map +1 -1
  110. package/dist/controllers/overflow_indicator_controller.d.ts +15 -6
  111. package/dist/controllers/overflow_indicator_controller.js +246 -27
  112. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  113. package/dist/controllers/overflow_menu_controller.d.ts +78 -19
  114. package/dist/controllers/overflow_menu_controller.js +381 -57
  115. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  116. package/dist/controllers/pagination_controller.d.ts +36 -5
  117. package/dist/controllers/pagination_controller.js +163 -32
  118. package/dist/controllers/pagination_controller.js.map +1 -1
  119. package/dist/controllers/password_reveal_controller.d.ts +49 -0
  120. package/dist/controllers/password_reveal_controller.js +117 -0
  121. package/dist/controllers/password_reveal_controller.js.map +1 -0
  122. package/dist/controllers/persist_controller.js +6 -6
  123. package/dist/controllers/persist_controller.js.map +1 -1
  124. package/dist/controllers/pointer_drag_controller.d.ts +2 -3
  125. package/dist/controllers/pointer_drag_controller.js +9 -1
  126. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  127. package/dist/controllers/popover_controller.d.ts +1 -2
  128. package/dist/controllers/popover_controller.js +2 -2
  129. package/dist/controllers/popover_controller.js.map +1 -1
  130. package/dist/controllers/portal_controller.d.ts +4 -4
  131. package/dist/controllers/portal_controller.js.map +1 -1
  132. package/dist/controllers/progress_controller.js.map +1 -1
  133. package/dist/controllers/radio_group_controller.js +22 -3
  134. package/dist/controllers/radio_group_controller.js.map +1 -1
  135. package/dist/controllers/range_slider_controller.d.ts +97 -0
  136. package/dist/controllers/range_slider_controller.js +192 -0
  137. package/dist/controllers/range_slider_controller.js.map +1 -0
  138. package/dist/controllers/rating_controller.js +16 -2
  139. package/dist/controllers/rating_controller.js.map +1 -1
  140. package/dist/controllers/read_more_controller.d.ts +52 -0
  141. package/dist/controllers/read_more_controller.js +238 -0
  142. package/dist/controllers/read_more_controller.js.map +1 -0
  143. package/dist/controllers/reading_progress_controller.js.map +1 -1
  144. package/dist/controllers/resizable_controller.d.ts +3 -1
  145. package/dist/controllers/resizable_controller.js +65 -1
  146. package/dist/controllers/resizable_controller.js.map +1 -1
  147. package/dist/controllers/roving_controller.js +17 -2
  148. package/dist/controllers/roving_controller.js.map +1 -1
  149. package/dist/controllers/scroll_area_controller.js +101 -14
  150. package/dist/controllers/scroll_area_controller.js.map +1 -1
  151. package/dist/controllers/scroll_restore_controller.d.ts +54 -0
  152. package/dist/controllers/scroll_restore_controller.js +93 -0
  153. package/dist/controllers/scroll_restore_controller.js.map +1 -0
  154. package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
  155. package/dist/controllers/scroll_visibility_controller.js +40 -6
  156. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  157. package/dist/controllers/scrollspy_controller.d.ts +64 -15
  158. package/dist/controllers/scrollspy_controller.js +369 -74
  159. package/dist/controllers/scrollspy_controller.js.map +1 -1
  160. package/dist/controllers/separator_controller.d.ts +69 -0
  161. package/dist/controllers/separator_controller.js +96 -0
  162. package/dist/controllers/separator_controller.js.map +1 -0
  163. package/dist/controllers/sidebar_controller.d.ts +107 -0
  164. package/dist/controllers/sidebar_controller.js +761 -0
  165. package/dist/controllers/sidebar_controller.js.map +1 -0
  166. package/dist/controllers/skeleton_controller.js +1 -1
  167. package/dist/controllers/skeleton_controller.js.map +1 -1
  168. package/dist/controllers/slider_controller.d.ts +11 -1
  169. package/dist/controllers/slider_controller.js +32 -6
  170. package/dist/controllers/slider_controller.js.map +1 -1
  171. package/dist/controllers/sortable_controller.d.ts +3 -1
  172. package/dist/controllers/sortable_controller.js +34 -3
  173. package/dist/controllers/sortable_controller.js.map +1 -1
  174. package/dist/controllers/spinner_controller.js +1 -1
  175. package/dist/controllers/spinner_controller.js.map +1 -1
  176. package/dist/controllers/stepper_controller.d.ts +4 -0
  177. package/dist/controllers/stepper_controller.js +28 -12
  178. package/dist/controllers/stepper_controller.js.map +1 -1
  179. package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
  180. package/dist/controllers/stick_to_bottom_controller.js +9 -4
  181. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  182. package/dist/controllers/sticky_observer_controller.d.ts +17 -4
  183. package/dist/controllers/sticky_observer_controller.js +109 -20
  184. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  185. package/dist/controllers/submit_once_controller.d.ts +2 -2
  186. package/dist/controllers/submit_once_controller.js.map +1 -1
  187. package/dist/controllers/switch_controller.js +1 -0
  188. package/dist/controllers/switch_controller.js.map +1 -1
  189. package/dist/controllers/tabs_controller.d.ts +13 -4
  190. package/dist/controllers/tabs_controller.js +26 -3
  191. package/dist/controllers/tabs_controller.js.map +1 -1
  192. package/dist/controllers/tags_input_controller.d.ts +91 -0
  193. package/dist/controllers/tags_input_controller.js +295 -0
  194. package/dist/controllers/tags_input_controller.js.map +1 -0
  195. package/dist/controllers/theme_controller.d.ts +1 -1
  196. package/dist/controllers/theme_controller.js +42 -13
  197. package/dist/controllers/theme_controller.js.map +1 -1
  198. package/dist/controllers/time_picker_controller.d.ts +67 -0
  199. package/dist/controllers/time_picker_controller.js +231 -0
  200. package/dist/controllers/time_picker_controller.js.map +1 -0
  201. package/dist/controllers/toast_controller.d.ts +5 -14
  202. package/dist/controllers/toast_controller.js +40 -14
  203. package/dist/controllers/toast_controller.js.map +1 -1
  204. package/dist/controllers/toggle_group_controller.js +23 -2
  205. package/dist/controllers/toggle_group_controller.js.map +1 -1
  206. package/dist/controllers/toolbar_controller.d.ts +53 -13
  207. package/dist/controllers/toolbar_controller.js +230 -31
  208. package/dist/controllers/toolbar_controller.js.map +1 -1
  209. package/dist/controllers/tooltip_controller.d.ts +1 -1
  210. package/dist/controllers/tooltip_controller.js.map +1 -1
  211. package/dist/controllers/transition_controller.d.ts +14 -8
  212. package/dist/controllers/transition_controller.js +153 -38
  213. package/dist/controllers/transition_controller.js.map +1 -1
  214. package/dist/controllers/tree_view_controller.d.ts +135 -0
  215. package/dist/controllers/tree_view_controller.js +691 -0
  216. package/dist/controllers/tree_view_controller.js.map +1 -0
  217. package/dist/index.d.ts +27 -1818
  218. package/dist/index.js +4256 -915
  219. package/dist/index.js.map +1 -1
  220. package/dist/inspector/cli.d.ts +375 -39
  221. package/dist/inspector/cli.js +228 -14
  222. package/dist/inspector/cli.js.map +1 -1
  223. package/dist/inspector/cli_bin.js +419 -48
  224. package/dist/inspector/cli_bin.js.map +1 -1
  225. package/dist/inspector/examples.json +26 -26
  226. package/dist/inspector/manifest.json +1054 -127
  227. package/package.json +5 -6
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/composition_tracker.ts","../../src/utils/option_scroll.ts","../../src/controllers/combobox_controller.ts"],"names":[],"mappings":"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","file":"combobox_controller.js","sourcesContent":["/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/**\n * Scroll-follow for virtually-focused listbox options.\n *\n * Listbox-style widgets that track their active option with\n * `aria-activedescendant` keep DOM focus on the input/trigger, so the\n * browser's native \"scroll the focused element into view\" never runs — in a\n * scrollable list (`max-height` + `overflow`) the active option can walk right\n * out of sight on ArrowDown/ArrowUp. This helper keeps it visible.\n *\n * It adjusts the LIST's own `scrollTop` only — deliberately not\n * `scrollIntoView({ block: \"nearest\" })`, which may also scroll ancestor\n * scrolling boxes (the page) when a floating popup pokes past a viewport\n * edge, desyncing anchored/marker-composed placements. (Command-palette keeps\n * its `scrollIntoView` because its page is scroll-locked behind the modal;\n * page-floating popups must use this helper instead.)\n */\n\n/**\n * Scrolls `option` into view within `list` by minimally adjusting\n * `list.scrollTop`. No-ops when the list does not actually scroll. Never\n * touches any other scrolling box.\n *\n * @param list - The scrollable listbox container.\n * @param option - The (virtually focused) option to keep visible.\n */\nexport function scrollOptionIntoView(list: HTMLElement, option: HTMLElement): void {\n if (list.scrollHeight <= list.clientHeight) return;\n const listRect = list.getBoundingClientRect();\n const optionRect = option.getBoundingClientRect();\n if (optionRect.top < listRect.top) {\n list.scrollTop -= listRect.top - optionRect.top;\n } else if (optionRect.bottom > listRect.bottom) {\n list.scrollTop += optionRect.bottom - listRect.bottom;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { scrollOptionIntoView } from \"../utils/option_scroll\";\n\n/**\n * Headless, accessible combobox behavior (list autocomplete).\n *\n * Markup contract (identifier: `stimeo--combobox`):\n * <div data-controller=\"stimeo--combobox\">\n * <input type=\"text\" role=\"combobox\" aria-expanded=\"false\"\n * aria-autocomplete=\"list\" aria-controls=\"listbox\"\n * data-stimeo--combobox-target=\"input\"\n * data-action=\"input->stimeo--combobox#filter\n * keydown->stimeo--combobox#onKeydown\n * focus->stimeo--combobox#open\n * click->stimeo--combobox#open\" />\n * <ul id=\"listbox\" role=\"listbox\" data-stimeo--combobox-target=\"list\" hidden>\n * <li role=\"option\" id=\"opt-apple\" data-value=\"apple\"\n * data-stimeo--combobox-target=\"option\"\n * data-action=\"click->stimeo--combobox#selectByClick\">Apple</li>\n * <!-- more options -->\n * </ul>\n * </div>\n *\n * Implements the WAI-ARIA APG **Combobox** pattern with a listbox popup and\n * list-autocomplete. Focus stays in the input; the active option is tracked with\n * `aria-activedescendant` rather than by moving DOM focus.\n *\n * @remarks\n * Behavior only. Options are authored in the DOM; the controller filters them by\n * toggling each option's `hidden` attribute (case-insensitive substring match on\n * its text). The consumer owns styling, typically keyed off `[aria-selected]`.\n * When an open listbox has no matching options, the root element gets\n * `data-stimeo--combobox-empty` so the consumer can style the empty state (hide\n * the list, show a \"no results\" node, …) — the library imposes no visuals.\n *\n * Behavior provided:\n * - Typing filters the options and opens the listbox.\n * - Focusing or clicking the input opens the listbox, re-filtered against the\n * current value (so re-opening with a non-matching value keeps the empty state).\n * - `ArrowDown`/`ArrowUp` move the active option (wrapping); `Enter` selects it;\n * `Escape` closes the listbox; `Home`/`End` jump to the first/last visible\n * option.\n * - Selecting an option fills the input (with the option's `data-value` if set,\n * otherwise its text) and closes the listbox.\n * - A click outside the combobox closes the listbox.\n */\nexport class ComboboxController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"list\", \"option\"];\n static actions = [\"close\", \"filter\", \"onKeydown\", \"open\", \"selectByClick\"] as const;\n static events = [\"selected\"] as const;\n\n declare readonly inputTarget: HTMLInputElement;\n declare readonly listTarget: HTMLElement;\n declare readonly optionTargets: HTMLElement[];\n declare readonly hasInputTarget: boolean;\n declare readonly hasListTarget: boolean;\n\n /** Index into the *visible* options of the active option, or -1 if none. */\n #activeIndex = -1;\n /**\n * Suppresses {@link open} for the duration of the programmatic re-focus in\n * `#select`, so committing a value (which returns focus to the input)\n * does not immediately re-open the listbox via a `focus`-bound action.\n */\n #suppressOpen = false;\n /** Owns IME lifecycle state; confirmed text re-filters the list once. */\n readonly #composition = new CompositionTracker({ onEnd: () => this.filter() });\n\n /** Starts closed with no active option and registers the outside-click listener. */\n override connect(): void {\n if (this.hasInputTarget) this.#composition.observe(this.inputTarget);\n this.close();\n document.addEventListener(\"click\", this.#onOutsideClick);\n }\n\n /** Removes the document-level listener registered in {@link connect}. */\n override disconnect(): void {\n this.#composition.disconnect();\n document.removeEventListener(\"click\", this.#onOutsideClick);\n }\n\n /** Tracks an input added initially or after connect. */\n inputTargetConnected(input: HTMLInputElement): void {\n this.#composition.observe(input);\n }\n\n /** Removes composition listeners when the active input is replaced or removed. */\n inputTargetDisconnected(input: HTMLInputElement): void {\n this.#composition.unobserve(input);\n }\n\n /** Filters confirmed input text and opens the listbox. */\n filter(event?: InputEvent): void {\n if (this.#composition.isComposing(event)) return;\n this.open();\n }\n\n /**\n * Opens the listbox, re-filtering the options against the current input value\n * so the visible options and empty state always match what is typed (e.g.\n * re-opening with a stale non-matching value still surfaces the empty state).\n */\n open(): void {\n if (!this.hasListTarget || this.#suppressOpen) return;\n this.#applyFilter();\n this.listTarget.hidden = false;\n this.inputTarget.setAttribute(\"aria-expanded\", \"true\");\n this.#setActive(-1);\n this.#reflectEmptyState();\n }\n\n /**\n * Hides options that don't match the current input value (case-insensitive\n * substring). An empty query shows every option. Does not change open state.\n */\n #applyFilter(): void {\n const query = this.inputTarget.value.trim().toLowerCase();\n for (const option of this.optionTargets) {\n const text = (option.textContent ?? \"\").trim().toLowerCase();\n option.hidden = query.length > 0 && !text.includes(query);\n }\n }\n\n /** Closes the listbox, clears the active option, and updates ARIA state. */\n close(): void {\n if (!this.hasListTarget) return;\n this.listTarget.hidden = true;\n this.inputTarget.setAttribute(\"aria-expanded\", \"false\");\n this.#setActive(-1);\n this.element.removeAttribute(\"data-stimeo--combobox-empty\");\n }\n\n /** Routes keyboard interaction per the APG combobox model. */\n onKeydown(event: KeyboardEvent): void {\n // Ignore keys fired during IME composition: the `Enter` that confirms a\n // candidate (and arrows that move within it) must not select an option or\n // close the popup. Controller-owned lifecycle state covers confirming events\n // that omit the standard per-event signal.\n if (this.#composition.isComposing(event)) return;\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n // open() re-filters, so read the visible set afterwards.\n if (this.#isClosed) this.open();\n const visible = this.#visibleOptions();\n if (visible.length > 0) {\n const next = this.#activeIndex === -1 ? 0 : (this.#activeIndex + 1) % visible.length;\n this.#setActive(next);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n if (this.#isClosed) this.open();\n const visible = this.#visibleOptions();\n if (visible.length > 0) {\n // From the input (no active option) ArrowUp jumps to the last option,\n // per the APG; otherwise it wraps backwards.\n const next =\n this.#activeIndex === -1\n ? visible.length - 1\n : (this.#activeIndex - 1 + visible.length) % visible.length;\n this.#setActive(next);\n }\n break;\n }\n case \"Home\":\n if (!this.#isClosed && this.#visibleOptions().length > 0) {\n event.preventDefault();\n this.#setActive(0);\n }\n break;\n case \"End\": {\n const visible = this.#visibleOptions();\n if (!this.#isClosed && visible.length > 0) {\n event.preventDefault();\n this.#setActive(visible.length - 1);\n }\n break;\n }\n case \"Enter\": {\n const visible = this.#visibleOptions();\n const active = this.#activeIndex === -1 ? undefined : visible[this.#activeIndex];\n if (active) {\n event.preventDefault();\n this.#select(active);\n }\n break;\n }\n case \"Escape\":\n // Layered-Escape contract: leave a press an inner handler already\n // owned (rule 1), and consume only while owning a dismissable state —\n // with the list already closed the press stays free for the shared\n // resolver, so an enclosing dialog still closes from a focused input.\n if (event.defaultPrevented || this.#isClosed) break;\n event.preventDefault();\n this.close();\n break;\n case \"Tab\":\n // Let focus leave naturally, but don't keep a stale popup open.\n this.close();\n break;\n default:\n break;\n }\n }\n\n /**\n * Closes the listbox when a click lands outside the combobox. Mirrors the menu\n * button's outside-click behavior; clicks on an option are inside the element,\n * so `#select` (not this handler) closes the popup after committing.\n */\n readonly #onOutsideClick = (event: MouseEvent): void => {\n if (!this.#isClosed && !this.element.contains(event.target as Node)) this.close();\n };\n\n /** Selects the clicked option. Bound via `data-action` (click). */\n selectByClick(event: Event): void {\n const option = (event.currentTarget as HTMLElement).closest<HTMLElement>('[role=\"option\"]');\n if (option) this.#select(option);\n }\n\n /** Commits an option: fills the input, closes the listbox, notifies listeners. */\n #select(option: HTMLElement): void {\n const value = option.dataset.value ?? (option.textContent ?? \"\").trim();\n const changed = this.inputTarget.value !== value;\n this.inputTarget.value = value;\n this.close();\n // Returning focus to the input would re-trigger a `focus`-bound open(); guard\n // it so the listbox stays closed after a selection.\n this.#suppressOpen = true;\n this.inputTarget.focus();\n this.#suppressOpen = false;\n if (changed) {\n // A native bubbling `change` (matching <select>/listbox semantics: only on\n // an actual value change) so form-level behaviors — validation re-checks,\n // auto-submit — hear the commit without knowing this widget. Deliberately\n // NOT `input`: that is this combobox's own filter trigger and would reopen\n // the popup on every selection.\n this.inputTarget.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n this.dispatch(\"selected\", { detail: { value } });\n }\n\n /**\n * Reflects whether the open listbox currently has zero matching options by\n * toggling `data-stimeo--combobox-empty` on the root element. Behavior only:\n * consumers decide how to present the empty state (hide the list, show a\n * \"no results\" node, etc.) via CSS keyed off this attribute.\n */\n #reflectEmptyState(): void {\n const empty = !this.#isClosed && this.#visibleOptions().length === 0;\n if (empty) {\n this.element.setAttribute(\"data-stimeo--combobox-empty\", \"\");\n } else {\n this.element.removeAttribute(\"data-stimeo--combobox-empty\");\n }\n }\n\n /**\n * Marks the visible option at `index` active via `aria-selected` and the\n * input's `aria-activedescendant`. Pass `-1` to clear the active option.\n */\n #setActive(index: number): void {\n this.#activeIndex = index;\n const visible = this.#visibleOptions();\n const active = index === -1 ? null : visible[index];\n // Clear aria-selected on every option (not just the visible ones) so a\n // previously-active option that became hidden by filtering doesn't keep a\n // stale selected state.\n for (const option of this.optionTargets) {\n option.setAttribute(\"aria-selected\", option === active ? \"true\" : \"false\");\n }\n if (active?.id) {\n this.inputTarget.setAttribute(\"aria-activedescendant\", active.id);\n } else {\n this.inputTarget.removeAttribute(\"aria-activedescendant\");\n }\n // Virtual focus never triggers the browser's native focus-scrolling, so a\n // scrollable list must follow the active option itself (list-only scroll).\n if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);\n }\n\n /** The options currently shown (not filtered out). */\n #visibleOptions(): HTMLElement[] {\n return this.optionTargets.filter((option) => !option.hidden);\n }\n\n /** Whether the listbox is currently hidden. */\n get #isClosed(): boolean {\n return !this.hasListTarget || this.listTarget.hidden !== false;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/active_option.ts","../../src/utils/arrow_step.ts","../../src/utils/composition_tracker.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/option_scroll.ts","../../src/controllers/combobox_controller.ts"],"names":[],"mappings":"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","file":"combobox_controller.js","sourcesContent":["/**\n * Reflects a single **active option** across an option set, writing only where\n * the value actually changes.\n *\n * Every controller that drives a virtually-focused list answers the same question\n * on each keystroke and each arrow repeat: *which one option is the active\n * candidate, and what does the rest of the set look like now?* Answering it by\n * writing `aria-selected` to **every** option is the obvious implementation, and\n * the one this helper exists to replace.\n *\n * **The write count is the point.** At most two options change per move (the old\n * active and the new one), while the option count is authored — a consumer can\n * render hundreds, and filtering re-runs this on each input event. An\n * unconditional pass costs one attribute mutation per option per keypress, each\n * one an observable DOM change that `MutationObserver`s and the a11y tree react\n * to. Comparing before writing turns O(n) mutations into O(1) without changing\n * what the DOM ends up saying.\n *\n * **`aria-selected` here means \"active candidate\", not \"committed selection\".**\n * That distinction decides where this helper may be used: in a combobox the\n * committed value lives *outside* the popup (the input's text), so\n * `aria-selected` is free to track virtual focus; in a listbox or a grid it is\n * the selection itself and must not be repurposed. Only the former kind belongs\n * here — which is why `listbox` marks its active row with `data-active` instead\n * and is deliberately not a consumer of this helper.\n *\n * Scope is deliberately narrow: this owns the option-set write and nothing else.\n * `aria-activedescendant` on the host, scrolling the active option into view, and\n * the `data-active` marker stay in the controllers, because each one differs\n * (different host element, different scroll container, and not every consumer\n * uses `data-active` at all). Folding them in would mean a helper with three\n * optional behaviours — more surface than the duplication it removes.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * #setActive(option: HTMLElement | null): void {\n * syncActiveOption(this.optionTargets, option);\n * // host-specific parts stay here: aria-activedescendant, scrolling, data-active\n * }\n * ```\n */\n\n/**\n * Writes `aria-selected` across `options` so that exactly `active` reads `\"true\"`.\n *\n * Skips every option already holding the right value, so a move mutates only the\n * two that changed. Pass `null` to clear the whole set — the state a closed or\n * empty-filtered popup needs.\n *\n * `options` must be the **full** set, not the currently visible subset: an option\n * hidden by filtering while it was active would otherwise keep a stale\n * `aria-selected=\"true\"` and resurface as selected when the filter widens.\n *\n * @param options - every option in the set, visible or not\n * @param active - the one option to mark active, or `null` to clear\n * @returns how many attributes were actually written\n */\nexport function syncActiveOption(\n options: Iterable<HTMLElement>,\n active: HTMLElement | null,\n): number {\n let written = 0;\n for (const option of options) {\n const next = option === active ? \"true\" : \"false\";\n if (option.getAttribute(\"aria-selected\") === next) continue;\n option.setAttribute(\"aria-selected\", next);\n written += 1;\n }\n return written;\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/** Minimal event shape carrying the platform's per-event composition signal. */\nexport interface CompositionSignal {\n readonly isComposing?: boolean;\n}\n\n/** Hooks that keep component-specific work outside {@link CompositionTracker}. */\nexport interface CompositionTrackerOptions {\n /** Runs after lifecycle state is set for a `compositionstart` event. */\n readonly onStart?: (event: Event) => void;\n /** Runs after lifecycle state is cleared for a `compositionend` event. */\n readonly onEnd?: (event: Event) => void;\n}\n\n/**\n * Owns IME composition listeners and transient state for one or more event targets.\n *\n * Some browsers omit `KeyboardEvent.isComposing` on the keydown that confirms a\n * conversion. Tracking `compositionstart` through `compositionend` supplies the\n * missing lifecycle signal without relying on deprecated numeric key codes.\n * Component policy stays with the consumer: filtering, validation, and submission\n * belong in the optional {@link CompositionTrackerOptions.onEnd} callback.\n *\n * @example\n * ```ts\n * #composition = new CompositionTracker({ onEnd: () => this.filter() });\n *\n * connect(): void {\n * this.#composition.observe(this.inputTarget);\n * }\n *\n * disconnect(): void {\n * this.#composition.disconnect();\n * }\n *\n * onKeydown(event: KeyboardEvent): void {\n * if (this.#composition.isComposing(event)) return;\n * }\n * ```\n */\nexport class CompositionTracker {\n readonly #observedTargets = new Set<EventTarget>();\n readonly #activeTargets = new Set<EventTarget>();\n readonly #onStart: ((event: Event) => void) | undefined;\n readonly #onEnd: ((event: Event) => void) | undefined;\n\n constructor(options: CompositionTrackerOptions = {}) {\n this.#onStart = options.onStart;\n this.#onEnd = options.onEnd;\n }\n\n /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */\n observe(target: EventTarget): void {\n if (this.#observedTargets.has(target)) return;\n target.addEventListener(\"compositionstart\", this.#handleStart);\n target.addEventListener(\"compositionend\", this.#handleEnd);\n this.#observedTargets.add(target);\n }\n\n /** Stops tracking one target and clears any active composition it owned. */\n unobserve(target: EventTarget): void {\n if (!this.#observedTargets.delete(target)) return;\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n this.#activeTargets.delete(target);\n }\n\n /** Releases every listener and clears state so reconnect starts cleanly. */\n disconnect(): void {\n for (const target of this.#observedTargets) {\n target.removeEventListener(\"compositionstart\", this.#handleStart);\n target.removeEventListener(\"compositionend\", this.#handleEnd);\n }\n this.#observedTargets.clear();\n this.#activeTargets.clear();\n }\n\n /** True when lifecycle tracking or the current event reports composition. */\n isComposing(event?: CompositionSignal): boolean {\n return this.#activeTargets.size > 0 || event?.isComposing === true;\n }\n\n readonly #handleStart = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.add(event.currentTarget);\n this.#onStart?.(event);\n };\n\n readonly #handleEnd = (event: Event): void => {\n if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);\n this.#onEnd?.(event);\n };\n}\n","/**\n * Collapses many target callbacks from one DOM mutation into a single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element, so\n * replacing a list of N options delivers N callbacks — but the useful unit of\n * work is \"reconcile against the DOM that resulted\", once, after the batch has\n * settled. Every controller that owns a reconcilable target set needs the same\n * shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers the *initial* target callbacks\n * ahead of `connect()`. Reconciling there would compute a fallback against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Scroll-follow for virtually-focused listbox options.\n *\n * Listbox-style widgets that track their active option with\n * `aria-activedescendant` keep DOM focus on the input/trigger, so the\n * browser's native \"scroll the focused element into view\" never runs — in a\n * scrollable list (`max-height` + `overflow`) the active option can walk right\n * out of sight on ArrowDown/ArrowUp. This helper keeps it visible.\n *\n * It adjusts the LIST's own `scrollTop` only — deliberately not\n * `scrollIntoView({ block: \"nearest\" })`, which may also scroll ancestor\n * scrolling boxes (the page) when a floating popup pokes past a viewport\n * edge, desyncing anchored/marker-composed placements. (Command-palette uses\n * `scrollIntoView` because its page is scroll-locked behind the modal;\n * page-floating popups must use this helper instead.)\n */\n\n/**\n * Scrolls `option` into view within `list` by minimally adjusting\n * `list.scrollTop`. No-ops when the list does not actually scroll. Never\n * touches any other scrolling box.\n *\n * @param list - The scrollable listbox container.\n * @param option - The (virtually focused) option to keep visible.\n */\nexport function scrollOptionIntoView(list: HTMLElement, option: HTMLElement): void {\n if (list.scrollHeight <= list.clientHeight) return;\n const listRect = list.getBoundingClientRect();\n const optionRect = option.getBoundingClientRect();\n if (optionRect.top < listRect.top) {\n list.scrollTop -= listRect.top - optionRect.top;\n } else if (optionRect.bottom > listRect.bottom) {\n list.scrollTop += optionRect.bottom - listRect.bottom;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { syncActiveOption } from \"../utils/active_option\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { CompositionTracker } from \"../utils/composition_tracker\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { scrollOptionIntoView } from \"../utils/option_scroll\";\n\n/**\n * Headless, accessible combobox behavior (list autocomplete).\n *\n * Markup contract (identifier: `stimeo--combobox`):\n * <div data-controller=\"stimeo--combobox\">\n * <input type=\"text\" role=\"combobox\" aria-expanded=\"false\"\n * aria-autocomplete=\"list\" aria-controls=\"listbox\"\n * data-stimeo--combobox-target=\"input\"\n * data-action=\"input->stimeo--combobox#filter\n * keydown->stimeo--combobox#onKeydown\n * focus->stimeo--combobox#open\n * click->stimeo--combobox#open\" />\n * <ul id=\"listbox\" role=\"listbox\" data-stimeo--combobox-target=\"list\" hidden>\n * <li role=\"option\" id=\"opt-apple\" data-value=\"apple\"\n * data-stimeo--combobox-target=\"option\"\n * data-action=\"click->stimeo--combobox#selectByClick\">Apple</li>\n * <!-- more options -->\n * </ul>\n * </div>\n *\n * Implements the WAI-ARIA APG **Combobox** pattern with a listbox popup and\n * list-autocomplete. Focus stays in the input; the active option is tracked with\n * `aria-activedescendant` rather than by moving DOM focus.\n *\n * @remarks\n * Behavior only. Options are authored in the DOM; the controller filters them by\n * toggling each option's `hidden` attribute (case-insensitive substring match on\n * its text). The consumer owns styling, typically keyed off `[aria-selected]`.\n * When an open listbox has no matching options, the root element gets\n * `data-stimeo--combobox-empty` so the consumer can style the empty state (hide\n * the list, show a \"no results\" node, …) — the library imposes no visuals.\n *\n * Behavior provided:\n * - Typing filters the options and opens the listbox.\n * - Focusing or clicking the input opens the listbox, re-filtered against the\n * current value (so re-opening with a non-matching value keeps the empty state).\n * - `ArrowDown`/`ArrowUp` move the active option (wrapping); `Enter` selects it;\n * `Escape` closes the listbox; `Home`/`End` jump to the first/last visible\n * option.\n * - Selecting an option fills the input (with the option's `data-value` if set,\n * otherwise its text) and closes the listbox.\n * - A click outside the combobox closes the listbox.\n */\nexport class ComboboxController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"list\", \"option\"];\n static actions = [\"close\", \"filter\", \"onKeydown\", \"open\", \"selectByClick\"] as const;\n static events = [\"selected\"] as const;\n\n declare readonly inputTarget: HTMLInputElement;\n declare readonly listTarget: HTMLElement;\n declare readonly optionTargets: HTMLElement[];\n declare readonly hasInputTarget: boolean;\n declare readonly hasListTarget: boolean;\n\n /** Stable ID of the active option; the live element is resolved before every use. */\n #activeId: string | null = null;\n #connected = false;\n /** Collapses one mutation batch of target callbacks into a single pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n /**\n * Suppresses {@link open} for the duration of the programmatic re-focus in\n * `#select`, so committing a value (which returns focus to the input)\n * does not immediately re-open the listbox via a `focus`-bound action.\n */\n #suppressOpen = false;\n /** Owns IME lifecycle state; confirmed text re-filters the list once. */\n readonly #composition = new CompositionTracker({ onEnd: () => this.filter() });\n\n /** Starts closed with no active option and registers the outside-click listener. */\n override connect(): void {\n if (this.hasInputTarget) this.#composition.observe(this.inputTarget);\n this.close();\n document.addEventListener(\"click\", this.#onOutsideClick, true);\n this.#connected = true;\n this.#reconcile.activate();\n }\n\n /** Removes the document-level listener registered in {@link connect}. */\n override disconnect(): void {\n this.#connected = false;\n this.#reconcile.cancel();\n this.#composition.disconnect();\n document.removeEventListener(\"click\", this.#onOutsideClick, true);\n }\n\n /** Tracks an input added initially or after connect. */\n inputTargetConnected(input: HTMLInputElement): void {\n this.#composition.observe(input);\n if (this.#connected) this.#queueOptionReconciliation();\n }\n\n /** Removes composition listeners when the active input is replaced or removed. */\n inputTargetDisconnected(input: HTMLInputElement): void {\n this.#composition.unobserve(input);\n }\n\n /** Establishes the active-state baseline, then reconciles a runtime addition. */\n optionTargetConnected(option: HTMLElement): void {\n option.setAttribute(\"aria-selected\", \"false\");\n if (this.#connected) this.#queueOptionReconciliation();\n }\n\n /** Clears the disconnected node's active state, then reconciles the survivors. */\n optionTargetDisconnected(option: HTMLElement): void {\n option.setAttribute(\"aria-selected\", \"false\");\n if (this.#connected) this.#queueOptionReconciliation();\n }\n\n /** Filters confirmed input text and opens the listbox. */\n filter(event?: InputEvent): void {\n if (this.#composition.isComposing(event)) return;\n this.open();\n }\n\n /**\n * Opens the listbox, re-filtering the options against the current input value\n * so the visible options and empty state always match what is typed (e.g.\n * re-opening with a stale non-matching value still surfaces the empty state).\n */\n open(): void {\n if (!this.hasListTarget || !this.hasInputTarget || this.#suppressOpen) return;\n this.#applyFilter();\n this.listTarget.hidden = false;\n this.inputTarget.setAttribute(\"aria-expanded\", \"true\");\n this.#setActive(-1);\n this.#reflectEmptyState();\n }\n\n /**\n * Hides options that don't match the current input value (case-insensitive\n * substring). An empty query shows every option. Does not change open state.\n */\n #applyFilter(): void {\n const query = this.inputTarget.value.trim().toLowerCase();\n for (const option of this.optionTargets) {\n const text = (option.textContent ?? \"\").trim().toLowerCase();\n option.hidden = query.length > 0 && !text.includes(query);\n }\n }\n\n /**\n * Closes the listbox, clears the active option, and updates ARIA state.\n *\n * Survives a missing input in both directions. {@link inputTargetConnected}\n * accepts an input added after connect, so dereferencing it here would abort\n * `connect()` before it registers the outside-click listener, and nothing\n * re-registers it later. Symmetrically, an input removed while the popup is\n * open must still let the popup come down — so hiding the list is\n * unconditional and only the ARIA half is guarded.\n */\n close(): void {\n if (!this.hasListTarget) return;\n this.listTarget.hidden = true;\n this.element.removeAttribute(\"data-stimeo--combobox-empty\");\n if (this.hasInputTarget) this.inputTarget.setAttribute(\"aria-expanded\", \"false\");\n this.#setActive(-1);\n }\n\n /** Routes keyboard interaction per the APG combobox model. */\n onKeydown(event: KeyboardEvent): void {\n // Ignore keys fired during IME composition: the `Enter` that confirms a\n // candidate (and arrows that move within it) must not select an option or\n // close the popup. Controller-owned lifecycle state covers confirming events\n // that omit the standard per-event signal.\n if (this.#composition.isComposing(event)) return;\n // A widget that already claimed the key (an enclosing composite consuming\n // arrows, a descendant editor) must not ALSO move the active option, commit\n // one, or drop the popup — composition depends on this yield.\n if (event.defaultPrevented) return;\n // A chorded arrow is the browser's, except the Alt+Down/Up this pattern\n // claims below. \"Someone already consumed it\" outranks it, so it is checked\n // after the yield above.\n if (isReservedArrowChord(event, [\"alt\"])) return;\n // The two chords the pattern claims. `Alt+Down` shows the popup *without*\n // moving focus into it, and `Alt+Up` returns focus and closes — which is\n // what separates them from the bare arrows below, where the point is to\n // move the active option.\n if (event.altKey && (event.key === \"ArrowDown\" || event.key === \"ArrowUp\")) {\n // Each chord is bound in one popup state only. In the other state the\n // pattern defines nothing, so the press stays the browser's rather than\n // being swallowed on a no-op.\n if (event.key === \"ArrowDown\" ? !this.#isClosed : this.#isClosed) return;\n event.preventDefault();\n if (event.key === \"ArrowDown\") {\n this.open();\n } else {\n // The popup never holds DOM focus here (the active option is virtual, via\n // aria-activedescendant), so \"return focus to the combobox\" is already\n // true and closing is the whole action. Calling focus() would re-trigger\n // the focus-bound open().\n this.close();\n }\n return;\n }\n if (!this.#isClosed) this.#reconcileActive();\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n // open() re-filters, so read the visible set afterwards.\n if (this.#isClosed) this.open();\n const visible = this.#visibleOptions();\n if (visible.length > 0) {\n const activeIndex = this.#findActiveIndex(visible);\n const next = activeIndex === -1 ? 0 : (activeIndex + 1) % visible.length;\n this.#setActive(next);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n if (this.#isClosed) this.open();\n const visible = this.#visibleOptions();\n if (visible.length > 0) {\n const activeIndex = this.#findActiveIndex(visible);\n // From the input (no active option) ArrowUp jumps to the last option,\n // per the APG; otherwise it wraps backwards.\n const next =\n activeIndex === -1\n ? visible.length - 1\n : (activeIndex - 1 + visible.length) % visible.length;\n this.#setActive(next);\n }\n break;\n }\n case \"Home\":\n if (!this.#isClosed && this.#visibleOptions().length > 0) {\n event.preventDefault();\n this.#setActive(0);\n }\n break;\n case \"End\": {\n const visible = this.#visibleOptions();\n if (!this.#isClosed && visible.length > 0) {\n event.preventDefault();\n this.#setActive(visible.length - 1);\n }\n break;\n }\n case \"Enter\": {\n const visible = this.#visibleOptions();\n const activeIndex = this.#findActiveIndex(visible);\n const active = activeIndex === -1 ? undefined : visible[activeIndex];\n if (active) {\n event.preventDefault();\n this.#select(active);\n }\n break;\n }\n case \"Escape\":\n // A press an inner handler already owned is yielded at the top of this\n // handler. Escape is consumed only while the list is open: with it closed\n // the press stays free for the shared resolver, so an enclosing dialog\n // still closes from a focused input.\n if (this.#isClosed) break;\n event.preventDefault();\n this.close();\n break;\n case \"Tab\":\n // Let focus leave naturally, but don't keep a stale popup open.\n this.close();\n break;\n default:\n break;\n }\n }\n\n /**\n * Closes the listbox when a click lands outside the combobox. Mirrors the menu\n * button's outside-click behavior; clicks on an option are inside the element,\n * so `#select` (not this handler) closes the popup after committing.\n */\n readonly #onOutsideClick = (event: MouseEvent): void => {\n if (!this.#isClosed && !this.element.contains(event.target as Node)) this.close();\n };\n\n /** Selects the clicked option. Bound via `data-action` (click). */\n selectByClick(event: Event): void {\n const option = (event.currentTarget as HTMLElement).closest<HTMLElement>('[role=\"option\"]');\n if (option && this.optionTargets.includes(option)) this.#select(option);\n }\n\n /**\n * Commits an option: fills the input, closes the listbox, notifies listeners.\n *\n * An input removed while the popup is open leaves the options clickable, and\n * {@link close} already survives that state. Selection does too: the popup\n * comes down and listeners still hear the choice, with only the field-bound\n * half — the value write, the focus return, and the native `change` — skipped,\n * because there is no field to carry them.\n */\n #select(option: HTMLElement): void {\n const value = option.dataset.value ?? (option.textContent ?? \"\").trim();\n if (!this.hasInputTarget) {\n this.close();\n this.dispatch(\"selected\", { detail: { value } });\n return;\n }\n const changed = this.inputTarget.value !== value;\n this.inputTarget.value = value;\n this.close();\n // Returning focus to the input would re-trigger a `focus`-bound open(); guard\n // it so the listbox stays closed after a selection.\n this.#suppressOpen = true;\n this.inputTarget.focus();\n this.#suppressOpen = false;\n if (changed) {\n // A native bubbling `change` (matching <select>/listbox semantics: only on\n // an actual value change) so form-level behaviors — validation re-checks,\n // auto-submit — hear the commit without knowing this widget. Deliberately\n // NOT `input`: that is this combobox's own filter trigger and would reopen\n // the popup on every selection.\n this.inputTarget.dispatchEvent(new Event(\"change\", { bubbles: true }));\n }\n this.dispatch(\"selected\", { detail: { value } });\n }\n\n /**\n * Reflects whether the open listbox currently has zero matching options by\n * toggling `data-stimeo--combobox-empty` on the root element. Behavior only:\n * consumers decide how to present the empty state (hide the list, show a\n * \"no results\" node, etc.) via CSS keyed off this attribute.\n */\n #reflectEmptyState(): void {\n const empty = !this.#isClosed && this.#visibleOptions().length === 0;\n if (empty) {\n this.element.setAttribute(\"data-stimeo--combobox-empty\", \"\");\n } else {\n this.element.removeAttribute(\"data-stimeo--combobox-empty\");\n }\n }\n\n /**\n * Marks the visible option at `index` active via `aria-selected` and the\n * input's `aria-activedescendant`. Pass `-1` to clear the active option.\n */\n #setActive(index: number): void {\n const visible = this.#visibleOptions();\n const active = index === -1 ? null : (visible[index] ?? null);\n // The full target set, not just the visible ones: an option hidden by\n // filtering while active would otherwise keep a stale selected state.\n syncActiveOption(this.optionTargets, active);\n this.#activeId = active?.id || null;\n if (this.hasInputTarget) {\n if (active?.id) {\n this.inputTarget.setAttribute(\"aria-activedescendant\", active.id);\n } else {\n this.inputTarget.removeAttribute(\"aria-activedescendant\");\n }\n }\n // Virtual focus never triggers the browser's native focus-scrolling, so a\n // scrollable list must follow the active option itself (list-only scroll).\n if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);\n }\n\n /** Re-applies filtering and active state after a target collection change. */\n #reconcileOptions(): void {\n if (this.hasInputTarget && !this.#isClosed) this.#applyFilter();\n this.#reconcileActive();\n this.#reflectEmptyState();\n }\n\n /** Preserves a surviving active ID; disappearance deliberately clears active state. */\n #reconcileActive(): void {\n if (!this.hasInputTarget || this.#isClosed) {\n this.#setActive(-1);\n return;\n }\n const visible = this.#visibleOptions();\n const index = this.#findActiveIndex(visible);\n const active = index === -1 ? null : (visible[index] ?? null);\n const selected = this.optionTargets.filter(\n (option) => option.getAttribute(\"aria-selected\") === \"true\",\n );\n const idref = this.inputTarget.getAttribute(\"aria-activedescendant\");\n const stateMatches = active\n ? this.#activeId === (active.id || null) &&\n selected.length === 1 &&\n selected[0] === active &&\n idref === (active.id || null)\n : this.#activeId === null && selected.length === 0 && idref === null;\n if (!stateMatches) this.#setActive(index);\n }\n\n /** Resolves the stable ID, with `aria-selected` as the ID-less compatibility marker. */\n #findActiveIndex(options: readonly HTMLElement[]): number {\n const activeId =\n (this.hasInputTarget ? this.inputTarget.getAttribute(\"aria-activedescendant\") : null) ??\n this.#activeId;\n if (activeId) return options.findIndex((option) => option.id === activeId);\n return options.findIndex((option) => option.getAttribute(\"aria-selected\") === \"true\");\n }\n\n /** Coalesces all target callbacks from one MutationObserver batch. */\n #queueOptionReconciliation(): void {\n this.#reconcile.schedule();\n }\n\n /** The options currently shown (not filtered out). */\n #visibleOptions(): HTMLElement[] {\n return this.optionTargets.filter((option) => !option.hidden);\n }\n\n /** Whether the listbox is currently hidden. */\n get #isClosed(): boolean {\n return !this.hasListTarget || this.listTarget.hidden !== false;\n }\n}\n"]}
@@ -82,6 +82,8 @@ declare class CommandPaletteController extends Controller<HTMLElement> {
82
82
  disconnect(): void;
83
83
  /** Initializes semantics for an option added before or after the controller connects. */
84
84
  optionTargetConnected(option: HTMLElement): void;
85
+ /** Clears active residue on the old node and reconciles the surviving targets. */
86
+ optionTargetDisconnected(option: HTMLElement): void;
85
87
  /** Tracks an input added initially or after connect without extra consumer actions. */
86
88
  inputTargetConnected(input: HTMLInputElement): void;
87
89
  /** Removes controller-owned listeners when the input target is replaced or removed. */
@@ -2,6 +2,24 @@ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  // src/controllers/command_palette_controller.ts
4
4
 
5
+ // src/utils/active_option.ts
6
+ function syncActiveOption(options, active) {
7
+ let written = 0;
8
+ for (const option of options) {
9
+ const next = option === active ? "true" : "false";
10
+ if (option.getAttribute("aria-selected") === next) continue;
11
+ option.setAttribute("aria-selected", next);
12
+ written += 1;
13
+ }
14
+ return written;
15
+ }
16
+
17
+ // src/utils/arrow_step.ts
18
+ function isReservedArrowChord(event, allow = []) {
19
+ if (!event.key.startsWith("Arrow")) return false;
20
+ return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
21
+ }
22
+
5
23
  // src/utils/composition_tracker.ts
6
24
  var CompositionTracker = class {
7
25
  #observedTargets = /* @__PURE__ */ new Set();
@@ -304,6 +322,39 @@ var FocusTrap = class {
304
322
  }
305
323
  };
306
324
 
325
+ // src/utils/microtask_coalescer.ts
326
+ var MicrotaskCoalescer = class {
327
+ #run;
328
+ #queued = false;
329
+ #active = false;
330
+ #generation = 0;
331
+ /** @param run - the single reconciliation pass, invoked at most once per batch. */
332
+ constructor(run) {
333
+ this.#run = run;
334
+ }
335
+ /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
336
+ activate() {
337
+ this.#active = true;
338
+ }
339
+ /** Closes the window and drops any pending pass; call from `disconnect()`. */
340
+ cancel() {
341
+ this.#active = false;
342
+ this.#queued = false;
343
+ this.#generation += 1;
344
+ }
345
+ /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
346
+ schedule() {
347
+ if (!this.#active || this.#queued) return;
348
+ this.#queued = true;
349
+ const generation = this.#generation;
350
+ queueMicrotask(() => {
351
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
352
+ this.#queued = false;
353
+ this.#run();
354
+ });
355
+ }
356
+ };
357
+
307
358
  // src/controllers/command_palette_controller.ts
308
359
  var CommandPaletteController = class _CommandPaletteController extends Controller {
309
360
  static #ORIGINAL_ARIA_DISABLED = "data-command-palette-original-aria-disabled";
@@ -323,8 +374,13 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
323
374
  "toggle"
324
375
  ];
325
376
  static events = ["select"];
326
- /** The index of the currently active option within the visible subset. */
327
- #activeIndex = -1;
377
+ /** Stable ID of the active option; the live target is resolved before every use. */
378
+ #activeId = null;
379
+ /** Visible/selectable target ID order captured for removal fallback. */
380
+ #activeOrder = [];
381
+ #connected = false;
382
+ /** Collapses one mutation batch of target callbacks into a single pass. */
383
+ #reconcile = new MicrotaskCoalescer(() => this.#reconcileActive());
328
384
  /** Owns IME lifecycle state; confirmed text re-filters an open palette once. */
329
385
  #composition = new CompositionTracker({
330
386
  onEnd: () => {
@@ -358,9 +414,13 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
358
414
  const shouldOpen = this.#isOpen || this.openValue;
359
415
  this.#resetToClosedState();
360
416
  if (shouldOpen) this.open();
417
+ this.#connected = true;
418
+ this.#reconcile.activate();
361
419
  }
362
420
  /** Tears down the global hotkey listener and reverts the modal side effects. */
363
421
  disconnect() {
422
+ this.#connected = false;
423
+ this.#reconcile.cancel();
364
424
  document.removeEventListener("keydown", this.#onGlobalKeydown);
365
425
  this.#composition.disconnect();
366
426
  this.#trap.deactivate({ restoreFocus: false });
@@ -369,10 +429,20 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
369
429
  /** Initializes semantics for an option added before or after the controller connects. */
370
430
  optionTargetConnected(option) {
371
431
  this.#syncOptionSemanticsFor(option);
432
+ option.setAttribute("aria-selected", "false");
433
+ option.removeAttribute("data-active");
434
+ if (this.#connected) this.#queueOptionReconciliation();
435
+ }
436
+ /** Clears active residue on the old node and reconciles the surviving targets. */
437
+ optionTargetDisconnected(option) {
438
+ option.setAttribute("aria-selected", "false");
439
+ option.removeAttribute("data-active");
440
+ if (this.#connected) this.#queueOptionReconciliation();
372
441
  }
373
442
  /** Tracks an input added initially or after connect without extra consumer actions. */
374
443
  inputTargetConnected(input) {
375
444
  this.#composition.observe(input);
445
+ if (this.#connected) this.#queueOptionReconciliation();
376
446
  }
377
447
  /** Removes controller-owned listeners when the input target is replaced or removed. */
378
448
  inputTargetDisconnected(input) {
@@ -412,7 +482,7 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
412
482
  const matches = searchText.includes(query);
413
483
  if (matches) {
414
484
  option.removeAttribute("hidden");
415
- if (!this.#isDisabled(option)) hasSelectableMatch = true;
485
+ if (this.#isNavigable(option)) hasSelectableMatch = true;
416
486
  } else {
417
487
  option.setAttribute("hidden", "true");
418
488
  }
@@ -427,8 +497,9 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
427
497
  const target = event.currentTarget;
428
498
  if (!target) return;
429
499
  const option = target.closest("[role='option']");
430
- if (option) this.#syncOptionSemanticsFor(option);
431
- if (option && !this.#isDisabled(option)) {
500
+ if (!option || !this.optionTargets.includes(option)) return;
501
+ this.#syncOptionSemanticsFor(option);
502
+ if (!option.hasAttribute("hidden") && !this.#isDisabled(option)) {
432
503
  this.#confirmSelection(option);
433
504
  }
434
505
  }
@@ -443,7 +514,10 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
443
514
  * focus — not only the input.
444
515
  */
445
516
  onKeydown(event) {
517
+ if (event.defaultPrevented) return;
518
+ if (isReservedArrowChord(event)) return;
446
519
  if (this.#composition.isComposing(event) || !this.#isOpen) return;
520
+ this.#reconcileActive();
447
521
  switch (event.key) {
448
522
  case "ArrowDown":
449
523
  event.preventDefault();
@@ -463,7 +537,9 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
463
537
  break;
464
538
  case "Enter": {
465
539
  event.preventDefault();
466
- const activeOption = this.#visibleOptions[this.#activeIndex];
540
+ const visible = this.#visibleOptions;
541
+ const activeIndex = this.#findActiveIndex(visible);
542
+ const activeOption = activeIndex < 0 ? void 0 : visible[activeIndex];
467
543
  if (activeOption) this.#confirmSelection(activeOption);
468
544
  break;
469
545
  }
@@ -472,7 +548,7 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
472
548
  #navigate(direction) {
473
549
  const visible = this.#visibleOptions;
474
550
  if (visible.length === 0) return;
475
- let newIndex = this.#activeIndex + direction;
551
+ let newIndex = this.#findActiveIndex(visible) + direction;
476
552
  if (newIndex >= visible.length) newIndex = 0;
477
553
  if (newIndex < 0) newIndex = visible.length - 1;
478
554
  this.#setActiveIndex(newIndex);
@@ -481,16 +557,16 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
481
557
  this.#syncOptionSemantics();
482
558
  const visible = this.#visibleOptions;
483
559
  const activeOption = visible[index] ?? null;
484
- this.#activeIndex = activeOption ? index : -1;
560
+ syncActiveOption(this.optionTargets, activeOption);
485
561
  for (const option of this.optionTargets) {
486
- option.setAttribute("aria-selected", "false");
487
- option.removeAttribute("data-active");
562
+ if (option === activeOption) option.setAttribute("data-active", "true");
563
+ else option.removeAttribute("data-active");
488
564
  }
489
565
  if (activeOption) {
490
- activeOption.setAttribute("aria-selected", "true");
491
- activeOption.setAttribute("data-active", "true");
492
566
  activeOption.scrollIntoView({ block: "nearest" });
493
567
  }
568
+ this.#activeId = activeOption?.id || null;
569
+ this.#activeOrder = activeOption ? visible.map((option) => option.id).filter(Boolean) : [];
494
570
  if (this.hasInputTarget) {
495
571
  if (activeOption?.id) {
496
572
  this.inputTarget.setAttribute("aria-activedescendant", activeOption.id);
@@ -499,7 +575,75 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
499
575
  }
500
576
  }
501
577
  }
578
+ /** Re-resolves active identity and component-specific fallback against live targets. */
579
+ #reconcileActive() {
580
+ if (!this.#isOpen) {
581
+ this.#setActiveIndex(-1);
582
+ this.#reflectEmptyState();
583
+ return;
584
+ }
585
+ const visible = this.#visibleOptions;
586
+ const currentIndex = this.#findActiveIndex(visible);
587
+ if (currentIndex >= 0) {
588
+ const active = visible[currentIndex] ?? null;
589
+ const options = this.optionTargets;
590
+ const marked = options.filter((option) => option.hasAttribute("data-active"));
591
+ const selected = options.filter((option) => option.getAttribute("aria-selected") === "true");
592
+ const stateMatches = this.#activeId === (active?.id || null) && marked.length === 1 && marked[0] === active && selected.length === 1 && selected[0] === active && (!this.hasInputTarget || this.inputTarget.getAttribute("aria-activedescendant") === (active?.id || null));
593
+ if (stateMatches) {
594
+ this.#activeOrder = visible.map((option) => option.id).filter(Boolean);
595
+ } else {
596
+ this.#setActiveIndex(currentIndex);
597
+ }
598
+ this.#reflectEmptyState();
599
+ return;
600
+ }
601
+ const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
602
+ this.#setActiveIndex(activeId ? this.#findFallbackIndex(visible, activeId) : -1);
603
+ this.#reflectEmptyState();
604
+ }
605
+ /** Resolves stable ID first, then active markers for the existing ID-less case. */
606
+ #findActiveIndex(options) {
607
+ const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
608
+ if (activeId) return options.findIndex((option) => option.id === activeId);
609
+ return options.findIndex(
610
+ (option) => option.hasAttribute("data-active") || option.getAttribute("aria-selected") === "true"
611
+ );
612
+ }
613
+ /** Chooses a surviving former successor, then predecessor, from selectable targets only. */
614
+ #findFallbackIndex(options, activeId) {
615
+ const oldIndex = this.#activeOrder.indexOf(activeId);
616
+ if (oldIndex < 0) return -1;
617
+ const indexesById = new Map(options.map((option, index) => [option.id, index]));
618
+ for (let index = oldIndex + 1; index < this.#activeOrder.length; index += 1) {
619
+ const fallback = indexesById.get(this.#activeOrder[index] ?? "");
620
+ if (fallback !== void 0) return fallback;
621
+ }
622
+ for (let index = oldIndex - 1; index >= 0; index -= 1) {
623
+ const fallback = indexesById.get(this.#activeOrder[index] ?? "");
624
+ if (fallback !== void 0) return fallback;
625
+ }
626
+ return -1;
627
+ }
628
+ /** Reflects whether any visible, navigable command remains. */
629
+ #reflectEmptyState() {
630
+ if (this.hasEmptyTarget) this.emptyTarget.hidden = this.#visibleOptions.length > 0;
631
+ }
632
+ /** Coalesces all target callbacks from one MutationObserver batch. */
633
+ #queueOptionReconciliation() {
634
+ this.#reconcile.schedule();
635
+ }
636
+ /**
637
+ * Runs `option`: dispatches `select` and closes.
638
+ *
639
+ * **Activation is refused here, not at the call sites.** Every path that runs a
640
+ * command funnels through this method, so one check covers keyboard, pointer,
641
+ * and anything added later. Per-call-site guards would leave `aria-disabled`
642
+ * options — which stay within the keyboard's reach — runnable from whichever
643
+ * path forgets to consult the predicate.
644
+ */
502
645
  #confirmSelection(option) {
646
+ if (this.#isDisabled(option)) return;
503
647
  const value = option.dataset.value || option.textContent || "";
504
648
  this.dispatch("select", { detail: { value, option } });
505
649
  this.close();
@@ -510,14 +654,33 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
510
654
  option.removeAttribute("hidden");
511
655
  }
512
656
  const hasSelectableOption = this.#visibleOptions.length > 0;
513
- if (this.hasEmptyTarget) this.emptyTarget.hidden = hasSelectableOption;
657
+ this.#reflectEmptyState();
514
658
  this.#setActiveIndex(hasSelectableOption ? 0 : -1);
515
659
  }
516
660
  get #visibleOptions() {
517
661
  return this.optionTargets.filter(
518
- (option) => !option.hasAttribute("hidden") && !this.#isDisabled(option)
662
+ (option) => !option.hasAttribute("hidden") && this.#isNavigable(option)
519
663
  );
520
664
  }
665
+ /**
666
+ * Whether virtual focus may land on `option`.
667
+ *
668
+ * Only `data-disabled` disqualifies. That attribute is this controller's own —
669
+ * authors reach for it to mark a row that is not a destination at all, such as
670
+ * a group heading — so it can mean "skip entirely" without contradicting ARIA.
671
+ *
672
+ * **`aria-disabled` does not disqualify.** It marks a command that must stay
673
+ * *discoverable*: `aria-activedescendant` names it, so a reader announces it as
674
+ * unavailable rather than hiding its existence. Virtual focus does
675
+ * not change that — pointing `aria-activedescendant` at a disabled option is
676
+ * ordinary ARIA. Keeping the two attributes distinct is what leaves both
677
+ * meanings expressible; collapsing them would take "show it, say it is
678
+ * unavailable, do not run it" away from consumers.
679
+ */
680
+ #isNavigable(option) {
681
+ return option.dataset.disabled !== "true";
682
+ }
683
+ /** Whether activating `option` may run. Suppressed for either disabled marker. */
521
684
  #isDisabled(option) {
522
685
  return option.dataset.disabled === "true" || option.getAttribute("aria-disabled") === "true";
523
686
  }
@@ -525,9 +688,18 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
525
688
  #syncOptionSemantics() {
526
689
  for (const option of this.optionTargets) this.#syncOptionSemanticsFor(option);
527
690
  }
528
- /** Reflects `data-disabled` to ARIA without losing a pre-existing authored value. */
691
+ /**
692
+ * Reflects `data-disabled` to ARIA without losing a pre-existing authored value,
693
+ * and puts `aria-selected` back to its baseline.
694
+ *
695
+ * Here `aria-selected` marks the *active* option, not a committed choice — the
696
+ * palette runs a command and keeps nothing selected — so an authored value is
697
+ * meaningless and is overwritten rather than preserved. Every caller either
698
+ * establishes the active option straight after (`filter`, `#setActiveIndex`) or
699
+ * is handling an option that cannot be the active one yet
700
+ * (`optionTargetConnected`).
701
+ */
529
702
  #syncOptionSemanticsFor(option) {
530
- if (!option.hasAttribute("aria-selected")) option.setAttribute("aria-selected", "false");
531
703
  const originalAttribute = _CommandPaletteController.#ORIGINAL_ARIA_DISABLED;
532
704
  const originalValue = option.getAttribute(originalAttribute);
533
705
  if (option.dataset.disabled === "true") {
@@ -581,8 +753,13 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
581
753
  }
582
754
  /** Resets transient open state so reconnect starts from a predictable closed snapshot. */
583
755
  #resetToClosedState() {
584
- this.#activeIndex = -1;
756
+ this.#activeId = null;
757
+ this.#activeOrder = [];
585
758
  this.openValue = false;
759
+ for (const option of this.optionTargets) {
760
+ option.setAttribute("aria-selected", "false");
761
+ option.removeAttribute("data-active");
762
+ }
586
763
  if (this.hasDialogTarget) {
587
764
  this.dialogTarget.hidden = true;
588
765
  }