stimeo-ui 0.3.0 → 0.5.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 (148) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/controllers/alert_dialog_controller.js +32 -5
  3. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  4. package/dist/controllers/announcer_controller.d.ts +32 -6
  5. package/dist/controllers/announcer_controller.js +255 -20
  6. package/dist/controllers/announcer_controller.js.map +1 -1
  7. package/dist/controllers/aspect_ratio_controller.d.ts +2 -2
  8. package/dist/controllers/aspect_ratio_controller.js +1 -1
  9. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  10. package/dist/controllers/breadcrumb_controller.js +5 -1
  11. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  12. package/dist/controllers/carousel_controller.js +5 -1
  13. package/dist/controllers/carousel_controller.js.map +1 -1
  14. package/dist/controllers/clipboard_controller.js +8 -3
  15. package/dist/controllers/clipboard_controller.js.map +1 -1
  16. package/dist/controllers/collapsible_controller.d.ts +1 -1
  17. package/dist/controllers/collapsible_controller.js +4 -1
  18. package/dist/controllers/collapsible_controller.js.map +1 -1
  19. package/dist/controllers/color_picker_controller.d.ts +4 -3
  20. package/dist/controllers/color_picker_controller.js +52 -2
  21. package/dist/controllers/color_picker_controller.js.map +1 -1
  22. package/dist/controllers/combobox_controller.js.map +1 -1
  23. package/dist/controllers/command_palette_controller.js +32 -5
  24. package/dist/controllers/command_palette_controller.js.map +1 -1
  25. package/dist/controllers/confirm_controller.js +32 -5
  26. package/dist/controllers/confirm_controller.js.map +1 -1
  27. package/dist/controllers/context_menu_controller.d.ts +1 -1
  28. package/dist/controllers/context_menu_controller.js +2 -2
  29. package/dist/controllers/context_menu_controller.js.map +1 -1
  30. package/dist/controllers/countdown_controller.d.ts +24 -8
  31. package/dist/controllers/countdown_controller.js +117 -13
  32. package/dist/controllers/countdown_controller.js.map +1 -1
  33. package/dist/controllers/date_range_picker_controller.d.ts +4 -1
  34. package/dist/controllers/date_range_picker_controller.js +55 -1
  35. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  36. package/dist/controllers/dialog_controller.js +32 -5
  37. package/dist/controllers/dialog_controller.js.map +1 -1
  38. package/dist/controllers/direct_upload_controller.d.ts +1 -1
  39. package/dist/controllers/direct_upload_controller.js +3 -3
  40. package/dist/controllers/direct_upload_controller.js.map +1 -1
  41. package/dist/controllers/dismissible_controller.js.map +1 -1
  42. package/dist/controllers/drawer_controller.js +32 -5
  43. package/dist/controllers/drawer_controller.js.map +1 -1
  44. package/dist/controllers/empty_state_controller.d.ts +36 -11
  45. package/dist/controllers/empty_state_controller.js +128 -23
  46. package/dist/controllers/empty_state_controller.js.map +1 -1
  47. package/dist/controllers/flash_controller.d.ts +25 -5
  48. package/dist/controllers/flash_controller.js +161 -21
  49. package/dist/controllers/flash_controller.js.map +1 -1
  50. package/dist/controllers/focus_controller.js +32 -5
  51. package/dist/controllers/focus_controller.js.map +1 -1
  52. package/dist/controllers/form_validation_controller.js +8 -2
  53. package/dist/controllers/form_validation_controller.js.map +1 -1
  54. package/dist/controllers/frame_loading_controller.d.ts +31 -3
  55. package/dist/controllers/frame_loading_controller.js +261 -27
  56. package/dist/controllers/frame_loading_controller.js.map +1 -1
  57. package/dist/controllers/highlight_controller.d.ts +8 -4
  58. package/dist/controllers/highlight_controller.js +38 -1
  59. package/dist/controllers/highlight_controller.js.map +1 -1
  60. package/dist/controllers/idle_controller.d.ts +2 -1
  61. package/dist/controllers/idle_controller.js +13 -2
  62. package/dist/controllers/idle_controller.js.map +1 -1
  63. package/dist/controllers/listbox_controller.js.map +1 -1
  64. package/dist/controllers/local_time_controller.d.ts +19 -3
  65. package/dist/controllers/local_time_controller.js +102 -6
  66. package/dist/controllers/local_time_controller.js.map +1 -1
  67. package/dist/controllers/masonry_controller.d.ts +1 -1
  68. package/dist/controllers/masonry_controller.js +1 -1
  69. package/dist/controllers/masonry_controller.js.map +1 -1
  70. package/dist/controllers/meter_controller.d.ts +22 -2
  71. package/dist/controllers/meter_controller.js +147 -26
  72. package/dist/controllers/meter_controller.js.map +1 -1
  73. package/dist/controllers/multi_select_controller.js.map +1 -1
  74. package/dist/controllers/network_status_controller.d.ts +25 -11
  75. package/dist/controllers/network_status_controller.js +29 -11
  76. package/dist/controllers/network_status_controller.js.map +1 -1
  77. package/dist/controllers/number_input_controller.d.ts +8 -0
  78. package/dist/controllers/number_input_controller.js +191 -24
  79. package/dist/controllers/number_input_controller.js.map +1 -1
  80. package/dist/controllers/overflow_menu_controller.js +34 -7
  81. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  82. package/dist/controllers/pagination_controller.js +5 -1
  83. package/dist/controllers/pagination_controller.js.map +1 -1
  84. package/dist/controllers/password_strength_controller.d.ts +1 -1
  85. package/dist/controllers/password_strength_controller.js +1 -1
  86. package/dist/controllers/password_strength_controller.js.map +1 -1
  87. package/dist/controllers/pointer_drag_controller.js +10 -0
  88. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  89. package/dist/controllers/portal_controller.js +10 -0
  90. package/dist/controllers/portal_controller.js.map +1 -1
  91. package/dist/controllers/progress_controller.d.ts +17 -2
  92. package/dist/controllers/progress_controller.js +123 -12
  93. package/dist/controllers/progress_controller.js.map +1 -1
  94. package/dist/controllers/range_slider_controller.d.ts +27 -1
  95. package/dist/controllers/range_slider_controller.js +449 -93
  96. package/dist/controllers/range_slider_controller.js.map +1 -1
  97. package/dist/controllers/rating_controller.d.ts +4 -1
  98. package/dist/controllers/rating_controller.js +55 -0
  99. package/dist/controllers/rating_controller.js.map +1 -1
  100. package/dist/controllers/relative_time_controller.d.ts +13 -0
  101. package/dist/controllers/relative_time_controller.js +135 -12
  102. package/dist/controllers/relative_time_controller.js.map +1 -1
  103. package/dist/controllers/scroll_area_controller.d.ts +1 -1
  104. package/dist/controllers/scroll_area_controller.js +1 -1
  105. package/dist/controllers/scroll_area_controller.js.map +1 -1
  106. package/dist/controllers/separator_controller.js +13 -17
  107. package/dist/controllers/separator_controller.js.map +1 -1
  108. package/dist/controllers/sidebar_controller.d.ts +1 -11
  109. package/dist/controllers/sidebar_controller.js +37 -8
  110. package/dist/controllers/sidebar_controller.js.map +1 -1
  111. package/dist/controllers/skeleton_controller.d.ts +7 -2
  112. package/dist/controllers/skeleton_controller.js +143 -22
  113. package/dist/controllers/skeleton_controller.js.map +1 -1
  114. package/dist/controllers/slider_controller.d.ts +17 -1
  115. package/dist/controllers/slider_controller.js +342 -50
  116. package/dist/controllers/slider_controller.js.map +1 -1
  117. package/dist/controllers/spinner_controller.d.ts +50 -12
  118. package/dist/controllers/spinner_controller.js +244 -28
  119. package/dist/controllers/spinner_controller.js.map +1 -1
  120. package/dist/controllers/step_indicator_controller.d.ts +13 -2
  121. package/dist/controllers/step_indicator_controller.js +85 -6
  122. package/dist/controllers/step_indicator_controller.js.map +1 -1
  123. package/dist/controllers/stepper_controller.js +2 -0
  124. package/dist/controllers/stepper_controller.js.map +1 -1
  125. package/dist/controllers/stick_to_bottom_controller.d.ts +26 -2
  126. package/dist/controllers/stick_to_bottom_controller.js +60 -10
  127. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  128. package/dist/controllers/switch_controller.d.ts +12 -8
  129. package/dist/controllers/switch_controller.js +162 -18
  130. package/dist/controllers/switch_controller.js.map +1 -1
  131. package/dist/controllers/textarea_autosize_controller.js +1 -1
  132. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  133. package/dist/controllers/time_picker_controller.d.ts +3 -0
  134. package/dist/controllers/time_picker_controller.js +6 -3
  135. package/dist/controllers/time_picker_controller.js.map +1 -1
  136. package/dist/controllers/tree_view_controller.d.ts +1 -2
  137. package/dist/controllers/tree_view_controller.js +19 -1
  138. package/dist/controllers/tree_view_controller.js.map +1 -1
  139. package/dist/index.js +2278 -596
  140. package/dist/index.js.map +1 -1
  141. package/dist/inspector/cli.d.ts +76 -1
  142. package/dist/inspector/cli.js +937 -76
  143. package/dist/inspector/cli.js.map +1 -1
  144. package/dist/inspector/cli_bin.js +997 -78
  145. package/dist/inspector/cli_bin.js.map +1 -1
  146. package/dist/inspector/examples.json +20 -20
  147. package/dist/inspector/manifest.json +428 -33
  148. package/package.json +3 -3
@@ -1 +1 @@
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"]}
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":";;;;;AA6DO,SAAS,gBAAA,CACd,SACA,MAAA,EACQ;AACR,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,IAAA,GAAO,MAAA,KAAW,MAAA,GAAS,MAAA,GAAS,OAAA;AAC1C,IAAA,IAAI,MAAA,CAAO,YAAA,CAAa,eAAe,CAAA,KAAM,IAAA,EAAM;AACnD,IAAA,MAAA,CAAO,YAAA,CAAa,iBAAiB,IAAI,CAAA;AACzC,IAAA,OAAA,IAAW,CAAA;AAAA,EACb;AACA,EAAA,OAAO,OAAA;AACT;;;ACsBO,SAAS,oBAAA,CACd,KAAA,EACA,KAAA,GAAkC,EAAC,EAC1B;AACT,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,UAAA,CAAW,OAAO,GAAG,OAAO,KAAA;AAC3C,EAAA,OACG,KAAA,CAAM,MAAA,IAAU,CAAC,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,IACrC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,KACvC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACvC,KAAA,CAAM,QAAA,IAAY,CAAC,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA;AAE9C;;;ACnEO,IAAM,qBAAN,MAAyB;AAAA,EACrB,gBAAA,uBAAuB,GAAA,EAAiB;AAAA,EACxC,cAAA,uBAAqB,GAAA,EAAiB;AAAA,EACtC,QAAA;AAAA,EACA,MAAA;AAAA,EAET,WAAA,CAAY,OAAA,GAAqC,EAAC,EAAG;AACnD,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,OAAA;AACxB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,KAAA;AAAA,EACxB;AAAA;AAAA,EAGA,QAAQ,MAAA,EAA2B;AACjC,IAAA,IAAI,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,MAAM,CAAA,EAAG;AACvC,IAAA,MAAA,CAAO,gBAAA,CAAiB,kBAAA,EAAoB,IAAA,CAAK,YAAY,CAAA;AAC7D,IAAA,MAAA,CAAO,gBAAA,CAAiB,gBAAA,EAAkB,IAAA,CAAK,UAAU,CAAA;AACzD,IAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,MAAM,CAAA;AAAA,EAClC;AAAA;AAAA,EAGA,UAAU,MAAA,EAA2B;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,CAAiB,MAAA,CAAO,MAAM,CAAA,EAAG;AAC3C,IAAA,MAAA,CAAO,mBAAA,CAAoB,kBAAA,EAAoB,IAAA,CAAK,YAAY,CAAA;AAChE,IAAA,MAAA,CAAO,mBAAA,CAAoB,gBAAA,EAAkB,IAAA,CAAK,UAAU,CAAA;AAC5D,IAAA,IAAA,CAAK,cAAA,CAAe,OAAO,MAAM,CAAA;AAAA,EACnC;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,gBAAA,EAAkB;AAC1C,MAAA,MAAA,CAAO,mBAAA,CAAoB,kBAAA,EAAoB,IAAA,CAAK,YAAY,CAAA;AAChE,MAAA,MAAA,CAAO,mBAAA,CAAoB,gBAAA,EAAkB,IAAA,CAAK,UAAU,CAAA;AAAA,IAC9D;AACA,IAAA,IAAA,CAAK,iBAAiB,KAAA,EAAM;AAC5B,IAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAAA,EAC5B;AAAA;AAAA,EAGA,YAAY,KAAA,EAAoC;AAC9C,IAAA,OAAO,IAAA,CAAK,cAAA,CAAe,IAAA,GAAO,CAAA,IAAK,OAAO,WAAA,KAAgB,IAAA;AAAA,EAChE;AAAA,EAES,YAAA,GAAe,CAAC,KAAA,KAAuB;AAC9C,IAAA,IAAI,MAAM,aAAA,EAAe,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,MAAM,aAAa,CAAA;AACpE,IAAA,IAAA,CAAK,WAAW,KAAK,CAAA;AAAA,EACvB,CAAA;AAAA,EAES,UAAA,GAAa,CAAC,KAAA,KAAuB;AAC5C,IAAA,IAAI,MAAM,aAAA,EAAe,IAAA,CAAK,cAAA,CAAe,MAAA,CAAO,MAAM,aAAa,CAAA;AACvE,IAAA,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,EACrB,CAAA;AACF,CAAA;;;ACrCO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AC/DO,SAAS,oBAAA,CAAqB,MAAmB,MAAA,EAA2B;AACjF,EAAA,IAAI,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,YAAA,EAAc;AAC5C,EAAA,MAAM,QAAA,GAAW,KAAK,qBAAA,EAAsB;AAC5C,EAAA,MAAM,UAAA,GAAa,OAAO,qBAAA,EAAsB;AAChD,EAAA,IAAI,UAAA,CAAW,GAAA,GAAM,QAAA,CAAS,GAAA,EAAK;AACjC,IAAA,IAAA,CAAK,SAAA,IAAa,QAAA,CAAS,GAAA,GAAM,UAAA,CAAW,GAAA;AAAA,EAC9C,CAAA,MAAA,IAAW,UAAA,CAAW,MAAA,GAAS,QAAA,CAAS,MAAA,EAAQ;AAC9C,IAAA,IAAA,CAAK,SAAA,IAAa,UAAA,CAAW,MAAA,GAAS,QAAA,CAAS,MAAA;AAAA,EACjD;AACF;;;ACgBO,IAAM,kBAAA,GAAN,cAAiC,UAAA,CAAwB;AAAA,EAC9D,OAAgB,OAAA,GAAU,CAAC,OAAA,EAAS,QAAQ,QAAQ,CAAA;AAAA,EACpD,OAAO,OAAA,GAAU,CAAC,SAAS,QAAA,EAAU,WAAA,EAAa,QAAQ,eAAe,CAAA;AAAA,EACzE,OAAO,MAAA,GAAS,CAAC,UAAU,CAAA;AAAA;AAAA,EAS3B,SAAA,GAA2B,IAAA;AAAA,EAC3B,UAAA,GAAa,KAAA;AAAA;AAAA,EAEJ,aAAa,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,mBAAmB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM3E,aAAA,GAAgB,KAAA;AAAA;AAAA,EAEP,YAAA,GAAe,IAAI,kBAAA,CAAmB,EAAE,OAAO,MAAM,IAAA,CAAK,MAAA,EAAO,EAAG,CAAA;AAAA;AAAA,EAGpE,OAAA,GAAgB;AACvB,IAAA,IAAI,KAAK,cAAA,EAAgB,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,KAAK,WAAW,CAAA;AACnE,IAAA,IAAA,CAAK,KAAA,EAAM;AACX,IAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,eAAA,EAAiB,IAAI,CAAA;AAC7D,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGS,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,IAAA,CAAK,WAAW,MAAA,EAAO;AACvB,IAAA,IAAA,CAAK,aAAa,UAAA,EAAW;AAC7B,IAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,eAAA,EAAiB,IAAI,CAAA;AAAA,EAClE;AAAA;AAAA,EAGA,qBAAqB,KAAA,EAA+B;AAClD,IAAA,IAAA,CAAK,YAAA,CAAa,QAAQ,KAAK,CAAA;AAC/B,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,0BAAA,EAA2B;AAAA,EACvD;AAAA;AAAA,EAGA,wBAAwB,KAAA,EAA+B;AACrD,IAAA,IAAA,CAAK,YAAA,CAAa,UAAU,KAAK,CAAA;AAAA,EACnC;AAAA;AAAA,EAGA,sBAAsB,MAAA,EAA2B;AAC/C,IAAA,MAAA,CAAO,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAC5C,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,0BAAA,EAA2B;AAAA,EACvD;AAAA;AAAA,EAGA,yBAAyB,MAAA,EAA2B;AAClD,IAAA,MAAA,CAAO,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAC5C,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,0BAAA,EAA2B;AAAA,EACvD;AAAA;AAAA,EAGA,OAAO,KAAA,EAA0B;AAC/B,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,WAAA,CAAY,KAAK,CAAA,EAAG;AAC1C,IAAA,IAAA,CAAK,IAAA,EAAK;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAA,GAAa;AACX,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,IAAiB,CAAC,IAAA,CAAK,cAAA,IAAkB,KAAK,aAAA,EAAe;AACvE,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,WAAW,MAAA,GAAS,KAAA;AACzB,IAAA,IAAA,CAAK,WAAA,CAAY,YAAA,CAAa,eAAA,EAAiB,MAAM,CAAA;AACrD,IAAA,IAAA,CAAK,WAAW,EAAE,CAAA;AAClB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAA,GAAqB;AACnB,IAAA,MAAM,QAAQ,IAAA,CAAK,WAAA,CAAY,KAAA,CAAM,IAAA,GAAO,WAAA,EAAY;AACxD,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,aAAA,EAAe;AACvC,MAAA,MAAM,QAAQ,MAAA,CAAO,WAAA,IAAe,EAAA,EAAI,IAAA,GAAO,WAAA,EAAY;AAC3D,MAAA,MAAA,CAAO,SAAS,KAAA,CAAM,MAAA,GAAS,KAAK,CAAC,IAAA,CAAK,SAAS,KAAK,CAAA;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,KAAA,GAAc;AACZ,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACzB,IAAA,IAAA,CAAK,WAAW,MAAA,GAAS,IAAA;AACzB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,6BAA6B,CAAA;AAC1D,IAAA,IAAI,KAAK,cAAA,EAAgB,IAAA,CAAK,WAAA,CAAY,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAC/E,IAAA,IAAA,CAAK,WAAW,EAAE,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,UAAU,KAAA,EAA4B;AAKpC,IAAA,IAAI,IAAA,CAAK,YAAA,CAAa,WAAA,CAAY,KAAK,CAAA,EAAG;AAI1C,IAAA,IAAI,MAAM,gBAAA,EAAkB;AAI5B,IAAA,IAAI,oBAAA,CAAqB,KAAA,EAAO,CAAC,KAAK,CAAC,CAAA,EAAG;AAK1C,IAAA,IAAI,MAAM,MAAA,KAAW,KAAA,CAAM,QAAQ,WAAA,IAAe,KAAA,CAAM,QAAQ,SAAA,CAAA,EAAY;AAI1E,MAAA,IAAI,MAAM,GAAA,KAAQ,WAAA,GAAc,CAAC,IAAA,CAAK,SAAA,GAAY,KAAK,SAAA,EAAW;AAClE,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAI,KAAA,CAAM,QAAQ,WAAA,EAAa;AAC7B,QAAA,IAAA,CAAK,IAAA,EAAK;AAAA,MACZ,CAAA,MAAO;AAKL,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,gBAAA,EAAiB;AAC3C,IAAA,QAAQ,MAAM,GAAA;AAAK,MACjB,KAAK,WAAA,EAAa;AAChB,QAAA,KAAA,CAAM,cAAA,EAAe;AAErB,QAAA,IAAI,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,IAAA,EAAK;AAC9B,QAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,QAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,UAAA,MAAM,WAAA,GAAc,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AACjD,UAAA,MAAM,OAAO,WAAA,KAAgB,EAAA,GAAK,CAAA,GAAA,CAAK,WAAA,GAAc,KAAK,OAAA,CAAQ,MAAA;AAClE,UAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AAAA,QACtB;AACA,QAAA;AAAA,MACF;AAAA,MACA,KAAK,SAAA,EAAW;AACd,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAI,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,IAAA,EAAK;AAC9B,QAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,QAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,UAAA,MAAM,WAAA,GAAc,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AAGjD,UAAA,MAAM,IAAA,GACJ,WAAA,KAAgB,EAAA,GACZ,OAAA,CAAQ,MAAA,GAAS,KAChB,WAAA,GAAc,CAAA,GAAI,OAAA,CAAQ,MAAA,IAAU,OAAA,CAAQ,MAAA;AACnD,UAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AAAA,QACtB;AACA,QAAA;AAAA,MACF;AAAA,MACA,KAAK,MAAA;AACH,QAAA,IAAI,CAAC,IAAA,CAAK,SAAA,IAAa,KAAK,eAAA,EAAgB,CAAE,SAAS,CAAA,EAAG;AACxD,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,IAAA,CAAK,WAAW,CAAC,CAAA;AAAA,QACnB;AACA,QAAA;AAAA,MACF,KAAK,KAAA,EAAO;AACV,QAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,QAAA,IAAI,CAAC,IAAA,CAAK,SAAA,IAAa,OAAA,CAAQ,SAAS,CAAA,EAAG;AACzC,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA;AAAA,QACpC;AACA,QAAA;AAAA,MACF;AAAA,MACA,KAAK,OAAA,EAAS;AACZ,QAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,QAAA,MAAM,WAAA,GAAc,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AACjD,QAAA,MAAM,MAAA,GAAS,WAAA,KAAgB,EAAA,GAAK,MAAA,GAAY,QAAQ,WAAW,CAAA;AACnE,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,QACrB;AACA,QAAA;AAAA,MACF;AAAA,MACA,KAAK,QAAA;AAKH,QAAA,IAAI,KAAK,SAAA,EAAW;AACpB,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,IAAA,CAAK,KAAA,EAAM;AACX,QAAA;AAAA,MACF,KAAK,KAAA;AAEH,QAAA,IAAA,CAAK,KAAA,EAAM;AACX,QAAA;AAEA;AACJ,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,eAAA,GAAkB,CAAC,KAAA,KAA4B;AACtD,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,IAAa,CAAC,IAAA,CAAK,OAAA,CAAQ,QAAA,CAAS,KAAA,CAAM,MAAc,CAAA,EAAG,IAAA,CAAK,KAAA,EAAM;AAAA,EAClF,CAAA;AAAA;AAAA,EAGA,cAAc,KAAA,EAAoB;AAChC,IAAA,MAAM,MAAA,GAAU,KAAA,CAAM,aAAA,CAA8B,OAAA,CAAqB,iBAAiB,CAAA;AAC1F,IAAA,IAAI,MAAA,IAAU,KAAK,aAAA,CAAc,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAQ,MAAA,EAA2B;AACjC,IAAA,MAAM,QAAQ,MAAA,CAAO,OAAA,CAAQ,UAAU,MAAA,CAAO,WAAA,IAAe,IAAI,IAAA,EAAK;AACtE,IAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AACxB,MAAA,IAAA,CAAK,KAAA,EAAM;AACX,MAAA,IAAA,CAAK,SAAS,UAAA,EAAY,EAAE,QAAQ,EAAE,KAAA,IAAS,CAAA;AAC/C,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,WAAA,CAAY,KAAA,KAAU,KAAA;AAC3C,IAAA,IAAA,CAAK,YAAY,KAAA,GAAQ,KAAA;AACzB,IAAA,IAAA,CAAK,KAAA,EAAM;AAGX,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AACvB,IAAA,IAAA,CAAK,aAAA,GAAgB,KAAA;AACrB,IAAA,IAAI,OAAA,EAAS;AAMX,MAAA,IAAA,CAAK,WAAA,CAAY,cAAc,IAAI,KAAA,CAAM,UAAU,EAAE,OAAA,EAAS,IAAA,EAAM,CAAC,CAAA;AAAA,IACvE;AACA,IAAA,IAAA,CAAK,SAAS,UAAA,EAAY,EAAE,QAAQ,EAAE,KAAA,IAAS,CAAA;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAA,GAA2B;AACzB,IAAA,MAAM,QAAQ,CAAC,IAAA,CAAK,aAAa,IAAA,CAAK,eAAA,GAAkB,MAAA,KAAW,CAAA;AACnE,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,6BAAA,EAA+B,EAAE,CAAA;AAAA,IAC7D,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,6BAA6B,CAAA;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,KAAA,EAAqB;AAC9B,IAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,IAAA,MAAM,SAAS,KAAA,KAAU,EAAA,GAAK,IAAA,GAAQ,OAAA,CAAQ,KAAK,CAAA,IAAK,IAAA;AAGxD,IAAA,gBAAA,CAAiB,IAAA,CAAK,eAAe,MAAM,CAAA;AAC3C,IAAA,IAAA,CAAK,SAAA,GAAY,QAAQ,EAAA,IAAM,IAAA;AAC/B,IAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,MAAA,IAAI,QAAQ,EAAA,EAAI;AACd,QAAA,IAAA,CAAK,WAAA,CAAY,YAAA,CAAa,uBAAA,EAAyB,MAAA,CAAO,EAAE,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,WAAA,CAAY,gBAAgB,uBAAuB,CAAA;AAAA,MAC1D;AAAA,IACF;AAGA,IAAA,IAAI,UAAU,IAAA,CAAK,aAAA,EAAe,oBAAA,CAAqB,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,EAChF;AAAA;AAAA,EAGA,iBAAA,GAA0B;AACxB,IAAA,IAAI,KAAK,cAAA,IAAkB,CAAC,IAAA,CAAK,SAAA,OAAgB,YAAA,EAAa;AAC9D,IAAA,IAAA,CAAK,gBAAA,EAAiB;AACtB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AAAA,EAC1B;AAAA;AAAA,EAGA,gBAAA,GAAyB;AACvB,IAAA,IAAI,CAAC,IAAA,CAAK,cAAA,IAAkB,IAAA,CAAK,SAAA,EAAW;AAC1C,MAAA,IAAA,CAAK,WAAW,EAAE,CAAA;AAClB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,gBAAA,CAAiB,OAAO,CAAA;AAC3C,IAAA,MAAM,SAAS,KAAA,KAAU,EAAA,GAAK,IAAA,GAAQ,OAAA,CAAQ,KAAK,CAAA,IAAK,IAAA;AACxD,IAAA,MAAM,QAAA,GAAW,KAAK,aAAA,CAAc,MAAA;AAAA,MAClC,CAAC,MAAA,KAAW,MAAA,CAAO,YAAA,CAAa,eAAe,CAAA,KAAM;AAAA,KACvD;AACA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,CAAY,YAAA,CAAa,uBAAuB,CAAA;AACnE,IAAA,MAAM,YAAA,GAAe,MAAA,GACjB,IAAA,CAAK,SAAA,MAAe,MAAA,CAAO,MAAM,IAAA,CAAA,IACjC,QAAA,CAAS,MAAA,KAAW,CAAA,IACpB,QAAA,CAAS,CAAC,MAAM,MAAA,IAChB,KAAA,MAAW,MAAA,CAAO,EAAA,IAAM,IAAA,CAAA,GACxB,IAAA,CAAK,cAAc,IAAA,IAAQ,QAAA,CAAS,MAAA,KAAW,CAAA,IAAK,KAAA,KAAU,IAAA;AAClE,IAAA,IAAI,CAAC,YAAA,EAAc,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA;AAAA,EAC1C;AAAA;AAAA,EAGA,iBAAiB,OAAA,EAAyC;AACxD,IAAA,MAAM,QAAA,GAAA,CACH,KAAK,cAAA,GAAiB,IAAA,CAAK,YAAY,YAAA,CAAa,uBAAuB,CAAA,GAAI,IAAA,KAChF,IAAA,CAAK,SAAA;AACP,IAAA,IAAI,QAAA,SAAiB,OAAA,CAAQ,SAAA,CAAU,CAAC,MAAA,KAAW,MAAA,CAAO,OAAO,QAAQ,CAAA;AACzE,IAAA,OAAO,OAAA,CAAQ,UAAU,CAAC,MAAA,KAAW,OAAO,YAAA,CAAa,eAAe,MAAM,MAAM,CAAA;AAAA,EACtF;AAAA;AAAA,EAGA,0BAAA,GAAmC;AACjC,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGA,eAAA,GAAiC;AAC/B,IAAA,OAAO,KAAK,aAAA,CAAc,MAAA,CAAO,CAAC,MAAA,KAAW,CAAC,OAAO,MAAM,CAAA;AAAA,EAC7D;AAAA;AAAA,EAGA,IAAI,SAAA,GAAqB;AACvB,IAAA,OAAO,CAAC,IAAA,CAAK,aAAA,IAAiB,IAAA,CAAK,WAAW,MAAA,KAAW,KAAA;AAAA,EAC3D;AACF","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 Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same 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 initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output 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"]}
@@ -67,6 +67,35 @@ var CompositionTracker = class {
67
67
  };
68
68
  };
69
69
 
70
+ // src/utils/before_cache_reset.ts
71
+ var BeforeCacheReset = class _BeforeCacheReset {
72
+ /** Every subscribed instance, iterated by the one shared document listener. */
73
+ static #subscribers = /* @__PURE__ */ new Set();
74
+ /** The shared listener; installed while at least one instance is subscribed. */
75
+ static #onBeforeCache = () => {
76
+ for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
77
+ };
78
+ #rewind;
79
+ /** @param rewind - the pass that returns this controller's state to its initial form. */
80
+ constructor(rewind) {
81
+ this.#rewind = rewind;
82
+ }
83
+ /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
84
+ activate() {
85
+ const first = _BeforeCacheReset.#subscribers.size === 0;
86
+ _BeforeCacheReset.#subscribers.add(this);
87
+ if (first) {
88
+ document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
89
+ }
90
+ }
91
+ /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
92
+ deactivate() {
93
+ _BeforeCacheReset.#subscribers.delete(this);
94
+ if (_BeforeCacheReset.#subscribers.size > 0) return;
95
+ document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
96
+ }
97
+ };
98
+
70
99
  // src/utils/escape_layer.ts
71
100
  var EscapeLayer = class _EscapeLayer {
72
101
  static #registries = /* @__PURE__ */ new WeakMap();
@@ -200,7 +229,7 @@ var FocusTrap = class {
200
229
  }
201
230
  if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
202
231
  document.addEventListener("keydown", this.#onKeydown);
203
- document.addEventListener("turbo:before-cache", this.#onBeforeCache);
232
+ this.#beforeCache.activate();
204
233
  const onEscape = this.#options.onEscape;
205
234
  if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
206
235
  if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
@@ -217,7 +246,7 @@ var FocusTrap = class {
217
246
  this.#activeState = false;
218
247
  this.#escapeLayer.deactivate();
219
248
  document.removeEventListener("keydown", this.#onKeydown);
220
- document.removeEventListener("turbo:before-cache", this.#onBeforeCache);
249
+ this.#beforeCache.deactivate();
221
250
  if (this.#scrollLocked) {
222
251
  document.body.style.overflow = this.#previousBodyOverflow;
223
252
  this.#scrollLocked = false;
@@ -241,9 +270,7 @@ var FocusTrap = class {
241
270
  * untouched (restore-open designs reopen against a clean baseline), and focus
242
271
  * is left alone mid-navigation. The listener lives only while active.
243
272
  */
244
- #onBeforeCache = () => {
245
- this.deactivate({ restoreFocus: false });
246
- };
273
+ #beforeCache = new BeforeCacheReset(() => this.deactivate({ restoreFocus: false }));
247
274
  /**
248
275
  * Handles `Tab` (focus trap) while active. `Escape` dismissal is owned by the
249
276
  * shared {@link EscapeLayer} resolver, so Tab trapping stays independent of