stimeo-ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/cable/index.d.ts +16 -16
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.d.ts +3 -2
  5. package/dist/controllers/accordion_controller.js +10 -0
  6. package/dist/controllers/accordion_controller.js.map +1 -1
  7. package/dist/controllers/alert_dialog_controller.d.ts +64 -0
  8. package/dist/controllers/alert_dialog_controller.js +318 -0
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -0
  10. package/dist/controllers/breadcrumb_controller.d.ts +97 -10
  11. package/dist/controllers/breadcrumb_controller.js +225 -13
  12. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  13. package/dist/controllers/calendar_controller.d.ts +11 -1
  14. package/dist/controllers/calendar_controller.js +89 -22
  15. package/dist/controllers/calendar_controller.js.map +1 -1
  16. package/dist/controllers/carousel_controller.d.ts +115 -0
  17. package/dist/controllers/carousel_controller.js +313 -0
  18. package/dist/controllers/carousel_controller.js.map +1 -0
  19. package/dist/controllers/clipboard_controller.d.ts +72 -0
  20. package/dist/controllers/clipboard_controller.js +144 -0
  21. package/dist/controllers/clipboard_controller.js.map +1 -0
  22. package/dist/controllers/collapsible_controller.d.ts +70 -0
  23. package/dist/controllers/collapsible_controller.js +327 -0
  24. package/dist/controllers/collapsible_controller.js.map +1 -0
  25. package/dist/controllers/color_picker_controller.d.ts +87 -0
  26. package/dist/controllers/color_picker_controller.js +252 -0
  27. package/dist/controllers/color_picker_controller.js.map +1 -0
  28. package/dist/controllers/combobox_controller.d.ts +14 -1
  29. package/dist/controllers/combobox_controller.js +162 -23
  30. package/dist/controllers/combobox_controller.js.map +1 -1
  31. package/dist/controllers/command_palette_controller.d.ts +2 -0
  32. package/dist/controllers/command_palette_controller.js +194 -17
  33. package/dist/controllers/command_palette_controller.js.map +1 -1
  34. package/dist/controllers/confirm_controller.js.map +1 -1
  35. package/dist/controllers/context_menu_controller.d.ts +7 -6
  36. package/dist/controllers/context_menu_controller.js +32 -10
  37. package/dist/controllers/context_menu_controller.js.map +1 -1
  38. package/dist/controllers/count_up_controller.js +8 -1
  39. package/dist/controllers/count_up_controller.js.map +1 -1
  40. package/dist/controllers/currency_input_controller.d.ts +72 -0
  41. package/dist/controllers/currency_input_controller.js +147 -0
  42. package/dist/controllers/currency_input_controller.js.map +1 -0
  43. package/dist/controllers/data_grid_controller.d.ts +89 -0
  44. package/dist/controllers/data_grid_controller.js +246 -0
  45. package/dist/controllers/data_grid_controller.js.map +1 -0
  46. package/dist/controllers/date_range_picker_controller.d.ts +88 -0
  47. package/dist/controllers/date_range_picker_controller.js +441 -0
  48. package/dist/controllers/date_range_picker_controller.js.map +1 -0
  49. package/dist/controllers/dialog_controller.js.map +1 -1
  50. package/dist/controllers/dismissible_controller.d.ts +57 -0
  51. package/dist/controllers/dismissible_controller.js +117 -0
  52. package/dist/controllers/dismissible_controller.js.map +1 -0
  53. package/dist/controllers/drawer_controller.d.ts +96 -0
  54. package/dist/controllers/drawer_controller.js +630 -0
  55. package/dist/controllers/drawer_controller.js.map +1 -0
  56. package/dist/controllers/dropdown_controller.d.ts +1 -2
  57. package/dist/controllers/dropdown_controller.js.map +1 -1
  58. package/dist/controllers/editable_controller.d.ts +73 -0
  59. package/dist/controllers/editable_controller.js +169 -0
  60. package/dist/controllers/editable_controller.js.map +1 -0
  61. package/dist/controllers/file_dropzone_controller.d.ts +92 -0
  62. package/dist/controllers/file_dropzone_controller.js +165 -0
  63. package/dist/controllers/file_dropzone_controller.js.map +1 -0
  64. package/dist/controllers/filter_controller.d.ts +60 -0
  65. package/dist/controllers/filter_controller.js +86 -0
  66. package/dist/controllers/filter_controller.js.map +1 -0
  67. package/dist/controllers/flash_controller.js +36 -5
  68. package/dist/controllers/flash_controller.js.map +1 -1
  69. package/dist/controllers/focus_controller.d.ts +1 -1
  70. package/dist/controllers/focus_controller.js.map +1 -1
  71. package/dist/controllers/form_field_controller.js.map +1 -1
  72. package/dist/controllers/form_validation_controller.js +1 -1
  73. package/dist/controllers/form_validation_controller.js.map +1 -1
  74. package/dist/controllers/highlight_controller.js +6 -4
  75. package/dist/controllers/highlight_controller.js.map +1 -1
  76. package/dist/controllers/hover_card_controller.d.ts +1 -1
  77. package/dist/controllers/hover_card_controller.js.map +1 -1
  78. package/dist/controllers/intersection_controller.js +67 -19
  79. package/dist/controllers/intersection_controller.js.map +1 -1
  80. package/dist/controllers/lazy_frame_controller.js +54 -11
  81. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  82. package/dist/controllers/listbox_controller.d.ts +9 -2
  83. package/dist/controllers/listbox_controller.js +257 -53
  84. package/dist/controllers/listbox_controller.js.map +1 -1
  85. package/dist/controllers/local_time_controller.js +2 -2
  86. package/dist/controllers/local_time_controller.js.map +1 -1
  87. package/dist/controllers/masonry_controller.d.ts +54 -0
  88. package/dist/controllers/masonry_controller.js +142 -0
  89. package/dist/controllers/masonry_controller.js.map +1 -0
  90. package/dist/controllers/menu_controller.d.ts +29 -11
  91. package/dist/controllers/menu_controller.js +104 -17
  92. package/dist/controllers/menu_controller.js.map +1 -1
  93. package/dist/controllers/menubar_controller.d.ts +142 -0
  94. package/dist/controllers/menubar_controller.js +785 -0
  95. package/dist/controllers/menubar_controller.js.map +1 -0
  96. package/dist/controllers/meter_controller.js.map +1 -1
  97. package/dist/controllers/multi_select_controller.d.ts +141 -0
  98. package/dist/controllers/multi_select_controller.js +755 -0
  99. package/dist/controllers/multi_select_controller.js.map +1 -0
  100. package/dist/controllers/navigation_menu_controller.d.ts +146 -0
  101. package/dist/controllers/navigation_menu_controller.js +511 -0
  102. package/dist/controllers/navigation_menu_controller.js.map +1 -0
  103. package/dist/controllers/nested_form_controller.d.ts +4 -5
  104. package/dist/controllers/nested_form_controller.js.map +1 -1
  105. package/dist/controllers/number_input_controller.d.ts +1 -1
  106. package/dist/controllers/number_input_controller.js +7 -0
  107. package/dist/controllers/number_input_controller.js.map +1 -1
  108. package/dist/controllers/otp_controller.js +18 -1
  109. package/dist/controllers/otp_controller.js.map +1 -1
  110. package/dist/controllers/overflow_indicator_controller.d.ts +15 -6
  111. package/dist/controllers/overflow_indicator_controller.js +246 -27
  112. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  113. package/dist/controllers/overflow_menu_controller.d.ts +78 -19
  114. package/dist/controllers/overflow_menu_controller.js +381 -57
  115. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  116. package/dist/controllers/pagination_controller.d.ts +36 -5
  117. package/dist/controllers/pagination_controller.js +163 -32
  118. package/dist/controllers/pagination_controller.js.map +1 -1
  119. package/dist/controllers/password_reveal_controller.d.ts +49 -0
  120. package/dist/controllers/password_reveal_controller.js +117 -0
  121. package/dist/controllers/password_reveal_controller.js.map +1 -0
  122. package/dist/controllers/persist_controller.js +6 -6
  123. package/dist/controllers/persist_controller.js.map +1 -1
  124. package/dist/controllers/pointer_drag_controller.d.ts +2 -3
  125. package/dist/controllers/pointer_drag_controller.js +9 -1
  126. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  127. package/dist/controllers/popover_controller.d.ts +1 -2
  128. package/dist/controllers/popover_controller.js +2 -2
  129. package/dist/controllers/popover_controller.js.map +1 -1
  130. package/dist/controllers/portal_controller.d.ts +4 -4
  131. package/dist/controllers/portal_controller.js.map +1 -1
  132. package/dist/controllers/progress_controller.js.map +1 -1
  133. package/dist/controllers/radio_group_controller.js +22 -3
  134. package/dist/controllers/radio_group_controller.js.map +1 -1
  135. package/dist/controllers/range_slider_controller.d.ts +97 -0
  136. package/dist/controllers/range_slider_controller.js +192 -0
  137. package/dist/controllers/range_slider_controller.js.map +1 -0
  138. package/dist/controllers/rating_controller.js +16 -2
  139. package/dist/controllers/rating_controller.js.map +1 -1
  140. package/dist/controllers/read_more_controller.d.ts +52 -0
  141. package/dist/controllers/read_more_controller.js +238 -0
  142. package/dist/controllers/read_more_controller.js.map +1 -0
  143. package/dist/controllers/reading_progress_controller.js.map +1 -1
  144. package/dist/controllers/resizable_controller.d.ts +3 -1
  145. package/dist/controllers/resizable_controller.js +65 -1
  146. package/dist/controllers/resizable_controller.js.map +1 -1
  147. package/dist/controllers/roving_controller.js +17 -2
  148. package/dist/controllers/roving_controller.js.map +1 -1
  149. package/dist/controllers/scroll_area_controller.js +101 -14
  150. package/dist/controllers/scroll_area_controller.js.map +1 -1
  151. package/dist/controllers/scroll_restore_controller.d.ts +54 -0
  152. package/dist/controllers/scroll_restore_controller.js +93 -0
  153. package/dist/controllers/scroll_restore_controller.js.map +1 -0
  154. package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
  155. package/dist/controllers/scroll_visibility_controller.js +40 -6
  156. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  157. package/dist/controllers/scrollspy_controller.d.ts +64 -15
  158. package/dist/controllers/scrollspy_controller.js +369 -74
  159. package/dist/controllers/scrollspy_controller.js.map +1 -1
  160. package/dist/controllers/separator_controller.d.ts +69 -0
  161. package/dist/controllers/separator_controller.js +96 -0
  162. package/dist/controllers/separator_controller.js.map +1 -0
  163. package/dist/controllers/sidebar_controller.d.ts +107 -0
  164. package/dist/controllers/sidebar_controller.js +761 -0
  165. package/dist/controllers/sidebar_controller.js.map +1 -0
  166. package/dist/controllers/skeleton_controller.js +1 -1
  167. package/dist/controllers/skeleton_controller.js.map +1 -1
  168. package/dist/controllers/slider_controller.d.ts +11 -1
  169. package/dist/controllers/slider_controller.js +32 -6
  170. package/dist/controllers/slider_controller.js.map +1 -1
  171. package/dist/controllers/sortable_controller.d.ts +3 -1
  172. package/dist/controllers/sortable_controller.js +34 -3
  173. package/dist/controllers/sortable_controller.js.map +1 -1
  174. package/dist/controllers/spinner_controller.js +1 -1
  175. package/dist/controllers/spinner_controller.js.map +1 -1
  176. package/dist/controllers/stepper_controller.d.ts +4 -0
  177. package/dist/controllers/stepper_controller.js +28 -12
  178. package/dist/controllers/stepper_controller.js.map +1 -1
  179. package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
  180. package/dist/controllers/stick_to_bottom_controller.js +9 -4
  181. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  182. package/dist/controllers/sticky_observer_controller.d.ts +17 -4
  183. package/dist/controllers/sticky_observer_controller.js +109 -20
  184. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  185. package/dist/controllers/submit_once_controller.d.ts +2 -2
  186. package/dist/controllers/submit_once_controller.js.map +1 -1
  187. package/dist/controllers/switch_controller.js +1 -0
  188. package/dist/controllers/switch_controller.js.map +1 -1
  189. package/dist/controllers/tabs_controller.d.ts +13 -4
  190. package/dist/controllers/tabs_controller.js +26 -3
  191. package/dist/controllers/tabs_controller.js.map +1 -1
  192. package/dist/controllers/tags_input_controller.d.ts +91 -0
  193. package/dist/controllers/tags_input_controller.js +295 -0
  194. package/dist/controllers/tags_input_controller.js.map +1 -0
  195. package/dist/controllers/theme_controller.d.ts +1 -1
  196. package/dist/controllers/theme_controller.js +42 -13
  197. package/dist/controllers/theme_controller.js.map +1 -1
  198. package/dist/controllers/time_picker_controller.d.ts +67 -0
  199. package/dist/controllers/time_picker_controller.js +231 -0
  200. package/dist/controllers/time_picker_controller.js.map +1 -0
  201. package/dist/controllers/toast_controller.d.ts +5 -14
  202. package/dist/controllers/toast_controller.js +40 -14
  203. package/dist/controllers/toast_controller.js.map +1 -1
  204. package/dist/controllers/toggle_group_controller.js +23 -2
  205. package/dist/controllers/toggle_group_controller.js.map +1 -1
  206. package/dist/controllers/toolbar_controller.d.ts +53 -13
  207. package/dist/controllers/toolbar_controller.js +230 -31
  208. package/dist/controllers/toolbar_controller.js.map +1 -1
  209. package/dist/controllers/tooltip_controller.d.ts +1 -1
  210. package/dist/controllers/tooltip_controller.js.map +1 -1
  211. package/dist/controllers/transition_controller.d.ts +14 -8
  212. package/dist/controllers/transition_controller.js +153 -38
  213. package/dist/controllers/transition_controller.js.map +1 -1
  214. package/dist/controllers/tree_view_controller.d.ts +135 -0
  215. package/dist/controllers/tree_view_controller.js +691 -0
  216. package/dist/controllers/tree_view_controller.js.map +1 -0
  217. package/dist/index.d.ts +27 -1818
  218. package/dist/index.js +4256 -915
  219. package/dist/index.js.map +1 -1
  220. package/dist/inspector/cli.d.ts +375 -39
  221. package/dist/inspector/cli.js +228 -14
  222. package/dist/inspector/cli.js.map +1 -1
  223. package/dist/inspector/cli_bin.js +419 -48
  224. package/dist/inspector/cli_bin.js.map +1 -1
  225. package/dist/inspector/examples.json +26 -26
  226. package/dist/inspector/manifest.json +1054 -127
  227. package/package.json +5 -6
@@ -0,0 +1,89 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ /**
4
+ * Headless, accessible **Data Grid** behavior: column sorting, row selection, and
5
+ * roving keyboard navigation over an interactive table.
6
+ *
7
+ * Markup contract (identifier: `stimeo--data-grid`):
8
+ * <table data-controller="stimeo--data-grid" role="grid" aria-label="Users"
9
+ * data-stimeo--data-grid-selection-value="single">
10
+ * <thead><tr role="row">
11
+ * <th role="columnheader" aria-sort="none" tabindex="-1"
12
+ * data-stimeo--data-grid-target="columnHeader"
13
+ * data-action="click->stimeo--data-grid#sort
14
+ * keydown->stimeo--data-grid#onKeydown">Name</th>
15
+ * </tr></thead>
16
+ * <tbody><tr role="row" aria-selected="false"
17
+ * data-stimeo--data-grid-target="row">
18
+ * <td role="gridcell" tabindex="0" data-stimeo--data-grid-target="cell"
19
+ * data-action="keydown->stimeo--data-grid#onKeydown">Jane</td>
20
+ * </tr></tbody>
21
+ * </table>
22
+ *
23
+ * Implements the WAI-ARIA APG **Grid** pattern plus `aria-sort`. The whole grid is
24
+ * a single Tab stop (roving `tabindex`: exactly one cell/header is `0`, the rest
25
+ * `-1`); arrow keys move both DOM focus and that tabbable position. Sort state is
26
+ * exposed via `aria-sort` on headers, selection via `aria-selected` on rows.
27
+ *
28
+ * @remarks
29
+ * Behavior only — the consumer performs the actual data sort/render in response to
30
+ * the `sort` event and owns all styling. No timers or observers are held, so there
31
+ * is nothing to leak across Turbo navigations; `connect()` rebuilds the single tab
32
+ * stop idempotently from the DOM.
33
+ *
34
+ * Behavior provided:
35
+ * - `Arrow*` move between cells (clamped at edges); `Home`/`End` to the row's
36
+ * first/last cell; `Ctrl+Home`/`Ctrl+End` to the grid's first/last cell.
37
+ * - `Enter`/`Space` cycles a header's sort (`none→ascending→descending`) or toggles
38
+ * the focused row's selection when selection is enabled.
39
+ */
40
+ declare class DataGridController extends Controller<HTMLElement> {
41
+ #private;
42
+ static targets: string[];
43
+ static values: {
44
+ selection: {
45
+ type: StringConstructor;
46
+ default: string;
47
+ };
48
+ };
49
+ static actions: readonly ["onKeydown", "sort", "toggleSelect"];
50
+ static events: readonly ["selectionchange", "sort"];
51
+ readonly columnHeaderTargets: HTMLElement[];
52
+ readonly rowTargets: HTMLElement[];
53
+ readonly cellTargets: HTMLElement[];
54
+ selectionValue: string;
55
+ /**
56
+ * Establishes a single tab stop across all navigable cells/headers and brings
57
+ * the rows to their baseline.
58
+ *
59
+ * Normalizing here rather than leaving it to {@link selectionValueChanged}
60
+ * guarantees exactly one pass per mount: a re-attached element reuses its
61
+ * cached Stimulus context, whose value observer already knows the `selection`
62
+ * attribute, so the Value callback does not fire a second time.
63
+ */
64
+ connect(): void;
65
+ /** Reopens the row callback for the next mount. */
66
+ disconnect(): void;
67
+ /**
68
+ * Keeps `aria-multiselectable` in step with the `selection` Value. Fires on connect
69
+ * (so it self-heals after a Turbo morph) and on any runtime change, so the ARIA
70
+ * never drifts from the selection logic, which reads `selectionValue` live.
71
+ */
72
+ selectionValueChanged(): void;
73
+ /**
74
+ * Re-establishes the row baseline for a row added after connect.
75
+ *
76
+ * Each pass walks every row, and Stimulus reports the authored rows one by one
77
+ * before `connect()`, so the mount is gated to keep it linear in the row count
78
+ * rather than quadratic; `connect()` runs the single baseline pass instead.
79
+ */
80
+ rowTargetConnected(): void;
81
+ /** Cycles the activated column header's sort and emits `sort`. */
82
+ sort(event: Event): void;
83
+ /** Toggles selection of the row owning the event target. Bound optionally. */
84
+ toggleSelect(event: Event): void;
85
+ /** Grid navigation + sort/select activation. Bound to cells and headers. */
86
+ onKeydown(event: KeyboardEvent): void;
87
+ }
88
+
89
+ export { DataGridController };
@@ -0,0 +1,246 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ // src/controllers/data_grid_controller.ts
4
+
5
+ // src/utils/logical_scroll.ts
6
+ function isRtl(element) {
7
+ return window.getComputedStyle(element).direction === "rtl";
8
+ }
9
+
10
+ // src/utils/arrow_step.ts
11
+ function logicalArrowKey(key, element) {
12
+ if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
13
+ if (!isRtl(element)) return key;
14
+ return key === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
15
+ }
16
+ function isReservedArrowChord(event, allow = []) {
17
+ if (!event.key.startsWith("Arrow")) return false;
18
+ return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
19
+ }
20
+
21
+ // src/controllers/data_grid_controller.ts
22
+ var SORT_CYCLE = ["none", "ascending", "descending"];
23
+ function nextSortDirection(current) {
24
+ const index = SORT_CYCLE.indexOf(current);
25
+ const from = index < 0 ? 0 : index;
26
+ return SORT_CYCLE[(from + 1) % SORT_CYCLE.length] ?? "ascending";
27
+ }
28
+ var DataGridController = class extends Controller {
29
+ static targets = ["columnHeader", "row", "cell"];
30
+ static values = {
31
+ selection: { type: String, default: "none" }
32
+ };
33
+ static actions = ["onKeydown", "sort", "toggleSelect"];
34
+ static events = ["selectionchange", "sort"];
35
+ /** Gates the row callback so it does not re-walk every row once per authored row on mount. */
36
+ #connected = false;
37
+ /**
38
+ * Establishes a single tab stop across all navigable cells/headers and brings
39
+ * the rows to their baseline.
40
+ *
41
+ * Normalizing here rather than leaving it to {@link selectionValueChanged}
42
+ * guarantees exactly one pass per mount: a re-attached element reuses its
43
+ * cached Stimulus context, whose value observer already knows the `selection`
44
+ * attribute, so the Value callback does not fire a second time.
45
+ */
46
+ connect() {
47
+ const cells = this.#navigableCells();
48
+ const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];
49
+ this.#setActiveCell(active, { focus: false });
50
+ this.#normalizeSelection();
51
+ this.#connected = true;
52
+ }
53
+ /** Reopens the row callback for the next mount. */
54
+ disconnect() {
55
+ this.#connected = false;
56
+ }
57
+ /**
58
+ * Keeps `aria-multiselectable` in step with the `selection` Value. Fires on connect
59
+ * (so it self-heals after a Turbo morph) and on any runtime change, so the ARIA
60
+ * never drifts from the selection logic, which reads `selectionValue` live.
61
+ */
62
+ selectionValueChanged() {
63
+ this.#syncSelectable();
64
+ this.#normalizeSelection();
65
+ }
66
+ /**
67
+ * Re-establishes the row baseline for a row added after connect.
68
+ *
69
+ * Each pass walks every row, and Stimulus reports the authored rows one by one
70
+ * before `connect()`, so the mount is gated to keep it linear in the row count
71
+ * rather than quadratic; `connect()` runs the single baseline pass instead.
72
+ */
73
+ rowTargetConnected() {
74
+ if (!this.#connected) return;
75
+ this.#normalizeSelection();
76
+ }
77
+ /**
78
+ * Brings the authored rows to the shape the APG requires, without changing
79
+ * which rows the author chose.
80
+ *
81
+ * Every selectable row gets an explicit value — an absent `aria-selected` means
82
+ * "not selectable" in ARIA, so a forgotten attribute hides a selectable row —
83
+ * and a single-select grid keeps at most one `true`, first in DOM order.
84
+ * A grid that declares `selection="none"` has no selectable rows, so the
85
+ * attribute is removed rather than written: in ARIA its absence is what "not
86
+ * selectable" looks like.
87
+ */
88
+ #normalizeSelection() {
89
+ const rows = this.rowTargets;
90
+ if (this.selectionValue === "none") {
91
+ for (const row of rows) row.removeAttribute("aria-selected");
92
+ return;
93
+ }
94
+ const single = this.selectionValue === "single";
95
+ const first = rows.find((row) => row.getAttribute("aria-selected") === "true");
96
+ for (const row of rows) {
97
+ if (single) {
98
+ row.setAttribute("aria-selected", row === first ? "true" : "false");
99
+ } else if (row.getAttribute("aria-selected") !== "true") {
100
+ row.setAttribute("aria-selected", "false");
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Mirrors `selection="multiple"` onto `aria-multiselectable` (APG Grid) so SRs
106
+ * announce that more than one row can be selected; cleared for single/none so a
107
+ * grid never carries a misleading attribute.
108
+ */
109
+ #syncSelectable() {
110
+ if (this.selectionValue === "multiple") {
111
+ this.element.setAttribute("aria-multiselectable", "true");
112
+ } else {
113
+ this.element.removeAttribute("aria-multiselectable");
114
+ }
115
+ }
116
+ /** Cycles the activated column header's sort and emits `sort`. */
117
+ sort(event) {
118
+ const header = event.currentTarget;
119
+ if (!this.columnHeaderTargets.includes(header)) return;
120
+ const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
121
+ for (const other of this.columnHeaderTargets) {
122
+ other.setAttribute("aria-sort", other === header ? direction : "none");
123
+ }
124
+ this.#setActiveCell(header, { focus: false });
125
+ this.dispatch("sort", { detail: { column: header, direction } });
126
+ }
127
+ /** Toggles selection of the row owning the event target. Bound optionally. */
128
+ toggleSelect(event) {
129
+ if (this.selectionValue === "none") return;
130
+ const row = event.currentTarget.closest("[role='row']");
131
+ if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
132
+ }
133
+ /** Grid navigation + sort/select activation. Bound to cells and headers. */
134
+ onKeydown(event) {
135
+ if (event.defaultPrevented) return;
136
+ if (isReservedArrowChord(event)) return;
137
+ const cell = event.currentTarget;
138
+ const matrix = this.#matrix();
139
+ const position = this.#locate(matrix, cell);
140
+ if (!position) return;
141
+ const [row, col] = position;
142
+ const rowCells = matrix[row] ?? [];
143
+ let target;
144
+ switch (logicalArrowKey(event.key, this.element)) {
145
+ case "ArrowRight":
146
+ target = this.#cellInRow(matrix, row, col + 1);
147
+ break;
148
+ case "ArrowLeft":
149
+ target = this.#cellInRow(matrix, row, Math.max(col - 1, 0));
150
+ break;
151
+ case "ArrowDown":
152
+ target = this.#cellInRow(matrix, Math.min(row + 1, matrix.length - 1), col);
153
+ break;
154
+ case "ArrowUp":
155
+ target = this.#cellInRow(matrix, Math.max(row - 1, 0), col);
156
+ break;
157
+ case "Home":
158
+ target = event.ctrlKey ? this.#cellInRow(matrix, 0, 0) : rowCells[0];
159
+ break;
160
+ case "End":
161
+ target = event.ctrlKey ? this.#cellInRow(matrix, matrix.length - 1, Number.POSITIVE_INFINITY) : rowCells[rowCells.length - 1];
162
+ break;
163
+ case "Enter":
164
+ case " ":
165
+ this.#activate(cell);
166
+ event.preventDefault();
167
+ return;
168
+ default:
169
+ return;
170
+ }
171
+ if (target) {
172
+ event.preventDefault();
173
+ this.#setActiveCell(target, { focus: true });
174
+ }
175
+ }
176
+ /** Performs a header's sort or a cell row's selection toggle on activation. */
177
+ #activate(cell) {
178
+ if (this.columnHeaderTargets.includes(cell)) {
179
+ this.#cycleSort(cell);
180
+ return;
181
+ }
182
+ if (this.selectionValue === "none") return;
183
+ const row = cell.closest("[role='row']");
184
+ if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
185
+ }
186
+ /** Shared sort logic for both click and keyboard activation. */
187
+ #cycleSort(header) {
188
+ const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
189
+ for (const other of this.columnHeaderTargets) {
190
+ other.setAttribute("aria-sort", other === header ? direction : "none");
191
+ }
192
+ this.dispatch("sort", { detail: { column: header, direction } });
193
+ }
194
+ /** Toggles a row's `aria-selected`, honoring single vs. multiple selection. */
195
+ #toggleRow(row) {
196
+ const selected = row.getAttribute("aria-selected") === "true";
197
+ if (this.selectionValue === "single") {
198
+ for (const other of this.rowTargets) {
199
+ if (other !== row) other.setAttribute("aria-selected", "false");
200
+ }
201
+ }
202
+ row.setAttribute("aria-selected", selected ? "false" : "true");
203
+ const rows = this.rowTargets.filter((r) => r.getAttribute("aria-selected") === "true");
204
+ this.dispatch("selectionchange", { detail: { rows } });
205
+ }
206
+ /** Makes `cell` the single tabbable cell (roving) and optionally focuses it. */
207
+ #setActiveCell(cell, { focus }) {
208
+ if (!cell) return;
209
+ for (const candidate of this.#navigableCells()) {
210
+ candidate.tabIndex = candidate === cell ? 0 : -1;
211
+ }
212
+ if (focus) cell.focus();
213
+ }
214
+ /** All navigable elements (headers + cells) in DOM order. */
215
+ #navigableCells() {
216
+ return this.#matrix().flat();
217
+ }
218
+ /** The grid as rows of navigable cells, derived from each `role="row"`. */
219
+ #matrix() {
220
+ const navigable = /* @__PURE__ */ new Set([...this.columnHeaderTargets, ...this.cellTargets]);
221
+ const rows = Array.from(this.element.querySelectorAll("[role='row']"));
222
+ return rows.map(
223
+ (row) => Array.from(row.children).filter(
224
+ (child) => navigable.has(child)
225
+ )
226
+ ).filter((cells) => cells.length > 0);
227
+ }
228
+ /** Finds `[rowIndex, colIndex]` of `cell` within `matrix`, or null. */
229
+ #locate(matrix, cell) {
230
+ for (let row = 0; row < matrix.length; row++) {
231
+ const col = (matrix[row] ?? []).indexOf(cell);
232
+ if (col !== -1) return [row, col];
233
+ }
234
+ return null;
235
+ }
236
+ /** The cell at `[row, col]`, clamping `col` to that row's last cell. */
237
+ #cellInRow(matrix, row, col) {
238
+ const cells = matrix[row];
239
+ if (!cells || cells.length === 0) return void 0;
240
+ return cells[Math.min(col, cells.length - 1)];
241
+ }
242
+ };
243
+
244
+ export { DataGridController };
245
+ //# sourceMappingURL=data_grid_controller.js.map
246
+ //# sourceMappingURL=data_grid_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/logical_scroll.ts","../../src/utils/arrow_step.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","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord, logicalArrowKey } from \"../utils/arrow_step\";\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 * @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.\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 */\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 /** Gates the row callback so it does not re-walk every row once per authored row on mount. */\n #connected = false;\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 const cells = this.#navigableCells();\n const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];\n this.#setActiveCell(active, { focus: false });\n this.#normalizeSelection();\n this.#connected = true;\n }\n\n /** Reopens the row callback for the next mount. */\n override disconnect(): void {\n this.#connected = false;\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 /**\n * Re-establishes the row baseline for a row added after connect.\n *\n * Each pass walks every row, and Stimulus reports the authored rows one by one\n * before `connect()`, so the mount is gated to keep it linear in the row count\n * rather than quadratic; `connect()` runs the single baseline pass instead.\n */\n rowTargetConnected(): void {\n if (!this.#connected) return;\n this.#normalizeSelection();\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\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 const row = (event.currentTarget as HTMLElement).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 const cell = event.currentTarget as HTMLElement;\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 });\n }\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 /** Shared sort logic for both click and keyboard activation. */\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, and\n // nothing else clears it for the rest of the session.\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 /** Makes `cell` the single tabbable cell (roving) and optionally focuses it. */\n #setActiveCell(cell: HTMLElement | undefined, { focus }: { focus: boolean }): void {\n if (!cell) return;\n for (const candidate of this.#navigableCells()) {\n candidate.tabIndex = candidate === cell ? 0 : -1;\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"]}
@@ -0,0 +1,88 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ /**
4
+ * Headless, accessible **date range picker** behavior. A derivative of
5
+ * {@link CalendarController}: it reuses the month-grid navigation model and adds
6
+ * two-point range selection, in-progress range preview, and presets.
7
+ *
8
+ * Markup contract (identifier: `stimeo--date-range-picker`):
9
+ * <div data-controller="stimeo--date-range-picker"
10
+ * data-stimeo--date-range-picker-min-value="2026-01-01"
11
+ * data-stimeo--date-range-picker-max-value="2026-12-31">
12
+ * <button data-action="stimeo--date-range-picker#prev">Prev</button>
13
+ * <span data-stimeo--date-range-picker-target="monthLabel" aria-live="polite"></span>
14
+ * <button data-action="stimeo--date-range-picker#next">Next</button>
15
+ * <div role="grid" data-stimeo--date-range-picker-target="grid">
16
+ * <!-- exactly 42 cell targets (7 days × 6 rows) -->
17
+ * <button role="gridcell" tabindex="-1"
18
+ * data-stimeo--date-range-picker-target="cell"
19
+ * data-action="click->stimeo--date-range-picker#selectDate
20
+ * mouseenter->stimeo--date-range-picker#previewTo
21
+ * focus->stimeo--date-range-picker#previewTo
22
+ * keydown->stimeo--date-range-picker#onKeydown"></button>
23
+ * </div>
24
+ * <button data-range="last7" data-action="stimeo--date-range-picker#applyPreset">…</button>
25
+ * <span role="status" aria-live="polite" data-stimeo--date-range-picker-target="status"></span>
26
+ * <input type="hidden" data-stimeo--date-range-picker-target="startField" />
27
+ * <input type="hidden" data-stimeo--date-range-picker-target="endField" />
28
+ * </div>
29
+ *
30
+ * @remarks
31
+ * Behavior only — the consumer styles the grid and renders the range using the
32
+ * `data-range-start` / `data-in-range` / `data-range-end` hooks. Assistive tech
33
+ * is told the two confirmed endpoints via `aria-selected`; inner cells use
34
+ * `data-in-range` (visual only) so the announcement stays to the two ends. The
35
+ * confirmed range is also mirrored to the live `status` region.
36
+ *
37
+ * Selection model: the first click/Enter sets a *pending* start and enters
38
+ * "selecting" mode (preview follows the pointer/focus); the second confirms the
39
+ * end (auto-swapped if earlier than the start) and dispatches `change`. Escape
40
+ * abandons an in-progress selection, restoring the last confirmed range.
41
+ */
42
+ declare class DateRangePickerController extends Controller<HTMLElement> {
43
+ #private;
44
+ static targets: string[];
45
+ static values: {
46
+ min: {
47
+ type: StringConstructor;
48
+ default: string;
49
+ };
50
+ max: {
51
+ type: StringConstructor;
52
+ default: string;
53
+ };
54
+ };
55
+ static actions: readonly ["applyPreset", "next", "onKeydown", "prev", "previewTo", "selectDate"];
56
+ static events: readonly ["change"];
57
+ readonly gridTarget: HTMLElement;
58
+ readonly hasGridTarget: boolean;
59
+ readonly monthLabelTarget: HTMLElement;
60
+ readonly cellTargets: HTMLElement[];
61
+ readonly statusTarget: HTMLElement;
62
+ readonly startFieldTarget: HTMLInputElement;
63
+ readonly endFieldTarget: HTMLInputElement;
64
+ readonly hasMonthLabelTarget: boolean;
65
+ readonly hasStatusTarget: boolean;
66
+ readonly hasStartFieldTarget: boolean;
67
+ readonly hasEndFieldTarget: boolean;
68
+ minValue: string;
69
+ maxValue: string;
70
+ /** Seeds the range from any pre-filled hidden fields and renders the grid. */
71
+ connect(): void;
72
+ /** Cancels any pending deferred focus so it never fires on a detached element. */
73
+ disconnect(): void;
74
+ /** Navigates to the previous month. */
75
+ prev(event?: Event): void;
76
+ /** Navigates to the next month. */
77
+ next(event?: Event): void;
78
+ /** Confirms a range endpoint from a clicked cell. */
79
+ selectDate(event: Event): void;
80
+ /** Previews the range up to a hovered/focused cell while selecting. */
81
+ previewTo(event: Event): void;
82
+ /** Applies a named preset (`today` / `last7` / `last30` / `thisMonth`). */
83
+ applyPreset(event: Event): void;
84
+ /** Grid keyboard navigation, selection (Enter/Space), and Escape-to-cancel. */
85
+ onKeydown(event: KeyboardEvent): void;
86
+ }
87
+
88
+ export { DateRangePickerController };