stimeo-ui 0.14.0 → 0.16.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 (279) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/README.md +120 -0
  3. package/dist/cable/index.js +123 -29
  4. package/dist/cable/index.js.map +1 -1
  5. package/dist/controllers/accordion_controller.d.ts +30 -3
  6. package/dist/controllers/accordion_controller.js +98 -9
  7. package/dist/controllers/accordion_controller.js.map +1 -1
  8. package/dist/controllers/alert_dialog_controller.d.ts +8 -7
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  10. package/dist/controllers/announcer_controller.d.ts +3 -2
  11. package/dist/controllers/announcer_controller.js +96 -62
  12. package/dist/controllers/announcer_controller.js.map +1 -1
  13. package/dist/controllers/auto_submit_controller.js +83 -7
  14. package/dist/controllers/auto_submit_controller.js.map +1 -1
  15. package/dist/controllers/avatar_controller.js +1 -1
  16. package/dist/controllers/avatar_controller.js.map +1 -1
  17. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  18. package/dist/controllers/breadcrumb_controller.js +38 -11
  19. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  20. package/dist/controllers/bulk_select_controller.d.ts +4 -4
  21. package/dist/controllers/bulk_select_controller.js +7 -6
  22. package/dist/controllers/bulk_select_controller.js.map +1 -1
  23. package/dist/controllers/calendar_controller.d.ts +101 -23
  24. package/dist/controllers/calendar_controller.js +340 -123
  25. package/dist/controllers/calendar_controller.js.map +1 -1
  26. package/dist/controllers/carousel_controller.d.ts +67 -22
  27. package/dist/controllers/carousel_controller.js +263 -38
  28. package/dist/controllers/carousel_controller.js.map +1 -1
  29. package/dist/controllers/character_counter_controller.d.ts +1 -1
  30. package/dist/controllers/character_counter_controller.js +40 -2
  31. package/dist/controllers/character_counter_controller.js.map +1 -1
  32. package/dist/controllers/checkbox_controller.js +81 -12
  33. package/dist/controllers/checkbox_controller.js.map +1 -1
  34. package/dist/controllers/clipboard_controller.d.ts +28 -2
  35. package/dist/controllers/clipboard_controller.js +63 -5
  36. package/dist/controllers/clipboard_controller.js.map +1 -1
  37. package/dist/controllers/collapsible_controller.d.ts +25 -1
  38. package/dist/controllers/collapsible_controller.js +99 -14
  39. package/dist/controllers/collapsible_controller.js.map +1 -1
  40. package/dist/controllers/color_picker_controller.d.ts +29 -8
  41. package/dist/controllers/color_picker_controller.js +80 -34
  42. package/dist/controllers/color_picker_controller.js.map +1 -1
  43. package/dist/controllers/combobox_controller.d.ts +10 -1
  44. package/dist/controllers/combobox_controller.js +106 -16
  45. package/dist/controllers/combobox_controller.js.map +1 -1
  46. package/dist/controllers/command_palette_controller.d.ts +3 -3
  47. package/dist/controllers/command_palette_controller.js +35 -3
  48. package/dist/controllers/command_palette_controller.js.map +1 -1
  49. package/dist/controllers/conditional_fields_controller.js +85 -17
  50. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  51. package/dist/controllers/confirm_controller.js +3 -0
  52. package/dist/controllers/confirm_controller.js.map +1 -1
  53. package/dist/controllers/context_menu_controller.d.ts +6 -0
  54. package/dist/controllers/context_menu_controller.js +32 -12
  55. package/dist/controllers/context_menu_controller.js.map +1 -1
  56. package/dist/controllers/count_up_controller.js.map +1 -1
  57. package/dist/controllers/countdown_controller.d.ts +30 -1
  58. package/dist/controllers/countdown_controller.js +129 -26
  59. package/dist/controllers/countdown_controller.js.map +1 -1
  60. package/dist/controllers/currency_input_controller.d.ts +77 -16
  61. package/dist/controllers/currency_input_controller.js +221 -67
  62. package/dist/controllers/currency_input_controller.js.map +1 -1
  63. package/dist/controllers/data_grid_controller.d.ts +63 -18
  64. package/dist/controllers/data_grid_controller.js +195 -29
  65. package/dist/controllers/data_grid_controller.js.map +1 -1
  66. package/dist/controllers/date_range_picker_controller.d.ts +41 -6
  67. package/dist/controllers/date_range_picker_controller.js +151 -30
  68. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  69. package/dist/controllers/dialog_controller.d.ts +10 -3
  70. package/dist/controllers/dialog_controller.js +35 -8
  71. package/dist/controllers/dialog_controller.js.map +1 -1
  72. package/dist/controllers/direct_upload_controller.js +22 -4
  73. package/dist/controllers/direct_upload_controller.js.map +1 -1
  74. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  75. package/dist/controllers/dirty_form_controller.js +46 -13
  76. package/dist/controllers/dirty_form_controller.js.map +1 -1
  77. package/dist/controllers/dismissible_controller.js +1 -0
  78. package/dist/controllers/dismissible_controller.js.map +1 -1
  79. package/dist/controllers/drawer_controller.d.ts +18 -10
  80. package/dist/controllers/drawer_controller.js +54 -19
  81. package/dist/controllers/drawer_controller.js.map +1 -1
  82. package/dist/controllers/dropdown_controller.d.ts +9 -3
  83. package/dist/controllers/dropdown_controller.js +36 -9
  84. package/dist/controllers/dropdown_controller.js.map +1 -1
  85. package/dist/controllers/editable_controller.js +34 -0
  86. package/dist/controllers/editable_controller.js.map +1 -1
  87. package/dist/controllers/file_dropzone_controller.js +144 -51
  88. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  89. package/dist/controllers/filter_controller.d.ts +10 -4
  90. package/dist/controllers/filter_controller.js +20 -6
  91. package/dist/controllers/filter_controller.js.map +1 -1
  92. package/dist/controllers/flash_controller.d.ts +26 -6
  93. package/dist/controllers/flash_controller.js +432 -71
  94. package/dist/controllers/flash_controller.js.map +1 -1
  95. package/dist/controllers/focus_controller.js +1 -0
  96. package/dist/controllers/focus_controller.js.map +1 -1
  97. package/dist/controllers/form_field_controller.js +7 -5
  98. package/dist/controllers/form_field_controller.js.map +1 -1
  99. package/dist/controllers/form_validation_controller.js +19 -13
  100. package/dist/controllers/form_validation_controller.js.map +1 -1
  101. package/dist/controllers/frame_loading_controller.js +45 -8
  102. package/dist/controllers/frame_loading_controller.js.map +1 -1
  103. package/dist/controllers/highlight_controller.js +82 -25
  104. package/dist/controllers/highlight_controller.js.map +1 -1
  105. package/dist/controllers/hover_card_controller.d.ts +10 -2
  106. package/dist/controllers/hover_card_controller.js +40 -14
  107. package/dist/controllers/hover_card_controller.js.map +1 -1
  108. package/dist/controllers/idle_controller.d.ts +16 -3
  109. package/dist/controllers/idle_controller.js +90 -5
  110. package/dist/controllers/idle_controller.js.map +1 -1
  111. package/dist/controllers/input_mask_controller.d.ts +5 -2
  112. package/dist/controllers/input_mask_controller.js +65 -9
  113. package/dist/controllers/input_mask_controller.js.map +1 -1
  114. package/dist/controllers/intersection_controller.js +3 -0
  115. package/dist/controllers/intersection_controller.js.map +1 -1
  116. package/dist/controllers/lazy_frame_controller.js +11 -2
  117. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  118. package/dist/controllers/listbox_controller.d.ts +50 -8
  119. package/dist/controllers/listbox_controller.js +203 -45
  120. package/dist/controllers/listbox_controller.js.map +1 -1
  121. package/dist/controllers/local_time_controller.js +10 -5
  122. package/dist/controllers/local_time_controller.js.map +1 -1
  123. package/dist/controllers/masonry_controller.d.ts +3 -3
  124. package/dist/controllers/masonry_controller.js +31 -15
  125. package/dist/controllers/masonry_controller.js.map +1 -1
  126. package/dist/controllers/menu_controller.d.ts +9 -3
  127. package/dist/controllers/menu_controller.js +45 -16
  128. package/dist/controllers/menu_controller.js.map +1 -1
  129. package/dist/controllers/menubar_controller.d.ts +11 -0
  130. package/dist/controllers/menubar_controller.js +58 -24
  131. package/dist/controllers/menubar_controller.js.map +1 -1
  132. package/dist/controllers/meter_controller.js +9 -5
  133. package/dist/controllers/meter_controller.js.map +1 -1
  134. package/dist/controllers/multi_select_controller.d.ts +18 -3
  135. package/dist/controllers/multi_select_controller.js +278 -104
  136. package/dist/controllers/multi_select_controller.js.map +1 -1
  137. package/dist/controllers/navigation_menu_controller.d.ts +11 -0
  138. package/dist/controllers/navigation_menu_controller.js +48 -15
  139. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  140. package/dist/controllers/nested_form_controller.js +37 -8
  141. package/dist/controllers/nested_form_controller.js.map +1 -1
  142. package/dist/controllers/network_status_controller.js +9 -1
  143. package/dist/controllers/network_status_controller.js.map +1 -1
  144. package/dist/controllers/number_input_controller.d.ts +36 -8
  145. package/dist/controllers/number_input_controller.js +124 -21
  146. package/dist/controllers/number_input_controller.js.map +1 -1
  147. package/dist/controllers/optimistic_controller.js +42 -5
  148. package/dist/controllers/optimistic_controller.js.map +1 -1
  149. package/dist/controllers/otp_controller.d.ts +22 -7
  150. package/dist/controllers/otp_controller.js +198 -55
  151. package/dist/controllers/otp_controller.js.map +1 -1
  152. package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
  153. package/dist/controllers/overflow_indicator_controller.js +115 -21
  154. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  155. package/dist/controllers/overflow_menu_controller.d.ts +26 -6
  156. package/dist/controllers/overflow_menu_controller.js +141 -44
  157. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  158. package/dist/controllers/pagination_controller.d.ts +19 -11
  159. package/dist/controllers/pagination_controller.js +74 -28
  160. package/dist/controllers/pagination_controller.js.map +1 -1
  161. package/dist/controllers/password_reveal_controller.d.ts +15 -1
  162. package/dist/controllers/password_reveal_controller.js +59 -2
  163. package/dist/controllers/password_reveal_controller.js.map +1 -1
  164. package/dist/controllers/persist_controller.js +30 -8
  165. package/dist/controllers/persist_controller.js.map +1 -1
  166. package/dist/controllers/pointer_drag_controller.js +131 -52
  167. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  168. package/dist/controllers/popover_controller.d.ts +9 -3
  169. package/dist/controllers/popover_controller.js +45 -11
  170. package/dist/controllers/popover_controller.js.map +1 -1
  171. package/dist/controllers/portal_controller.d.ts +1 -1
  172. package/dist/controllers/portal_controller.js +6 -2
  173. package/dist/controllers/portal_controller.js.map +1 -1
  174. package/dist/controllers/preview_guard_controller.js +16 -1
  175. package/dist/controllers/preview_guard_controller.js.map +1 -1
  176. package/dist/controllers/progress_controller.js +8 -4
  177. package/dist/controllers/progress_controller.js.map +1 -1
  178. package/dist/controllers/radio_group_controller.d.ts +6 -4
  179. package/dist/controllers/radio_group_controller.js +42 -17
  180. package/dist/controllers/radio_group_controller.js.map +1 -1
  181. package/dist/controllers/range_slider_controller.d.ts +49 -1
  182. package/dist/controllers/range_slider_controller.js +88 -42
  183. package/dist/controllers/range_slider_controller.js.map +1 -1
  184. package/dist/controllers/rating_controller.d.ts +14 -3
  185. package/dist/controllers/rating_controller.js +39 -15
  186. package/dist/controllers/rating_controller.js.map +1 -1
  187. package/dist/controllers/read_more_controller.d.ts +24 -2
  188. package/dist/controllers/read_more_controller.js +100 -7
  189. package/dist/controllers/read_more_controller.js.map +1 -1
  190. package/dist/controllers/reading_progress_controller.js +65 -19
  191. package/dist/controllers/reading_progress_controller.js.map +1 -1
  192. package/dist/controllers/relative_time_controller.js +10 -5
  193. package/dist/controllers/relative_time_controller.js.map +1 -1
  194. package/dist/controllers/resizable_controller.d.ts +16 -2
  195. package/dist/controllers/resizable_controller.js +82 -22
  196. package/dist/controllers/resizable_controller.js.map +1 -1
  197. package/dist/controllers/scroll_area_controller.js +75 -27
  198. package/dist/controllers/scroll_area_controller.js.map +1 -1
  199. package/dist/controllers/scroll_restore_controller.js +37 -16
  200. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  201. package/dist/controllers/scroll_visibility_controller.js +49 -30
  202. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  203. package/dist/controllers/scrollspy_controller.d.ts +3 -2
  204. package/dist/controllers/scrollspy_controller.js +71 -26
  205. package/dist/controllers/scrollspy_controller.js.map +1 -1
  206. package/dist/controllers/separator_controller.d.ts +41 -14
  207. package/dist/controllers/separator_controller.js +66 -37
  208. package/dist/controllers/separator_controller.js.map +1 -1
  209. package/dist/controllers/sidebar_controller.d.ts +20 -3
  210. package/dist/controllers/sidebar_controller.js +77 -18
  211. package/dist/controllers/sidebar_controller.js.map +1 -1
  212. package/dist/controllers/skeleton_controller.js +6 -1
  213. package/dist/controllers/skeleton_controller.js.map +1 -1
  214. package/dist/controllers/slider_controller.d.ts +45 -7
  215. package/dist/controllers/slider_controller.js +82 -47
  216. package/dist/controllers/slider_controller.js.map +1 -1
  217. package/dist/controllers/smart_sticky_header_controller.js +60 -26
  218. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  219. package/dist/controllers/sortable_controller.js +17 -2
  220. package/dist/controllers/sortable_controller.js.map +1 -1
  221. package/dist/controllers/spinner_controller.js +10 -2
  222. package/dist/controllers/spinner_controller.js.map +1 -1
  223. package/dist/controllers/step_indicator_controller.d.ts +19 -17
  224. package/dist/controllers/step_indicator_controller.js +18 -17
  225. package/dist/controllers/step_indicator_controller.js.map +1 -1
  226. package/dist/controllers/stepper_controller.d.ts +34 -9
  227. package/dist/controllers/stepper_controller.js +101 -19
  228. package/dist/controllers/stepper_controller.js.map +1 -1
  229. package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
  230. package/dist/controllers/stick_to_bottom_controller.js +104 -8
  231. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  232. package/dist/controllers/submit_once_controller.d.ts +3 -2
  233. package/dist/controllers/submit_once_controller.js +45 -9
  234. package/dist/controllers/submit_once_controller.js.map +1 -1
  235. package/dist/controllers/switch_controller.d.ts +29 -7
  236. package/dist/controllers/switch_controller.js +101 -10
  237. package/dist/controllers/switch_controller.js.map +1 -1
  238. package/dist/controllers/tabs_controller.d.ts +12 -0
  239. package/dist/controllers/tabs_controller.js +21 -2
  240. package/dist/controllers/tabs_controller.js.map +1 -1
  241. package/dist/controllers/tags_input_controller.d.ts +15 -3
  242. package/dist/controllers/tags_input_controller.js +209 -59
  243. package/dist/controllers/tags_input_controller.js.map +1 -1
  244. package/dist/controllers/textarea_autosize_controller.js +29 -3
  245. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  246. package/dist/controllers/theme_controller.d.ts +20 -3
  247. package/dist/controllers/theme_controller.js +64 -14
  248. package/dist/controllers/theme_controller.js.map +1 -1
  249. package/dist/controllers/time_picker_controller.js +23 -8
  250. package/dist/controllers/time_picker_controller.js.map +1 -1
  251. package/dist/controllers/toast_controller.d.ts +55 -15
  252. package/dist/controllers/toast_controller.js +451 -105
  253. package/dist/controllers/toast_controller.js.map +1 -1
  254. package/dist/controllers/toggle_group_controller.d.ts +49 -7
  255. package/dist/controllers/toggle_group_controller.js +159 -23
  256. package/dist/controllers/toggle_group_controller.js.map +1 -1
  257. package/dist/controllers/toolbar_controller.js +32 -0
  258. package/dist/controllers/toolbar_controller.js.map +1 -1
  259. package/dist/controllers/tooltip_controller.d.ts +8 -0
  260. package/dist/controllers/tooltip_controller.js +39 -13
  261. package/dist/controllers/tooltip_controller.js.map +1 -1
  262. package/dist/controllers/transition_controller.js +4 -0
  263. package/dist/controllers/transition_controller.js.map +1 -1
  264. package/dist/controllers/tree_view_controller.d.ts +39 -8
  265. package/dist/controllers/tree_view_controller.js +169 -16
  266. package/dist/controllers/tree_view_controller.js.map +1 -1
  267. package/dist/index.d.ts +28 -1
  268. package/dist/index.js +5002 -1911
  269. package/dist/index.js.map +1 -1
  270. package/dist/inspector/cli.d.ts +72 -6
  271. package/dist/inspector/cli.js +262 -51
  272. package/dist/inspector/cli.js.map +1 -1
  273. package/dist/inspector/cli_bin.js +309 -51
  274. package/dist/inspector/cli_bin.js.map +1 -1
  275. package/dist/inspector/examples.json +40 -40
  276. package/dist/inspector/manifest.json +529 -61
  277. package/dist/positioning/index.js +2 -0
  278. package/dist/positioning/index.js.map +1 -1
  279. package/package.json +2 -2
@@ -83,6 +83,16 @@ var BeforeCacheReset = class _BeforeCacheReset {
83
83
  }
84
84
  };
85
85
 
86
+ // src/utils/field_mirror.ts
87
+ function writeField(field, value) {
88
+ if (field.value === value) return false;
89
+ field.value = value;
90
+ return true;
91
+ }
92
+ function commitField(target) {
93
+ target.dispatchEvent(new Event("change", { bubbles: true }));
94
+ }
95
+
86
96
  // src/utils/microtask_coalescer.ts
87
97
  var MicrotaskCoalescer = class {
88
98
  #run;
@@ -201,11 +211,14 @@ var RatingController = class extends Controller {
201
211
  #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
202
212
  #connected = false;
203
213
  #rescuedFocus = false;
214
+ /** The rating shown last, which the next move is measured from. */
215
+ #shown = 0;
204
216
  /** Reflects declarative state without announcing an initial user change. */
205
217
  connect() {
206
218
  this.#repaint.activate();
207
219
  this.#beforeCache.activate();
208
- this.#apply(this.#normalize(this.valueValue), { focus: false });
220
+ this.#shown = this.#normalize(this.valueValue);
221
+ this.#apply(this.#shown, { focus: false });
209
222
  this.#connected = true;
210
223
  }
211
224
  /** Drops a queued reconciliation and hands every borrowed attribute back. */
@@ -306,29 +319,35 @@ var RatingController = class extends Controller {
306
319
  this.#commit(next, { focus: true });
307
320
  }
308
321
  /**
309
- * Repaints one settled target/Value mutation batch and reports only a value
310
- * this controller had to normalize.
322
+ * Repaints one settled target/Value mutation batch and reports a rating shown
323
+ * that moved from the one shown before it.
311
324
  *
312
325
  * @stimeoRenderRoot
313
326
  */
314
327
  #reconcileScale() {
315
- const requested = this.valueValue;
316
- const value = this.#normalize(requested);
328
+ const value = this.#normalize(this.valueValue);
329
+ const previous = this.#shown;
330
+ this.#shown = value;
317
331
  this.#apply(value, { focus: false });
318
- if (!Object.is(value, requested)) {
319
- this.dispatch("reconcile", { detail: { value } });
320
- }
332
+ if (value !== previous) this.dispatch("reconcile", { detail: { value } });
321
333
  }
322
- /** Applies one user operation and emits only when its committed value changes. */
334
+ /**
335
+ * Applies one user operation and emits only when the rating shown changes.
336
+ *
337
+ * The user's value is written into `value` before anything reports it, so a
338
+ * listener of the field's native `change` already reads it there, and the pass
339
+ * that the write starts finds nothing left to report.
340
+ */
323
341
  #commit(raw, { focus }) {
324
- const previous = this.#normalize(this.valueValue);
342
+ const previous = this.#shown;
325
343
  const value = this.#normalize(raw);
326
- this.#apply(value, { focus });
344
+ if (!Object.is(this.valueValue, value)) this.valueValue = value;
345
+ this.#shown = value;
346
+ this.#apply(value, { focus, notify: true });
327
347
  if (value !== previous) this.dispatch("change", { detail: { value } });
328
348
  }
329
- /** Synchronizes value, ARIA, roving focus, form state, and the visual fill hook. */
330
- #apply(value, { focus }) {
331
- if (!Object.is(this.valueValue, value)) this.valueValue = value;
349
+ /** Synchronizes ARIA, roving focus, form state, and the visual fill hook. */
350
+ #apply(value, { focus, notify = false }) {
332
351
  this.symbolTargets.forEach((symbol, index) => {
333
352
  symbol.setAttribute("aria-checked", value > 0 && index + 1 === value ? "true" : "false");
334
353
  });
@@ -338,9 +357,14 @@ var RatingController = class extends Controller {
338
357
  const returning = this.#releaseReadonly();
339
358
  this.#roving.setActive(value > 0 ? value - 1 : 0, { focus: focus || returning });
340
359
  }
341
- if (this.hasFieldTarget) this.fieldTarget.value = String(value);
360
+ this.#mirrorField(value, notify);
342
361
  this.#setFillRange(value);
343
362
  }
363
+ /** Mirrors `value` into the optional form field, reporting only a user's move. */
364
+ #mirrorField(value, notify) {
365
+ if (!this.hasFieldTarget) return;
366
+ if (writeField(this.fieldTarget, String(value)) && notify) commitField(this.fieldTarget);
367
+ }
344
368
  /** Marks the first `range` symbols with the consumer-owned fill hook. */
345
369
  #setFillRange(range) {
346
370
  this.symbolTargets.forEach((symbol, index) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/attribute_lease.ts","../../src/utils/before_cache_reset.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/roving_tabindex.ts","../../src/utils/tabindex_loan.ts","../../src/controllers/rating_controller.ts"],"names":[],"mappings":"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","file":"rating_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/**\n * True when a press arrived carrying any modifier, for the keys the browser and\n * the OS own outright: return without calling `preventDefault()` and without\n * moving any state.\n *\n * `Control+Home` and `Control+End` jump the document to its ends, and a widget\n * that swallows them makes the shortcut work or not depending on where focus\n * happens to sit. {@link isReservedArrowChord} answers the same question for the\n * arrows, but returns `false` for every other key so that this one can decide.\n *\n * There is no `allow` list here on purpose. APG assigns no modifier chord to\n * `Home`/`End`, so a widget that wanted one would be claiming a combination the\n * pattern never gave it.\n *\n * @example\n * ```ts\n * case \"Home\":\n * case \"End\":\n * if (hasModifierChord(event)) return;\n * ```\n */\nexport function hasModifierChord(event: KeyboardEvent): boolean {\n return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n}\n","/** One temporarily controlled attribute value and the authored value it displaced. */\ninterface AttributeLeaseRecord {\n readonly original: string | null;\n written: string | null;\n}\n\n/**\n * Temporarily controls one attribute across a changing set of elements.\n *\n * The first write remembers the authored value, including the distinction between\n * an absent attribute and an authored empty string. Returning a lease restores that\n * value only while the attribute still matches the controller's last write. If a\n * consumer changed it in the meantime, the consumer owns the new value and teardown\n * leaves it alone.\n *\n * A `null` write deliberately removes the attribute while retaining the lease. This\n * is useful for derived ARIA whose valid absence is itself controller state, such as\n * an unbounded `aria-valuemin` or a blank spinbutton's `aria-valuenow`.\n *\n * The lease has no lifecycle of its own, so it never subscribes to document events.\n * A consumer that must not let a derived value become the authored baseline of a\n * cached page returns its leases from its own `turbo:before-cache` rewind, where the\n * matching `disconnect()` also releases the subscription.\n */\nexport class AttributeLease<T extends Element = Element> {\n readonly #attribute: string;\n readonly #records = new Map<T, AttributeLeaseRecord>();\n\n /** @param attribute - The attribute whose temporary values this lease owns. */\n constructor(attribute: string) {\n this.#attribute = attribute;\n }\n\n /** Writes or removes the leased attribute while preserving its authored value. */\n write(element: T, value: string | null): void {\n const existing = this.#records.get(element);\n if (existing) {\n existing.written = value;\n } else {\n this.#records.set(element, {\n original: element.getAttribute(this.#attribute),\n written: value,\n });\n }\n\n this.#reflect(element, value);\n }\n\n /** Returns one lease without overwriting a value subsequently authored by a consumer. */\n return(element: T): void {\n const record = this.#records.get(element);\n if (!record) return;\n this.#records.delete(element);\n const stillOwned = element.getAttribute(this.#attribute) === record.written;\n if (stillOwned) this.#reflect(element, record.original);\n }\n\n /** Reflects only a real value transition, avoiding self-triggered mutation work. */\n #reflect(element: T, value: string | null): void {\n if (element.getAttribute(this.#attribute) === value) return;\n if (value === null) element.removeAttribute(this.#attribute);\n else element.setAttribute(this.#attribute, value);\n }\n\n /** Returns every outstanding lease using the same ownership check as {@link return}. */\n returnAll(): void {\n for (const element of Array.from(this.#records.keys())) this.return(element);\n }\n}\n","/**\n * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way.\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * 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. A caller\n * that already captured one event-scoped snapshot may pass it to\n * {@link RovingTabindex.setActive}, avoiding a second live-target scan while\n * preserving one coherent collection for the whole operation.\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 * and `items` to reuse an event-scoped collection snapshot.\n */\n setActive(\n index: number,\n options: { focus?: boolean; items?: readonly HTMLElement[] } = {},\n ): void {\n const { focus = false } = options;\n const items = options.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","import { BeforeCacheReset } from \"./before_cache_reset\";\n\n/**\n * Shared bookkeeping for a `tabindex` a controller lends an element temporarily.\n *\n * A controller that must move focus somewhere the author never made focusable\n * (a landmark root, a scroll destination) reaches for the same trick: add a\n * `tabindex` just-in-time and hand it back once it is no longer needed. The\n * borrow is the easy half; the return is what the two conditions below are for.\n *\n * **Returning needs two conditions, not one.** Owning the borrow is not enough:\n * the attribute must also still hold the value this instance wrote. A consumer\n * that changed it afterwards — `tabindex=\"0\"` to make the root its own Tab stop\n * — owns it now, and removing it there silently discards authored markup. The\n * bookkeeping is dropped either way, since the loan is over regardless of who\n * ends up owning the value.\n *\n * **Never borrow over an existing value.** An element that already carries a\n * `tabindex` is the author's to control, so there is nothing to lend and nothing\n * to return.\n *\n * Every live loan also owns a shared `turbo:before-cache` subscription. The loan\n * is returned before Turbo can copy it into a snapshot, so consumers get cache\n * safety without duplicating a lifecycle hook; `returnAll()` removes the\n * subscription again as soon as no loan remains.\n *\n * The registry is keyed by element, so a controller borrowing on a single\n * element (`this.element`) and one borrowing across a changing set of targets\n * use the same API — the single-element case is a set of one. It holds no\n * opinion about *when* to borrow or where focus goes next; that stays in the\n * controller.\n *\n * **The API is deliberately two methods.** This file's own doc block is dropped\n * from `dist`, but every member comment is inlined into **each** consumer entry\n * (`tsup` builds with `splitting: false`), so rationale belongs here, only the\n * contract belongs on the members, and every method no consumer calls is still\n * paid for once per consumer entry.\n *\n * @example\n * ```ts\n * readonly #tabindex = new TabindexLoan();\n *\n * #rescueFocus() {\n * this.#tabindex.lend(this.element);\n * this.element.focus();\n * }\n *\n * disconnect() {\n * this.#tabindex.returnAll();\n * }\n * ```\n */\nexport class TabindexLoan<T extends HTMLElement = HTMLElement> {\n readonly #value: string;\n readonly #lent = new Set<T>();\n /** Returns live loans before Turbo can copy them into its page snapshot. */\n readonly #beforeCache = new BeforeCacheReset(() => this.returnAll());\n\n /**\n * @param value - the `tabindex` to lend. `\"-1\"` (the default) is\n * programmatically focusable but not a Tab stop; `\"0\"` is a real Tab stop,\n * which a scroll region with no focusable content of its own needs.\n */\n constructor(value: string = \"-1\") {\n this.#value = value;\n }\n\n /** Lends `element` the value; no-ops when it already carries a `tabindex`. */\n lend(element: T): void {\n if (element.hasAttribute(\"tabindex\")) return;\n element.setAttribute(\"tabindex\", this.#value);\n this.#lent.add(element);\n // Subscribe only while a real loan exists. Keeping this guarantee here means\n // every consumer is Turbo-safe without another lifecycle hook to remember.\n this.#beforeCache.activate();\n }\n\n /** Takes back every loan whose value is still the one that was lent. */\n returnAll(): void {\n for (const element of this.#lent) {\n if (element.getAttribute(\"tabindex\") === this.#value) element.removeAttribute(\"tabindex\");\n }\n this.#lent.clear();\n this.#beforeCache.deactivate();\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { AttributeLease } from \"../utils/attribute_lease\";\nimport { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { RovingTabindex } from \"../utils/roving_tabindex\";\nimport { TabindexLoan } from \"../utils/tabindex_loan\";\n\n/**\n * Headless, accessible rating behavior over an ordinal symbol sequence.\n *\n * Markup contract (identifier: `stimeo--rating`):\n * <div data-controller=\"stimeo--rating\" role=\"radiogroup\" aria-label=\"Rating\"\n * data-stimeo--rating-value-value=\"3\">\n * <span role=\"radio\" aria-checked=\"false\" aria-label=\"1 star\" tabindex=\"-1\"\n * data-stimeo--rating-target=\"symbol\"\n * data-action=\"click->stimeo--rating#select\n * mouseenter->stimeo--rating#preview\n * mouseleave->stimeo--rating#endPreview\n * focus->stimeo--rating#preview\n * blur->stimeo--rating#endPreview\n * keydown->stimeo--rating#onKeydown\">★</span>\n * <!-- later symbols continue the 1..N scale in DOM order -->\n * <input type=\"hidden\" data-stimeo--rating-target=\"field\" />\n * </div>\n *\n * Implements the WAI-ARIA APG **Radio Group** pattern as an ordinal scale. The\n * live DOM order is the sole source of symbol values: the first symbol is 1 and\n * the last is N. Unlike a generic radio group, arrows deliberately clamp rather\n * than wrap because the values have an ordered lower and upper bound.\n *\n * `change` and `reconcile` dispatch `{ value: number }`.\n *\n * @remarks\n * Behavior only — consumers style `[aria-checked]` and `data-rating-hover`. In\n * `readonly` mode the group becomes `role=\"img\"`; the consumer supplies the\n * human-readable accessible name (for example, \"Rated 3 of 5\"). Focus standing on\n * a symbol when that mode begins lands on the root and returns to the Tab stop\n * when it ends, so it is never left on a node outside the accessibility tree.\n *\n * `stimeo--rating:change` is reserved for a user operation that changes the\n * committed value. A DOM or configuration reconciliation that clamps the value\n * emits `stimeo--rating:reconcile` instead. Initial reflection emits neither.\n */\nexport class RatingController extends Controller<HTMLElement> {\n static override targets = [\"symbol\", \"field\"];\n static override values = {\n value: { type: Number, default: 0 },\n clearable: { type: Boolean, default: true },\n readonly: { type: Boolean, default: false },\n };\n static actions = [\"endPreview\", \"onKeydown\", \"preview\", \"select\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly symbolTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare valueValue: number;\n declare clearableValue: boolean;\n declare readonlyValue: boolean;\n\n readonly #roving = new RovingTabindex(() => this.symbolTargets);\n readonly #rootRole = new AttributeLease<HTMLElement>(\"role\");\n readonly #symbolRole = new AttributeLease<HTMLElement>(\"role\");\n readonly #symbolAriaHidden = new AttributeLease<HTMLElement>(\"aria-hidden\");\n readonly #rootTabindex = new TabindexLoan();\n readonly #repaint = new MicrotaskCoalescer(() => this.#reconcileScale());\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n #connected = false;\n #rescuedFocus = false;\n\n /** Reflects declarative state without announcing an initial user change. */\n override connect(): void {\n this.#repaint.activate();\n this.#beforeCache.activate();\n this.#apply(this.#normalize(this.valueValue), { focus: false });\n this.#connected = true;\n }\n\n /** Drops a queued reconciliation and hands every borrowed attribute back. */\n override disconnect(): void {\n this.#connected = false;\n this.#repaint.cancel();\n this.#beforeCache.deactivate();\n this.#releaseReadonly();\n }\n\n /** Removes a runtime-added symbol's authored Tab stop before the batch repaint. */\n symbolTargetConnected(symbol: HTMLElement): void {\n if (this.#connected === false) return;\n symbol.tabIndex = -1;\n this.#repaint.schedule();\n }\n\n /** Releases readonly ownership and reconciles the remaining DOM-ordered scale. */\n symbolTargetDisconnected(symbol: HTMLElement): void {\n this.#symbolRole.return(symbol);\n this.#symbolAriaHidden.return(symbol);\n this.#repaint.schedule();\n }\n\n /** Reflects into a hidden field added or replaced after connection. */\n fieldTargetConnected(): void {\n this.#repaint.schedule();\n }\n\n /** Reconciles after a hidden field is removed or replaced. */\n fieldTargetDisconnected(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code or a Turbo morph changes `value`. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code changes whether value 0 is permitted. */\n clearableValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code enters or leaves the readonly snapshot. */\n readonlyValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Selects or clears the clicked symbol. Bound via `data-action` (click). */\n select(event: Event): void {\n if (this.readonlyValue) return;\n const ordinal = this.#symbolOrdinal(event.currentTarget);\n if (ordinal === null) return;\n const current = this.#normalize(this.valueValue);\n this.#commit(this.clearableValue && ordinal === current ? 0 : ordinal, {\n focus: ordinal === current,\n });\n }\n\n /** Previews a fill range on hover or focus without committing it. */\n preview(event: Event): void {\n if (this.readonlyValue) return;\n const ordinal = this.#symbolOrdinal(event.currentTarget);\n if (ordinal !== null) this.#setFillRange(ordinal);\n }\n\n /** Restores the fill range after hover or focus leaves a symbol. */\n endPreview(): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.#normalize(this.valueValue));\n }\n\n /** Arrow/Home/End/Space/Delete keyboard control, clamped without wrapping. */\n onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented || isReservedArrowChord(event) || this.readonlyValue) return;\n\n const current = this.#normalize(this.valueValue);\n let next: number | null = null;\n const rtl = isRtl(this.element);\n\n // Horizontal keys follow writing direction. Vertical keys express value:\n // ArrowUp is always more and ArrowDown is always less.\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = current + (event.key === \"ArrowRight\" && rtl ? -1 : 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = current - (event.key === \"ArrowLeft\" && rtl ? -1 : 1);\n break;\n case \"Home\":\n next = this.#minValue;\n break;\n case \"End\":\n next = this.symbolTargets.length;\n break;\n case \" \":\n case \"Enter\":\n next = this.#symbolOrdinal(event.currentTarget);\n break;\n case \"Delete\":\n case \"Backspace\":\n // A pointer clears by pressing the committed symbol again. This is the\n // keyboard's way to the same state, kept off Space so that a symbol\n // announced as a radio still answers Space the way a radio does.\n if (!this.clearableValue) return;\n next = 0;\n break;\n default:\n return;\n }\n\n if (next === null) return;\n event.preventDefault();\n this.#commit(next, { focus: true });\n }\n\n /**\n * Repaints one settled target/Value mutation batch and reports only a value\n * this controller had to normalize.\n *\n * @stimeoRenderRoot\n */\n #reconcileScale(): void {\n const requested = this.valueValue;\n const value = this.#normalize(requested);\n this.#apply(value, { focus: false });\n if (!Object.is(value, requested)) {\n this.dispatch(\"reconcile\", { detail: { value } });\n }\n }\n\n /** Applies one user operation and emits only when its committed value changes. */\n #commit(raw: number, { focus }: { focus: boolean }): void {\n const previous = this.#normalize(this.valueValue);\n const value = this.#normalize(raw);\n this.#apply(value, { focus });\n if (value !== previous) this.dispatch(\"change\", { detail: { value } });\n }\n\n /** Synchronizes value, ARIA, roving focus, form state, and the visual fill hook. */\n #apply(value: number, { focus }: { focus: boolean }): void {\n if (!Object.is(this.valueValue, value)) this.valueValue = value;\n this.symbolTargets.forEach((symbol, index) => {\n symbol.setAttribute(\"aria-checked\", value > 0 && index + 1 === value ? \"true\" : \"false\");\n });\n\n if (this.readonlyValue) {\n this.#applyReadonly();\n } else {\n const returning = this.#releaseReadonly();\n this.#roving.setActive(value > 0 ? value - 1 : 0, { focus: focus || returning });\n }\n\n if (this.hasFieldTarget) this.fieldTarget.value = String(value);\n this.#setFillRange(value);\n }\n\n /** Marks the first `range` symbols with the consumer-owned fill hook. */\n #setFillRange(range: number): void {\n this.symbolTargets.forEach((symbol, index) => {\n symbol.toggleAttribute(\"data-rating-hover\", range > 0 && index < range);\n });\n }\n\n /**\n * Temporarily turns the radiogroup into a non-interactive image snapshot.\n *\n * Each lease is returned before it is taken again, so a value the consumer\n * wrote while readonly becomes the value the lease restores on release. The\n * return is a no-op on an attribute still holding this controller's own\n * write, which is the ordinary case.\n */\n #applyReadonly(): void {\n this.#rescueFocus();\n this.#rootRole.return(this.element);\n this.#rootRole.write(this.element, \"img\");\n for (const symbol of this.symbolTargets) {\n this.#symbolRole.return(symbol);\n this.#symbolRole.write(symbol, null);\n this.#symbolAriaHidden.return(symbol);\n this.#symbolAriaHidden.write(symbol, \"true\");\n }\n this.#roving.setActive(-1);\n }\n\n /**\n * Hands every readonly borrowing back before Turbo clones the page.\n *\n * The snapshot is taken while the controller is still connected, so an\n * element left as `role=\"img\"` with hidden symbols is what a restored page\n * connects against — and that markup would be read as the authored one,\n * leaving no way back to the radiogroup. Rewinding first keeps the cached\n * copy identical to what the consumer wrote.\n */\n #rewindForCache(): void {\n this.#releaseReadonly();\n const value = this.#normalize(this.valueValue);\n this.#roving.setActive(value > 0 ? value - 1 : 0);\n }\n\n /**\n * Lands focus on the root before the symbols leave the accessibility tree.\n *\n * A symbol holding focus when readonly begins would keep it while losing its\n * role and gaining `aria-hidden`, stranding the user on a node no longer in\n * the tree. The root is the one element that survives the transition named:\n * it carries the consumer's accessible name under `role=\"img\"`.\n */\n #rescueFocus(): void {\n const active = document.activeElement;\n if (!(active instanceof HTMLElement) || active === this.element) return;\n if (!this.element.contains(active)) return;\n this.#rootTabindex.lend(this.element);\n this.element.focus();\n this.#rescuedFocus = true;\n }\n\n /**\n * Restores authored roles and visibility after leaving readonly mode.\n *\n * @returns whether focus is standing on the root because {@link #rescueFocus}\n * put it there, and therefore belongs back on the Tab stop.\n */\n #releaseReadonly(): boolean {\n const returning = this.#rescuedFocus && document.activeElement === this.element;\n this.#rescuedFocus = false;\n this.#rootRole.return(this.element);\n this.#symbolRole.returnAll();\n this.#symbolAriaHidden.returnAll();\n this.#rootTabindex.returnAll();\n return returning;\n }\n\n /** Normalizes a raw value to an integer ordinal in the live DOM range. */\n #normalize(raw: number): number {\n const maximum = this.symbolTargets.length;\n const ordinal = Number.isFinite(raw) ? Math.round(raw) : this.#minValue;\n return Math.min(maximum, Math.max(this.#minValue, ordinal));\n }\n\n /** Lowest selectable value: 0 when clearable, otherwise 1. */\n get #minValue(): number {\n return this.clearableValue ? 0 : 1;\n }\n\n /** Returns a target's 1-based position, or null when the action host is invalid. */\n #symbolOrdinal(target: EventTarget | null): number | null {\n const targets: readonly (EventTarget | null)[] = this.symbolTargets;\n const index = targets.indexOf(target);\n return index < 0 ? null : index + 1;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/attribute_lease.ts","../../src/utils/before_cache_reset.ts","../../src/utils/field_mirror.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/roving_tabindex.ts","../../src/utils/tabindex_loan.ts","../../src/controllers/rating_controller.ts"],"names":[],"mappings":";;;;;AAiBO,SAAS,MAAM,OAAA,EAA2B;AAC/C,EAAA,OAAO,MAAA,CAAO,gBAAA,CAAiB,OAAO,CAAA,CAAE,SAAA,KAAc,KAAA;AACxD;;;AC4EO,SAAS,oBAAA,CACd,KAAA,EACA,KAAA,GAAkC,EAAC,EAC1B;AACT,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,UAAA,CAAW,OAAO,GAAG,OAAO,KAAA;AAC3C,EAAA,OACG,KAAA,CAAM,MAAA,IAAU,CAAC,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,IACrC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,KACvC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACvC,KAAA,CAAM,QAAA,IAAY,CAAC,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA;AAE9C;;;AClFO,IAAM,iBAAN,MAAkD;AAAA,EAC9C,UAAA;AAAA,EACA,QAAA,uBAAe,GAAA,EAA6B;AAAA;AAAA,EAGrD,YAAY,SAAA,EAAmB;AAC7B,IAAA,IAAA,CAAK,UAAA,GAAa,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,KAAA,CAAM,SAAY,KAAA,EAA4B;AAC5C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA;AAC1C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AAAA,IACrB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,QAAA,CAAS,IAAI,OAAA,EAAS;AAAA,QACzB,QAAA,EAAU,OAAA,CAAQ,YAAA,CAAa,IAAA,CAAK,UAAU,CAAA;AAAA,QAC9C,OAAA,EAAS;AAAA,OACV,CAAA;AAAA,IACH;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS,SAAS,KAAK,CAAA;AAAA,EAC9B;AAAA;AAAA,EAGA,OAAO,OAAA,EAAkB;AACvB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA;AACxC,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,IAAA,CAAK,QAAA,CAAS,OAAO,OAAO,CAAA;AAC5B,IAAA,MAAM,aAAa,OAAA,CAAQ,YAAA,CAAa,IAAA,CAAK,UAAU,MAAM,MAAA,CAAO,OAAA;AACpE,IAAA,IAAI,UAAA,EAAY,IAAA,CAAK,QAAA,CAAS,OAAA,EAAS,OAAO,QAAQ,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,QAAA,CAAS,SAAY,KAAA,EAA4B;AAC/C,IAAA,IAAI,OAAA,CAAQ,YAAA,CAAa,IAAA,CAAK,UAAU,MAAM,KAAA,EAAO;AACrD,IAAA,IAAI,KAAA,KAAU,IAAA,EAAM,OAAA,CAAQ,eAAA,CAAgB,KAAK,UAAU,CAAA;AAAA,SACtD,OAAA,CAAQ,YAAA,CAAa,IAAA,CAAK,UAAA,EAAY,KAAK,CAAA;AAAA,EAClD;AAAA;AAAA,EAGA,SAAA,GAAkB;AAChB,IAAA,KAAA,MAAW,OAAA,IAAW,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA;AAAA,EAC7E;AACF,CAAA;;;ACzBO,IAAM,gBAAA,GAAN,MAAM,iBAAA,CAAiB;AAAA;AAAA,EAE5B,OAAgB,YAAA,mBAAe,IAAI,GAAA,EAAsB;AAAA;AAAA,EAGzD,OAAgB,iBAAiB,MAAY;AAC3C,IAAA,KAAA,MAAW,UAAA,IAAc,iBAAA,CAAiB,YAAA,EAAc,UAAA,CAAW,OAAA,EAAQ;AAAA,EAC7E,CAAA;AAAA,EAES,OAAA;AAAA;AAAA,EAGT,YAAY,MAAA,EAAoB;AAC9B,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,MAAM,KAAA,GAAQ,iBAAA,CAAiB,YAAA,CAAa,IAAA,KAAS,CAAA;AACrD,IAAA,iBAAA,CAAiB,YAAA,CAAa,IAAI,IAAI,CAAA;AACtC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAA,EAAsB,iBAAA,CAAiB,cAAc,CAAA;AAAA,IACjF;AAAA,EACF;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,iBAAA,CAAiB,YAAA,CAAa,OAAO,IAAI,CAAA;AACzC,IAAA,IAAI,iBAAA,CAAiB,YAAA,CAAa,IAAA,GAAO,CAAA,EAAG;AAC5C,IAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAA,EAAsB,iBAAA,CAAiB,cAAc,CAAA;AAAA,EACpF;AACF,CAAA;;;ACtCO,SAAS,UAAA,CAAW,OAAyB,KAAA,EAAwB;AAC1E,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,KAAA,EAAO,OAAO,KAAA;AAClC,EAAA,KAAA,CAAM,KAAA,GAAQ,KAAA;AACd,EAAA,OAAO,IAAA;AACT;AA0DO,SAAS,YAAY,MAAA,EAA2B;AACrD,EAAA,MAAA,CAAO,aAAA,CAAc,IAAI,KAAA,CAAM,QAAA,EAAU,EAAE,OAAA,EAAS,IAAA,EAAM,CAAC,CAAA;AAC7D;;;AC/CO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AChEO,IAAM,iBAAN,MAAqB;AAAA;AAAA,EAEjB,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT,YAAY,QAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AAAA,EACnB;AAAA;AAAA,EAGA,IAAI,WAAA,GAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,WAAU,CAAE,SAAA,CAAU,CAAC,IAAA,KAAS,IAAA,CAAK,aAAa,CAAC,CAAA;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,SAAA,CACE,KAAA,EACA,OAAA,GAA+D,EAAC,EAC1D;AACN,IAAA,MAAM,EAAE,KAAA,GAAQ,KAAA,EAAM,GAAI,OAAA;AAC1B,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,IAAA,CAAK,SAAA,EAAU;AAC9C,IAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAA,KAAM;AACzB,MAAA,IAAA,CAAK,QAAA,GAAW,CAAA,KAAM,KAAA,GAAQ,CAAA,GAAI,EAAA;AAAA,IACpC,CAAC,CAAA;AACD,IAAA,IAAI,KAAA,EAAO,KAAA,CAAM,KAAK,CAAA,EAAG,KAAA,EAAM;AAAA,EACjC;AACF,CAAA;;;ACVO,IAAM,eAAN,MAAwD;AAAA,EACpD,MAAA;AAAA,EACA,KAAA,uBAAY,GAAA,EAAO;AAAA;AAAA,EAEnB,eAAe,IAAI,gBAAA,CAAiB,MAAM,IAAA,CAAK,WAAW,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnE,WAAA,CAAY,QAAgB,IAAA,EAAM;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AAAA,EAChB;AAAA;AAAA,EAGA,KAAK,OAAA,EAAkB;AACrB,IAAA,IAAI,OAAA,CAAQ,YAAA,CAAa,UAAU,CAAA,EAAG;AACtC,IAAA,OAAA,CAAQ,YAAA,CAAa,UAAA,EAAY,IAAA,CAAK,MAAM,CAAA;AAC5C,IAAA,IAAA,CAAK,KAAA,CAAM,IAAI,OAAO,CAAA;AAGtB,IAAA,IAAA,CAAK,aAAa,QAAA,EAAS;AAAA,EAC7B;AAAA;AAAA,EAGA,SAAA,GAAkB;AAChB,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,KAAA,EAAO;AAChC,MAAA,IAAI,OAAA,CAAQ,aAAa,UAAU,CAAA,KAAM,KAAK,MAAA,EAAQ,OAAA,CAAQ,gBAAgB,UAAU,CAAA;AAAA,IAC1F;AACA,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AACjB,IAAA,IAAA,CAAK,aAAa,UAAA,EAAW;AAAA,EAC/B;AACF,CAAA;;;AC5BO,IAAM,gBAAA,GAAN,cAA+B,UAAA,CAAwB;AAAA,EAC5D,OAAgB,OAAA,GAAU,CAAC,QAAA,EAAU,OAAO,CAAA;AAAA,EAC5C,OAAgB,MAAA,GAAS;AAAA,IACvB,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,IAClC,SAAA,EAAW,EAAE,IAAA,EAAM,OAAA,EAAS,SAAS,IAAA,EAAK;AAAA,IAC1C,QAAA,EAAU,EAAE,IAAA,EAAM,OAAA,EAAS,SAAS,KAAA;AAAM,GAC5C;AAAA,EACA,OAAO,OAAA,GAAU,CAAC,YAAA,EAAc,WAAA,EAAa,WAAW,QAAQ,CAAA;AAAA,EAChE,OAAO,MAAA,GAAS,CAAC,QAAA,EAAU,WAAW,CAAA;AAAA,EAS7B,OAAA,GAAU,IAAI,cAAA,CAAe,MAAM,KAAK,aAAa,CAAA;AAAA,EACrD,SAAA,GAAY,IAAI,cAAA,CAA4B,MAAM,CAAA;AAAA,EAClD,WAAA,GAAc,IAAI,cAAA,CAA4B,MAAM,CAAA;AAAA,EACpD,iBAAA,GAAoB,IAAI,cAAA,CAA4B,aAAa,CAAA;AAAA,EACjE,aAAA,GAAgB,IAAI,YAAA,EAAa;AAAA,EACjC,WAAW,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,iBAAiB,CAAA;AAAA,EAC9D,eAAe,IAAI,gBAAA,CAAiB,MAAM,IAAA,CAAK,iBAAiB,CAAA;AAAA,EACzE,UAAA,GAAa,KAAA;AAAA,EACb,aAAA,GAAgB,KAAA;AAAA;AAAA,EAGhB,MAAA,GAAS,CAAA;AAAA;AAAA,EAGA,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AACvB,IAAA,IAAA,CAAK,aAAa,QAAA,EAAS;AAC3B,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAC7C,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,MAAA,EAAQ,EAAE,KAAA,EAAO,OAAO,CAAA;AACzC,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA,EACpB;AAAA;AAAA,EAGS,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,IAAA,CAAK,SAAS,MAAA,EAAO;AACrB,IAAA,IAAA,CAAK,aAAa,UAAA,EAAW;AAC7B,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB;AAAA;AAAA,EAGA,sBAAsB,MAAA,EAA2B;AAC/C,IAAA,IAAI,IAAA,CAAK,eAAe,KAAA,EAAO;AAC/B,IAAA,MAAA,CAAO,QAAA,GAAW,EAAA;AAClB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,yBAAyB,MAAA,EAA2B;AAClD,IAAA,IAAA,CAAK,WAAA,CAAY,OAAO,MAAM,CAAA;AAC9B,IAAA,IAAA,CAAK,iBAAA,CAAkB,OAAO,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,uBAAA,GAAgC;AAC9B,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,iBAAA,GAA0B;AACxB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,qBAAA,GAA8B;AAC5B,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAA,CAAK,SAAS,QAAA,EAAS;AAAA,EACzB;AAAA;AAAA,EAGA,OAAO,KAAA,EAAoB;AACzB,IAAA,IAAI,KAAK,aAAA,EAAe;AACxB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,CAAe,KAAA,CAAM,aAAa,CAAA;AACvD,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAC/C,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK,cAAA,IAAkB,OAAA,KAAY,OAAA,GAAU,IAAI,OAAA,EAAS;AAAA,MACrE,OAAO,OAAA,KAAY;AAAA,KACpB,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,QAAQ,KAAA,EAAoB;AAC1B,IAAA,IAAI,KAAK,aAAA,EAAe;AACxB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,CAAe,KAAA,CAAM,aAAa,CAAA;AACvD,IAAA,IAAI,OAAA,KAAY,IAAA,EAAM,IAAA,CAAK,aAAA,CAAc,OAAO,CAAA;AAAA,EAClD;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,IAAI,KAAK,aAAA,EAAe;AACxB,IAAA,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAC,CAAA;AAAA,EACrD;AAAA;AAAA,EAGA,UAAU,KAAA,EAA4B;AACpC,IAAA,IAAI,MAAM,gBAAA,IAAoB,oBAAA,CAAqB,KAAK,CAAA,IAAK,KAAK,aAAA,EAAe;AAEjF,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAC/C,IAAA,IAAI,IAAA,GAAsB,IAAA;AAC1B,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA;AAI9B,IAAA,QAAQ,MAAM,GAAA;AAAK,MACjB,KAAK,YAAA;AAAA,MACL,KAAK,SAAA;AACH,QAAA,IAAA,GAAO,OAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,YAAA,IAAgB,MAAM,EAAA,GAAK,CAAA,CAAA;AAC3D,QAAA;AAAA,MACF,KAAK,WAAA;AAAA,MACL,KAAK,WAAA;AACH,QAAA,IAAA,GAAO,OAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,WAAA,IAAe,MAAM,EAAA,GAAK,CAAA,CAAA;AAC1D,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,IAAA,GAAO,IAAA,CAAK,SAAA;AACZ,QAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,IAAA,GAAO,KAAK,aAAA,CAAc,MAAA;AAC1B,QAAA;AAAA,MACF,KAAK,GAAA;AAAA,MACL,KAAK,OAAA;AACH,QAAA,IAAA,GAAO,IAAA,CAAK,cAAA,CAAe,KAAA,CAAM,aAAa,CAAA;AAC9C,QAAA;AAAA,MACF,KAAK,QAAA;AAAA,MACL,KAAK,WAAA;AAIH,QAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AAC1B,QAAA,IAAA,GAAO,CAAA;AACP,QAAA;AAAA,MACF;AACE,QAAA;AAAA;AAGJ,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAA,GAAwB;AACtB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAC7C,IAAA,MAAM,WAAW,IAAA,CAAK,MAAA;AAGtB,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO,EAAE,KAAA,EAAO,OAAO,CAAA;AACnC,IAAA,IAAI,KAAA,KAAU,QAAA,EAAU,IAAA,CAAK,QAAA,CAAS,WAAA,EAAa,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAM,EAAG,CAAA;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAA,CAAQ,GAAA,EAAa,EAAE,KAAA,EAAM,EAA6B;AACxD,IAAA,MAAM,WAAW,IAAA,CAAK,MAAA;AACtB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA;AACjC,IAAA,IAAI,CAAC,OAAO,EAAA,CAAG,IAAA,CAAK,YAAY,KAAK,CAAA,OAAQ,UAAA,GAAa,KAAA;AAC1D,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,OAAO,KAAA,EAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,MAAM,CAAA;AAC1C,IAAA,IAAI,KAAA,KAAU,QAAA,EAAU,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAM,EAAG,CAAA;AAAA,EACvE;AAAA;AAAA,EAGA,OAAO,KAAA,EAAe,EAAE,KAAA,EAAO,MAAA,GAAS,OAAM,EAA+C;AAC3F,IAAA,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,CAAC,MAAA,EAAQ,KAAA,KAAU;AAC5C,MAAA,MAAA,CAAO,YAAA,CAAa,gBAAgB,KAAA,GAAQ,CAAA,IAAK,QAAQ,CAAA,KAAM,KAAA,GAAQ,SAAS,OAAO,CAAA;AAAA,IACzF,CAAC,CAAA;AAED,IAAA,IAAI,KAAK,aAAA,EAAe;AACtB,MAAA,IAAA,CAAK,cAAA,EAAe;AAAA,IACtB,CAAA,MAAO;AACL,MAAA,MAAM,SAAA,GAAY,KAAK,gBAAA,EAAiB;AACxC,MAAA,IAAA,CAAK,OAAA,CAAQ,SAAA,CAAU,KAAA,GAAQ,CAAA,GAAI,KAAA,GAAQ,CAAA,GAAI,CAAA,EAAG,EAAE,KAAA,EAAO,KAAA,IAAS,SAAA,EAAW,CAAA;AAAA,IACjF;AAEA,IAAA,IAAA,CAAK,YAAA,CAAa,OAAO,MAAM,CAAA;AAC/B,IAAA,IAAA,CAAK,cAAc,KAAK,CAAA;AAAA,EAC1B;AAAA;AAAA,EAGA,YAAA,CAAa,OAAe,MAAA,EAAuB;AACjD,IAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AAC1B,IAAA,IAAI,UAAA,CAAW,IAAA,CAAK,WAAA,EAAa,MAAA,CAAO,KAAK,CAAC,CAAA,IAAK,MAAA,EAAQ,WAAA,CAAY,IAAA,CAAK,WAAW,CAAA;AAAA,EACzF;AAAA;AAAA,EAGA,cAAc,KAAA,EAAqB;AACjC,IAAA,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,CAAC,MAAA,EAAQ,KAAA,KAAU;AAC5C,MAAA,MAAA,CAAO,eAAA,CAAgB,mBAAA,EAAqB,KAAA,GAAQ,CAAA,IAAK,QAAQ,KAAK,CAAA;AAAA,IACxE,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cAAA,GAAuB;AACrB,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAClC,IAAA,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,IAAA,CAAK,OAAA,EAAS,KAAK,CAAA;AACxC,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,aAAA,EAAe;AACvC,MAAA,IAAA,CAAK,WAAA,CAAY,OAAO,MAAM,CAAA;AAC9B,MAAA,IAAA,CAAK,WAAA,CAAY,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AACnC,MAAA,IAAA,CAAK,iBAAA,CAAkB,OAAO,MAAM,CAAA;AACpC,MAAA,IAAA,CAAK,iBAAA,CAAkB,KAAA,CAAM,MAAA,EAAQ,MAAM,CAAA;AAAA,IAC7C;AACA,IAAA,IAAA,CAAK,OAAA,CAAQ,UAAU,EAAE,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,eAAA,GAAwB;AACtB,IAAA,IAAA,CAAK,gBAAA,EAAiB;AACtB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAC7C,IAAA,IAAA,CAAK,QAAQ,SAAA,CAAU,KAAA,GAAQ,CAAA,GAAI,KAAA,GAAQ,IAAI,CAAC,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,YAAA,GAAqB;AACnB,IAAA,MAAM,SAAS,QAAA,CAAS,aAAA;AACxB,IAAA,IAAI,EAAE,MAAA,YAAkB,WAAA,CAAA,IAAgB,MAAA,KAAW,KAAK,OAAA,EAAS;AACjE,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,EAAG;AACpC,IAAA,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AACpC,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAA,GAA4B;AAC1B,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,aAAA,IAAiB,QAAA,CAAS,kBAAkB,IAAA,CAAK,OAAA;AACxE,IAAA,IAAA,CAAK,aAAA,GAAgB,KAAA;AACrB,IAAA,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAClC,IAAA,IAAA,CAAK,YAAY,SAAA,EAAU;AAC3B,IAAA,IAAA,CAAK,kBAAkB,SAAA,EAAU;AACjC,IAAA,IAAA,CAAK,cAAc,SAAA,EAAU;AAC7B,IAAA,OAAO,SAAA;AAAA,EACT;AAAA;AAAA,EAGA,WAAW,GAAA,EAAqB;AAC9B,IAAA,MAAM,OAAA,GAAU,KAAK,aAAA,CAAc,MAAA;AACnC,IAAA,MAAM,OAAA,GAAU,OAAO,QAAA,CAAS,GAAG,IAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,IAAA,CAAK,SAAA;AAC9D,IAAA,OAAO,IAAA,CAAK,IAAI,OAAA,EAAS,IAAA,CAAK,IAAI,IAAA,CAAK,SAAA,EAAW,OAAO,CAAC,CAAA;AAAA,EAC5D;AAAA;AAAA,EAGA,IAAI,SAAA,GAAoB;AACtB,IAAA,OAAO,IAAA,CAAK,iBAAiB,CAAA,GAAI,CAAA;AAAA,EACnC;AAAA;AAAA,EAGA,eAAe,MAAA,EAA2C;AACxD,IAAA,MAAM,UAA2C,IAAA,CAAK,aAAA;AACtD,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA;AACpC,IAAA,OAAO,KAAA,GAAQ,CAAA,GAAI,IAAA,GAAO,KAAA,GAAQ,CAAA;AAAA,EACpC;AACF","file":"rating_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/**\n * True when a press arrived carrying any modifier, for the keys the browser and\n * the OS own outright: return without calling `preventDefault()` and without\n * moving any state.\n *\n * `Control+Home` and `Control+End` jump the document to its ends, and a widget\n * that swallows them makes the shortcut work or not depending on where focus\n * happens to sit. {@link isReservedArrowChord} answers the same question for the\n * arrows, but returns `false` for every other key so that this one can decide.\n *\n * There is no `allow` list here on purpose. APG assigns no modifier chord to\n * `Home`/`End`, so a widget that wanted one would be claiming a combination the\n * pattern never gave it.\n *\n * @example\n * ```ts\n * case \"Home\":\n * case \"End\":\n * if (hasModifierChord(event)) return;\n * ```\n */\nexport function hasModifierChord(event: KeyboardEvent): boolean {\n return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n}\n","/** One temporarily controlled attribute value and the authored value it displaced. */\ninterface AttributeLeaseRecord {\n readonly original: string | null;\n written: string | null;\n}\n\n/**\n * Temporarily controls one attribute across a changing set of elements.\n *\n * The first write remembers the authored value, including the distinction between\n * an absent attribute and an authored empty string. Returning a lease restores that\n * value only while the attribute still matches the controller's last write. If a\n * consumer changed it in the meantime, the consumer owns the new value and teardown\n * leaves it alone.\n *\n * A `null` write deliberately removes the attribute while retaining the lease. This\n * is useful for derived ARIA whose valid absence is itself controller state, such as\n * an unbounded `aria-valuemin` or a blank spinbutton's `aria-valuenow`.\n *\n * The lease has no lifecycle of its own, so it never subscribes to document events.\n * A consumer that must not let a derived value become the authored baseline of a\n * cached page returns its leases from its own `turbo:before-cache` rewind, where the\n * matching `disconnect()` also releases the subscription.\n */\nexport class AttributeLease<T extends Element = Element> {\n readonly #attribute: string;\n readonly #records = new Map<T, AttributeLeaseRecord>();\n\n /** @param attribute - The attribute whose temporary values this lease owns. */\n constructor(attribute: string) {\n this.#attribute = attribute;\n }\n\n /** Writes or removes the leased attribute while preserving its authored value. */\n write(element: T, value: string | null): void {\n const existing = this.#records.get(element);\n if (existing) {\n existing.written = value;\n } else {\n this.#records.set(element, {\n original: element.getAttribute(this.#attribute),\n written: value,\n });\n }\n\n this.#reflect(element, value);\n }\n\n /** Returns one lease without overwriting a value subsequently authored by a consumer. */\n return(element: T): void {\n const record = this.#records.get(element);\n if (!record) return;\n this.#records.delete(element);\n const stillOwned = element.getAttribute(this.#attribute) === record.written;\n if (stillOwned) this.#reflect(element, record.original);\n }\n\n /** Reflects only a real value transition, avoiding self-triggered mutation work. */\n #reflect(element: T, value: string | null): void {\n if (element.getAttribute(this.#attribute) === value) return;\n if (value === null) element.removeAttribute(this.#attribute);\n else element.setAttribute(this.#attribute, value);\n }\n\n /** Returns every outstanding lease using the same ownership check as {@link return}. */\n returnAll(): void {\n for (const element of Array.from(this.#records.keys())) this.return(element);\n }\n}\n","/**\n * Runs a controller's \"return to the initial state\" pass just before Turbo\n * caches the page.\n *\n * **`disconnect()` cannot do this job, for two independent reasons.** Turbo\n * queues the clone from this event rather than taking it here, and the body swap\n * that runs the controller's `disconnect()` is queued separately — so which of\n * the two lands first is not something a controller can rely on, and a rewind\n * written in `disconnect()` may reach only the DOM being thrown away. In the\n * other direction, `disconnect()` also fires on an in-page move (Stimulus tears\n * down and reconnects the same element), where rewinding would wipe a\n * legitimately in-progress interaction — a spinner mid-load would vanish. One\n * timing is unreliable, the other is too eager; `turbo:before-cache` is the only\n * point that is exactly \"the page is about to be frozen\".\n *\n * Scope is the subscription only: registering on `activate()`, unregistering on\n * `deactivate()`, and one shared document listener no matter how many instances\n * are live. *What* to return to its initial state — which `data-state`, which\n * `hidden`, which `aria-busy` — stays in the controller, because no two\n * consumers answer it the same way.\n *\n * **Rewind state, not appearance.** The pass writes attributes the controller\n * itself owns; the visual result of those attributes is the consumer's CSS, and\n * a library that reached for style or class names would be guessing at markup\n * it does not own.\n *\n * Both entry points are idempotent, so the lifecycle hooks can call them\n * unconditionally: a second `activate()` does not double-subscribe and does not\n * make the callback run twice, and `deactivate()` on an instance that never\n * subscribed is a no-op.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #beforeCache = new BeforeCacheReset(() => this.#rewind());\n *\n * connect() { this.#beforeCache.activate(); }\n * disconnect() { this.#beforeCache.deactivate(); }\n * ```\n */\nexport class BeforeCacheReset {\n /** Every subscribed instance, iterated by the one shared document listener. */\n static readonly #subscribers = new Set<BeforeCacheReset>();\n\n /** The shared listener; installed while at least one instance is subscribed. */\n static readonly #onBeforeCache = (): void => {\n for (const subscriber of BeforeCacheReset.#subscribers) subscriber.#rewind();\n };\n\n readonly #rewind: () => void;\n\n /** @param rewind - the pass that returns this controller's state to its initial form. */\n constructor(rewind: () => void) {\n this.#rewind = rewind;\n }\n\n /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */\n activate(): void {\n const first = BeforeCacheReset.#subscribers.size === 0;\n BeforeCacheReset.#subscribers.add(this);\n if (first) {\n document.addEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n }\n\n /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */\n deactivate(): void {\n BeforeCacheReset.#subscribers.delete(this);\n if (BeforeCacheReset.#subscribers.size > 0) return;\n document.removeEventListener(\"turbo:before-cache\", BeforeCacheReset.#onBeforeCache);\n }\n}\n","/**\n * The hidden form fields a widget keeps in step with the state it owns.\n *\n * A widget whose value lives in ARIA attributes submits nothing on its own, so\n * it mirrors that value into `<input type=\"hidden\">` — one field for a single\n * value, a container of generated fields for a set. The mirror is one-way: the\n * widget's own state stays the source of truth and the field is what the form\n * reads.\n *\n * The write and the report are separate calls because they answer to different\n * rules. A mirror is refreshed on connect, after a morph, and whenever the\n * widget repairs itself — none of which a form should treat as an edit. Only\n * the user's own commit reports one, which is why {@link commitField} is never\n * folded into the writes.\n */\n\n/** How a widget's generated fields identify themselves to the form. */\nexport interface GeneratedFieldOptions {\n /** The `name` every generated input submits under. */\n readonly name: string;\n /** The `form` id, for a widget that sits outside the form it submits to. */\n readonly form?: string;\n}\n\n/**\n * Mirrors one value into a hidden field.\n *\n * @returns Whether the field's value moved.\n *\n * @example\n * ```ts\n * if (writeField(this.fieldTarget, String(value)) && userDriven) {\n * commitField(this.fieldTarget);\n * }\n * ```\n */\nexport function writeField(field: HTMLInputElement, value: string): boolean {\n if (field.value === value) return false;\n field.value = value;\n return true;\n}\n\n/**\n * Mirrors a set of values into `container` as generated hidden fields, in the\n * order given.\n *\n * A container that already submits exactly this set is left untouched, children\n * and all — the mirror is refreshed far more often than it moves (every connect,\n * morph and repair), and a rebuild that changes nothing is still a DOM mutation\n * a consumer's observer and a `:empty` rule can see.\n *\n * @returns Whether the submitted set moved — its values, their order, the\n * `name`, or the `form`.\n */\nexport function writeFields(\n container: HTMLElement,\n values: readonly string[],\n { name, form = \"\" }: GeneratedFieldOptions,\n): boolean {\n const current = [...container.children];\n const submits =\n current.length === values.length &&\n current.every(\n (child, index) =>\n child instanceof HTMLInputElement &&\n child.type === \"hidden\" &&\n child.value === values[index] &&\n child.name === name &&\n (child.getAttribute(\"form\") ?? \"\") === form,\n );\n if (submits) return false;\n\n container.replaceChildren(\n ...values.map((value) => {\n const input = document.createElement(\"input\");\n input.type = \"hidden\";\n input.name = name;\n input.value = value;\n if (form !== \"\") input.setAttribute(\"form\", form);\n return input;\n }),\n );\n return true;\n}\n\n/**\n * Reports a committed value from `target` the way a native form control does:\n * one bubbling, non-cancelable `change`.\n *\n * Call it only for a move the user made. Form-level behaviors listen for this —\n * validation re-checks, auto-submit — and a mirror refreshed by a morph or a\n * repair is not something the user submitted.\n *\n * `target` is the element that carries the value: the hidden field for a single\n * mirrored value, the container for a generated set (whose own inputs are\n * replaced on every write), or a widget's own native control where that is what\n * the form reads.\n */\nexport function commitField(target: HTMLElement): void {\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\n/**\n * Reports an edit a widget made on the user's behalf, the way the browser\n * reports its own: one bubbling `input`, then one bubbling `change`.\n *\n * This is for a widget that owns the stepping or picking its *own* native\n * control would otherwise do — writing `control.value` from script fires\n * nothing, so a form listening for either event never hears the edit. Use\n * {@link commitField} instead where the element is a mirror of state held\n * elsewhere: a hidden field has no native edit to imitate, so it reports the\n * commit alone.\n *\n * Call it only for a move the user made, and only where the widget really did\n * the writing — an edit the browser already reported would be doubled.\n */\nexport function commitEdit(control: HTMLElement): void {\n control.dispatchEvent(new Event(\"input\", { bubbles: true }));\n control.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * 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. A caller\n * that already captured one event-scoped snapshot may pass it to\n * {@link RovingTabindex.setActive}, avoiding a second live-target scan while\n * preserving one coherent collection for the whole operation.\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 * and `items` to reuse an event-scoped collection snapshot.\n */\n setActive(\n index: number,\n options: { focus?: boolean; items?: readonly HTMLElement[] } = {},\n ): void {\n const { focus = false } = options;\n const items = options.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","import { BeforeCacheReset } from \"./before_cache_reset\";\n\n/**\n * Shared bookkeeping for a `tabindex` a controller lends an element temporarily.\n *\n * A controller that must move focus somewhere the author never made focusable\n * (a landmark root, a scroll destination) reaches for the same trick: add a\n * `tabindex` just-in-time and hand it back once it is no longer needed. The\n * borrow is the easy half; the return is what the two conditions below are for.\n *\n * **Returning needs two conditions, not one.** Owning the borrow is not enough:\n * the attribute must also still hold the value this instance wrote. A consumer\n * that changed it afterwards — `tabindex=\"0\"` to make the root its own Tab stop\n * — owns it now, and removing it there silently discards authored markup. The\n * bookkeeping is dropped either way, since the loan is over regardless of who\n * ends up owning the value.\n *\n * **Never borrow over an existing value.** An element that already carries a\n * `tabindex` is the author's to control, so there is nothing to lend and nothing\n * to return.\n *\n * Every live loan also owns a shared `turbo:before-cache` subscription. The loan\n * is returned before Turbo can copy it into a snapshot, so consumers get cache\n * safety without duplicating a lifecycle hook; `returnAll()` removes the\n * subscription again as soon as no loan remains.\n *\n * The registry is keyed by element, so a controller borrowing on a single\n * element (`this.element`) and one borrowing across a changing set of targets\n * use the same API — the single-element case is a set of one. It holds no\n * opinion about *when* to borrow or where focus goes next; that stays in the\n * controller.\n *\n * **The API is deliberately two methods.** This file's own doc block is dropped\n * from `dist`, but every member comment is inlined into **each** consumer entry\n * (`tsup` builds with `splitting: false`), so rationale belongs here, only the\n * contract belongs on the members, and every method no consumer calls is still\n * paid for once per consumer entry.\n *\n * @example\n * ```ts\n * readonly #tabindex = new TabindexLoan();\n *\n * #rescueFocus() {\n * this.#tabindex.lend(this.element);\n * this.element.focus();\n * }\n *\n * disconnect() {\n * this.#tabindex.returnAll();\n * }\n * ```\n */\nexport class TabindexLoan<T extends HTMLElement = HTMLElement> {\n readonly #value: string;\n readonly #lent = new Set<T>();\n /** Returns live loans before Turbo can copy them into its page snapshot. */\n readonly #beforeCache = new BeforeCacheReset(() => this.returnAll());\n\n /**\n * @param value - the `tabindex` to lend. `\"-1\"` (the default) is\n * programmatically focusable but not a Tab stop; `\"0\"` is a real Tab stop,\n * which a scroll region with no focusable content of its own needs.\n */\n constructor(value: string = \"-1\") {\n this.#value = value;\n }\n\n /** Lends `element` the value; no-ops when it already carries a `tabindex`. */\n lend(element: T): void {\n if (element.hasAttribute(\"tabindex\")) return;\n element.setAttribute(\"tabindex\", this.#value);\n this.#lent.add(element);\n // Subscribe only while a real loan exists. Keeping this guarantee here means\n // every consumer is Turbo-safe without another lifecycle hook to remember.\n this.#beforeCache.activate();\n }\n\n /** Takes back every loan whose value is still the one that was lent. */\n returnAll(): void {\n for (const element of this.#lent) {\n if (element.getAttribute(\"tabindex\") === this.#value) element.removeAttribute(\"tabindex\");\n }\n this.#lent.clear();\n this.#beforeCache.deactivate();\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { AttributeLease } from \"../utils/attribute_lease\";\nimport { BeforeCacheReset } from \"../utils/before_cache_reset\";\nimport { commitField, writeField } from \"../utils/field_mirror\";\nimport { isRtl } from \"../utils/logical_scroll\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { RovingTabindex } from \"../utils/roving_tabindex\";\nimport { TabindexLoan } from \"../utils/tabindex_loan\";\n\n/**\n * Headless, accessible rating behavior over an ordinal symbol sequence.\n *\n * Markup contract (identifier: `stimeo--rating`):\n * <div data-controller=\"stimeo--rating\" role=\"radiogroup\" aria-label=\"Rating\"\n * data-stimeo--rating-value-value=\"3\">\n * <span role=\"radio\" aria-checked=\"false\" aria-label=\"1 star\" tabindex=\"-1\"\n * data-stimeo--rating-target=\"symbol\"\n * data-action=\"click->stimeo--rating#select\n * mouseenter->stimeo--rating#preview\n * mouseleave->stimeo--rating#endPreview\n * focus->stimeo--rating#preview\n * blur->stimeo--rating#endPreview\n * keydown->stimeo--rating#onKeydown\">★</span>\n * <!-- later symbols continue the 1..N scale in DOM order -->\n * <input type=\"hidden\" data-stimeo--rating-target=\"field\" />\n * </div>\n *\n * Implements the WAI-ARIA APG **Radio Group** pattern as an ordinal scale. The\n * live DOM order is the sole source of symbol values: the first symbol is 1 and\n * the last is N. Unlike a generic radio group, arrows deliberately clamp rather\n * than wrap because the values have an ordered lower and upper bound.\n *\n * `change` and `reconcile` dispatch `{ value: number }`. A rating the user set\n * also emits a native bubbling `change` from the hidden `field`, the way a form\n * control does, so `stimeo--auto-submit` and form-level validation hear it; a\n * repaint driven by the `value` Value, by a replacement field, or by the\n * controller's own normalization refreshes the mirror silently.\n *\n * @remarks\n * Behavior only — consumers style `[aria-checked]` and `data-rating-hover`. In\n * `readonly` mode the group becomes `role=\"img\"`; the consumer supplies the\n * human-readable accessible name (for example, \"Rated 3 of 5\"). Focus standing on\n * a symbol when that mode begins lands on the root and returns to the Tab stop\n * when it ends, so it is never left on a node outside the accessibility tree.\n *\n * The `value` Value is the page's request. The rating shown is that request\n * normalized to the live scale, and normalizing never writes the Value back: a\n * value clamped by a shorter scale or by `clearable` comes back once the scale\n * or `clearable` allows it again. A value the user sets is written into it.\n *\n * `stimeo--rating:change` is reserved for a user operation that changes the\n * committed value. Once connected, any other move of the rating shown — a\n * `value` written by application code or a morph, a symbol added or removed, a\n * `clearable` change — emits `stimeo--rating:reconcile` once per batch, measured\n * from the rating last shown. Initial reflection emits neither.\n */\nexport class RatingController extends Controller<HTMLElement> {\n static override targets = [\"symbol\", \"field\"];\n static override values = {\n value: { type: Number, default: 0 },\n clearable: { type: Boolean, default: true },\n readonly: { type: Boolean, default: false },\n };\n static actions = [\"endPreview\", \"onKeydown\", \"preview\", \"select\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly symbolTargets: HTMLElement[];\n declare readonly fieldTarget: HTMLInputElement;\n declare readonly hasFieldTarget: boolean;\n declare valueValue: number;\n declare clearableValue: boolean;\n declare readonlyValue: boolean;\n\n readonly #roving = new RovingTabindex(() => this.symbolTargets);\n readonly #rootRole = new AttributeLease<HTMLElement>(\"role\");\n readonly #symbolRole = new AttributeLease<HTMLElement>(\"role\");\n readonly #symbolAriaHidden = new AttributeLease<HTMLElement>(\"aria-hidden\");\n readonly #rootTabindex = new TabindexLoan();\n readonly #repaint = new MicrotaskCoalescer(() => this.#reconcileScale());\n readonly #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());\n #connected = false;\n #rescuedFocus = false;\n\n /** The rating shown last, which the next move is measured from. */\n #shown = 0;\n\n /** Reflects declarative state without announcing an initial user change. */\n override connect(): void {\n this.#repaint.activate();\n this.#beforeCache.activate();\n this.#shown = this.#normalize(this.valueValue);\n this.#apply(this.#shown, { focus: false });\n this.#connected = true;\n }\n\n /** Drops a queued reconciliation and hands every borrowed attribute back. */\n override disconnect(): void {\n this.#connected = false;\n this.#repaint.cancel();\n this.#beforeCache.deactivate();\n this.#releaseReadonly();\n }\n\n /** Removes a runtime-added symbol's authored Tab stop before the batch repaint. */\n symbolTargetConnected(symbol: HTMLElement): void {\n if (this.#connected === false) return;\n symbol.tabIndex = -1;\n this.#repaint.schedule();\n }\n\n /** Releases readonly ownership and reconciles the remaining DOM-ordered scale. */\n symbolTargetDisconnected(symbol: HTMLElement): void {\n this.#symbolRole.return(symbol);\n this.#symbolAriaHidden.return(symbol);\n this.#repaint.schedule();\n }\n\n /** Reflects into a hidden field added or replaced after connection. */\n fieldTargetConnected(): void {\n this.#repaint.schedule();\n }\n\n /** Reconciles after a hidden field is removed or replaced. */\n fieldTargetDisconnected(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code or a Turbo morph changes `value`. */\n valueValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code changes whether value 0 is permitted. */\n clearableValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Repaints when application code enters or leaves the readonly snapshot. */\n readonlyValueChanged(): void {\n this.#repaint.schedule();\n }\n\n /** Selects or clears the clicked symbol. Bound via `data-action` (click). */\n select(event: Event): void {\n if (this.readonlyValue) return;\n const ordinal = this.#symbolOrdinal(event.currentTarget);\n if (ordinal === null) return;\n const current = this.#normalize(this.valueValue);\n this.#commit(this.clearableValue && ordinal === current ? 0 : ordinal, {\n focus: ordinal === current,\n });\n }\n\n /** Previews a fill range on hover or focus without committing it. */\n preview(event: Event): void {\n if (this.readonlyValue) return;\n const ordinal = this.#symbolOrdinal(event.currentTarget);\n if (ordinal !== null) this.#setFillRange(ordinal);\n }\n\n /** Restores the fill range after hover or focus leaves a symbol. */\n endPreview(): void {\n if (this.readonlyValue) return;\n this.#setFillRange(this.#normalize(this.valueValue));\n }\n\n /** Arrow/Home/End/Space/Delete keyboard control, clamped without wrapping. */\n onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented || isReservedArrowChord(event) || this.readonlyValue) return;\n\n const current = this.#normalize(this.valueValue);\n let next: number | null = null;\n const rtl = isRtl(this.element);\n\n // Horizontal keys follow writing direction. Vertical keys express value:\n // ArrowUp is always more and ArrowDown is always less.\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n next = current + (event.key === \"ArrowRight\" && rtl ? -1 : 1);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n next = current - (event.key === \"ArrowLeft\" && rtl ? -1 : 1);\n break;\n case \"Home\":\n next = this.#minValue;\n break;\n case \"End\":\n next = this.symbolTargets.length;\n break;\n case \" \":\n case \"Enter\":\n next = this.#symbolOrdinal(event.currentTarget);\n break;\n case \"Delete\":\n case \"Backspace\":\n // A pointer clears by pressing the committed symbol again. This is the\n // keyboard's way to the same state, kept off Space so that a symbol\n // announced as a radio still answers Space the way a radio does.\n if (!this.clearableValue) return;\n next = 0;\n break;\n default:\n return;\n }\n\n if (next === null) return;\n event.preventDefault();\n this.#commit(next, { focus: true });\n }\n\n /**\n * Repaints one settled target/Value mutation batch and reports a rating shown\n * that moved from the one shown before it.\n *\n * @stimeoRenderRoot\n */\n #reconcileScale(): void {\n const value = this.#normalize(this.valueValue);\n const previous = this.#shown;\n // Settled before the report, so a listener that moves the rating on is\n // measured from the value it was just told about.\n this.#shown = value;\n this.#apply(value, { focus: false });\n if (value !== previous) this.dispatch(\"reconcile\", { detail: { value } });\n }\n\n /**\n * Applies one user operation and emits only when the rating shown changes.\n *\n * The user's value is written into `value` before anything reports it, so a\n * listener of the field's native `change` already reads it there, and the pass\n * that the write starts finds nothing left to report.\n */\n #commit(raw: number, { focus }: { focus: boolean }): void {\n const previous = this.#shown;\n const value = this.#normalize(raw);\n if (!Object.is(this.valueValue, value)) this.valueValue = value;\n this.#shown = value;\n this.#apply(value, { focus, notify: true });\n if (value !== previous) this.dispatch(\"change\", { detail: { value } });\n }\n\n /** Synchronizes ARIA, roving focus, form state, and the visual fill hook. */\n #apply(value: number, { focus, notify = false }: { focus: boolean; notify?: boolean }): void {\n this.symbolTargets.forEach((symbol, index) => {\n symbol.setAttribute(\"aria-checked\", value > 0 && index + 1 === value ? \"true\" : \"false\");\n });\n\n if (this.readonlyValue) {\n this.#applyReadonly();\n } else {\n const returning = this.#releaseReadonly();\n this.#roving.setActive(value > 0 ? value - 1 : 0, { focus: focus || returning });\n }\n\n this.#mirrorField(value, notify);\n this.#setFillRange(value);\n }\n\n /** Mirrors `value` into the optional form field, reporting only a user's move. */\n #mirrorField(value: number, notify: boolean): void {\n if (!this.hasFieldTarget) return;\n if (writeField(this.fieldTarget, String(value)) && notify) commitField(this.fieldTarget);\n }\n\n /** Marks the first `range` symbols with the consumer-owned fill hook. */\n #setFillRange(range: number): void {\n this.symbolTargets.forEach((symbol, index) => {\n symbol.toggleAttribute(\"data-rating-hover\", range > 0 && index < range);\n });\n }\n\n /**\n * Temporarily turns the radiogroup into a non-interactive image snapshot.\n *\n * Each lease is returned before it is taken again, so a value the consumer\n * wrote while readonly becomes the value the lease restores on release. The\n * return is a no-op on an attribute still holding this controller's own\n * write, which is the ordinary case.\n */\n #applyReadonly(): void {\n this.#rescueFocus();\n this.#rootRole.return(this.element);\n this.#rootRole.write(this.element, \"img\");\n for (const symbol of this.symbolTargets) {\n this.#symbolRole.return(symbol);\n this.#symbolRole.write(symbol, null);\n this.#symbolAriaHidden.return(symbol);\n this.#symbolAriaHidden.write(symbol, \"true\");\n }\n this.#roving.setActive(-1);\n }\n\n /**\n * Hands every readonly borrowing back before Turbo clones the page.\n *\n * The snapshot is taken while the controller is still connected, so an\n * element left as `role=\"img\"` with hidden symbols is what a restored page\n * connects against — and that markup would be read as the authored one,\n * leaving no way back to the radiogroup. Rewinding first keeps the cached\n * copy identical to what the consumer wrote.\n */\n #rewindForCache(): void {\n this.#releaseReadonly();\n const value = this.#normalize(this.valueValue);\n this.#roving.setActive(value > 0 ? value - 1 : 0);\n }\n\n /**\n * Lands focus on the root before the symbols leave the accessibility tree.\n *\n * A symbol holding focus when readonly begins would keep it while losing its\n * role and gaining `aria-hidden`, stranding the user on a node no longer in\n * the tree. The root is the one element that survives the transition named:\n * it carries the consumer's accessible name under `role=\"img\"`.\n */\n #rescueFocus(): void {\n const active = document.activeElement;\n if (!(active instanceof HTMLElement) || active === this.element) return;\n if (!this.element.contains(active)) return;\n this.#rootTabindex.lend(this.element);\n this.element.focus();\n this.#rescuedFocus = true;\n }\n\n /**\n * Restores authored roles and visibility after leaving readonly mode.\n *\n * @returns whether focus is standing on the root because {@link #rescueFocus}\n * put it there, and therefore belongs back on the Tab stop.\n */\n #releaseReadonly(): boolean {\n const returning = this.#rescuedFocus && document.activeElement === this.element;\n this.#rescuedFocus = false;\n this.#rootRole.return(this.element);\n this.#symbolRole.returnAll();\n this.#symbolAriaHidden.returnAll();\n this.#rootTabindex.returnAll();\n return returning;\n }\n\n /** Normalizes a raw value to an integer ordinal in the live DOM range. */\n #normalize(raw: number): number {\n const maximum = this.symbolTargets.length;\n const ordinal = Number.isFinite(raw) ? Math.round(raw) : this.#minValue;\n return Math.min(maximum, Math.max(this.#minValue, ordinal));\n }\n\n /** Lowest selectable value: 0 when clearable, otherwise 1. */\n get #minValue(): number {\n return this.clearableValue ? 0 : 1;\n }\n\n /** Returns a target's 1-based position, or null when the action host is invalid. */\n #symbolOrdinal(target: EventTarget | null): number | null {\n const targets: readonly (EventTarget | null)[] = this.symbolTargets;\n const index = targets.indexOf(target);\n return index < 0 ? null : index + 1;\n }\n}\n"]}
@@ -8,7 +8,10 @@ import { Controller } from '@hotwired/stimulus';
8
8
  * <p id="bio" data-stimeo--read-more-target="content" data-state="collapsed">…</p>
9
9
  * <button data-stimeo--read-more-target="trigger"
10
10
  * data-action="click->stimeo--read-more#toggle"
11
- * aria-expanded="false" aria-controls="bio" hidden>Read more</button>
11
+ * aria-expanded="false" aria-controls="bio" hidden>
12
+ * <span data-stimeo--read-more-target="collapsedLabel">Read more</span>
13
+ * <span data-stimeo--read-more-target="expandedLabel" hidden>Read less</span>
14
+ * </button>
12
15
  * </div>
13
16
  *
14
17
  * There is no dedicated APG widget; the toggle borrows the **Disclosure**
@@ -23,6 +26,20 @@ import { Controller } from '@hotwired/stimulus';
23
26
  * content is not actually clamped (it fits), the toggle is `hidden` so no
24
27
  * pointless "read more" is offered. Resizes, content changes, and target
25
28
  * replacement re-evaluate it; hiding a focused toggle waits until blur.
29
+ *
30
+ * The trigger takes an optional **label pair**: where both `collapsedLabel` and
31
+ * `expandedLabel` sit inside it, `hidden` follows the expanded state — the half that
32
+ * belongs to the state is shown and the other hidden, over whatever visibility the
33
+ * markup declares. A half whose counterpart is missing is left as authored, since
34
+ * hiding it would take the trigger's only label with it. The accessible name is the
35
+ * consumer's to write.
36
+ *
37
+ * Each move of the expanded state is reported: `stimeo--read-more:open` and
38
+ * `stimeo--read-more:close` dispatch `{ reason: StateReason }`, after
39
+ * `data-state` and `aria-expanded` are written. Both are informational, so
40
+ * neither is cancelable. {@link toggle} is the only thing that moves the state,
41
+ * so the baseline {@link connect} establishes, the re-evaluation that follows
42
+ * a resize or target churn, and {@link disconnect} say nothing.
26
43
  */
27
44
  declare class ReadMoreController extends Controller<HTMLElement> {
28
45
  #private;
@@ -34,8 +51,11 @@ declare class ReadMoreController extends Controller<HTMLElement> {
34
51
  };
35
52
  };
36
53
  static actions: readonly ["toggle"];
54
+ static events: readonly ["close", "open"];
37
55
  readonly contentTarget: HTMLElement;
38
56
  readonly triggerTarget: HTMLElement;
57
+ readonly expandedLabelTargets: HTMLElement[];
58
+ readonly collapsedLabelTargets: HTMLElement[];
39
59
  readonly hasContentTarget: boolean;
40
60
  readonly hasTriggerTarget: boolean;
41
61
  collapsedValue: boolean;
@@ -45,8 +65,10 @@ declare class ReadMoreController extends Controller<HTMLElement> {
45
65
  contentTargetDisconnected(): void;
46
66
  triggerTargetConnected(): void;
47
67
  triggerTargetDisconnected(trigger: HTMLElement): void;
68
+ expandedLabelTargetConnected(): void;
69
+ collapsedLabelTargetConnected(): void;
48
70
  /** Toggles between the collapsed (clamped) and expanded states. */
49
- toggle(): void;
71
+ toggle(event?: Event): void;
50
72
  }
51
73
 
52
74
  export { ReadMoreController };
@@ -63,10 +63,15 @@ var LayoutObserver = class {
63
63
  #resizeObserverFactory;
64
64
  #resizeObserver = null;
65
65
  #observingViewport = false;
66
+ #loadContainer = null;
66
67
  /** Stable bound handler so add/removeEventListener target the same reference. */
67
68
  #handleViewportResize = () => {
68
69
  this.#callback();
69
70
  };
71
+ /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
72
+ #handleDescendantLoad = () => {
73
+ this.#callback();
74
+ };
70
75
  constructor(callback, options = {}) {
71
76
  this.#callback = callback;
72
77
  this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
@@ -102,24 +107,97 @@ var LayoutObserver = class {
102
107
  window.removeEventListener("resize", this.#handleViewportResize);
103
108
  }
104
109
  /**
105
- * Releases every observation: disconnects the {@link ResizeObserver} and
106
- * removes the viewport listener. Safe to call multiple times. Call this from a
107
- * controller's `disconnect()`.
110
+ * Starts reporting a `load` from anywhere inside `container` — an image or a
111
+ * frame settling changes the box it sits in, and it measures as zero high until
112
+ * then. `load` does not bubble, so the subscription is a capture-phase listener
113
+ * on the container itself and nothing the caller spells.
114
+ *
115
+ * **One container at a time.** A further call moves the observation, so a widget
116
+ * whose content element is swapped at runtime releases the element it let go by
117
+ * naming the new one — there is no second place for the release to drift from.
118
+ */
119
+ observeDescendantLoads(container) {
120
+ this.unobserveDescendantLoads();
121
+ this.#loadContainer = container;
122
+ container.addEventListener("load", this.#handleDescendantLoad, true);
123
+ }
124
+ /** Stops reporting descendant loads without affecting element or viewport observation. */
125
+ unobserveDescendantLoads() {
126
+ this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
127
+ this.#loadContainer = null;
128
+ }
129
+ /**
130
+ * Releases every observation: disconnects the {@link ResizeObserver} and removes
131
+ * the viewport and descendant-load listeners. Safe to call multiple times. Call
132
+ * this from a controller's `disconnect()`.
108
133
  */
109
134
  disconnect() {
110
135
  this.#resizeObserver?.disconnect();
111
136
  this.#resizeObserver = null;
112
137
  this.unobserveViewport();
138
+ this.unobserveDescendantLoads();
139
+ }
140
+ };
141
+
142
+ // src/utils/state_reason.ts
143
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
144
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
145
+ function stateReasonFor(event) {
146
+ if (!event) return "api";
147
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
148
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
149
+ return "user";
150
+ }
151
+
152
+ // src/utils/state_regions.ts
153
+ var StateRegions = class {
154
+ #whenTrue;
155
+ #whenFalse;
156
+ #taken = /* @__PURE__ */ new WeakSet();
157
+ constructor(options) {
158
+ this.#whenTrue = options.whenTrue;
159
+ this.#whenFalse = options.whenFalse ?? null;
160
+ }
161
+ /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
162
+ reflect(host, isTrue) {
163
+ const shown = this.#inside(host, this.#whenTrue());
164
+ if (!this.#whenFalse) {
165
+ for (const region of shown) this.#write(region, !isTrue);
166
+ return;
167
+ }
168
+ const hiddenSide = this.#inside(host, this.#whenFalse());
169
+ if (shown.length === 0 || hiddenSide.length === 0) {
170
+ for (const region of shown) this.#give(region);
171
+ for (const region of hiddenSide) this.#give(region);
172
+ return;
173
+ }
174
+ for (const region of shown) this.#write(region, !isTrue);
175
+ for (const region of hiddenSide) this.#write(region, isTrue);
176
+ }
177
+ /** Writes `hidden` where it moves, noting which regions it takes out of view. */
178
+ #write(region, hidden) {
179
+ if (hidden) this.#taken.add(region);
180
+ else this.#taken.delete(region);
181
+ if (region.hidden !== hidden) region.hidden = hidden;
182
+ }
183
+ /** Returns a region this instance hid; one it never hid keeps what it carries. */
184
+ #give(region) {
185
+ if (this.#taken.delete(region)) region.hidden = false;
186
+ }
187
+ /** The declared regions that sit within `host`, which may be the host itself. */
188
+ #inside(host, regions) {
189
+ return regions.filter((region) => host.contains(region));
113
190
  }
114
191
  };
115
192
 
116
193
  // src/controllers/read_more_controller.ts
117
194
  var ReadMoreController = class extends Controller {
118
- static targets = ["content", "trigger"];
195
+ static targets = ["content", "trigger", "expandedLabel", "collapsedLabel"];
119
196
  static values = {
120
197
  collapsed: { type: Boolean, default: true }
121
198
  };
122
199
  static actions = ["toggle"];
200
+ static events = ["close", "open"];
123
201
  #connected = false;
124
202
  #collapsed = true;
125
203
  #observedContent = null;
@@ -128,6 +206,11 @@ var ReadMoreController = class extends Controller {
128
206
  if (this.#connected) this.#evaluateOverflow();
129
207
  };
130
208
  #layout = new LayoutObserver(this.#update);
209
+ /** Owns `hidden` on the trigger's two labels. */
210
+ #labels = new StateRegions({
211
+ whenTrue: () => this.expandedLabelTargets,
212
+ whenFalse: () => this.collapsedLabelTargets
213
+ });
131
214
  /** Holds the trigger's hide back while it has focus; re-evaluates on blur. */
132
215
  #deferredHide = new BlurDeferral(() => {
133
216
  this.#update();
@@ -155,12 +238,21 @@ var ReadMoreController = class extends Controller {
155
238
  this.#deferredHide.release(trigger);
156
239
  this.#syncTargets();
157
240
  }
241
+ expandedLabelTargetConnected() {
242
+ this.#syncTargets();
243
+ }
244
+ collapsedLabelTargetConnected() {
245
+ this.#syncTargets();
246
+ }
158
247
  /** Toggles between the collapsed (clamped) and expanded states. */
159
- toggle() {
248
+ toggle(event) {
160
249
  if (!this.#connected) return;
161
250
  this.#collapsed = !this.#collapsed;
162
251
  this.#reflect();
163
252
  this.#evaluateOverflow();
253
+ const detail = { reason: stateReasonFor(event) };
254
+ if (this.#collapsed) this.dispatch("close", { detail, cancelable: false });
255
+ else this.dispatch("open", { detail, cancelable: false });
164
256
  }
165
257
  #initialCollapsed() {
166
258
  if (this.hasContentTarget) {
@@ -176,6 +268,7 @@ var ReadMoreController = class extends Controller {
176
268
  }
177
269
  if (this.hasTriggerTarget) {
178
270
  this.triggerTarget.setAttribute("aria-expanded", this.#collapsed ? "false" : "true");
271
+ this.#labels.reflect(this.triggerTarget, !this.#collapsed);
179
272
  }
180
273
  }
181
274
  #syncTargets() {
@@ -192,7 +285,7 @@ var ReadMoreController = class extends Controller {
192
285
  this.#observedContent = next;
193
286
  this.#layout.observe(next);
194
287
  this.#layout.observeViewport();
195
- next.addEventListener("load", this.#update, true);
288
+ this.#layout.observeDescendantLoads(next);
196
289
  if (typeof MutationObserver !== "undefined") {
197
290
  this.#contentMutationObserver = new MutationObserver(this.#update);
198
291
  this.#contentMutationObserver.observe(next, {
@@ -206,7 +299,7 @@ var ReadMoreController = class extends Controller {
206
299
  this.#deferredHide.releaseAll();
207
300
  if (this.#observedContent) {
208
301
  this.#layout.unobserve(this.#observedContent);
209
- this.#observedContent.removeEventListener("load", this.#update, true);
302
+ this.#layout.unobserveDescendantLoads();
210
303
  }
211
304
  this.#observedContent = null;
212
305
  this.#contentMutationObserver?.disconnect();