stimeo-ui 0.15.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 (267) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +120 -0
  3. package/dist/cable/index.js +63 -15
  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 +8 -1
  12. package/dist/controllers/announcer_controller.js.map +1 -1
  13. package/dist/controllers/auto_submit_controller.js +43 -2
  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 +9 -8
  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 +85 -23
  24. package/dist/controllers/calendar_controller.js +323 -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 +54 -10
  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 +56 -11
  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/countdown_controller.d.ts +30 -1
  57. package/dist/controllers/countdown_controller.js +129 -26
  58. package/dist/controllers/countdown_controller.js.map +1 -1
  59. package/dist/controllers/currency_input_controller.d.ts +74 -15
  60. package/dist/controllers/currency_input_controller.js +205 -58
  61. package/dist/controllers/currency_input_controller.js.map +1 -1
  62. package/dist/controllers/data_grid_controller.d.ts +63 -18
  63. package/dist/controllers/data_grid_controller.js +195 -29
  64. package/dist/controllers/data_grid_controller.js.map +1 -1
  65. package/dist/controllers/date_range_picker_controller.d.ts +34 -6
  66. package/dist/controllers/date_range_picker_controller.js +139 -27
  67. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  68. package/dist/controllers/dialog_controller.d.ts +10 -3
  69. package/dist/controllers/dialog_controller.js +35 -8
  70. package/dist/controllers/dialog_controller.js.map +1 -1
  71. package/dist/controllers/direct_upload_controller.js +22 -4
  72. package/dist/controllers/direct_upload_controller.js.map +1 -1
  73. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  74. package/dist/controllers/dirty_form_controller.js +14 -1
  75. package/dist/controllers/dirty_form_controller.js.map +1 -1
  76. package/dist/controllers/dismissible_controller.js +1 -0
  77. package/dist/controllers/dismissible_controller.js.map +1 -1
  78. package/dist/controllers/drawer_controller.d.ts +18 -10
  79. package/dist/controllers/drawer_controller.js +54 -19
  80. package/dist/controllers/drawer_controller.js.map +1 -1
  81. package/dist/controllers/dropdown_controller.d.ts +9 -3
  82. package/dist/controllers/dropdown_controller.js +36 -9
  83. package/dist/controllers/dropdown_controller.js.map +1 -1
  84. package/dist/controllers/editable_controller.js +34 -0
  85. package/dist/controllers/editable_controller.js.map +1 -1
  86. package/dist/controllers/file_dropzone_controller.js +144 -51
  87. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  88. package/dist/controllers/filter_controller.d.ts +10 -4
  89. package/dist/controllers/filter_controller.js +20 -6
  90. package/dist/controllers/filter_controller.js.map +1 -1
  91. package/dist/controllers/flash_controller.d.ts +26 -6
  92. package/dist/controllers/flash_controller.js +305 -46
  93. package/dist/controllers/flash_controller.js.map +1 -1
  94. package/dist/controllers/focus_controller.js +1 -0
  95. package/dist/controllers/focus_controller.js.map +1 -1
  96. package/dist/controllers/form_field_controller.js +7 -5
  97. package/dist/controllers/form_field_controller.js.map +1 -1
  98. package/dist/controllers/form_validation_controller.js +19 -13
  99. package/dist/controllers/form_validation_controller.js.map +1 -1
  100. package/dist/controllers/frame_loading_controller.js +16 -2
  101. package/dist/controllers/frame_loading_controller.js.map +1 -1
  102. package/dist/controllers/highlight_controller.js +19 -2
  103. package/dist/controllers/highlight_controller.js.map +1 -1
  104. package/dist/controllers/hover_card_controller.d.ts +10 -2
  105. package/dist/controllers/hover_card_controller.js +40 -14
  106. package/dist/controllers/hover_card_controller.js.map +1 -1
  107. package/dist/controllers/idle_controller.d.ts +16 -3
  108. package/dist/controllers/idle_controller.js +90 -5
  109. package/dist/controllers/idle_controller.js.map +1 -1
  110. package/dist/controllers/input_mask_controller.d.ts +5 -2
  111. package/dist/controllers/input_mask_controller.js +65 -9
  112. package/dist/controllers/input_mask_controller.js.map +1 -1
  113. package/dist/controllers/intersection_controller.js +3 -0
  114. package/dist/controllers/intersection_controller.js.map +1 -1
  115. package/dist/controllers/lazy_frame_controller.js +11 -2
  116. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  117. package/dist/controllers/listbox_controller.d.ts +50 -8
  118. package/dist/controllers/listbox_controller.js +203 -45
  119. package/dist/controllers/listbox_controller.js.map +1 -1
  120. package/dist/controllers/masonry_controller.js +0 -2
  121. package/dist/controllers/masonry_controller.js.map +1 -1
  122. package/dist/controllers/menu_controller.d.ts +9 -3
  123. package/dist/controllers/menu_controller.js +45 -16
  124. package/dist/controllers/menu_controller.js.map +1 -1
  125. package/dist/controllers/menubar_controller.d.ts +11 -0
  126. package/dist/controllers/menubar_controller.js +58 -24
  127. package/dist/controllers/menubar_controller.js.map +1 -1
  128. package/dist/controllers/meter_controller.js +9 -5
  129. package/dist/controllers/meter_controller.js.map +1 -1
  130. package/dist/controllers/multi_select_controller.d.ts +18 -3
  131. package/dist/controllers/multi_select_controller.js +278 -104
  132. package/dist/controllers/multi_select_controller.js.map +1 -1
  133. package/dist/controllers/navigation_menu_controller.d.ts +11 -0
  134. package/dist/controllers/navigation_menu_controller.js +48 -15
  135. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  136. package/dist/controllers/nested_form_controller.js +37 -8
  137. package/dist/controllers/nested_form_controller.js.map +1 -1
  138. package/dist/controllers/network_status_controller.js +9 -1
  139. package/dist/controllers/network_status_controller.js.map +1 -1
  140. package/dist/controllers/number_input_controller.d.ts +36 -8
  141. package/dist/controllers/number_input_controller.js +124 -21
  142. package/dist/controllers/number_input_controller.js.map +1 -1
  143. package/dist/controllers/optimistic_controller.js +14 -1
  144. package/dist/controllers/optimistic_controller.js.map +1 -1
  145. package/dist/controllers/otp_controller.d.ts +22 -7
  146. package/dist/controllers/otp_controller.js +198 -55
  147. package/dist/controllers/otp_controller.js.map +1 -1
  148. package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
  149. package/dist/controllers/overflow_indicator_controller.js +84 -13
  150. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  151. package/dist/controllers/overflow_menu_controller.d.ts +26 -6
  152. package/dist/controllers/overflow_menu_controller.js +112 -41
  153. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  154. package/dist/controllers/pagination_controller.d.ts +19 -11
  155. package/dist/controllers/pagination_controller.js +74 -28
  156. package/dist/controllers/pagination_controller.js.map +1 -1
  157. package/dist/controllers/password_reveal_controller.d.ts +15 -1
  158. package/dist/controllers/password_reveal_controller.js +59 -2
  159. package/dist/controllers/password_reveal_controller.js.map +1 -1
  160. package/dist/controllers/persist_controller.js +30 -8
  161. package/dist/controllers/persist_controller.js.map +1 -1
  162. package/dist/controllers/pointer_drag_controller.js +131 -52
  163. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  164. package/dist/controllers/popover_controller.d.ts +9 -3
  165. package/dist/controllers/popover_controller.js +45 -11
  166. package/dist/controllers/popover_controller.js.map +1 -1
  167. package/dist/controllers/portal_controller.d.ts +1 -1
  168. package/dist/controllers/portal_controller.js +6 -2
  169. package/dist/controllers/portal_controller.js.map +1 -1
  170. package/dist/controllers/preview_guard_controller.js +16 -1
  171. package/dist/controllers/preview_guard_controller.js.map +1 -1
  172. package/dist/controllers/progress_controller.js +8 -4
  173. package/dist/controllers/progress_controller.js.map +1 -1
  174. package/dist/controllers/radio_group_controller.d.ts +6 -4
  175. package/dist/controllers/radio_group_controller.js +42 -17
  176. package/dist/controllers/radio_group_controller.js.map +1 -1
  177. package/dist/controllers/range_slider_controller.d.ts +49 -1
  178. package/dist/controllers/range_slider_controller.js +88 -42
  179. package/dist/controllers/range_slider_controller.js.map +1 -1
  180. package/dist/controllers/rating_controller.d.ts +14 -3
  181. package/dist/controllers/rating_controller.js +39 -15
  182. package/dist/controllers/rating_controller.js.map +1 -1
  183. package/dist/controllers/read_more_controller.d.ts +24 -2
  184. package/dist/controllers/read_more_controller.js +69 -2
  185. package/dist/controllers/read_more_controller.js.map +1 -1
  186. package/dist/controllers/resizable_controller.d.ts +16 -2
  187. package/dist/controllers/resizable_controller.js +82 -22
  188. package/dist/controllers/resizable_controller.js.map +1 -1
  189. package/dist/controllers/scroll_area_controller.js +5 -1
  190. package/dist/controllers/scroll_area_controller.js.map +1 -1
  191. package/dist/controllers/scroll_visibility_controller.js +0 -1
  192. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  193. package/dist/controllers/scrollspy_controller.d.ts +3 -2
  194. package/dist/controllers/scrollspy_controller.js +6 -0
  195. package/dist/controllers/scrollspy_controller.js.map +1 -1
  196. package/dist/controllers/separator_controller.d.ts +41 -14
  197. package/dist/controllers/separator_controller.js +66 -37
  198. package/dist/controllers/separator_controller.js.map +1 -1
  199. package/dist/controllers/sidebar_controller.d.ts +20 -3
  200. package/dist/controllers/sidebar_controller.js +77 -18
  201. package/dist/controllers/sidebar_controller.js.map +1 -1
  202. package/dist/controllers/skeleton_controller.js +6 -1
  203. package/dist/controllers/skeleton_controller.js.map +1 -1
  204. package/dist/controllers/slider_controller.d.ts +45 -7
  205. package/dist/controllers/slider_controller.js +82 -47
  206. package/dist/controllers/slider_controller.js.map +1 -1
  207. package/dist/controllers/smart_sticky_header_controller.js +11 -1
  208. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  209. package/dist/controllers/sortable_controller.js +17 -2
  210. package/dist/controllers/sortable_controller.js.map +1 -1
  211. package/dist/controllers/spinner_controller.js +10 -2
  212. package/dist/controllers/spinner_controller.js.map +1 -1
  213. package/dist/controllers/step_indicator_controller.d.ts +19 -17
  214. package/dist/controllers/step_indicator_controller.js +18 -17
  215. package/dist/controllers/step_indicator_controller.js.map +1 -1
  216. package/dist/controllers/stepper_controller.d.ts +34 -9
  217. package/dist/controllers/stepper_controller.js +101 -19
  218. package/dist/controllers/stepper_controller.js.map +1 -1
  219. package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
  220. package/dist/controllers/stick_to_bottom_controller.js +75 -5
  221. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  222. package/dist/controllers/submit_once_controller.d.ts +3 -2
  223. package/dist/controllers/submit_once_controller.js +16 -3
  224. package/dist/controllers/submit_once_controller.js.map +1 -1
  225. package/dist/controllers/switch_controller.d.ts +29 -7
  226. package/dist/controllers/switch_controller.js +101 -10
  227. package/dist/controllers/switch_controller.js.map +1 -1
  228. package/dist/controllers/tabs_controller.d.ts +12 -0
  229. package/dist/controllers/tabs_controller.js +21 -2
  230. package/dist/controllers/tabs_controller.js.map +1 -1
  231. package/dist/controllers/tags_input_controller.d.ts +15 -3
  232. package/dist/controllers/tags_input_controller.js +209 -59
  233. package/dist/controllers/tags_input_controller.js.map +1 -1
  234. package/dist/controllers/theme_controller.d.ts +20 -3
  235. package/dist/controllers/theme_controller.js +64 -14
  236. package/dist/controllers/theme_controller.js.map +1 -1
  237. package/dist/controllers/time_picker_controller.js +23 -8
  238. package/dist/controllers/time_picker_controller.js.map +1 -1
  239. package/dist/controllers/toast_controller.d.ts +52 -15
  240. package/dist/controllers/toast_controller.js +337 -54
  241. package/dist/controllers/toast_controller.js.map +1 -1
  242. package/dist/controllers/toggle_group_controller.d.ts +49 -7
  243. package/dist/controllers/toggle_group_controller.js +159 -23
  244. package/dist/controllers/toggle_group_controller.js.map +1 -1
  245. package/dist/controllers/toolbar_controller.js +32 -0
  246. package/dist/controllers/toolbar_controller.js.map +1 -1
  247. package/dist/controllers/tooltip_controller.d.ts +8 -0
  248. package/dist/controllers/tooltip_controller.js +39 -13
  249. package/dist/controllers/tooltip_controller.js.map +1 -1
  250. package/dist/controllers/transition_controller.js +4 -0
  251. package/dist/controllers/transition_controller.js.map +1 -1
  252. package/dist/controllers/tree_view_controller.d.ts +39 -8
  253. package/dist/controllers/tree_view_controller.js +169 -16
  254. package/dist/controllers/tree_view_controller.js.map +1 -1
  255. package/dist/index.d.ts +28 -1
  256. package/dist/index.js +4588 -1535
  257. package/dist/index.js.map +1 -1
  258. package/dist/inspector/cli.d.ts +72 -6
  259. package/dist/inspector/cli.js +262 -51
  260. package/dist/inspector/cli.js.map +1 -1
  261. package/dist/inspector/cli_bin.js +309 -51
  262. package/dist/inspector/cli_bin.js.map +1 -1
  263. package/dist/inspector/examples.json +40 -40
  264. package/dist/inspector/manifest.json +525 -59
  265. package/dist/positioning/index.js +2 -0
  266. package/dist/positioning/index.js.map +1 -1
  267. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/interactive_host.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/data_grid_controller.ts"],"names":[],"mappings":"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","file":"data_grid_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","/**\n * Native interactive elements whose built-in activation must not be replaced\n * wholesale by a controller. Deliberately excludes `[tabindex]`: a generic\n * element made focusable is the supported host for several headless patterns.\n * Contenteditable is handled by {@link isInteractiveHost} because its state is\n * inherited and its keywords are ASCII-case-insensitive.\n */\nexport const INTERACTIVE_HOST_SELECTOR =\n \"button, input, select, textarea, label, a[href], area[href], summary, details, audio[controls], video[controls], iframe, object, embed\";\n\n/**\n * Whether an element owns native activation or an inherited editing surface.\n *\n * The `contenteditable` missing and invalid states inherit. Walking explicitly\n * also keeps `contenteditable=\"false\"` as a real boundary inside an editable\n * ancestor instead of treating any ancestor attribute as decisive.\n */\nexport function isInteractiveHost(element: HTMLElement): boolean {\n if (element.matches(INTERACTIVE_HOST_SELECTOR)) return true;\n\n let current: HTMLElement | null = element;\n while (current) {\n const raw = current.getAttribute(\"contenteditable\");\n if (raw !== null) {\n const value = raw.trim().toLowerCase();\n if (value === \"false\") return false;\n if (value === \"\" || value === \"true\" || value === \"plaintext-only\") return true;\n }\n current = current.parentElement;\n }\n return false;\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","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { INTERACTIVE_HOST_SELECTOR, isInteractiveHost } from \"../utils/interactive_host\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** Cycle order for a sortable column header's `aria-sort`. */\nconst SORT_CYCLE = [\"none\", \"ascending\", \"descending\"] as const;\ntype SortDirection = (typeof SORT_CYCLE)[number];\n\n/**\n * Returns the next `aria-sort` direction in the cycle after `current`. Unknown or\n * ARIA-only values (e.g. `\"other\"`, or an empty/missing attribute) are treated as\n * `\"none\"`, so the first activation always advances to `\"ascending\"` instead of\n * stalling.\n */\nfunction nextSortDirection(current: string): SortDirection {\n const index = SORT_CYCLE.indexOf(current as SortDirection);\n const from = index < 0 ? 0 : index;\n return SORT_CYCLE[(from + 1) % SORT_CYCLE.length] ?? \"ascending\";\n}\n\n/**\n * Headless, accessible **Data Grid** behavior: column sorting, row selection, and\n * roving keyboard navigation over an interactive table.\n *\n * Markup contract (identifier: `stimeo--data-grid`):\n * <table data-controller=\"stimeo--data-grid\" role=\"grid\" aria-label=\"Users\"\n * data-stimeo--data-grid-selection-value=\"single\">\n * <thead><tr role=\"row\">\n * <th role=\"columnheader\" aria-sort=\"none\" tabindex=\"-1\"\n * data-stimeo--data-grid-target=\"columnHeader\"\n * data-action=\"click->stimeo--data-grid#sort\n * keydown->stimeo--data-grid#onKeydown\">Name</th>\n * </tr></thead>\n * <tbody><tr role=\"row\" aria-selected=\"false\"\n * data-stimeo--data-grid-target=\"row\">\n * <td role=\"gridcell\" tabindex=\"0\" data-stimeo--data-grid-target=\"cell\"\n * data-action=\"keydown->stimeo--data-grid#onKeydown\">Jane</td>\n * </tr></tbody>\n * </table>\n *\n * Implements the WAI-ARIA APG **Grid** pattern plus `aria-sort`. The whole grid is\n * a single Tab stop (roving `tabindex`: exactly one cell/header is `0`, the rest\n * `-1`); arrow keys move both DOM focus and that tabbable position. Sort state is\n * exposed via `aria-sort` on headers, selection via `aria-selected` on rows.\n *\n * `selectionchange` dispatches `{ rows: HTMLElement[] }`; `sort` dispatches\n * `{ column: HTMLElement, direction: \"ascending\" | \"descending\" | \"none\" }`.\n *\n * @remarks\n * Behavior only — the consumer performs the actual data sort/render in response to\n * the `sort` event and owns all styling. No timers or observers are held, so there\n * is nothing to leak across Turbo navigations; `connect()` rebuilds the single tab\n * stop idempotently from the DOM, and the target callbacks rebuild it again after\n * rows or cells are added or removed at runtime.\n *\n * Behavior provided:\n * - `Arrow*` move between cells (clamped at edges); `Home`/`End` to the row's\n * first/last cell; `Ctrl+Home`/`Ctrl+End` to the grid's first/last cell.\n * - `Enter`/`Space` cycles a header's sort (`none→ascending→descending`) or toggles\n * the focused row's selection when selection is enabled.\n *\n * Consumer contract — controls nested inside a cell or header:\n * - APG's grid hosts working controls in its cells, so a keystroke or click that\n * reached one — a nested button, link or form control, an editable host, or any\n * widget that already called `preventDefault()` — is left to it entirely: no\n * move, no sort, no selection. The one control that hands the event on is a\n * sortable header's own `<button>`, whose activation is what the click carries.\n * - A control keeps its own Tab behavior, so give it `tabindex=\"-1\"` to preserve\n * the grid's single Tab stop. A sortable `columnheader` hosting a `<button>` is\n * the common case: the roving position stays on the header, and the button's\n * own activation reaches `sort` through the click that bubbles to it.\n * - Host `role=\"gridcell\"` / `role=\"columnheader\"` on a non-interactive element\n * (`td`, `th`); an interactive host makes the grid stand down on that cell.\n */\nexport class DataGridController extends Controller<HTMLElement> {\n static override targets = [\"columnHeader\", \"row\", \"cell\"];\n static override values = {\n selection: { type: String, default: \"none\" },\n };\n static actions = [\"onKeydown\", \"sort\", \"toggleSelect\"] as const;\n static events = [\"selectionchange\", \"sort\"] as const;\n\n declare readonly columnHeaderTargets: HTMLElement[];\n declare readonly rowTargets: HTMLElement[];\n declare readonly cellTargets: HTMLElement[];\n declare selectionValue: string;\n\n /**\n * Collapses the per-element target callbacks of one DOM mutation into a single\n * baseline pass, and refuses to run before `connect()` or after `disconnect()`.\n *\n * Stimulus reports every target one at a time, so an ungated pass would re-walk\n * the whole grid once per authored cell on mount and once per streamed cell\n * afterwards — quadratic in the cell count both times.\n */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#restoreBaseline());\n\n /**\n * Establishes a single tab stop across all navigable cells/headers and brings\n * the rows to their baseline.\n *\n * Normalizing here rather than leaving it to {@link selectionValueChanged}\n * guarantees exactly one pass per mount: a re-attached element reuses its\n * cached Stimulus context, whose value observer already knows the `selection`\n * attribute, so the Value callback does not fire a second time.\n */\n override connect(): void {\n this.#restoreBaseline();\n this.#reconcile.activate();\n }\n\n /** Closes the reconcile window so a queued pass cannot run against a detached tree. */\n override disconnect(): void {\n this.#reconcile.cancel();\n }\n\n /**\n * Rebuilds both DOM-owned baselines from the live grid: exactly one navigable\n * cell is in the Tab sequence, and every selectable row carries an explicit\n * `aria-selected`.\n *\n * The tab stop keeps whichever cell already holds it, so a rebuild triggered by\n * an unrelated row arriving does not throw the user's position away; only when\n * no cell holds it — the grid is fresh, or the holder was removed — does the\n * first navigable cell take over. Without that fallback a grid whose active row\n * is removed keeps every cell at `-1` and drops out of the Tab sequence\n * entirely.\n */\n #restoreBaseline(): void {\n const cells = this.#navigableCells();\n const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];\n if (active) this.#setActiveCell(active, { focus: false }, cells);\n this.#normalizeSelection();\n }\n\n /**\n * Keeps `aria-multiselectable` in step with the `selection` Value. Fires on connect\n * (so it self-heals after a Turbo morph) and on any runtime change, so the ARIA\n * never drifts from the selection logic, which reads `selectionValue` live.\n */\n selectionValueChanged(): void {\n this.#syncSelectable();\n this.#normalizeSelection();\n }\n\n /** Re-establishes the baselines for a row added after connect. */\n rowTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a cell joins the grid after connect. */\n cellTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a cell leaves the grid. */\n cellTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a header joins the grid after connect. */\n columnHeaderTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a header leaves the grid. */\n columnHeaderTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /**\n * Brings the authored rows to the shape the APG requires, without changing\n * which rows the author chose.\n *\n * Every selectable row gets an explicit value — an absent `aria-selected` means\n * \"not selectable\" in ARIA, so a forgotten attribute hides a selectable row —\n * and a single-select grid keeps at most one `true`, first in DOM order.\n * A grid that declares `selection=\"none\"` has no selectable rows, so the\n * attribute is removed rather than written: in ARIA its absence is what \"not\n * selectable\" looks like.\n */\n #normalizeSelection(): void {\n const rows = this.rowTargets;\n if (this.selectionValue === \"none\") {\n // Reclaim, do not merely skip: a grid that *became* unselectable would\n // otherwise keep announcing rows as selected while the logic refuses to\n // change them. In ARIA the absence of the attribute is what \"not\n // selectable\" looks like, so the rows have to lose it outright.\n for (const row of rows) row.removeAttribute(\"aria-selected\");\n return;\n }\n const single = this.selectionValue === \"single\";\n const first = rows.find((row) => row.getAttribute(\"aria-selected\") === \"true\");\n for (const row of rows) {\n if (single) {\n row.setAttribute(\"aria-selected\", row === first ? \"true\" : \"false\");\n } else if (row.getAttribute(\"aria-selected\") !== \"true\") {\n row.setAttribute(\"aria-selected\", \"false\");\n }\n }\n }\n\n /**\n * Mirrors `selection=\"multiple\"` onto `aria-multiselectable` (APG Grid) so SRs\n * announce that more than one row can be selected; cleared for single/none so a\n * grid never carries a misleading attribute.\n */\n #syncSelectable(): void {\n if (this.selectionValue === \"multiple\") {\n this.element.setAttribute(\"aria-multiselectable\", \"true\");\n } else {\n this.element.removeAttribute(\"aria-multiselectable\");\n }\n }\n\n /** Cycles the activated column header's sort and emits `sort`. */\n sort(event: Event): void {\n const header = event.currentTarget as HTMLElement;\n if (!this.columnHeaderTargets.includes(header)) return;\n if (event.defaultPrevented) return;\n // A sortable header hosts a `<button>`, and that button's activation is\n // exactly what this click carries, so it is the one control that does not\n // take the event away. A link or a field inside the header is its own\n // destination, and sorting on its click would act in parallel.\n const control = this.#claimingControl(event, header);\n if (control && !(control instanceof HTMLButtonElement)) return;\n\n const direction = nextSortDirection(header.getAttribute(\"aria-sort\") ?? \"none\");\n\n // Only one column is sorted at a time: reset the others to `none`.\n for (const other of this.columnHeaderTargets) {\n other.setAttribute(\"aria-sort\", other === header ? direction : \"none\");\n }\n\n this.#setActiveCell(header, { focus: false });\n this.dispatch(\"sort\", { detail: { column: header, direction } });\n }\n\n /** Toggles selection of the row owning the event target. Bound optionally. */\n toggleSelect(event: Event): void {\n // The pointer path guards on `selection=\"none\"` exactly as the keyboard path\n // does, so a grid that declares itself unselectable never grows selected rows.\n if (this.selectionValue === \"none\") return;\n // A widget that handled the click owns it, exactly as the keyboard path\n // stands down on a keystroke a descendant consumed.\n if (event.defaultPrevented) return;\n const host = event.currentTarget as HTMLElement;\n if (this.#claimedByDescendant(event, host)) return;\n const row = host.closest<HTMLElement>(\"[role='row']\");\n if (row && this.rowTargets.includes(row)) this.#toggleRow(row);\n }\n\n /** Grid navigation + sort/select activation. Bound to cells and headers. */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (event.isComposing) return;\n const cell = event.currentTarget as HTMLElement;\n // A native control inside the cell never calls `preventDefault()` — its\n // activation IS the default action — so the yield above cannot see it. This\n // recognises those by element shape instead.\n if (this.#claimedByDescendant(event, cell)) return;\n const matrix = this.#matrix();\n const position = this.#locate(matrix, cell);\n if (!position) return;\n const [row, col] = position;\n const rowCells = matrix[row] ?? [];\n\n let target: HTMLElement | undefined;\n // Logical, not physical. The key is normalised rather than the\n // delta negated: these two branches are not mirror images — their guards\n // differ — so swapping the key keeps each guard with its own direction.\n switch (logicalArrowKey(event.key, this.element)) {\n case \"ArrowRight\":\n target = this.#cellInRow(matrix, row, col + 1);\n break;\n case \"ArrowLeft\":\n target = this.#cellInRow(matrix, row, Math.max(col - 1, 0));\n break;\n case \"ArrowDown\":\n target = this.#cellInRow(matrix, Math.min(row + 1, matrix.length - 1), col);\n break;\n case \"ArrowUp\":\n target = this.#cellInRow(matrix, Math.max(row - 1, 0), col);\n break;\n case \"Home\":\n target = event.ctrlKey ? this.#cellInRow(matrix, 0, 0) : rowCells[0];\n break;\n case \"End\":\n target = event.ctrlKey\n ? this.#cellInRow(matrix, matrix.length - 1, Number.POSITIVE_INFINITY)\n : rowCells[rowCells.length - 1];\n break;\n case \"Enter\":\n case \" \":\n this.#activate(cell);\n event.preventDefault();\n return;\n default:\n return;\n }\n\n if (target) {\n event.preventDefault();\n this.#setActiveCell(target, { focus: true }, matrix.flat());\n }\n }\n\n /**\n * Whether the event was addressed to a control inside `host` rather than to the\n * grid.\n *\n * Cells and headers hold consumer markup, and APG's grid pattern expects that\n * markup to include working controls — a row action button, an inline editor.\n * Those own their own keystrokes and clicks, so the grid stands down entirely\n * rather than acting in parallel. An editable host (its `contenteditable` state\n * is inherited, so the walk is explicit) counts the same way.\n */\n #claimedByDescendant(event: Event, host: HTMLElement): boolean {\n return this.#claimingControl(event, host) !== null;\n }\n\n /**\n * The nested control this event belongs to, or `null` when the host owns it.\n *\n * Naming the control, rather than answering yes or no, is what lets the click\n * path treat a sortable header's `<button>` as the activation it is while every\n * other control still takes the event away.\n */\n #claimingControl(event: Event, host: HTMLElement): HTMLElement | null {\n const source = event.target as HTMLElement;\n const control = source.closest<HTMLElement>(INTERACTIVE_HOST_SELECTOR);\n if (control && host.contains(control)) return control;\n return isInteractiveHost(source) ? source : null;\n }\n\n /** Performs a header's sort or a cell row's selection toggle on activation. */\n #activate(cell: HTMLElement): void {\n if (this.columnHeaderTargets.includes(cell)) {\n this.#cycleSort(cell);\n return;\n }\n if (this.selectionValue === \"none\") return;\n const row = cell.closest<HTMLElement>(\"[role='row']\");\n if (row && this.rowTargets.includes(row)) this.#toggleRow(row);\n }\n\n /** Cycles a header's sort on keyboard activation and emits `sort`. */\n #cycleSort(header: HTMLElement): void {\n const direction = nextSortDirection(header.getAttribute(\"aria-sort\") ?? \"none\");\n for (const other of this.columnHeaderTargets) {\n other.setAttribute(\"aria-sort\", other === header ? direction : \"none\");\n }\n this.dispatch(\"sort\", { detail: { column: header, direction } });\n }\n\n /** Toggles a row's `aria-selected`, honoring single vs. multiple selection. */\n #toggleRow(row: HTMLElement): void {\n const selected = row.getAttribute(\"aria-selected\") === \"true\";\n // Enforce single-ness on every toggle, not only when turning a row on:\n // switching one *off* would otherwise leave a second `true` behind until\n // the next baseline pass.\n if (this.selectionValue === \"single\") {\n for (const other of this.rowTargets) {\n if (other !== row) other.setAttribute(\"aria-selected\", \"false\");\n }\n }\n row.setAttribute(\"aria-selected\", selected ? \"false\" : \"true\");\n\n const rows = this.rowTargets.filter((r) => r.getAttribute(\"aria-selected\") === \"true\");\n this.dispatch(\"selectionchange\", { detail: { rows } });\n }\n\n /**\n * Makes `cell` the single tabbable cell (roving) and optionally focuses it.\n *\n * `cells` lets a caller that already walked the grid hand its collection over,\n * so one keystroke rebuilds the matrix once instead of twice. The write is\n * skipped where the attribute already holds the wanted value — comparing the\n * attribute rather than the IDL property, because a cell with no `tabindex` at\n * all reports `-1` and would then never receive the attribute it needs to be\n * focusable.\n */\n #setActiveCell(\n cell: HTMLElement,\n { focus }: { focus: boolean },\n cells?: readonly HTMLElement[],\n ): void {\n for (const candidate of cells ?? this.#navigableCells()) {\n const wanted = candidate === cell ? \"0\" : \"-1\";\n if (candidate.getAttribute(\"tabindex\") !== wanted) {\n candidate.setAttribute(\"tabindex\", wanted);\n }\n }\n if (focus) cell.focus();\n }\n\n /** All navigable elements (headers + cells) in DOM order. */\n #navigableCells(): HTMLElement[] {\n return this.#matrix().flat();\n }\n\n /** The grid as rows of navigable cells, derived from each `role=\"row\"`. */\n #matrix(): HTMLElement[][] {\n const navigable = new Set<HTMLElement>([...this.columnHeaderTargets, ...this.cellTargets]);\n const rows = Array.from(this.element.querySelectorAll<HTMLElement>(\"[role='row']\"));\n return rows\n .map((row) =>\n Array.from(row.children).filter((child): child is HTMLElement =>\n navigable.has(child as HTMLElement),\n ),\n )\n .filter((cells) => cells.length > 0);\n }\n\n /** Finds `[rowIndex, colIndex]` of `cell` within `matrix`, or null. */\n #locate(matrix: HTMLElement[][], cell: HTMLElement): [number, number] | null {\n for (let row = 0; row < matrix.length; row++) {\n const col = (matrix[row] ?? []).indexOf(cell);\n if (col !== -1) return [row, col];\n }\n return null;\n }\n\n /** The cell at `[row, col]`, clamping `col` to that row's last cell. */\n #cellInRow(matrix: HTMLElement[][], row: number, col: number): HTMLElement | undefined {\n const cells = matrix[row];\n if (!cells || cells.length === 0) return undefined;\n return cells[Math.min(col, cells.length - 1)];\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.ts","../../src/utils/field_mirror.ts","../../src/utils/interactive_host.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/data_grid_controller.ts"],"names":[],"mappings":"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","file":"data_grid_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","/**\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 * Native interactive elements whose built-in activation must not be replaced\n * wholesale by a controller. Deliberately excludes `[tabindex]`: a generic\n * element made focusable is the supported host for several headless patterns.\n * Contenteditable is handled by {@link isInteractiveHost} because its state is\n * inherited and its keywords are ASCII-case-insensitive.\n */\nexport const INTERACTIVE_HOST_SELECTOR =\n \"button, input, select, textarea, label, a[href], area[href], summary, details, audio[controls], video[controls], iframe, object, embed\";\n\n/**\n * Whether an element owns native activation or an inherited editing surface.\n *\n * The `contenteditable` missing and invalid states inherit. Walking explicitly\n * also keeps `contenteditable=\"false\"` as a real boundary inside an editable\n * ancestor instead of treating any ancestor attribute as decisive.\n */\nexport function isInteractiveHost(element: HTMLElement): boolean {\n if (element.matches(INTERACTIVE_HOST_SELECTOR)) return true;\n\n let current: HTMLElement | null = element;\n while (current) {\n const raw = current.getAttribute(\"contenteditable\");\n if (raw !== null) {\n const value = raw.trim().toLowerCase();\n if (value === \"false\") return false;\n if (value === \"\" || value === \"true\" || value === \"plaintext-only\") return true;\n }\n current = current.parentElement;\n }\n return false;\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","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\nimport { commitField, writeFields } from \"../utils/field_mirror\";\nimport { INTERACTIVE_HOST_SELECTOR, isInteractiveHost } from \"../utils/interactive_host\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** Cycle order for a sortable column header's `aria-sort`. */\nconst SORT_CYCLE = [\"none\", \"ascending\", \"descending\"] as const;\ntype SortDirection = (typeof SORT_CYCLE)[number];\n\n/** Row attributes a page can rewrite in place that move the published selection. */\nconst OBSERVED_ATTRIBUTES = [\"aria-selected\", \"data-value\"];\n\n/** The published selection: each selected row with the value it submits. */\ntype Selection = ReadonlyMap<HTMLElement, string | undefined>;\n\n/**\n * Sets `name` on `element` only when its value differs. A same-value write still\n * queues a mutation record for every observer on the page, so an unchanged state\n * writes nothing.\n */\nfunction setAttributeIfChanged(element: Element, name: string, value: string): void {\n if (element.getAttribute(name) !== value) element.setAttribute(name, value);\n}\n\n/** Whether two selections hold the same rows submitting the same values, in any order. */\nfunction sameSelection(a: Selection, b: Selection): boolean {\n if (a.size !== b.size) return false;\n for (const [row, value] of a) {\n if (!b.has(row) || b.get(row) !== value) return false;\n }\n return true;\n}\n\n/**\n * Returns the next `aria-sort` direction in the cycle after `current`. Unknown or\n * ARIA-only values (e.g. `\"other\"`, or an empty/missing attribute) are treated as\n * `\"none\"`, so the first activation always advances to `\"ascending\"` instead of\n * stalling.\n */\nfunction nextSortDirection(current: string): SortDirection {\n const index = SORT_CYCLE.indexOf(current as SortDirection);\n const from = index < 0 ? 0 : index;\n return SORT_CYCLE[(from + 1) % SORT_CYCLE.length] ?? \"ascending\";\n}\n\n/**\n * Headless, accessible **Data Grid** behavior: column sorting, row selection, and\n * roving keyboard navigation over an interactive table.\n *\n * Markup contract (identifier: `stimeo--data-grid`):\n * <table data-controller=\"stimeo--data-grid\" role=\"grid\" aria-label=\"Users\"\n * data-stimeo--data-grid-selection-value=\"single\"\n * data-stimeo--data-grid-name-value=\"user_id\">\n * <caption><div data-stimeo--data-grid-target=\"fields\"></div></caption>\n * <thead><tr role=\"row\">\n * <th role=\"columnheader\" aria-sort=\"none\" tabindex=\"-1\"\n * data-stimeo--data-grid-target=\"columnHeader\"\n * data-action=\"click->stimeo--data-grid#sort\n * keydown->stimeo--data-grid#onKeydown\">Name</th>\n * </tr></thead>\n * <tbody><tr role=\"row\" aria-selected=\"false\" data-value=\"7\"\n * data-stimeo--data-grid-target=\"row\">\n * <td role=\"gridcell\" tabindex=\"0\" data-stimeo--data-grid-target=\"cell\"\n * data-action=\"keydown->stimeo--data-grid#onKeydown\">Jane</td>\n * </tr></tbody>\n * </table>\n *\n * Implements the WAI-ARIA APG **Grid** pattern plus `aria-sort`. The whole grid is\n * a single Tab stop (roving `tabindex`: exactly one cell/header is `0`, the rest\n * `-1`); arrow keys move both DOM focus and that tabbable position. Sort state is\n * exposed via `aria-sort` on headers, selection via `aria-selected` on rows.\n *\n * `selectionchange` dispatches `{ rows: HTMLElement[] }` when the user changes the\n * selection; `sort` dispatches\n * `{ column: HTMLElement, direction: \"ascending\" | \"descending\" | \"none\" }`.\n *\n * `reconcile` dispatches `{ rows: HTMLElement[] }` — the selected rows, as\n * `selectionchange` carries them — when the page moves the selection instead: a\n * selected row removed or dropped from the targets, a morph or a script writing\n * a row's `aria-selected` or a selected row's `data-value`, a selected row that\n * arrives ahead of the selection in `single` mode, or a runtime `selection` that\n * collapses it. One batch of such changes reports once, and only when the\n * selected rows or the values they submit differ from the selection last settled\n * — on connect, by the user, or by the previous `reconcile` — so connecting\n * reports nothing.\n *\n * With a `fields` target the selected rows are mirrored into `name`d hidden\n * inputs so the selection can be submitted and read server-side — one per\n * selected row that carries a `data-value`, in DOM order; a row without one\n * submits nothing. `form` points them at a `<form>` by id when the container\n * sits outside it. A selection the user made emits a native bubbling `change`\n * from the container, the way a form control does, so `stimeo--auto-submit` and\n * form-level validation hear it. The mirror is refreshed silently on connect,\n * on a replacement container, on row churn, and whenever `selection`, `name` or\n * `form` changes; a selection the page moved is reported by `reconcile` alone.\n *\n * @remarks\n * Behavior only — the consumer performs the actual data sort/render in response to\n * the `sort` event and owns all styling. While connected a `MutationObserver`\n * watches the rows' `aria-selected` and `data-value`, which a morph can rewrite\n * without adding or removing a target; `disconnect()` releases it and drops a\n * pending pass. `connect()` rebuilds the single tab stop idempotently from the\n * DOM, and the target callbacks rebuild it again after rows or cells are added or\n * removed at runtime.\n *\n * Behavior provided:\n * - `Arrow*` move between cells (clamped at edges); `Home`/`End` to the row's\n * first/last cell; `Ctrl+Home`/`Ctrl+End` to the grid's first/last cell.\n * - `Enter`/`Space` cycles a header's sort (`none→ascending→descending`) or toggles\n * the focused row's selection when selection is enabled.\n *\n * Consumer contract — controls nested inside a cell or header:\n * - APG's grid hosts working controls in its cells, so a keystroke or click that\n * reached one — a nested button, link or form control, an editable host, or any\n * widget that already called `preventDefault()` — is left to it entirely: no\n * move, no sort, no selection. The one control that hands the event on is a\n * sortable header's own `<button>`, whose activation is what the click carries.\n * - A control keeps its own Tab behavior, so give it `tabindex=\"-1\"` to preserve\n * the grid's single Tab stop. A sortable `columnheader` hosting a `<button>` is\n * the common case: the roving position stays on the header, and the button's\n * own activation reaches `sort` through the click that bubbles to it.\n * - Host `role=\"gridcell\"` / `role=\"columnheader\"` on a non-interactive element\n * (`td`, `th`); an interactive host makes the grid stand down on that cell.\n */\nexport class DataGridController extends Controller<HTMLElement> {\n static override targets = [\"columnHeader\", \"row\", \"cell\", \"fields\"];\n static override values = {\n selection: { type: String, default: \"none\" },\n name: { type: String, default: \"rows[]\" },\n form: { type: String, default: \"\" },\n };\n static actions = [\"onKeydown\", \"sort\", \"toggleSelect\"] as const;\n static events = [\"selectionchange\", \"sort\", \"reconcile\"] as const;\n\n declare readonly columnHeaderTargets: HTMLElement[];\n declare readonly rowTargets: HTMLElement[];\n declare readonly cellTargets: HTMLElement[];\n declare readonly fieldsTarget: HTMLElement;\n declare readonly hasFieldsTarget: boolean;\n declare selectionValue: string;\n declare nameValue: string;\n declare formValue: string;\n\n /**\n * Collapses the target callbacks, Value changes and observed row writes of one\n * DOM mutation into a single pass, and refuses to run before `connect()` or\n * after `disconnect()`.\n *\n * Stimulus reports every target one at a time, so an ungated pass would re-walk\n * the whole grid once per authored cell on mount and once per streamed cell\n * afterwards — quadratic in the cell count both times.\n */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileRows());\n /** Watches the row attributes a page can rewrite in place; set while connected. */\n #observer: MutationObserver | null = null;\n /** The selection last settled: on connect, by the user, or by a reported pass. */\n #settled: Selection = new Map();\n\n /**\n * Establishes a single tab stop across all navigable cells/headers, brings the\n * rows to their baseline, and settles the selection without reporting it.\n *\n * The whole baseline runs here rather than in the Value callbacks: Stimulus\n * delivers those before `connect()`, where the pass is refused, and a\n * re-attached element reuses its cached context, whose value observer does not\n * fire again.\n */\n override connect(): void {\n this.#restoreBaseline();\n this.#settled = this.#selection();\n this.#reconcile.activate();\n this.#observeRows();\n }\n\n /** Releases the row observer and drops a queued pass, so neither outlives the element. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#observer?.disconnect();\n this.#observer = null;\n }\n\n /**\n * Rebuilds the DOM-owned baselines from the live grid: exactly one navigable\n * cell is in the Tab sequence, `aria-multiselectable` follows `selection`, every\n * selectable row carries an explicit `aria-selected`, and the fields mirror the\n * selected rows.\n *\n * The tab stop keeps whichever cell already holds it, so a rebuild triggered by\n * an unrelated row arriving does not throw the user's position away; only when\n * no cell holds it — the grid is fresh, or the holder was removed — does the\n * first navigable cell take over. Without that fallback a grid whose active row\n * is removed keeps every cell at `-1` and drops out of the Tab sequence\n * entirely.\n */\n #restoreBaseline(): void {\n const cells = this.#navigableCells();\n const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];\n if (active) this.#setActiveCell(active, { focus: false }, cells);\n this.#syncSelectable();\n const pageWrote = this.#takePageRecords();\n this.#normalizeSelection();\n this.#dropOwnRecords(pageWrote);\n this.#mirrorFields(false);\n }\n\n /**\n * The pass the page's changes run: the baselines are rebuilt, then a selection\n * that moved is reported. The report comes last, so rows a subscriber rewrites\n * are the next pass's to settle.\n */\n #reconcileRows(): void {\n this.#restoreBaseline();\n this.#reportMove();\n }\n\n /**\n * Reports the selection as `reconcile` when it differs from the one last\n * settled. The settled selection is replaced before dispatching, so a move a\n * subscriber makes is measured against what it was told.\n */\n #reportMove(): void {\n const selection = this.#selection();\n if (sameSelection(selection, this.#settled)) return;\n this.#settled = selection;\n this.dispatch(\"reconcile\", { detail: { rows: [...selection.keys()] } });\n }\n\n /**\n * Re-renders `aria-multiselectable`, the rows and the fields when application\n * code (or a Turbo morph) changes `selection` at runtime; a selection the new\n * mode collapses is reported as `reconcile`.\n */\n selectionValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Rebuilds the submitted fields when the public name changes at runtime. */\n nameValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Repoints the submitted fields when the owning form changes at runtime. */\n formValueChanged(): void {\n this.#reconcile.schedule();\n }\n\n /** Seeds a fields container inserted after connect from the current selection. */\n fieldsTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the baselines for a row added after connect. */\n rowTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Settles the selection again when a row leaves the grid or drops its target token. */\n rowTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a cell joins the grid after connect. */\n cellTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a cell leaves the grid. */\n cellTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a header joins the grid after connect. */\n columnHeaderTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Re-establishes the tab stop when a header leaves the grid. */\n columnHeaderTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /**\n * Brings the authored rows to the shape the APG requires, without changing\n * which rows the author chose.\n *\n * Every selectable row gets an explicit value — an absent `aria-selected` means\n * \"not selectable\" in ARIA, so a forgotten attribute hides a selectable row —\n * and a single-select grid keeps at most one `true`, first in DOM order.\n * A grid that declares `selection=\"none\"` has no selectable rows, so the\n * attribute is removed rather than written: in ARIA its absence is what \"not\n * selectable\" looks like.\n *\n * @stimeoRenderRoot\n */\n #normalizeSelection(): void {\n const rows = this.rowTargets;\n if (this.selectionValue === \"none\") {\n // Reclaim, do not merely skip: a grid that *became* unselectable would\n // otherwise keep announcing rows as selected while the logic refuses to\n // change them. In ARIA the absence of the attribute is what \"not\n // selectable\" looks like, so the rows have to lose it outright.\n for (const row of rows) row.removeAttribute(\"aria-selected\");\n return;\n }\n const single = this.selectionValue === \"single\";\n const first = rows.find((row) => row.getAttribute(\"aria-selected\") === \"true\");\n for (const row of rows) {\n if (single) {\n setAttributeIfChanged(row, \"aria-selected\", row === first ? \"true\" : \"false\");\n } else if (row.getAttribute(\"aria-selected\") !== \"true\") {\n setAttributeIfChanged(row, \"aria-selected\", \"false\");\n }\n }\n }\n\n /**\n * Mirrors `selection=\"multiple\"` onto `aria-multiselectable` (APG Grid) so SRs\n * announce that more than one row can be selected; cleared for single/none so a\n * grid never carries a misleading attribute.\n *\n * @stimeoRenderRoot\n */\n #syncSelectable(): void {\n if (this.selectionValue === \"multiple\") {\n setAttributeIfChanged(this.element, \"aria-multiselectable\", \"true\");\n } else {\n this.element.removeAttribute(\"aria-multiselectable\");\n }\n }\n\n /** Cycles the activated column header's sort and emits `sort`. */\n sort(event: Event): void {\n const header = event.currentTarget as HTMLElement;\n if (!this.columnHeaderTargets.includes(header)) return;\n if (event.defaultPrevented) return;\n // A sortable header hosts a `<button>`, and that button's activation is\n // exactly what this click carries, so it is the one control that does not\n // take the event away. A link or a field inside the header is its own\n // destination, and sorting on its click would act in parallel.\n const control = this.#claimingControl(event, header);\n if (control && !(control instanceof HTMLButtonElement)) return;\n\n const direction = nextSortDirection(header.getAttribute(\"aria-sort\") ?? \"none\");\n\n // Only one column is sorted at a time: reset the others to `none`.\n for (const other of this.columnHeaderTargets) {\n other.setAttribute(\"aria-sort\", other === header ? direction : \"none\");\n }\n\n this.#setActiveCell(header, { focus: false });\n this.dispatch(\"sort\", { detail: { column: header, direction } });\n }\n\n /** Toggles selection of the row owning the event target. Bound optionally. */\n toggleSelect(event: Event): void {\n // The pointer path guards on `selection=\"none\"` exactly as the keyboard path\n // does, so a grid that declares itself unselectable never grows selected rows.\n if (this.selectionValue === \"none\") return;\n // A widget that handled the click owns it, exactly as the keyboard path\n // stands down on a keystroke a descendant consumed.\n if (event.defaultPrevented) return;\n const host = event.currentTarget as HTMLElement;\n if (this.#claimedByDescendant(event, host)) return;\n const row = host.closest<HTMLElement>(\"[role='row']\");\n if (row && this.rowTargets.includes(row)) this.#toggleRow(row);\n }\n\n /** Grid navigation + sort/select activation. Bound to cells and headers. */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n if (event.isComposing) return;\n const cell = event.currentTarget as HTMLElement;\n // A native control inside the cell never calls `preventDefault()` — its\n // activation IS the default action — so the yield above cannot see it. This\n // recognises those by element shape instead.\n if (this.#claimedByDescendant(event, cell)) return;\n const matrix = this.#matrix();\n const position = this.#locate(matrix, cell);\n if (!position) return;\n const [row, col] = position;\n const rowCells = matrix[row] ?? [];\n\n let target: HTMLElement | undefined;\n // Logical, not physical. The key is normalised rather than the\n // delta negated: these two branches are not mirror images — their guards\n // differ — so swapping the key keeps each guard with its own direction.\n switch (logicalArrowKey(event.key, this.element)) {\n case \"ArrowRight\":\n target = this.#cellInRow(matrix, row, col + 1);\n break;\n case \"ArrowLeft\":\n target = this.#cellInRow(matrix, row, Math.max(col - 1, 0));\n break;\n case \"ArrowDown\":\n target = this.#cellInRow(matrix, Math.min(row + 1, matrix.length - 1), col);\n break;\n case \"ArrowUp\":\n target = this.#cellInRow(matrix, Math.max(row - 1, 0), col);\n break;\n case \"Home\":\n target = event.ctrlKey ? this.#cellInRow(matrix, 0, 0) : rowCells[0];\n break;\n case \"End\":\n target = event.ctrlKey\n ? this.#cellInRow(matrix, matrix.length - 1, Number.POSITIVE_INFINITY)\n : rowCells[rowCells.length - 1];\n break;\n case \"Enter\":\n case \" \":\n this.#activate(cell);\n event.preventDefault();\n return;\n default:\n return;\n }\n\n if (target) {\n event.preventDefault();\n this.#setActiveCell(target, { focus: true }, matrix.flat());\n }\n }\n\n /**\n * Whether the event was addressed to a control inside `host` rather than to the\n * grid.\n *\n * Cells and headers hold consumer markup, and APG's grid pattern expects that\n * markup to include working controls — a row action button, an inline editor.\n * Those own their own keystrokes and clicks, so the grid stands down entirely\n * rather than acting in parallel. An editable host (its `contenteditable` state\n * is inherited, so the walk is explicit) counts the same way.\n */\n #claimedByDescendant(event: Event, host: HTMLElement): boolean {\n return this.#claimingControl(event, host) !== null;\n }\n\n /**\n * The nested control this event belongs to, or `null` when the host owns it.\n *\n * Naming the control, rather than answering yes or no, is what lets the click\n * path treat a sortable header's `<button>` as the activation it is while every\n * other control still takes the event away.\n */\n #claimingControl(event: Event, host: HTMLElement): HTMLElement | null {\n const source = event.target as HTMLElement;\n const control = source.closest<HTMLElement>(INTERACTIVE_HOST_SELECTOR);\n if (control && host.contains(control)) return control;\n return isInteractiveHost(source) ? source : null;\n }\n\n /**\n * Performs a header's sort or a cell row's selection toggle on activation.\n *\n * @stimeoRuntimeOnly `selection` decides whether this one activation toggles the row; the sort it\n * cycles does not depend on it.\n */\n #activate(cell: HTMLElement): void {\n if (this.columnHeaderTargets.includes(cell)) {\n this.#cycleSort(cell);\n return;\n }\n if (this.selectionValue === \"none\") return;\n const row = cell.closest<HTMLElement>(\"[role='row']\");\n if (row && this.rowTargets.includes(row)) this.#toggleRow(row);\n }\n\n /** Cycles a header's sort on keyboard activation and emits `sort`. */\n #cycleSort(header: HTMLElement): void {\n const direction = nextSortDirection(header.getAttribute(\"aria-sort\") ?? \"none\");\n for (const other of this.columnHeaderTargets) {\n other.setAttribute(\"aria-sort\", other === header ? direction : \"none\");\n }\n this.dispatch(\"sort\", { detail: { column: header, direction } });\n }\n\n /**\n * Toggles a row's `aria-selected`, honoring single vs. multiple selection.\n *\n * @stimeoRuntimeOnly `selection` decides how this one toggle treats the other rows.\n */\n #toggleRow(row: HTMLElement): void {\n const selected = row.getAttribute(\"aria-selected\") === \"true\";\n const pageWrote = this.#takePageRecords();\n // Enforce single-ness on every toggle, not only when turning a row on:\n // switching one *off* would otherwise leave a second `true` behind until\n // the next baseline pass.\n if (this.selectionValue === \"single\") {\n for (const other of this.rowTargets) {\n if (other !== row) setAttributeIfChanged(other, \"aria-selected\", \"false\");\n }\n }\n row.setAttribute(\"aria-selected\", selected ? \"false\" : \"true\");\n this.#dropOwnRecords(pageWrote);\n\n // Settled before anything is reported, so a listener that moves the rows\n // again is measured against this selection.\n this.#settled = this.#selection();\n this.#mirrorFields(true);\n this.dispatch(\"selectionchange\", { detail: { rows: [...this.#settled.keys()] } });\n }\n\n /** The selected rows in DOM order, each with the `data-value` it submits, if any. */\n #selection(): Selection {\n return new Map(\n this.rowTargets\n .filter((row) => row.getAttribute(\"aria-selected\") === \"true\")\n .map((row) => [row, row.dataset.value] as const),\n );\n }\n\n /**\n * Watches the rows' `aria-selected` and `data-value`. Every write the grid makes\n * to them sits between a take and a drop of the observer's queue, so each record\n * the callback receives is the page's, and only a record on one of the grid's own\n * rows schedules a pass.\n */\n #observeRows(): void {\n const observer = new MutationObserver((records) => {\n if (this.#concernsRows(records)) this.#reconcile.schedule();\n });\n observer.observe(this.element, {\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n this.#observer = observer;\n }\n\n /**\n * Empties the observer's queue before the grid writes to the rows, and says\n * whether the page had queued anything about them. Those records still owe a\n * pass: a toggle in `multiple` mode rewrites only its own row, so it does not fold\n * in what the page wrote to the others.\n */\n #takePageRecords(): boolean {\n const records = this.#observer?.takeRecords() ?? [];\n return records.length > 0 && this.#concernsRows(records);\n }\n\n /**\n * Whether any record is about one of this grid's own rows. A widget nested in a\n * cell writes the same attributes on its own elements, and those are no reason\n * to reconcile the grid.\n */\n #concernsRows(records: readonly MutationRecord[]): boolean {\n const rows = new Set<Node>(this.rowTargets);\n return records.some((record) => rows.has(record.target));\n }\n\n /**\n * Drops the records the grid's own writes just queued, so the observer never\n * takes them for the page's, then schedules the pass the page's records owe.\n */\n #dropOwnRecords(pageWrote: boolean): void {\n this.#observer?.takeRecords();\n if (pageWrote) this.#reconcile.schedule();\n }\n\n /**\n * Mirrors the selected rows' `data-value` into the optional fields container.\n * A selected row without one submits nothing, so a grid whose rows carry no\n * value submits an empty set rather than a row of blanks.\n *\n * @stimeoRenderRoot\n */\n #mirrorFields(notify: boolean): void {\n if (!this.hasFieldsTarget) return;\n const values = this.rowTargets\n .filter((row) => row.getAttribute(\"aria-selected\") === \"true\")\n .map((row) => row.dataset.value)\n .filter((value): value is string => value !== undefined);\n const options = { name: this.nameValue, form: this.formValue };\n if (writeFields(this.fieldsTarget, values, options) && notify) {\n commitField(this.fieldsTarget);\n }\n }\n\n /**\n * Makes `cell` the single tabbable cell (roving) and optionally focuses it.\n *\n * `cells` lets a caller that already walked the grid hand its collection over,\n * so one keystroke rebuilds the matrix once instead of twice. The write is\n * skipped where the attribute already holds the wanted value — comparing the\n * attribute rather than the IDL property, because a cell with no `tabindex` at\n * all reports `-1` and would then never receive the attribute it needs to be\n * focusable.\n */\n #setActiveCell(\n cell: HTMLElement,\n { focus }: { focus: boolean },\n cells?: readonly HTMLElement[],\n ): void {\n for (const candidate of cells ?? this.#navigableCells()) {\n const wanted = candidate === cell ? \"0\" : \"-1\";\n if (candidate.getAttribute(\"tabindex\") !== wanted) {\n candidate.setAttribute(\"tabindex\", wanted);\n }\n }\n if (focus) cell.focus();\n }\n\n /** All navigable elements (headers + cells) in DOM order. */\n #navigableCells(): HTMLElement[] {\n return this.#matrix().flat();\n }\n\n /** The grid as rows of navigable cells, derived from each `role=\"row\"`. */\n #matrix(): HTMLElement[][] {\n const navigable = new Set<HTMLElement>([...this.columnHeaderTargets, ...this.cellTargets]);\n const rows = Array.from(this.element.querySelectorAll<HTMLElement>(\"[role='row']\"));\n return rows\n .map((row) =>\n Array.from(row.children).filter((child): child is HTMLElement =>\n navigable.has(child as HTMLElement),\n ),\n )\n .filter((cells) => cells.length > 0);\n }\n\n /** Finds `[rowIndex, colIndex]` of `cell` within `matrix`, or null. */\n #locate(matrix: HTMLElement[][], cell: HTMLElement): [number, number] | null {\n for (let row = 0; row < matrix.length; row++) {\n const col = (matrix[row] ?? []).indexOf(cell);\n if (col !== -1) return [row, col];\n }\n return null;\n }\n\n /** The cell at `[row, col]`, clamping `col` to that row's last cell. */\n #cellInRow(matrix: HTMLElement[][], row: number, col: number): HTMLElement | undefined {\n const cells = matrix[row];\n if (!cells || cells.length === 0) return undefined;\n return cells[Math.min(col, cells.length - 1)];\n }\n}\n"]}
@@ -33,13 +33,26 @@ import { Controller } from '@hotwired/stimulus';
33
33
  * `data-range-start` / `data-in-range` / `data-range-end` hooks. Assistive tech
34
34
  * is told the two confirmed endpoints via `aria-selected`; inner cells use
35
35
  * `data-in-range` (visual only) so the announcement stays to the two ends. The
36
- * confirmed range is also mirrored to the live `status` region.
36
+ * range the user confirms is also announced in the live `status` region.
37
37
  *
38
38
  * Selection model: the first click/Enter sets a *pending* start and enters
39
39
  * "selecting" mode (preview follows the pointer/focus); the second confirms the
40
- * end (auto-swapped if earlier than the start) and dispatches `change` with
41
- * `{ start, end }` ISO dates. Escape abandons an in-progress selection,
42
- * restoring the last confirmed range.
40
+ * end (auto-swapped if earlier than the start), and a preset confirms its range
41
+ * in one step. Escape abandons an in-progress selection, restoring the last
42
+ * confirmed range.
43
+ *
44
+ * A confirmed range is painted — the cells, both hidden fields and the status —
45
+ * before anything reports it. Each hidden field that moved then emits a native
46
+ * bubbling `change`, the way a form control does, so `stimeo--auto-submit` and
47
+ * form-level validation hear it; `monthchange` follows when the paint moved the
48
+ * month, and last `change` dispatches `{ start: string, end: string }`, the ISO
49
+ * range the picker holds. A bound a listener moves meanwhile is applied by the
50
+ * repaint its Value callback runs, so the `reconcile` it causes follows the
51
+ * `change`. A listener that replaces the range before these reports are all out
52
+ * — by confirming another range, or with a repaint that narrows this one — has
53
+ * the replacing range report itself: what is still pending for the replaced
54
+ * range is not sent, and focus stays where the replacing move put it. The seed
55
+ * `connect()` reads out of the hidden fields is written back silently.
43
56
  *
44
57
  * Availability is declarative on both axes: `min` / `max` bound the selectable
45
58
  * interval and `disabledDates` excludes individual dates inside it. Both are
@@ -49,6 +62,17 @@ import { Controller } from '@hotwired/stimulus';
49
62
  * navigation dispatches `monthchange` with `{ month }` as a notification (for
50
63
  * fetching a month's availability, say); the fetched result comes back in
51
64
  * through `disabledDates`, which repaints on its own.
65
+ *
66
+ * The same predicate holds the confirmed range. When a change to `min`, `max`
67
+ * or `disabledDates` leaves an end unselectable, the paint narrows the range:
68
+ * each end moves inward to the nearest day that can still be chosen, and a
69
+ * range with no such day left is cleared. The hidden fields follow without a
70
+ * native `change`, the status keeps announcing what the user confirmed, and
71
+ * `reconcile` dispatches `{ start: string, end: string }` — the narrowed range,
72
+ * empty strings once cleared — rather than `change`, which stays the user's.
73
+ * `connect()` narrows the range it reads from the fields the same way,
74
+ * silently. An in-progress selection whose start becomes unselectable is
75
+ * dropped, as Escape would drop it, and the next pick starts a new one.
52
76
  */
53
77
  declare class DateRangePickerController extends Controller<HTMLElement> {
54
78
  #private;
@@ -72,7 +96,7 @@ declare class DateRangePickerController extends Controller<HTMLElement> {
72
96
  };
73
97
  };
74
98
  static actions: readonly ["applyPreset", "next", "onKeydown", "prev", "previewTo", "selectDate"];
75
- static events: readonly ["change", "monthchange"];
99
+ static events: readonly ["change", "monthchange", "reconcile"];
76
100
  readonly gridTarget: HTMLElement;
77
101
  readonly hasGridTarget: boolean;
78
102
  readonly monthLabelTarget: HTMLElement;
@@ -88,7 +112,11 @@ declare class DateRangePickerController extends Controller<HTMLElement> {
88
112
  maxValue: string;
89
113
  disabledDatesValue: string;
90
114
  localeValue: string;
91
- /** Seeds and normalizes the range from optional hidden fields, then paints the grid. */
115
+ /**
116
+ * Seeds the range from optional hidden fields — ordered, and narrowed to the
117
+ * days that can be chosen — then paints the grid, which writes the range back
118
+ * to the fields silently.
119
+ */
92
120
  connect(): void;
93
121
  /** Cancels any pending deferred focus so it never fires on a detached element. */
94
122
  disconnect(): void;
@@ -114,6 +114,16 @@ function monthLabelFormatter(locale) {
114
114
  return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
115
115
  }
116
116
 
117
+ // src/utils/field_mirror.ts
118
+ function writeField(field, value) {
119
+ if (field.value === value) return false;
120
+ field.value = value;
121
+ return true;
122
+ }
123
+ function commitField(target) {
124
+ target.dispatchEvent(new Event("change", { bubbles: true }));
125
+ }
126
+
117
127
  // src/utils/locale.ts
118
128
  function resolveLocale(element, declared = "") {
119
129
  return declared || element.closest("[lang]")?.getAttribute("lang") || void 0;
@@ -219,6 +229,11 @@ function parseStringList(raw, fallback = []) {
219
229
  return parsed.filter((entry) => typeof entry === "string");
220
230
  }
221
231
 
232
+ // src/utils/target_selector.ts
233
+ function targetSelector(identifier, name) {
234
+ return `[data-${identifier}-target~="${name}"]`;
235
+ }
236
+
222
237
  // src/controllers/date_range_picker_controller.ts
223
238
  var GRID_SIZE = 42;
224
239
  var DateRangePickerController = class extends Controller {
@@ -233,7 +248,7 @@ var DateRangePickerController = class extends Controller {
233
248
  locale: { type: String, default: "" }
234
249
  };
235
250
  static actions = ["applyPreset", "next", "onKeydown", "prev", "previewTo", "selectDate"];
236
- static events = ["change", "monthchange"];
251
+ static events = ["change", "monthchange", "reconcile"];
237
252
  /** The month currently rendered, as `YYYY-MM`. */
238
253
  #viewMonth = "";
239
254
  /** The confirmed range endpoints (ISO), or "" when unset. */
@@ -247,6 +262,13 @@ var DateRangePickerController = class extends Controller {
247
262
  #focusedDate = /* @__PURE__ */ new Date();
248
263
  /** Deferred focus after an async month transition (cancelled on teardown). */
249
264
  #focusTimer = new SafeTimeout();
265
+ /**
266
+ * How many times the confirmed range has moved — a commit or a narrowing — so
267
+ * a report still pending for a range a listener has since replaced can tell.
268
+ */
269
+ #rangeMoves = 0;
270
+ /** How many month transitions have started, so one a listener starts mid-paint keeps its focus. */
271
+ #transitions = 0;
250
272
  /** The declared unavailable dates, indexed for the per-cell paint lookup. */
251
273
  #disabledDates = /* @__PURE__ */ new Set();
252
274
  /** Rewinds an unfinished selection before Turbo snapshots the page. */
@@ -260,17 +282,22 @@ var DateRangePickerController = class extends Controller {
260
282
  #repaint = new MicrotaskCoalescer(() => {
261
283
  this.#render();
262
284
  });
263
- /** Seeds and normalizes the range from optional hidden fields, then paints the grid. */
285
+ /**
286
+ * Seeds the range from optional hidden fields — ordered, and narrowed to the
287
+ * days that can be chosen — then paints the grid, which writes the range back
288
+ * to the fields silently.
289
+ */
264
290
  connect() {
265
291
  this.#repaint.activate();
266
292
  this.#beforeCache.activate();
267
293
  const authoredStart = this.hasStartFieldTarget ? normalizeISO(this.startFieldTarget.value) : "";
268
294
  const authoredEnd = this.hasEndFieldTarget ? normalizeISO(this.endFieldTarget.value) : "";
269
- [this.#startDate, this.#endDate] = orderRange(authoredStart, authoredEnd);
295
+ [this.#startDate, this.#endDate] = this.#selectableRange(
296
+ ...orderRange(authoredStart, authoredEnd)
297
+ );
270
298
  this.#pendingStart = "";
271
299
  this.#previewDate = "";
272
300
  this.#announcedMonth = null;
273
- this.#commitFields();
274
301
  const anchor = parseISODateString(this.#startDate) ?? this.#clampToBounds(/* @__PURE__ */ new Date()) ?? /* @__PURE__ */ new Date();
275
302
  this.#focusedDate = anchor;
276
303
  this.#viewMonth = toISOMonthString(anchor);
@@ -345,16 +372,11 @@ var DateRangePickerController = class extends Controller {
345
372
  if (!intersection) return;
346
373
  const { start, end } = intersection;
347
374
  if (!this.#isSelectable(start) || !this.#isSelectable(end)) return;
348
- this.#startDate = start;
349
- this.#endDate = end;
350
- this.#pendingStart = "";
351
- this.#previewDate = "";
375
+ const move = this.#setConfirmed(start, end);
352
376
  const endDate = parseISODateString(end);
353
377
  if (endDate) this.#focusedDate = endDate;
354
- this.#commitFields();
355
- this.#transitionTo(toISOMonthString(parseISODateString(end) ?? /* @__PURE__ */ new Date()), end);
356
- this.#announce();
357
- this.dispatch("change", { detail: { start, end } });
378
+ this.#transitionTo(toISOMonthString(parseISODateString(end) ?? /* @__PURE__ */ new Date()), end, true);
379
+ this.#reportConfirmed(move);
358
380
  }
359
381
  /** Grid keyboard navigation, selection (Enter/Space), and Escape-to-cancel. */
360
382
  onKeydown(event) {
@@ -413,7 +435,7 @@ var DateRangePickerController = class extends Controller {
413
435
  }
414
436
  /** Records a chosen date as either the pending start or the confirmed end. */
415
437
  #choose(date) {
416
- if (!this.#pendingStart) {
438
+ if (!this.#pendingStart || !this.#isSelectable(this.#pendingStart)) {
417
439
  this.#pendingStart = date;
418
440
  this.#previewDate = date;
419
441
  const parsed = parseISODateString(date);
@@ -422,14 +444,32 @@ var DateRangePickerController = class extends Controller {
422
444
  return;
423
445
  }
424
446
  const [start, end] = date < this.#pendingStart ? [date, this.#pendingStart] : [this.#pendingStart, date];
447
+ const move = this.#setConfirmed(start, end);
448
+ this.#render(true);
449
+ this.#reportConfirmed(move);
450
+ }
451
+ /**
452
+ * Makes `start`–`end` the confirmed range, ending any selection in progress,
453
+ * ahead of the paint that shows it.
454
+ *
455
+ * @returns The move count the range is now at, which its `change` checks.
456
+ */
457
+ #setConfirmed(start, end) {
425
458
  this.#startDate = start;
426
459
  this.#endDate = end;
427
460
  this.#pendingStart = "";
428
461
  this.#previewDate = "";
429
- this.#commitFields();
430
- this.#render();
431
- this.#announce();
432
- this.dispatch("change", { detail: { start, end } });
462
+ this.#rangeMoves += 1;
463
+ return this.#rangeMoves;
464
+ }
465
+ /**
466
+ * Dispatches `change` for the range confirmed at `move`, once its paint has
467
+ * reported it through the fields. A listener of those reports that moved the
468
+ * range on has had the range it moved to reported instead.
469
+ */
470
+ #reportConfirmed(move) {
471
+ if (move !== this.#rangeMoves) return;
472
+ this.dispatch("change", { detail: { start: this.#startDate, end: this.#endDate } });
433
473
  }
434
474
  /** Moves roving focus to `date`, transitioning the month when needed. */
435
475
  #moveFocusTo(date) {
@@ -438,12 +478,21 @@ var DateRangePickerController = class extends Controller {
438
478
  if (this.#pendingStart && this.#isSelectable(iso)) this.#previewDate = iso;
439
479
  this.#transitionTo(toISOMonthString(date), iso);
440
480
  }
441
- /** Renders `month`, then focuses the cell for `dateStr` (deferred if async). */
442
- #transitionTo(month, dateStr) {
481
+ /**
482
+ * Renders `month`, then focuses the cell for `dateStr` (deferred if async).
483
+ * A listener of the paint's reports that started another move has placed
484
+ * focus itself, so this one leaves it there.
485
+ *
486
+ * @param confirmed - Whether the paint shows a range the user has just confirmed.
487
+ */
488
+ #transitionTo(month, dateStr, confirmed = false) {
443
489
  const isTransition = month !== this.#viewMonth;
444
490
  this.#focusTimer.clearAll();
445
491
  this.#viewMonth = month;
446
- this.#render();
492
+ this.#transitions += 1;
493
+ const transition = this.#transitions;
494
+ this.#render(confirmed);
495
+ if (transition !== this.#transitions) return;
447
496
  const focusCell = () => {
448
497
  this.cellTargets.find((c) => c.getAttribute("data-date") === dateStr)?.focus();
449
498
  };
@@ -465,15 +514,25 @@ var DateRangePickerController = class extends Controller {
465
514
  this.#render();
466
515
  }
467
516
  /**
468
- * Builds the six-week grid and binds range/roving/disabled state per cell.
517
+ * Builds the six-week grid and binds range/roving/disabled state per cell,
518
+ * mirrors the confirmed range to the hidden fields, then reports what the
519
+ * paint moved.
520
+ *
521
+ * @param confirmed - Whether the paint shows a range the user has just
522
+ * confirmed, which the status then announces and each moved field reports.
469
523
  *
470
524
  * @stimeoRenderRoot
471
525
  */
472
- #render() {
526
+ #render(confirmed = false) {
473
527
  const info = parseISOMonthString(this.#viewMonth);
474
528
  if (!info) return;
475
529
  const { year, month } = info;
530
+ if (this.#pendingStart && !this.#isSelectable(this.#pendingStart)) {
531
+ this.#pendingStart = "";
532
+ this.#previewDate = "";
533
+ }
476
534
  if (this.#previewDate && !this.#isSelectable(this.#previewDate)) this.#previewDate = "";
535
+ const narrowed = this.#narrowConfirmedRange();
477
536
  if (this.hasMonthLabelTarget) {
478
537
  const formatter = monthLabelFormatter(resolveLocale(this.element, this.localeValue));
479
538
  this.monthLabelTarget.textContent = formatter.format(new Date(year, month - 1, 1));
@@ -513,11 +572,54 @@ var DateRangePickerController = class extends Controller {
513
572
  const fallback = cells.find((cell) => cell.getAttribute("data-outside") === "false") ?? cells[0];
514
573
  fallback?.setAttribute("tabindex", "0");
515
574
  }
575
+ const move = this.#rangeMoves;
576
+ const moved = this.#mirrorFields();
577
+ if (confirmed) {
578
+ this.#announce();
579
+ for (const [field, value] of moved) {
580
+ if (field.value === value) commitField(field);
581
+ }
582
+ }
516
583
  const previous = this.#announcedMonth;
517
584
  this.#announcedMonth = this.#viewMonth;
518
585
  if (previous !== null && previous !== this.#viewMonth) {
519
586
  this.dispatch("monthchange", { detail: { month: this.#viewMonth } });
520
587
  }
588
+ if (narrowed && move === this.#rangeMoves) {
589
+ this.dispatch("reconcile", { detail: { start: this.#startDate, end: this.#endDate } });
590
+ }
591
+ }
592
+ /**
593
+ * Narrows the confirmed range to days that can still be chosen. The paint
594
+ * mirrors it to the fields without reporting a commit.
595
+ *
596
+ * @returns Whether the range moved.
597
+ */
598
+ #narrowConfirmedRange() {
599
+ const [start, end] = this.#selectableRange(this.#startDate, this.#endDate);
600
+ if (start === this.#startDate && end === this.#endDate) return false;
601
+ this.#startDate = start;
602
+ this.#endDate = end;
603
+ this.#rangeMoves += 1;
604
+ return true;
605
+ }
606
+ /**
607
+ * The range `start`–`end` with each end moved inward to the nearest day that
608
+ * can be chosen, or two empty strings when none of its days can be. A lone
609
+ * endpoint is the one-day range it names, and stays lone.
610
+ */
611
+ #selectableRange(start, end) {
612
+ let from = parseISODateString(start || end);
613
+ let to = parseISODateString(end || start);
614
+ if (!from || !to) return [start, end];
615
+ while (from.getTime() <= to.getTime() && !this.#isSelectable(toISODateString(from))) {
616
+ from = addDays(from, 1);
617
+ }
618
+ while (to.getTime() >= from.getTime() && !this.#isSelectable(toISODateString(to))) {
619
+ to = addDays(to, -1);
620
+ }
621
+ if (from.getTime() > to.getTime()) return ["", ""];
622
+ return [start ? toISODateString(from) : "", end ? toISODateString(to) : ""];
521
623
  }
522
624
  /** The ordered [start, end] pair to paint: the preview while selecting, else confirmed. */
523
625
  #visualRange() {
@@ -527,10 +629,20 @@ var DateRangePickerController = class extends Controller {
527
629
  }
528
630
  return [this.#startDate, this.#endDate];
529
631
  }
530
- /** Writes the confirmed range to the hidden fields. */
531
- #commitFields() {
532
- if (this.hasStartFieldTarget) this.startFieldTarget.value = this.#startDate;
533
- if (this.hasEndFieldTarget) this.endFieldTarget.value = this.#endDate;
632
+ /**
633
+ * Writes the confirmed range to the hidden fields.
634
+ *
635
+ * @returns Each field whose value moved, with the value written to it.
636
+ */
637
+ #mirrorFields() {
638
+ const moved = [];
639
+ if (this.hasStartFieldTarget && writeField(this.startFieldTarget, this.#startDate)) {
640
+ moved.push([this.startFieldTarget, this.#startDate]);
641
+ }
642
+ if (this.hasEndFieldTarget && writeField(this.endFieldTarget, this.#endDate)) {
643
+ moved.push([this.endFieldTarget, this.#endDate]);
644
+ }
645
+ return moved;
534
646
  }
535
647
  /** Announces the confirmed range in the live status region. */
536
648
  #announce() {
@@ -590,7 +702,7 @@ var DateRangePickerController = class extends Controller {
590
702
  /** Resolves the cell element from an event target, or null. */
591
703
  #cellFrom(target) {
592
704
  return target?.closest(
593
- "[data-stimeo--date-range-picker-target='cell']"
705
+ targetSelector(this.identifier, "cell")
594
706
  ) ?? null;
595
707
  }
596
708
  };