stimeo-ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/cable/index.d.ts +16 -16
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.d.ts +3 -2
  5. package/dist/controllers/accordion_controller.js +10 -0
  6. package/dist/controllers/accordion_controller.js.map +1 -1
  7. package/dist/controllers/alert_dialog_controller.d.ts +64 -0
  8. package/dist/controllers/alert_dialog_controller.js +318 -0
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -0
  10. package/dist/controllers/breadcrumb_controller.d.ts +97 -10
  11. package/dist/controllers/breadcrumb_controller.js +225 -13
  12. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  13. package/dist/controllers/calendar_controller.d.ts +11 -1
  14. package/dist/controllers/calendar_controller.js +89 -22
  15. package/dist/controllers/calendar_controller.js.map +1 -1
  16. package/dist/controllers/carousel_controller.d.ts +115 -0
  17. package/dist/controllers/carousel_controller.js +313 -0
  18. package/dist/controllers/carousel_controller.js.map +1 -0
  19. package/dist/controllers/clipboard_controller.d.ts +72 -0
  20. package/dist/controllers/clipboard_controller.js +144 -0
  21. package/dist/controllers/clipboard_controller.js.map +1 -0
  22. package/dist/controllers/collapsible_controller.d.ts +70 -0
  23. package/dist/controllers/collapsible_controller.js +327 -0
  24. package/dist/controllers/collapsible_controller.js.map +1 -0
  25. package/dist/controllers/color_picker_controller.d.ts +87 -0
  26. package/dist/controllers/color_picker_controller.js +252 -0
  27. package/dist/controllers/color_picker_controller.js.map +1 -0
  28. package/dist/controllers/combobox_controller.d.ts +14 -1
  29. package/dist/controllers/combobox_controller.js +162 -23
  30. package/dist/controllers/combobox_controller.js.map +1 -1
  31. package/dist/controllers/command_palette_controller.d.ts +2 -0
  32. package/dist/controllers/command_palette_controller.js +194 -17
  33. package/dist/controllers/command_palette_controller.js.map +1 -1
  34. package/dist/controllers/confirm_controller.js.map +1 -1
  35. package/dist/controllers/context_menu_controller.d.ts +7 -6
  36. package/dist/controllers/context_menu_controller.js +32 -10
  37. package/dist/controllers/context_menu_controller.js.map +1 -1
  38. package/dist/controllers/count_up_controller.js +8 -1
  39. package/dist/controllers/count_up_controller.js.map +1 -1
  40. package/dist/controllers/currency_input_controller.d.ts +72 -0
  41. package/dist/controllers/currency_input_controller.js +147 -0
  42. package/dist/controllers/currency_input_controller.js.map +1 -0
  43. package/dist/controllers/data_grid_controller.d.ts +89 -0
  44. package/dist/controllers/data_grid_controller.js +246 -0
  45. package/dist/controllers/data_grid_controller.js.map +1 -0
  46. package/dist/controllers/date_range_picker_controller.d.ts +88 -0
  47. package/dist/controllers/date_range_picker_controller.js +441 -0
  48. package/dist/controllers/date_range_picker_controller.js.map +1 -0
  49. package/dist/controllers/dialog_controller.js.map +1 -1
  50. package/dist/controllers/dismissible_controller.d.ts +57 -0
  51. package/dist/controllers/dismissible_controller.js +117 -0
  52. package/dist/controllers/dismissible_controller.js.map +1 -0
  53. package/dist/controllers/drawer_controller.d.ts +96 -0
  54. package/dist/controllers/drawer_controller.js +630 -0
  55. package/dist/controllers/drawer_controller.js.map +1 -0
  56. package/dist/controllers/dropdown_controller.d.ts +1 -2
  57. package/dist/controllers/dropdown_controller.js.map +1 -1
  58. package/dist/controllers/editable_controller.d.ts +73 -0
  59. package/dist/controllers/editable_controller.js +169 -0
  60. package/dist/controllers/editable_controller.js.map +1 -0
  61. package/dist/controllers/file_dropzone_controller.d.ts +92 -0
  62. package/dist/controllers/file_dropzone_controller.js +165 -0
  63. package/dist/controllers/file_dropzone_controller.js.map +1 -0
  64. package/dist/controllers/filter_controller.d.ts +60 -0
  65. package/dist/controllers/filter_controller.js +86 -0
  66. package/dist/controllers/filter_controller.js.map +1 -0
  67. package/dist/controllers/flash_controller.js +36 -5
  68. package/dist/controllers/flash_controller.js.map +1 -1
  69. package/dist/controllers/focus_controller.d.ts +1 -1
  70. package/dist/controllers/focus_controller.js.map +1 -1
  71. package/dist/controllers/form_field_controller.js.map +1 -1
  72. package/dist/controllers/form_validation_controller.js +1 -1
  73. package/dist/controllers/form_validation_controller.js.map +1 -1
  74. package/dist/controllers/highlight_controller.js +6 -4
  75. package/dist/controllers/highlight_controller.js.map +1 -1
  76. package/dist/controllers/hover_card_controller.d.ts +1 -1
  77. package/dist/controllers/hover_card_controller.js.map +1 -1
  78. package/dist/controllers/intersection_controller.js +67 -19
  79. package/dist/controllers/intersection_controller.js.map +1 -1
  80. package/dist/controllers/lazy_frame_controller.js +54 -11
  81. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  82. package/dist/controllers/listbox_controller.d.ts +9 -2
  83. package/dist/controllers/listbox_controller.js +257 -53
  84. package/dist/controllers/listbox_controller.js.map +1 -1
  85. package/dist/controllers/local_time_controller.js +2 -2
  86. package/dist/controllers/local_time_controller.js.map +1 -1
  87. package/dist/controllers/masonry_controller.d.ts +54 -0
  88. package/dist/controllers/masonry_controller.js +142 -0
  89. package/dist/controllers/masonry_controller.js.map +1 -0
  90. package/dist/controllers/menu_controller.d.ts +29 -11
  91. package/dist/controllers/menu_controller.js +104 -17
  92. package/dist/controllers/menu_controller.js.map +1 -1
  93. package/dist/controllers/menubar_controller.d.ts +142 -0
  94. package/dist/controllers/menubar_controller.js +785 -0
  95. package/dist/controllers/menubar_controller.js.map +1 -0
  96. package/dist/controllers/meter_controller.js.map +1 -1
  97. package/dist/controllers/multi_select_controller.d.ts +141 -0
  98. package/dist/controllers/multi_select_controller.js +755 -0
  99. package/dist/controllers/multi_select_controller.js.map +1 -0
  100. package/dist/controllers/navigation_menu_controller.d.ts +146 -0
  101. package/dist/controllers/navigation_menu_controller.js +511 -0
  102. package/dist/controllers/navigation_menu_controller.js.map +1 -0
  103. package/dist/controllers/nested_form_controller.d.ts +4 -5
  104. package/dist/controllers/nested_form_controller.js.map +1 -1
  105. package/dist/controllers/number_input_controller.d.ts +1 -1
  106. package/dist/controllers/number_input_controller.js +7 -0
  107. package/dist/controllers/number_input_controller.js.map +1 -1
  108. package/dist/controllers/otp_controller.js +18 -1
  109. package/dist/controllers/otp_controller.js.map +1 -1
  110. package/dist/controllers/overflow_indicator_controller.d.ts +15 -6
  111. package/dist/controllers/overflow_indicator_controller.js +246 -27
  112. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  113. package/dist/controllers/overflow_menu_controller.d.ts +78 -19
  114. package/dist/controllers/overflow_menu_controller.js +381 -57
  115. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  116. package/dist/controllers/pagination_controller.d.ts +36 -5
  117. package/dist/controllers/pagination_controller.js +163 -32
  118. package/dist/controllers/pagination_controller.js.map +1 -1
  119. package/dist/controllers/password_reveal_controller.d.ts +49 -0
  120. package/dist/controllers/password_reveal_controller.js +117 -0
  121. package/dist/controllers/password_reveal_controller.js.map +1 -0
  122. package/dist/controllers/persist_controller.js +6 -6
  123. package/dist/controllers/persist_controller.js.map +1 -1
  124. package/dist/controllers/pointer_drag_controller.d.ts +2 -3
  125. package/dist/controllers/pointer_drag_controller.js +9 -1
  126. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  127. package/dist/controllers/popover_controller.d.ts +1 -2
  128. package/dist/controllers/popover_controller.js +2 -2
  129. package/dist/controllers/popover_controller.js.map +1 -1
  130. package/dist/controllers/portal_controller.d.ts +4 -4
  131. package/dist/controllers/portal_controller.js.map +1 -1
  132. package/dist/controllers/progress_controller.js.map +1 -1
  133. package/dist/controllers/radio_group_controller.js +22 -3
  134. package/dist/controllers/radio_group_controller.js.map +1 -1
  135. package/dist/controllers/range_slider_controller.d.ts +97 -0
  136. package/dist/controllers/range_slider_controller.js +192 -0
  137. package/dist/controllers/range_slider_controller.js.map +1 -0
  138. package/dist/controllers/rating_controller.js +16 -2
  139. package/dist/controllers/rating_controller.js.map +1 -1
  140. package/dist/controllers/read_more_controller.d.ts +52 -0
  141. package/dist/controllers/read_more_controller.js +238 -0
  142. package/dist/controllers/read_more_controller.js.map +1 -0
  143. package/dist/controllers/reading_progress_controller.js.map +1 -1
  144. package/dist/controllers/resizable_controller.d.ts +3 -1
  145. package/dist/controllers/resizable_controller.js +65 -1
  146. package/dist/controllers/resizable_controller.js.map +1 -1
  147. package/dist/controllers/roving_controller.js +17 -2
  148. package/dist/controllers/roving_controller.js.map +1 -1
  149. package/dist/controllers/scroll_area_controller.js +101 -14
  150. package/dist/controllers/scroll_area_controller.js.map +1 -1
  151. package/dist/controllers/scroll_restore_controller.d.ts +54 -0
  152. package/dist/controllers/scroll_restore_controller.js +93 -0
  153. package/dist/controllers/scroll_restore_controller.js.map +1 -0
  154. package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
  155. package/dist/controllers/scroll_visibility_controller.js +40 -6
  156. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  157. package/dist/controllers/scrollspy_controller.d.ts +64 -15
  158. package/dist/controllers/scrollspy_controller.js +369 -74
  159. package/dist/controllers/scrollspy_controller.js.map +1 -1
  160. package/dist/controllers/separator_controller.d.ts +69 -0
  161. package/dist/controllers/separator_controller.js +96 -0
  162. package/dist/controllers/separator_controller.js.map +1 -0
  163. package/dist/controllers/sidebar_controller.d.ts +107 -0
  164. package/dist/controllers/sidebar_controller.js +761 -0
  165. package/dist/controllers/sidebar_controller.js.map +1 -0
  166. package/dist/controllers/skeleton_controller.js +1 -1
  167. package/dist/controllers/skeleton_controller.js.map +1 -1
  168. package/dist/controllers/slider_controller.d.ts +11 -1
  169. package/dist/controllers/slider_controller.js +32 -6
  170. package/dist/controllers/slider_controller.js.map +1 -1
  171. package/dist/controllers/sortable_controller.d.ts +3 -1
  172. package/dist/controllers/sortable_controller.js +34 -3
  173. package/dist/controllers/sortable_controller.js.map +1 -1
  174. package/dist/controllers/spinner_controller.js +1 -1
  175. package/dist/controllers/spinner_controller.js.map +1 -1
  176. package/dist/controllers/stepper_controller.d.ts +4 -0
  177. package/dist/controllers/stepper_controller.js +28 -12
  178. package/dist/controllers/stepper_controller.js.map +1 -1
  179. package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
  180. package/dist/controllers/stick_to_bottom_controller.js +9 -4
  181. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  182. package/dist/controllers/sticky_observer_controller.d.ts +17 -4
  183. package/dist/controllers/sticky_observer_controller.js +109 -20
  184. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  185. package/dist/controllers/submit_once_controller.d.ts +2 -2
  186. package/dist/controllers/submit_once_controller.js.map +1 -1
  187. package/dist/controllers/switch_controller.js +1 -0
  188. package/dist/controllers/switch_controller.js.map +1 -1
  189. package/dist/controllers/tabs_controller.d.ts +13 -4
  190. package/dist/controllers/tabs_controller.js +26 -3
  191. package/dist/controllers/tabs_controller.js.map +1 -1
  192. package/dist/controllers/tags_input_controller.d.ts +91 -0
  193. package/dist/controllers/tags_input_controller.js +295 -0
  194. package/dist/controllers/tags_input_controller.js.map +1 -0
  195. package/dist/controllers/theme_controller.d.ts +1 -1
  196. package/dist/controllers/theme_controller.js +42 -13
  197. package/dist/controllers/theme_controller.js.map +1 -1
  198. package/dist/controllers/time_picker_controller.d.ts +67 -0
  199. package/dist/controllers/time_picker_controller.js +231 -0
  200. package/dist/controllers/time_picker_controller.js.map +1 -0
  201. package/dist/controllers/toast_controller.d.ts +5 -14
  202. package/dist/controllers/toast_controller.js +40 -14
  203. package/dist/controllers/toast_controller.js.map +1 -1
  204. package/dist/controllers/toggle_group_controller.js +23 -2
  205. package/dist/controllers/toggle_group_controller.js.map +1 -1
  206. package/dist/controllers/toolbar_controller.d.ts +53 -13
  207. package/dist/controllers/toolbar_controller.js +230 -31
  208. package/dist/controllers/toolbar_controller.js.map +1 -1
  209. package/dist/controllers/tooltip_controller.d.ts +1 -1
  210. package/dist/controllers/tooltip_controller.js.map +1 -1
  211. package/dist/controllers/transition_controller.d.ts +14 -8
  212. package/dist/controllers/transition_controller.js +153 -38
  213. package/dist/controllers/transition_controller.js.map +1 -1
  214. package/dist/controllers/tree_view_controller.d.ts +135 -0
  215. package/dist/controllers/tree_view_controller.js +691 -0
  216. package/dist/controllers/tree_view_controller.js.map +1 -0
  217. package/dist/index.d.ts +27 -1818
  218. package/dist/index.js +4256 -915
  219. package/dist/index.js.map +1 -1
  220. package/dist/inspector/cli.d.ts +375 -39
  221. package/dist/inspector/cli.js +228 -14
  222. package/dist/inspector/cli.js.map +1 -1
  223. package/dist/inspector/cli_bin.js +419 -48
  224. package/dist/inspector/cli_bin.js.map +1 -1
  225. package/dist/inspector/examples.json +26 -26
  226. package/dist/inspector/manifest.json +1054 -127
  227. package/package.json +5 -6
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/escape_layer.ts","../../src/utils/roving_tabindex.ts","../../src/utils/safe_timeout.ts","../../src/utils/typeahead.ts","../../src/controllers/menubar_controller.ts"],"names":["top"],"mappings":"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","file":"menubar_controller.js","sourcesContent":["/** Normalized scroll position and maximum distance on one logical axis. */\nexport interface LogicalScrollMetrics {\n position: number;\n max: number;\n}\n\n/**\n * Whether horizontal scrolling on `element` follows right-to-left inline flow.\n *\n * Resolved from the **computed** `direction`, so the authoring contract is the\n * usual `dir=\"rtl\"` (or a stylesheet) on the element or any ancestor.\n *\n * Scope: horizontal writing modes. A vertical writing mode (`writing-mode:\n * vertical-rl`) also inverts the horizontal axis, which this check does not\n * model — vertical writing modes are out of scope for the scroll utilities\n * (their consumers describe axes as horizontal/vertical, not inline/block).\n */\nexport function isRtl(element: Element): boolean {\n return window.getComputedStyle(element).direction === \"rtl\";\n}\n\n/**\n * Returns scroll distance from the logical start edge.\n *\n * CSSOM View exposes standards-mode RTL horizontal offsets as `0` at the inline\n * start (right) and increasingly negative values toward the inline end (left).\n * The normalized position is always clamped to `[0, max]`, which also absorbs\n * Safari's elastic overscroll values.\n */\nexport function logicalScrollMetrics(\n element: HTMLElement,\n horizontal: boolean,\n): LogicalScrollMetrics {\n const max = Math.max(\n 0,\n horizontal\n ? element.scrollWidth - element.clientWidth\n : element.scrollHeight - element.clientHeight,\n );\n const raw = horizontal ? element.scrollLeft : element.scrollTop;\n const position = horizontal && isRtl(element) ? -raw : raw;\n return { position: Math.min(max, Math.max(0, position)), max };\n}\n\n/**\n * Converts a logical start/end delta to the physical value accepted by\n * `Element.scrollBy`.\n */\nexport function physicalScrollDelta(\n element: HTMLElement,\n horizontal: boolean,\n logicalDelta: number,\n): number {\n return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;\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","/**\n * Single resolver for layered Escape dismissal.\n *\n * Every Escape-dismissable overlay layer (modal focus traps, disclosure\n * overlays like dropdown/popover/menu, and hover-triggered transient layers)\n * registers here while it is open. One document-level listener per document\n * resolves each press to exactly one owner and invokes that layer's\n * {@link EscapeLayerOptions.onDismiss} — controllers never listen for a\n * dismissing Escape themselves.\n *\n * @remarks\n * Each document owns an activation-ordered stack. The owner of a press is the\n * **topmost layer whose {@link EscapeLayerOptions.claims} passes**; a layer\n * that declines is transparent, so a background overlay opened behind a modal\n * never blocks it. Because a layer opened from within another is necessarily\n * activated later, LIFO order is also inner-first for nested layers — no DOM\n * inspection is needed.\n *\n * The shared listener runs in the document bubble phase and honors\n * `event.defaultPrevented`, so an element-level widget handler that consumes\n * Escape first (an editor cancelling its edit, a combobox closing its list)\n * always wins over every registered layer — the deepest handler resolves the\n * press. A keydown that is part of an IME composition (`event.isComposing`)\n * cancels the composition, never a layer, and is ignored here for every layer\n * at once.\n *\n * A WeakMap keeps documents collectible; the listener is installed only while\n * a document's stack is non-empty, and controller lifecycle hooks guarantee\n * that disconnected layers never remain registered.\n */\n\n/** Behavior a layer registers when it activates. */\nexport interface EscapeLayerOptions {\n /**\n * Dismisses the layer. Called by the shared resolver when this layer owns a\n * press; the resolver has already consumed the event (`preventDefault()`),\n * so the callback only needs to close and place focus per the widget's\n * contract.\n */\n onDismiss: () => void;\n /**\n * Whether the layer claims the current press. Evaluated per press, so it can\n * depend on live state (e.g. \"focus is inside me or fell to the body\"). A\n * declining layer is skipped and the next layer down is consulted; omitting\n * it means the layer always claims while active.\n */\n claims?: () => boolean;\n}\n\n/** A document's stack plus the one shared listener bound to it. */\ninterface EscapeLayerRegistry {\n stack: EscapeLayer[];\n onKeydown: (event: KeyboardEvent) => void;\n}\n\n/**\n * Claims predicate shared by the click-opened disclosure overlays (dropdown /\n * popover / navigation-menu / menu / context-menu / menubar): the layer claims\n * a press while focus is inside `element`, or after focus fell to the body —\n * a click on non-focusable overlay content blurs to `<body>`, and Escape must\n * still close the overlay (the \"body-focus rescue\"). A press made after focus\n * moved to another interactive element is declined, so closing never yanks\n * focus away from where the user deliberately went.\n */\nexport function claimsWhileFocusWithin(element: Element): () => boolean {\n return () => {\n const active = element.ownerDocument.activeElement;\n return active === null || active === element.ownerDocument.body || element.contains(active);\n };\n}\n\nexport class EscapeLayer {\n static readonly #registries = new WeakMap<Document, EscapeLayerRegistry>();\n\n #ownerDocument: Document | null = null;\n /** Dismissal callback while active; `null` when inactive. */\n #onDismiss: (() => void) | null = null;\n /** Live predicate deciding whether the layer claims a press; `null` = always. */\n #claims: (() => boolean) | null = null;\n\n /**\n * Activates this layer at the top of its document's Escape stack, installing\n * the document's shared resolver listener if this is its first layer.\n * Re-activating an already-active layer moves it to the top.\n */\n activate(ownerDocument: Document = document, options: EscapeLayerOptions): void {\n this.deactivate();\n let registry = EscapeLayer.#registries.get(ownerDocument);\n if (!registry) {\n registry = EscapeLayer.#createRegistry();\n EscapeLayer.#registries.set(ownerDocument, registry);\n ownerDocument.addEventListener(\"keydown\", registry.onKeydown);\n }\n registry.stack.push(this);\n this.#ownerDocument = ownerDocument;\n this.#onDismiss = options.onDismiss;\n this.#claims = options.claims ?? null;\n }\n\n /**\n * Removes this layer from its document's Escape stack, uninstalling the\n * shared listener when the stack empties. Safe to call when inactive.\n */\n deactivate(): void {\n const ownerDocument = this.#ownerDocument;\n if (!ownerDocument) return;\n\n const registry = EscapeLayer.#registries.get(ownerDocument);\n if (registry) {\n const index = registry.stack.lastIndexOf(this);\n if (index >= 0) registry.stack.splice(index, 1);\n if (registry.stack.length === 0) {\n ownerDocument.removeEventListener(\"keydown\", registry.onKeydown);\n EscapeLayer.#registries.delete(ownerDocument);\n }\n }\n this.#ownerDocument = null;\n this.#onDismiss = null;\n this.#claims = null;\n }\n\n /**\n * Whether this active layer would own a press right now: it is the topmost\n * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests\n * and diagnostics — production dismissal goes through the shared listener.\n */\n get ownsEscape(): boolean {\n const ownerDocument = this.#ownerDocument;\n if (!ownerDocument) return false;\n const registry = EscapeLayer.#registries.get(ownerDocument);\n if (!registry) return false;\n return EscapeLayer.#resolveOwner(registry.stack) === this;\n }\n\n /** Builds a document's registry with its shared resolver listener. */\n static #createRegistry(): EscapeLayerRegistry {\n const registry: EscapeLayerRegistry = {\n stack: [],\n onKeydown: (event: KeyboardEvent): void => {\n if (event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n const owner = EscapeLayer.#resolveOwner(registry.stack);\n if (!owner) return;\n event.preventDefault();\n owner.#onDismiss?.();\n },\n };\n return registry;\n }\n\n /** The topmost stack layer whose claims predicate passes, or `null`. */\n static #resolveOwner(stack: EscapeLayer[]): EscapeLayer | null {\n for (let index = stack.length - 1; index >= 0; index--) {\n const layer = stack[index];\n if (!layer) continue;\n if (layer.#claims && !layer.#claims()) continue;\n return layer;\n }\n return null;\n }\n}\n","/**\n * Low-level roving-tabindex primitive shared by composite-widget controllers.\n *\n * The APG roving-tabindex pattern keeps a composite widget a single Tab stop:\n * exactly one item is in the Tab sequence (`tabindex=\"0\"`) while the rest are\n * removed from it (`tabindex=\"-1\"`), and the arrow keys move both DOM focus and\n * that single tabbable position together. {@link RovingTabindex} owns *only* that\n * mechanical bookkeeping — \"which one item is tabbable, and move focus there\".\n *\n * It is intentionally **policy-free**. Orientation, wrapping vs. clamping,\n * selection-follows-focus, typeahead, and `Home`/`End` semantics differ per APG\n * pattern (Radio Group, Toolbar, Rating, …); folding them into one helper would\n * flatten those widgets to a lowest common denominator and lose each pattern's\n * correctness. Those decisions therefore stay in each controller, which calls\n * {@link RovingTabindex.setActive} with an index it computed itself (optionally\n * via the pure {@link rovingMove} helper).\n *\n * @remarks\n * Items are read lazily through a getter so a controller can add or remove\n * targets (Stimulus re-scans the DOM) without re-wiring this helper.\n */\nexport class RovingTabindex {\n /** Returns the current ordered item elements; called on every operation. */\n readonly #getItems: () => HTMLElement[];\n\n /**\n * @param getItems - Returns the current ordered item elements. Called on every\n * operation so the live target list is always used.\n */\n constructor(getItems: () => HTMLElement[]) {\n this.#getItems = getItems;\n }\n\n /** Index of the currently tabbable item (`tabindex=\"0\"`), or `-1` if none. */\n get activeIndex(): number {\n return this.#getItems().findIndex((item) => item.tabIndex === 0);\n }\n\n /**\n * Makes exactly the item at `index` tabbable (`tabindex=\"0\"`) and removes every\n * other item from the Tab sequence (`tabindex=\"-1\"`). An out-of-range `index`\n * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express\n * \"nothing is currently tabbable\".\n *\n * @param index - Position of the item to make tabbable.\n * @param options - Pass `{ focus: true }` to also move DOM focus to that item.\n */\n setActive(index: number, { focus = false }: { focus?: boolean } = {}): void {\n const items = this.#getItems();\n items.forEach((item, i) => {\n item.tabIndex = i === index ? 0 : -1;\n });\n if (focus) items[index]?.focus();\n }\n}\n\n/** Edge behavior for {@link rovingMove}: cycle past the ends, or stop at them. */\nexport type RovingWrap = \"wrap\" | \"clamp\";\n\n/**\n * Pure helper that resolves the target index for a one-step directional move.\n *\n * Keyboard/orientation mapping stays in the caller: it decides that a key means\n * `delta` `+1` (next) or `-1` (previous) and whether the widget should `\"wrap\"`\n * (Radio Group, Toolbar) or `\"clamp\"` at the ends.\n *\n * @param current - The index focus is moving from.\n * @param length - Number of items in the set.\n * @param delta - `+1` to move to the next item, `-1` for the previous.\n * @param wrap - `\"wrap\"` cycles around the ends; `\"clamp\"` stops at them.\n * @returns The resolved index, or `-1` when there are no items.\n */\nexport function rovingMove(\n current: number,\n length: number,\n delta: number,\n wrap: RovingWrap,\n): number {\n if (length === 0) return -1;\n const next = current + delta;\n if (wrap === \"wrap\") return (next + length) % length;\n return Math.min(length - 1, Math.max(0, next));\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","/**\n * Shared type-ahead (first-letter navigation) primitive for composite widgets.\n *\n * The APG asks list-like widgets to move focus by typed characters: one\n * character jumps to the next item starting with it, and several characters\n * typed in quick succession narrow the match to that prefix. Getting that right\n * needs three pieces of bookkeeping — accumulating the query, dropping it after\n * an idle window, and resolving the label each candidate is matched against —\n * and single-sourcing them here keeps every widget on the same rules instead of\n * letting them drift apart.\n *\n * The helpers here own *only* that mechanical part. They are deliberately\n * **policy-free** in the same sense as `RovingTabindex`: which elements are\n * candidates, where the search starts, and what happens on a match (real DOM\n * focus, a roving tab stop, or a virtual `aria-activedescendant`) differ per APG\n * pattern, so each controller keeps those decisions and calls\n * {@link findTypeaheadMatch} with a candidate list it assembled itself.\n *\n * Three decisions are worth stating up front:\n *\n * - **A repeated character collapses the stored query**, rather than deriving a\n * shorter one at search time. That is what lets a follow-up character resume\n * narrowing: `s`, `s`, `e` searches `\"se\"`, not the dead `\"sse\"` that no label\n * can match. Growing the query instead leaves the widget frozen on exactly\n * that sequence, which is why the choice is made here and not per widget.\n * - **`aria-label` wins over text**, so type-ahead matches what a screen reader\n * announces — but only when it contributes a name. A blank (empty or\n * whitespace-only) one is skipped, because accname skips it too: honoring it\n * would leave an element that AT announces by its text unreachable by that\n * text. This mirrors the accessible-name computation rather than the raw text.\n * - **`Space` and composition input are never type-ahead.** `Space` natively\n * activates a `<button>`-based item, and characters still being composed are\n * not a committed query.\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 contracts belong on the members.\n */\n\nimport { SafeTimeout } from \"./safe_timeout\";\n\n/** Idle window after which an accumulated query is dropped, in milliseconds. */\nexport const TYPEAHEAD_RESET_MS = 500;\n\n/** Construction options for {@link Typeahead}. */\nexport interface TypeaheadOptions {\n /** Idle window before the query resets. Defaults to {@link TYPEAHEAD_RESET_MS}. */\n resetMs?: number;\n}\n\n/**\n * Accumulates typed characters into a search query and drops it when idle.\n *\n * @remarks\n * Owns a {@link SafeTimeout} of its own, so a consumer's `#timers.clearAll()` does\n * **not** reach the pending reset — call {@link Typeahead.reset} from `disconnect()`.\n */\nexport class Typeahead {\n /** Timer registry for the pending idle reset; private so `reset()` is the only exit. */\n readonly #timers = new SafeTimeout();\n /** Idle window before the query resets, in milliseconds. */\n readonly #resetMs: number;\n /** The accumulated lowercase query, empty when idle. */\n #query = \"\";\n /** Id of the pending reset timer, `0` when none is scheduled. */\n #timerId = 0;\n\n /** @param options - Overrides for the idle window. */\n constructor({ resetMs = TYPEAHEAD_RESET_MS }: TypeaheadOptions = {}) {\n this.#resetMs = resetMs;\n }\n\n /** The query a search would currently run with; empty while idle. */\n get query(): string {\n return this.#query;\n }\n\n /**\n * Folds `key` into the query, restarts the idle window, and returns the query to\n * search with. A repeated character collapses the query to that one character.\n */\n push(key: string): string {\n const char = key.toLowerCase();\n const repeated = this.#query.length > 0 && [...this.#query].every((c) => c === char);\n this.#query = repeated ? char : this.#query + char;\n this.#timers.clear(this.#timerId);\n this.#timerId = this.#timers.set(() => this.reset(), this.#resetMs);\n return this.#query;\n }\n\n /** Clears the query and cancels the pending idle reset. */\n reset(): void {\n this.#query = \"\";\n this.#timers.clear(this.#timerId);\n this.#timerId = 0;\n }\n}\n\n/** Whether `event` is a bare printable character usable for type-ahead. */\nexport function isTypeaheadKey(event: KeyboardEvent): boolean {\n return (\n event.key.length === 1 &&\n event.key !== \" \" &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n !event.isComposing\n );\n}\n\n/**\n * The name `element` is matched under, normalized for comparison.\n *\n * @param fallbackText - Produces the name when `aria-label` contributes none.\n * Defaults to the element's `textContent`; pass a narrower source when part of\n * the subtree is not part of the name (a tree item's nested child group, say).\n * Called lazily, so an `aria-label` costs no subtree walk.\n */\nexport function typeaheadLabel(element: HTMLElement, fallbackText?: () => string): string {\n // Blank means absent, matching accname: a whitespace-only `aria-label` is\n // skipped there too, so honoring it here would make an element that a screen\n // reader announces by its text unreachable by that text.\n const label = element.getAttribute(\"aria-label\")?.trim();\n if (label) return label.toLowerCase();\n const text = fallbackText ? fallbackText() : (element.textContent ?? \"\");\n return text.trim().toLowerCase();\n}\n\n/**\n * Index of the first item after `from` whose label starts with `query`, wrapping\n * and evaluating `from` itself last, or `-1` when nothing matches.\n *\n * @param items - Candidates in navigation order, already filtered by the caller.\n * @param from - Index the search moves on from; `-1` starts at the first item. Any\n * out-of-range value is folded back in, so `indexOf` results pass straight through.\n * @param query - Lowercase query, normally the return of {@link Typeahead.push}.\n * @param label - Resolves a candidate's name. Defaults to {@link typeaheadLabel}.\n */\nexport function findTypeaheadMatch(\n items: readonly HTMLElement[],\n from: number,\n query: string,\n label: (item: HTMLElement) => string = (item) => typeaheadLabel(item),\n): number {\n // An empty query would prefix-match everything; an empty list never enters the\n // loop, so it needs no guard of its own.\n if (query === \"\") return -1;\n const count = items.length;\n for (let step = 1; step <= count; step += 1) {\n // Folded twice so a negative `from` still lands on a real slot: `%` keeps the\n // sign of its left operand, and a negative index would read `undefined`.\n const index = (((from + step) % count) + count) % count;\n const candidate = items[index];\n if (candidate && label(candidate).startsWith(query)) return index;\n }\n return -1;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { claimsWhileFocusWithin, EscapeLayer } from \"../utils/escape_layer\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { RovingTabindex } from \"../utils/roving_tabindex\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { findTypeaheadMatch, isTypeaheadKey, Typeahead } from \"../utils/typeahead\";\n\n/** Where to land focus when a menu opens. */\ntype OpenFocus = \"first\" | \"last\";\n\n/**\n * Attributes whose runtime change flips a top item's navigability, watched so the\n * single Tab stop survives a consumer disabling or hiding the item that held it.\n *\n * `tabindex` is deliberately absent — this controller writes it, and observing it\n * would feed back into the observer. `aria-disabled` is absent for a different\n * reason: per the APG it keeps an item focusable and only blocks activation, so\n * it never changes *which* top can hold the Tab stop.\n */\nconst STATE_ATTRIBUTES = [\"disabled\", \"hidden\"];\n\n/**\n * Headless, accessible **menubar** behavior.\n *\n * Markup contract (identifier: `stimeo--menubar`):\n * <div data-controller=\"stimeo--menubar\" role=\"menubar\" aria-label=\"Main\">\n * <button role=\"menuitem\" aria-haspopup=\"menu\" aria-expanded=\"false\"\n * aria-controls=\"m-file\" data-stimeo--menubar-target=\"top\"\n * data-action=\"click->stimeo--menubar#toggle\n * keydown->stimeo--menubar#onTopKeydown\">File</button>\n * <ul id=\"m-file\" role=\"menu\" data-stimeo--menubar-target=\"menu\" hidden>\n * <li role=\"none\">\n * <button role=\"menuitem\" tabindex=\"-1\" data-stimeo--menubar-target=\"item\"\n * data-action=\"click->stimeo--menubar#activate\n * keydown->stimeo--menubar#onItemKeydown\">New</button>\n * </li>\n * </ul>\n * <!-- more top items + menus -->\n * </div>\n *\n * Implements the WAI-ARIA APG **Menubar** pattern (single level): the top items\n * form one Tab stop via roving tabindex and the arrow keys move between them;\n * `ArrowDown`/`Enter`/`Space` open a menu (`ArrowUp` opens it at the last item),\n * the arrow keys then move within the menu, and pressing `ArrowLeft`/`ArrowRight`\n * while a menu is open jumps to the adjacent top menu. `Escape` closes and\n * returns focus to the owning top item — while a menu is open the menubar is a\n * layer on the shared {@link EscapeLayer} stack, claiming a press only while\n * focus is inside the controller or fell to the body, so one keypress closes\n * exactly one layer. `Tab` and an outside click close.\n *\n * @remarks\n * Behavior only. Each top item↔menu pair is linked by `aria-controls`/`id` (not by\n * position), so *pairing* does not depend on the markup order. Where the order does\n * show is native `Tab`/`Shift+Tab`: the browser walks the DOM, so `Shift+Tab` out of\n * a menu lands on the owning top item only while the menu follows it — write the\n * conventional owner-then-menu order to get the destinations described here. Menu\n * placement and viewport-edge collision are out of scope — static placement is the\n * consumer's CSS, and dynamic placement is delegated to the opt-in\n * `stimeo-ui/positioning` module (never imported here, keeping the core\n * zero-dependency). Roving mechanics across the top items are delegated to\n * {@link RovingTabindex}.\n *\n * Focus-restoration contract: closing via `Escape` or an item activation returns\n * focus to the owning top item; `Tab` and an outside click deliberately do **not**\n * — they close where the user is already heading, and yanking focus back would\n * fight the pointer/Tab move.\n *\n * Disabledness follows the APG's two-attribute split, which is the consumer's way\n * of saying which one they mean:\n * - **`hidden` / natively `disabled`** — removed from the roving set entirely, so\n * the lone Tab stop never lands on an unfocusable top item (which would drop the\n * whole menubar out of the Tab order) and the arrow keys never stall on it.\n * - **`aria-disabled=\"true\"`** — still reached by the arrow keys (\"disabled menu\n * items are focusable but cannot be activated\"), so a screen-reader user can\n * discover it; only *activation* is suppressed. Clicks on it — including the one\n * a browser synthesizes for Enter/Space — are swallowed before consumer handlers\n * run, and an `aria-disabled` top never opens its menu, since opening the popup\n * is activation. Moving onto it still closes whatever menu was open, which the\n * APG makes unconditional for a horizontal move.\n *\n * Arrow directions are logical: `ArrowRight` moves to the next top item under LTR\n * and to the previous one under RTL, on the bar and from inside an open menu. A top\n * item may also be a **plain command** with no `aria-controls` at all: moving to it\n * behaves the same way — the open menu closes and it takes the roving focus. A top\n * item whose `aria-controls` names no menu target is broken markup instead, and the\n * open/closed state is left alone rather than acted on by guesswork.\n *\n * A menu with no navigable items still opens (its `aria-expanded` flips and focus\n * stays on the top item) so a consumer that fills a menu asynchronously keeps\n * working; `Escape` or a second click closes it. While such a menu is\n * structurally empty the consumer **must** carry `aria-busy=\"true\"` on it and drop\n * that once the items land — `role=\"menu\"` requires owned `menuitem`s, and this\n * controller cannot tell \"still loading\" from \"nothing to show\", so it never\n * infers the state. A menu whose items exist but are all inert is not busy.\n *\n * Runtime DOM edits (Turbo Streams, a consumer swapping menus in) are reconciled\n * from the live targets: expanded flags, menu visibility, the single Tab stop, and\n * Escape-stack membership are re-derived whenever a top/menu target is added or\n * removed, and whenever `disabled`/`hidden` changes under the menubar.\n */\nexport class MenubarController extends Controller<HTMLElement> {\n static override targets = [\"top\", \"menu\", \"item\"];\n static actions = [\"activate\", \"onItemKeydown\", \"onTopKeydown\", \"toggle\"] as const;\n\n declare readonly topTargets: HTMLButtonElement[];\n declare readonly menuTargets: HTMLElement[];\n declare readonly itemTargets: HTMLButtonElement[];\n\n /** Roving tabindex across the top-level menuitems (one Tab stop). */\n readonly #roving = new RovingTabindex(() => this.topTargets);\n /** Typeahead query and its idle-reset timer (scoped to the open menu). */\n readonly #typeahead = new Typeahead();\n readonly #timers = new SafeTimeout();\n\n /** Escape-stack membership while a menu is open; the shared resolver dismisses via it. */\n readonly #escapeLayer = new EscapeLayer();\n /** Whether {@link #escapeLayer} is currently on the stack (see {@link #syncEscapeLayer}). */\n #layerActive = false;\n\n /** Watches `disabled`/`hidden` under the menubar; `null` while disconnected. */\n #observer: MutationObserver | null = null;\n /**\n * Live between `connect()` and `disconnect()`. Stimulus reports the *initial*\n * targets before `connect()` and re-reports them as disconnected afterwards;\n * gating on this keeps the target callbacks from clobbering the authored Tab\n * stop on mount and from resurrecting one after teardown.\n */\n #connected = false;\n\n /** The element inside the menubar that last took DOM focus, if any. */\n #focused: HTMLElement | null = null;\n\n /**\n * Records where DOM focus sits inside the menubar. `focusin` bubbles, so one\n * listener covers the top items and everything nested in their menus. The\n * record is needed because a top or menu becomes unreachable *before* this\n * controller hears about it, by which point the browser has already reset\n * `document.activeElement`.\n */\n readonly #onFocusIn = (event: FocusEvent): void => {\n this.#focused = event.target as HTMLElement | null;\n };\n\n /**\n * Forgets the record once focus genuinely lands on something else, so a later\n * mutation cannot pull focus back in. A `relatedTarget` of `null` is the case\n * that must be kept: it is what \"focus went nowhere\" looks like, which is\n * exactly the state the rescue exists for.\n */\n readonly #onFocusOut = (event: FocusEvent): void => {\n const next = event.relatedTarget as HTMLElement | null;\n if (next && !this.element.contains(next)) this.#focused = null;\n };\n\n /**\n * Establishes the closed baseline and the single tab stop: keep an existing tab\n * stop when it is still navigable (so a Turbo cache restore preserves the user's\n * position), else fall back to the first navigable top item.\n *\n * The outside-click listener is registered in the **capture** phase: in the\n * bubble phase an inside handler that removes its own click target detaches the\n * node before `contains()` runs, so an *inside* click would read as outside and\n * close the menu.\n */\n override connect(): void {\n this.#closeAllMenus();\n this.#reconcile();\n this.element.addEventListener(\"click\", this.#onDisabledClickCapture, true);\n this.element.addEventListener(\"focusin\", this.#onFocusIn);\n this.element.addEventListener(\"focusout\", this.#onFocusOut);\n document.addEventListener(\"click\", this.#onOutsideClick, true);\n if (typeof MutationObserver !== \"undefined\") {\n this.#observer = new MutationObserver(() => this.#reconcile());\n this.#observer.observe(this.element, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: STATE_ATTRIBUTES,\n });\n }\n this.#connected = true;\n }\n\n /** Removes the listeners, stack membership, and any pending timer (typeahead / Tab close). */\n override disconnect(): void {\n this.#connected = false;\n this.#escapeLayer.deactivate();\n this.#layerActive = false;\n this.element.removeEventListener(\"click\", this.#onDisabledClickCapture, true);\n this.element.removeEventListener(\"focusin\", this.#onFocusIn);\n this.element.removeEventListener(\"focusout\", this.#onFocusOut);\n document.removeEventListener(\"click\", this.#onOutsideClick, true);\n this.#focused = null;\n this.#observer?.disconnect();\n this.#observer = null;\n // The typeahead owns its own timer registry, so `#timers.clearAll()` does not\n // reach its pending reset — drop it explicitly or it outlives the element.\n this.#typeahead.reset();\n this.#timers.clearAll();\n }\n\n /**\n * A top item added at runtime is dropped out of the Tab sequence first — a fresh\n * `<button>` is tabbable by default, which would leave the menubar with two Tab\n * stops — before the lone stop is re-established.\n */\n topTargetConnected(top: HTMLButtonElement): void {\n if (!this.#connected) return;\n top.tabIndex = -1;\n this.#reconcile();\n }\n\n /**\n * Removing a top item can strand two things: the Tab stop it held, and the menu\n * it owned (which nothing could close afterwards).\n *\n * The departing element is handed back in a neutral state first. A target can\n * leave without leaving the document — a morph that only drops the\n * `data-*-target` token keeps the node — and it would then sit in the page\n * carrying this controller's `tabindex=\"0\"` (a second Tab stop next to the one\n * re-established below) and an `aria-expanded=\"true\"` nothing can collapse.\n * When the node really is gone these writes are harmless no-ops.\n */\n topTargetDisconnected(top: HTMLButtonElement): void {\n if (!this.#connected) return;\n top.tabIndex = -1;\n if (this.#isExpanded(top)) top.setAttribute(\"aria-expanded\", \"false\");\n this.#reconcile();\n }\n\n /** See {@link MenubarController.menuTargetDisconnected}. */\n menuTargetConnected(): void {\n if (!this.#connected) return;\n this.#reconcile();\n }\n\n /**\n * A menu removed while open leaves its owner claiming `aria-expanded=\"true\"` for\n * a popup that no longer exists, and the menubar holding an Escape layer that\n * would swallow presses meant for something else.\n *\n * As with {@link MenubarController.topTargetDisconnected}, the departing menu is\n * closed first: a token-only removal leaves a visible popup in the page that no\n * key and no click can dismiss any more.\n */\n menuTargetDisconnected(menu: HTMLElement): void {\n if (!this.#connected) return;\n if (!menu.hidden) menu.hidden = true;\n this.#reconcile();\n }\n\n /** Toggles a top item's menu. Bound via `data-action` (click on the top item). */\n toggle(event: Event): void {\n const top = event.currentTarget as HTMLButtonElement;\n if (this.#isExpanded(top)) {\n this.#closeMenu(top);\n } else {\n this.#openMenu(top, \"first\");\n }\n }\n\n /** Keyboard handling while focus is on a top item. */\n onTopKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key (a grabbed drag handle, an\n // inline editor) must not ALSO move the roving focus — composition depends on\n // this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n const tops = this.#navigableTops;\n const index = tops.indexOf(event.currentTarget as HTMLButtonElement);\n if (index === -1) return;\n const length = tops.length;\n // Whether a menu is currently open governs whether horizontal moves *open*\n // the adjacent menu or merely move the roving focus between top items.\n const anyOpen = this.#isAnyOpen;\n\n // Logical, not physical. APG defines these as \"next / previous control\", and\n // says a vertical arrangement swaps in Down/Up for the same meaning — so the\n // pair is one axis's spelling of an order, and the order reverses with the\n // writing direction. Read from the controller element: the container is what\n // lays the items out, and a child may carry its own `dir` (an LTR input inside\n // an RTL form is ordinary authoring).\n const step = isRtl(this.element) ? -1 : 1;\n\n switch (event.key) {\n case \"ArrowRight\":\n event.preventDefault();\n this.#gotoTop(tops[(index + step + length) % length], anyOpen);\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n this.#gotoTop(tops[(index - step + length) % length], anyOpen);\n break;\n case \"ArrowDown\":\n event.preventDefault();\n this.#openMenu(tops[index], \"first\");\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.#openMenu(tops[index], \"last\");\n break;\n case \"Home\":\n event.preventDefault();\n this.#gotoTop(tops[0], anyOpen);\n break;\n case \"End\":\n event.preventDefault();\n this.#gotoTop(tops[length - 1], anyOpen);\n break;\n case \"Tab\":\n // Focus is leaving the menubar: close, but let the browser's own Tab move\n // run first (see #closeMenusSoon).\n this.#closeMenusSoon();\n break;\n default:\n break;\n }\n // Enter/Space are intentionally left to the native button click, which runs\n // toggle(): it opens the menu at its first item, or closes it when that menu\n // is already open. Handling them here would double-fire.\n // Escape is owned by the shared EscapeLayer resolver (see #dismissOpenMenu).\n }\n\n /** Keyboard handling while focus is on a menu item. */\n onItemKeydown(event: KeyboardEvent): void {\n // See onTopKeydown: a key a descendant already consumed is not ours.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n const item = event.currentTarget as HTMLButtonElement;\n const menu = item.closest<HTMLElement>(\"[role='menu']\");\n if (!menu) return;\n const items = this.#itemsIn(menu);\n // -1 when the key came from a non-navigable item (still focusable by mouse):\n // the relative moves below then land on the first navigable item.\n const index = items.indexOf(item);\n const length = items.length;\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n this.#focusAt(items, (index + 1) % length);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.#focusAt(items, (index - 1 + length) % length);\n break;\n case \"Home\":\n event.preventDefault();\n this.#focusAt(items, 0);\n break;\n case \"End\":\n event.preventDefault();\n this.#focusAt(items, length - 1);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n this.#moveToAdjacentMenu(menu, isRtl(this.element) ? -1 : 1);\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n this.#moveToAdjacentMenu(menu, isRtl(this.element) ? 1 : -1);\n break;\n case \"Tab\":\n // Per APG, Tab closes the menubar but lets focus move on naturally.\n this.#closeMenusSoon();\n break;\n default:\n if (isTypeaheadKey(event)) {\n event.preventDefault();\n this.#typeaheadTo(items, index, event.key);\n }\n break;\n }\n // Enter/Space activate the item via its native button click → activate().\n }\n\n /** Closes the owning menu after an item is activated and refocuses its top. */\n activate(event: Event): void {\n const item = event.currentTarget as HTMLElement;\n const menu = item.closest<HTMLElement>(\"[role='menu']\");\n const top = this.#topFor(menu);\n this.#closeAllMenus();\n this.#focusTop(top);\n }\n\n /** Moves the roving focus to `top`, opening its menu when one was open. */\n #gotoTop(top: HTMLButtonElement | undefined, reopen: boolean): void {\n if (!top) return;\n if (reopen) {\n this.#openMenu(top, \"first\");\n } else {\n // The roving index is resolved against the full target list, since that is\n // what carries the tabindex bookkeeping.\n this.#roving.setActive(this.topTargets.indexOf(top), { focus: true });\n }\n }\n\n /**\n * Opens `top`'s menu (closing others) and focuses its first/last item.\n *\n * Three kinds of top item never open a menu:\n * - **`aria-disabled`** — focusable but never activated, and opening a popup is\n * activation.\n * - **plain command** (no `aria-controls` at all) — a legitimate top item that\n * simply has no popup.\n * - **dangling `aria-controls`** (names a menu that is not a target) — broken\n * markup.\n *\n * The first two still take the roving focus, and any open menu closes: the APG\n * makes closing unconditional for the horizontal move (\"closes the submenu,\n * moves focus to the next menubar item, and *optionally* opens that item's\n * submenu\"), so only the opening half is skipped. Leaving the old menu open\n * would strand a popup that no longer contains focus.\n *\n * The dangling case instead leaves the open/closed state and focus untouched\n * rather than closing everything and dropping focus to the body — but only on\n * this \"open a menu\" path. A dangling top is still an ordinary roving\n * destination while nothing is open.\n */\n #openMenu(top: HTMLButtonElement | null | undefined, focus: OpenFocus): void {\n if (!top) return;\n // Resolve the menu *before* touching any state, so the dangling case below\n // can bail out without having changed anything.\n const menu = this.#menuFor(top);\n if (!menu || this.#isActivationBlocked(top)) {\n // Broken markup (an `aria-controls` that resolves to nothing) is the one\n // case that must not act on a guess.\n if (menu || !top.hasAttribute(\"aria-controls\")) {\n this.#closeAllMenus();\n this.#focusTop(top);\n }\n return;\n }\n // A reopen must discard a pending Tab close, or that stale task would slam\n // the freshly opened menu shut on the next tick.\n this.#timers.clearAll();\n this.#closeAllMenus();\n menu.hidden = false;\n top.setAttribute(\"aria-expanded\", \"true\");\n this.#escapeLayer.activate(document, {\n onDismiss: () => this.#dismissOpenMenu(),\n claims: claimsWhileFocusWithin(this.element),\n });\n // Opening always re-registers, which puts this layer back on top of the\n // stack — the menubar is the innermost thing the user just interacted with.\n this.#layerActive = true;\n this.#roving.setActive(this.topTargets.indexOf(top));\n const items = this.#itemsIn(menu);\n this.#focusAt(items, focus === \"first\" ? 0 : items.length - 1);\n }\n\n /**\n * Hides `top`'s menu and reflects the collapsed state. A top item that controls\n * no menu (a plain command in an otherwise popup-bearing menubar) is left alone\n * — stamping `aria-expanded=\"false\"` on it would announce a popup it lacks.\n */\n #closeMenu(top: HTMLButtonElement | null): void {\n if (!top) return;\n const menu = this.#menuFor(top);\n if (!menu) return;\n menu.hidden = true;\n top.setAttribute(\"aria-expanded\", \"false\");\n this.#syncEscapeLayer();\n }\n\n /** Closes every menu and resets the typeahead query. */\n #closeAllMenus(): void {\n for (const top of this.topTargets) this.#closeMenu(top);\n this.#typeahead.reset();\n }\n\n /**\n * Closes every menu on the next task instead of synchronously.\n *\n * Used by `Tab`: closing right away removes the focused element before the\n * browser performs its own Tab move, which can restart traversal at the\n * document head and lose the user's place in the Tab order.\n */\n #closeMenusSoon(): void {\n if (!this.#isAnyOpen) return;\n this.#timers.clearAll();\n this.#timers.set(() => this.#closeAllMenus(), 0);\n }\n\n /**\n * Escape path, invoked by the shared resolver: pressed inside an open menu it\n * closes that menu and returns focus to its top item (the APG behavior);\n * pressed while focus is on a top item (or fell to the body) it closes every\n * menu without moving focus.\n */\n #dismissOpenMenu(): void {\n const active = document.activeElement;\n const menu = this.menuTargets.find((candidate) => candidate.contains(active));\n if (menu) {\n const top = this.#topFor(menu);\n this.#closeMenu(top);\n this.#focusTop(top);\n return;\n }\n this.#closeAllMenus();\n }\n\n /** Opens the menu of the navigable top item `delta` steps from `menu`'s owner. */\n #moveToAdjacentMenu(menu: HTMLElement, delta: number): void {\n const top = this.#topFor(menu);\n if (!top) return;\n const tops = this.#navigableTops;\n const current = tops.indexOf(top);\n if (current === -1) return;\n const next = (current + delta + tops.length) % tops.length;\n this.#openMenu(tops[next], \"first\");\n }\n\n /**\n * Closes when a click lands outside the controller's element. Focus is left on\n * whatever the user clicked — see the focus-restoration contract in the class\n * docs.\n */\n readonly #onOutsideClick = (event: MouseEvent): void => {\n if (this.#isAnyOpen && !this.element.contains(event.target as Node)) this.#closeAllMenus();\n };\n\n /**\n * Captures clicks so `aria-disabled` top items and commands cannot reach\n * consumer handlers (nor `toggle`/`activate`). Native Enter/Space activation\n * also synthesizes a click and is blocked here; natively `disabled` buttons\n * dispatch no click at all.\n */\n readonly #onDisabledClickCapture = (event: MouseEvent): void => {\n const target = event.target;\n if (!(target instanceof Node)) return;\n const blocked = [...this.topTargets, ...this.itemTargets].some(\n (element) => this.#isActivationBlocked(element) && element.contains(target),\n );\n if (!blocked) return;\n event.preventDefault();\n event.stopImmediatePropagation();\n };\n\n /**\n * Re-derives every piece of state this controller owns from the live targets:\n * expanded flags, menu visibility, the single Tab stop, and Escape-stack\n * membership. Runtime DOM edits can leave those out of step with each other — a\n * top can claim `aria-expanded=\"true\"` after its menu target was removed or after\n * being hidden itself, a menu can stay visible after its owning top was removed,\n * an open pair can appear from a morph with no layer registered for it, and the\n * Tab stop can end up on a now-inert top, on a runtime-added one, or on none at\n * all. Nothing is remembered between calls (except which side of the Escape stack\n * this layer is on, which the stack itself does not expose), so the outcome is the\n * same whichever mutation arrived and calling it more often than needed is free.\n */\n #reconcile(): void {\n for (const top of this.topTargets) {\n // A top is expanded only while its own menu resolves *and* is visible.\n // Both halves matter: the menu target can be removed outright, and a Turbo\n // Stream `replace` swaps it for a fresh (contract-`hidden`) one — which\n // still resolves, so a \"did it disappear?\" test alone would leave the top\n // claiming a popup nobody can see, and the Escape layer registered with it.\n // Only tops that already declare the attribute are touched: stamping\n // `aria-expanded` on a plain command would announce a popup it lacks.\n // The owner must also still be reachable: a consumer that `hidden`s or\n // natively disables an expanded top would otherwise leave its popup on\n // screen with no way back to the element that owns it.\n const own = this.#menuFor(top);\n if (this.#isExpanded(top) && (!own || own.hidden || !this.#isNavigable(top))) {\n top.setAttribute(\"aria-expanded\", \"false\");\n }\n }\n for (const menu of this.menuTargets) {\n if (menu.hidden) continue;\n // A menu with no expanded owner can never be closed by the user again.\n const owner = this.#topFor(menu);\n if (!owner || !this.#isExpanded(owner)) menu.hidden = true;\n }\n this.#ensureTabStop();\n this.#rescueFocus();\n this.#syncEscapeLayer();\n }\n\n /**\n * Returns DOM focus to the menubar when the element holding it became\n * unreachable — hidden, natively disabled, or removed — and the document had\n * nowhere to put it. The destination is the top item that now owns the Tab\n * stop, which is where `Escape` from a closed menu would have left the user.\n *\n * Focus is only *restored*, never *stolen*: it moves solely when the tracked\n * element can no longer take it **and** focus is either already gone or still\n * sitting on that element. The second half is what makes a `hidden` ancestor\n * work — a browser blurs the element it hides on its next style pass, not when\n * the attribute is written, so waiting to observe `<body>` here would always\n * come too early. Removal is the other side of the same condition: the browser\n * has already fallen back to the body, or left a detached `activeElement`.\n */\n #rescueFocus(): void {\n const focused = this.#focused;\n if (!focused) return;\n // An ordinary close — Escape, Tab, an outside click — also leaves the focused\n // menu item inside a hidden subtree, but nothing went away and the user is\n // already on their way somewhere. Only an owner that can no longer be reached\n // is a reason to move focus, so the owner decides, not the item.\n const owner = this.topTargets.find(\n (top) => top === focused || this.#menuFor(top)?.contains(focused),\n );\n if (focused.isConnected && owner && this.#isNavigable(owner)) return;\n const doc = this.element.ownerDocument;\n const active = doc.activeElement;\n const lost =\n active === null ||\n active === doc.body ||\n active === doc.documentElement ||\n !active.isConnected ||\n active === focused ||\n focused.contains(active);\n if (!lost) return;\n const index = this.#roving.activeIndex;\n const top = index === -1 ? undefined : this.topTargets[index];\n if (top && top !== focused && !focused.contains(top)) top.focus();\n }\n\n /**\n * Brings Escape-stack membership back in line with what the DOM now says is\n * open, in both directions.\n *\n * The deactivate half is the common one (something closed). The activate half\n * covers a menu that became open *without* going through {@link #openMenu} —\n * a Turbo morph that patches `aria-expanded` and the menu's `hidden` in place,\n * which is legitimate here because the DOM is this controller's only source of\n * truth. Without it the popup is visible but `Escape` does nothing, since no\n * layer is registered to claim the press.\n *\n * `#layerActive` exists because re-activating an already-active layer moves it\n * to the top of the stack, which would reshuffle nested layers on every\n * unrelated mutation. Registering only on the false→true edge keeps activation\n * ordered by when each layer actually opened.\n */\n #syncEscapeLayer(): void {\n const open = this.#isAnyOpen;\n if (open === this.#layerActive) return;\n if (open) {\n this.#escapeLayer.activate(document, {\n onDismiss: () => this.#dismissOpenMenu(),\n claims: claimsWhileFocusWithin(this.element),\n });\n } else {\n this.#escapeLayer.deactivate();\n }\n this.#layerActive = open;\n }\n\n /**\n * Re-establishes the single Tab stop: keep the current one while it is still\n * navigable, else hand it to the first navigable top item. Every other top is\n * explicitly removed from the Tab sequence, so a top that arrived tabbable\n * cannot leave two stops behind. No Tab stop at all (`-1`) happens only when\n * every top is inert, and is recovered from as soon as one becomes navigable.\n */\n #ensureTabStop(): void {\n const active = this.#roving.activeIndex;\n const activeTop = active === -1 ? undefined : this.topTargets[active];\n if (activeTop && this.#isNavigable(activeTop)) {\n this.#roving.setActive(active);\n return;\n }\n const first = this.#navigableTops[0];\n this.#roving.setActive(first ? this.topTargets.indexOf(first) : -1);\n }\n\n /** The menu element controlled by `top` (matched by `aria-controls`/`id`). */\n #menuFor(top: HTMLElement): HTMLElement | null {\n const id = top.getAttribute(\"aria-controls\");\n // Resolve against this controller's own menu targets (not a global id lookup)\n // so it stays scoped to this menubar instance.\n return id ? (this.menuTargets.find((menu) => menu.id === id) ?? null) : null;\n }\n\n /** The top item that controls `menu` (reverse of `#menuFor`). */\n #topFor(menu: HTMLElement | null): HTMLButtonElement | null {\n if (!menu) return null;\n return this.topTargets.find((top) => top.getAttribute(\"aria-controls\") === menu.id) ?? null;\n }\n\n /** The navigable item targets that live inside `menu`, in DOM order. */\n #itemsIn(menu: HTMLElement): HTMLButtonElement[] {\n return this.itemTargets.filter((item) => menu.contains(item) && this.#isNavigable(item));\n }\n\n /** Top items eligible for the roving tab stop and the arrow keys. */\n get #navigableTops(): HTMLButtonElement[] {\n return this.topTargets.filter((top) => this.#isNavigable(top));\n }\n\n /**\n * Whether `element` can take roving focus: `hidden` and natively `disabled`\n * controls are out of reach and are skipped. `aria-disabled` is deliberately\n * **not** checked — the APG keeps such items focusable so they stay\n * discoverable, and {@link MenubarController.#isActivationBlocked} is what\n * suppresses acting on them. CSS-only visibility is not detectable headlessly\n * and stays the consumer's responsibility.\n */\n #isNavigable(element: HTMLButtonElement): boolean {\n if (element.hasAttribute(\"hidden\")) return false;\n return !element.disabled;\n }\n\n /** Whether `element` is `aria-disabled`: reachable, but never activated. */\n #isActivationBlocked(element: Element): boolean {\n return element.getAttribute(\"aria-disabled\") === \"true\";\n }\n\n /** Moves DOM focus to the item at `index` (no-op if out of range). */\n #focusAt(items: HTMLElement[], index: number): void {\n items[index]?.focus();\n }\n\n /** Returns roving focus to a top item (and makes it the single tab stop). */\n #focusTop(top: HTMLButtonElement | null): void {\n if (!top) return;\n this.#roving.setActive(this.topTargets.indexOf(top), { focus: true });\n }\n\n /** Whether `top`'s menu is currently expanded. */\n #isExpanded(top: HTMLElement): boolean {\n return top.getAttribute(\"aria-expanded\") === \"true\";\n }\n\n /** Whether any top item's menu is currently open. */\n get #isAnyOpen(): boolean {\n return this.topTargets.some((top) => this.#isExpanded(top));\n }\n\n /** Advances focus to the next item in `items` matching the accumulated query. */\n #typeaheadTo(items: HTMLElement[], current: number, key: string): void {\n const index = findTypeaheadMatch(items, current, this.#typeahead.push(key));\n if (index !== -1) items[index]?.focus();\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/coerce.ts","../../src/controllers/meter_controller.ts"],"names":[],"mappings":"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","file":"meter_controller.js","sourcesContent":["/**\n * Numeric coercion shared by the value-bearing status controllers (progress,\n * meter).\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { toFiniteNumber } from \"../utils/coerce\";\n\n/**\n * Event shape `setValue` accepts: an action param `amount` or a `detail.value`.\n * Both are typed `number | string` because, while Stimulus coerces numeric action\n * params to numbers, a `meter:set` CustomEvent (or a non-numeric-looking param)\n * may carry a string; {@link MeterController.setValue} normalizes either form.\n */\ntype SetValueEvent = Event & {\n params?: { amount?: number | string };\n detail?: { value?: number | string };\n};\n\n/** Threshold segment a value falls into, reflected on `data-state`. */\ntype MeterState = \"low\" | \"medium\" | \"high\";\n\n/**\n * Headless meter behavior backed by the WAI-ARIA `meter` role.\n *\n * Markup contract (identifier: `stimeo--meter`):\n * <div data-controller=\"stimeo--meter\" role=\"meter\" aria-label=\"Disk usage\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"72\"\n * data-stimeo--meter-value-value=\"72\"\n * data-stimeo--meter-low-value=\"50\" data-stimeo--meter-high-value=\"80\">\n * <div data-stimeo--meter-target=\"bar\"></div>\n * </div>\n *\n * A `meter` is a *point-in-time* scalar within a known range (disk usage,\n * battery, score) — distinct from {@link ProgressController}'s task progress.\n * The controller syncs the ARIA value attributes and, when `low`/`high`\n * thresholds are present, classifies the value into a `low`/`medium`/`high`\n * segment on `data-state` so the consumer can color the bar.\n *\n * @remarks\n * Behavior only. Because state must not be conveyed by color alone (WCAG 1.4.1),\n * a consumer-provided `valueText` template feeds `aria-valuetext` so the segment\n * is also available as text. Threshold presence is read from the *attributes*\n * (an absent attribute means \"no threshold\"), not from a sentinel value.\n */\nexport class MeterController extends Controller<HTMLElement> {\n static override targets = [\"bar\"];\n static override values = {\n value: { type: Number, default: 0 },\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n low: { type: Number, default: 0 },\n high: { type: Number, default: 0 },\n optimum: { type: Number, default: 0 },\n valueText: { type: String, default: \"\" },\n };\n static actions = [\"setValue\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly barTarget: HTMLElement;\n declare readonly hasBarTarget: boolean;\n\n declare valueValue: number;\n declare minValue: number;\n declare maxValue: number;\n declare lowValue: number;\n declare highValue: number;\n declare optimumValue: number;\n declare valueTextValue: string;\n\n override connect(): void {\n this.#render();\n }\n\n /**\n * Updates the measured value from an action param (`amount`) or a\n * `detail.value` CustomEvent, syncs ARIA and `data-state`, and dispatches\n * `change` with the value, ratio, and computed segment.\n */\n setValue(event: SetValueEvent): void {\n const next = toFiniteNumber(event.params?.amount ?? event.detail?.value);\n if (next === null) return;\n this.valueValue = this.#clamp(next);\n this.#render();\n this.dispatch(\"change\", {\n detail: { value: this.valueValue, ratio: this.#ratio, state: this.#state },\n });\n }\n\n /** Clamps `raw` into the configured `[min, max]` range. */\n #clamp(raw: number): number {\n return Math.min(this.maxValue, Math.max(this.minValue, raw));\n }\n\n /** Current fraction of the range in `[0, 1]`; `0` when the range is empty. */\n get #ratio(): number {\n const span = this.maxValue - this.minValue;\n if (span <= 0) return 0;\n return (this.#clamp(this.valueValue) - this.minValue) / span;\n }\n\n /** Whether a threshold attribute is present (absent = no threshold). */\n #hasThreshold(name: \"low\" | \"high\"): boolean {\n return this.element.hasAttribute(`data-stimeo--meter-${name}-value`);\n }\n\n /**\n * Classifies the value into a `low`/`medium`/`high` segment. Values at or\n * below `low` are `low`; at or above `high` are `high`; otherwise `medium`.\n * With neither threshold present, everything is `medium`.\n */\n get #state(): MeterState {\n const value = this.#clamp(this.valueValue);\n if (this.#hasThreshold(\"low\") && value <= this.lowValue) return \"low\";\n if (this.#hasThreshold(\"high\") && value >= this.highValue) return \"high\";\n return \"medium\";\n }\n\n /** Reflects value/range onto ARIA, the segment onto `data-state`, and the ratio. */\n #render(): void {\n const value = this.#clamp(this.valueValue);\n this.element.setAttribute(\"aria-valuemin\", String(this.minValue));\n this.element.setAttribute(\"aria-valuemax\", String(this.maxValue));\n this.element.setAttribute(\"aria-valuenow\", String(value));\n this.element.style.setProperty(\"--stimeo-meter-ratio\", String(this.#ratio));\n this.element.setAttribute(\"data-state\", this.#state);\n this.#applyValueText(value);\n }\n\n /**\n * Sets `aria-valuetext` from the consumer-provided template, substituting\n * `{value}`, `{percent}`, and `{state}`. Kept i18n-neutral in the library;\n * cleared when no template is given.\n */\n #applyValueText(value: number): void {\n if (this.valueTextValue.length === 0) {\n this.element.removeAttribute(\"aria-valuetext\");\n return;\n }\n const percent = Math.round(this.#ratio * 100);\n const text = this.valueTextValue\n .replaceAll(\"{value}\", String(value))\n .replaceAll(\"{percent}\", String(percent))\n .replaceAll(\"{state}\", this.#state);\n this.element.setAttribute(\"aria-valuetext\", text);\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/coerce.ts","../../src/controllers/meter_controller.ts"],"names":[],"mappings":"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","file":"meter_controller.js","sourcesContent":["/**\n * Numeric coercion shared by the value-bearing controllers (progress, meter,\n * color-picker).\n *\n * Stimulus already coerces numeric action params to numbers, but a value can also\n * arrive as a string — via a `*:set` CustomEvent `detail`, or an action param\n * whose attribute does not look numeric. Centralizing the parse keeps `setValue`\n * tolerant of either form while rejecting anything that is not a finite number.\n */\n\n/**\n * Coerces `raw` to a finite number, or returns `null` when it is absent, empty,\n * or not parseable. Empty strings are treated as \"no value\" rather than `0`, so a\n * stray blank param cannot silently reset the value.\n */\nexport function toFiniteNumber(raw: number | string | null | undefined): number | null {\n if (raw === null || raw === undefined || raw === \"\") return null;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { toFiniteNumber } from \"../utils/coerce\";\n\n/**\n * Event shape `setValue` accepts: an action param `amount` or a `detail.value`.\n * Both are typed `number | string` because, while Stimulus coerces numeric action\n * params to numbers, a `meter:set` CustomEvent (or a non-numeric-looking param)\n * may carry a string; {@link MeterController.setValue} normalizes either form.\n */\ntype SetValueEvent = Event & {\n params?: { amount?: number | string };\n detail?: { value?: number | string };\n};\n\n/** Threshold segment a value falls into, reflected on `data-state`. */\ntype MeterState = \"low\" | \"medium\" | \"high\";\n\n/**\n * Headless meter behavior backed by the WAI-ARIA `meter` role.\n *\n * Markup contract (identifier: `stimeo--meter`):\n * <div data-controller=\"stimeo--meter\" role=\"meter\" aria-label=\"Disk usage\"\n * aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"72\"\n * data-stimeo--meter-value-value=\"72\"\n * data-stimeo--meter-low-value=\"50\" data-stimeo--meter-high-value=\"80\">\n * <div data-stimeo--meter-target=\"bar\"></div>\n * </div>\n *\n * A `meter` is a *point-in-time* scalar within a known range (disk usage,\n * battery, score) — distinct from {@link ProgressController}'s task progress.\n * The controller syncs the ARIA value attributes and, when `low`/`high`\n * thresholds are present, classifies the value into a `low`/`medium`/`high`\n * segment on `data-state` so the consumer can color the bar.\n *\n * @remarks\n * Behavior only. Because state must not be conveyed by color alone (WCAG 1.4.1),\n * a consumer-provided `valueText` template feeds `aria-valuetext` so the segment\n * is also available as text. Threshold presence is read from the *attributes*\n * (an absent attribute means \"no threshold\"), not from a sentinel value.\n */\nexport class MeterController extends Controller<HTMLElement> {\n static override targets = [\"bar\"];\n static override values = {\n value: { type: Number, default: 0 },\n min: { type: Number, default: 0 },\n max: { type: Number, default: 100 },\n low: { type: Number, default: 0 },\n high: { type: Number, default: 0 },\n optimum: { type: Number, default: 0 },\n valueText: { type: String, default: \"\" },\n };\n static actions = [\"setValue\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly barTarget: HTMLElement;\n declare readonly hasBarTarget: boolean;\n\n declare valueValue: number;\n declare minValue: number;\n declare maxValue: number;\n declare lowValue: number;\n declare highValue: number;\n declare optimumValue: number;\n declare valueTextValue: string;\n\n override connect(): void {\n this.#render();\n }\n\n /**\n * Updates the measured value from an action param (`amount`) or a\n * `detail.value` CustomEvent, syncs ARIA and `data-state`, and dispatches\n * `change` with the value, ratio, and computed segment.\n */\n setValue(event: SetValueEvent): void {\n const next = toFiniteNumber(event.params?.amount ?? event.detail?.value);\n if (next === null) return;\n this.valueValue = this.#clamp(next);\n this.#render();\n this.dispatch(\"change\", {\n detail: { value: this.valueValue, ratio: this.#ratio, state: this.#state },\n });\n }\n\n /** Clamps `raw` into the configured `[min, max]` range. */\n #clamp(raw: number): number {\n return Math.min(this.maxValue, Math.max(this.minValue, raw));\n }\n\n /** Current fraction of the range in `[0, 1]`; `0` when the range is empty. */\n get #ratio(): number {\n const span = this.maxValue - this.minValue;\n if (span <= 0) return 0;\n return (this.#clamp(this.valueValue) - this.minValue) / span;\n }\n\n /** Whether a threshold attribute is present (absent = no threshold). */\n #hasThreshold(name: \"low\" | \"high\"): boolean {\n return this.element.hasAttribute(`data-stimeo--meter-${name}-value`);\n }\n\n /**\n * Classifies the value into a `low`/`medium`/`high` segment. Values at or\n * below `low` are `low`; at or above `high` are `high`; otherwise `medium`.\n * With neither threshold present, everything is `medium`.\n */\n get #state(): MeterState {\n const value = this.#clamp(this.valueValue);\n if (this.#hasThreshold(\"low\") && value <= this.lowValue) return \"low\";\n if (this.#hasThreshold(\"high\") && value >= this.highValue) return \"high\";\n return \"medium\";\n }\n\n /** Reflects value/range onto ARIA, the segment onto `data-state`, and the ratio. */\n #render(): void {\n const value = this.#clamp(this.valueValue);\n this.element.setAttribute(\"aria-valuemin\", String(this.minValue));\n this.element.setAttribute(\"aria-valuemax\", String(this.maxValue));\n this.element.setAttribute(\"aria-valuenow\", String(value));\n this.element.style.setProperty(\"--stimeo-meter-ratio\", String(this.#ratio));\n this.element.setAttribute(\"data-state\", this.#state);\n this.#applyValueText(value);\n }\n\n /**\n * Sets `aria-valuetext` from the consumer-provided template, substituting\n * `{value}`, `{percent}`, and `{state}`. Kept i18n-neutral in the library;\n * cleared when no template is given.\n */\n #applyValueText(value: number): void {\n if (this.valueTextValue.length === 0) {\n this.element.removeAttribute(\"aria-valuetext\");\n return;\n }\n const percent = Math.round(this.#ratio * 100);\n const text = this.valueTextValue\n .replaceAll(\"{value}\", String(value))\n .replaceAll(\"{percent}\", String(percent))\n .replaceAll(\"{state}\", this.#state);\n this.element.setAttribute(\"aria-valuetext\", text);\n }\n}\n"]}
@@ -0,0 +1,141 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ /**
4
+ * Headless, accessible multi-select combobox with chips.
5
+ *
6
+ * Markup contract (identifier: `stimeo--multi-select`):
7
+ * <div data-controller="stimeo--multi-select">
8
+ * <ul data-stimeo--multi-select-target="tags" aria-label="Selected"></ul>
9
+ * <input type="text" role="combobox" aria-expanded="false"
10
+ * aria-autocomplete="list" aria-controls="ms-list"
11
+ * data-stimeo--multi-select-target="input"
12
+ * data-action="input->stimeo--multi-select#filter
13
+ * keydown->stimeo--multi-select#onKeydown
14
+ * focus->stimeo--multi-select#open" />
15
+ * <ul id="ms-list" role="listbox" aria-multiselectable="true" hidden
16
+ * data-stimeo--multi-select-target="list">
17
+ * <li id="ms-opt-1" role="option" aria-selected="false" data-value="apple"
18
+ * data-stimeo--multi-select-target="option"
19
+ * data-action="click->stimeo--multi-select#toggleOption">Apple</li>
20
+ * </ul>
21
+ * <span role="status" aria-live="polite" class="visually-hidden"
22
+ * data-stimeo--multi-select-target="status"></span>
23
+ * <!-- Optional: submit the selection as name="fruits[]" hidden inputs. -->
24
+ * <div data-stimeo--multi-select-target="fields"></div>
25
+ * <template data-stimeo--multi-select-target="tagTemplate">…</template>
26
+ * </div>
27
+ *
28
+ * Implements the WAI-ARIA APG **Combobox** pattern in its list-autocomplete,
29
+ * multi-select form. Focus stays on the input; the active option is tracked with
30
+ * `aria-activedescendant` and selection with `aria-selected`. Selected options are
31
+ * mirrored as removable chips. For single selection use {@link ListboxController}
32
+ * or Combobox; for free-text tags use {@link TagsInputController}.
33
+ *
34
+ * Behavior provided:
35
+ * - Typing filters options by substring and opens the list; `ArrowDown`/`ArrowUp`
36
+ * (wrapping), `Home`/`End` move the active option; `Enter` toggles it (the list
37
+ * stays open); `Escape`/`Tab`/outside click close.
38
+ * - Toggling syncs `aria-selected`, adds/removes a `Remove {label}` chip, mirrors
39
+ * the live region, and dispatches `stimeo--multi-select:change` with `values`;
40
+ * filtering dispatches `stimeo--multi-select:filter` for async candidates.
41
+ * - The chips are one roving Tab stop: `ArrowLeft`/`ArrowRight` move between them,
42
+ * `Delete`/`Backspace` remove the focused chip, and `Backspace` on an empty
43
+ * input removes the last; removal re-homes focus to a neighbor or the input.
44
+ * - `max` caps the selection (`0` = unlimited).
45
+ * - With a `fields` target the selected values are mirrored into named hidden
46
+ * inputs (default name `options[]`), so the selection submits with a normal
47
+ * form and no consumer JS — parity with {@link TagsInputController}. An optional
48
+ * `form` value sets the hidden inputs' `form` attribute, associating them with a
49
+ * `<form>` by id even when the picker lives outside it. Without a `fields`
50
+ * target the hidden inputs are simply not written.
51
+ */
52
+ declare class MultiSelectController extends Controller<HTMLElement> {
53
+ #private;
54
+ static targets: string[];
55
+ static values: {
56
+ max: {
57
+ type: NumberConstructor;
58
+ default: number;
59
+ };
60
+ name: {
61
+ type: StringConstructor;
62
+ default: string;
63
+ };
64
+ form: {
65
+ type: StringConstructor;
66
+ default: string;
67
+ };
68
+ };
69
+ static actions: readonly ["close", "filter", "onKeydown", "open", "toggleOption"];
70
+ static events: readonly ["change", "filter"];
71
+ readonly inputTarget: HTMLInputElement;
72
+ readonly listTarget: HTMLElement;
73
+ readonly optionTargets: HTMLElement[];
74
+ readonly tagsTarget: HTMLElement;
75
+ readonly tagTargets: HTMLElement[];
76
+ readonly tagTemplateTarget: HTMLTemplateElement;
77
+ readonly statusTarget: HTMLElement;
78
+ readonly fieldsTarget: HTMLElement;
79
+ readonly hasListTarget: boolean;
80
+ readonly hasInputTarget: boolean;
81
+ readonly hasTagsTarget: boolean;
82
+ readonly hasTagTemplateTarget: boolean;
83
+ readonly hasStatusTarget: boolean;
84
+ readonly hasFieldsTarget: boolean;
85
+ maxValue: number;
86
+ nameValue: string;
87
+ formValue: string;
88
+ readonly hasFormValue: boolean;
89
+ /** Starts closed, syncs chips for any pre-selected options, and listens out. */
90
+ connect(): void;
91
+ /** Tears down document and chip listeners on disconnect (Turbo included). */
92
+ disconnect(): void;
93
+ /** Reconciles active state after an option target is added at runtime. */
94
+ optionTargetConnected(): void;
95
+ /** Cleans a removed target and reconciles active state against the surviving DOM. */
96
+ optionTargetDisconnected(option: HTMLElement): void;
97
+ /**
98
+ * Tracks an input added initially or after connect, and makes it describe the
99
+ * widget that is actually on screen: a swapped-in input arrives with the
100
+ * authored ARIA of a fresh node while this controller still holds the popup
101
+ * state, and the open path cannot repair that (it seeds an active option only
102
+ * when there is none), so a live list would go unannounced.
103
+ */
104
+ inputTargetConnected(input: HTMLInputElement): void;
105
+ /** Removes composition listeners when the active input is replaced or removed. */
106
+ inputTargetDisconnected(input: HTMLInputElement): void;
107
+ /** Filters confirmed input text, opens, and re-seeds the active option. */
108
+ filter(event?: InputEvent): void;
109
+ /**
110
+ * Opens the list and activates the first visible option when none is active.
111
+ *
112
+ * Needs the input, which owns `aria-expanded` and `aria-activedescendant`: a
113
+ * list shown without one is a popup no assistive technology is told about. So
114
+ * opening is skipped entirely, where {@link close} still closes.
115
+ */
116
+ open(): void;
117
+ /**
118
+ * Closes the list and clears the active option.
119
+ *
120
+ * Survives a missing input in both directions. `connect()` calls this second,
121
+ * so dereferencing the input here would throw before the chips, the roving
122
+ * seed, the chip listeners, the hidden fields and the outside-click listener —
123
+ * and Stimulus keeps the controller alive after that throw, so none of them
124
+ * would ever run and the selection would silently stop submitting. An input
125
+ * removed while the list is open must still let it come down *and* forget its
126
+ * active option, so only the `aria-expanded` write is guarded.
127
+ */
128
+ close(): void;
129
+ /** Routes input keyboard interaction per the multi-select combobox model. */
130
+ onKeydown(event: KeyboardEvent): void;
131
+ /**
132
+ * Toggles the clicked option's selection. Bound via `data-action`. Focus is
133
+ * re-homed to the input afterwards: options are non-focusable, so the click blurs
134
+ * the input to `body` — and with the list deliberately staying open, every
135
+ * keyboard affordance (Escape, arrows, typing) is bound to the input and would
136
+ * otherwise go dead until the user clicks back in ("focus stays on the input").
137
+ */
138
+ toggleOption(event: Event): void;
139
+ }
140
+
141
+ export { MultiSelectController };