stimeo-ui 0.2.0 → 0.2.1
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.
- package/CHANGELOG.md +59 -0
- package/dist/controllers/accordion_controller.d.ts +3 -2
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +64 -0
- package/dist/controllers/alert_dialog_controller.js +318 -0
- package/dist/controllers/alert_dialog_controller.js.map +1 -0
- package/dist/controllers/carousel_controller.d.ts +100 -0
- package/dist/controllers/carousel_controller.js +272 -0
- package/dist/controllers/carousel_controller.js.map +1 -0
- package/dist/controllers/clipboard_controller.d.ts +72 -0
- package/dist/controllers/clipboard_controller.js +144 -0
- package/dist/controllers/clipboard_controller.js.map +1 -0
- package/dist/controllers/collapsible_controller.d.ts +70 -0
- package/dist/controllers/collapsible_controller.js +327 -0
- package/dist/controllers/collapsible_controller.js.map +1 -0
- package/dist/controllers/color_picker_controller.d.ts +76 -0
- package/dist/controllers/color_picker_controller.js +213 -0
- package/dist/controllers/color_picker_controller.js.map +1 -0
- package/dist/controllers/count_up_controller.js +8 -1
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +72 -0
- package/dist/controllers/currency_input_controller.js +147 -0
- package/dist/controllers/currency_input_controller.js.map +1 -0
- package/dist/controllers/data_grid_controller.d.ts +71 -0
- package/dist/controllers/data_grid_controller.js +168 -0
- package/dist/controllers/data_grid_controller.js.map +1 -0
- package/dist/controllers/date_range_picker_controller.d.ts +87 -0
- package/dist/controllers/date_range_picker_controller.js +417 -0
- package/dist/controllers/date_range_picker_controller.js.map +1 -0
- package/dist/controllers/dismissible_controller.d.ts +57 -0
- package/dist/controllers/dismissible_controller.js +117 -0
- package/dist/controllers/dismissible_controller.js.map +1 -0
- package/dist/controllers/drawer_controller.d.ts +96 -0
- package/dist/controllers/drawer_controller.js +630 -0
- package/dist/controllers/drawer_controller.js.map +1 -0
- package/dist/controllers/editable_controller.d.ts +73 -0
- package/dist/controllers/editable_controller.js +168 -0
- package/dist/controllers/editable_controller.js.map +1 -0
- package/dist/controllers/file_dropzone_controller.d.ts +92 -0
- package/dist/controllers/file_dropzone_controller.js +165 -0
- package/dist/controllers/file_dropzone_controller.js.map +1 -0
- package/dist/controllers/filter_controller.d.ts +60 -0
- package/dist/controllers/filter_controller.js +86 -0
- package/dist/controllers/filter_controller.js.map +1 -0
- package/dist/controllers/flash_controller.js +36 -5
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.js +6 -4
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +41 -18
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +33 -11
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +54 -0
- package/dist/controllers/masonry_controller.js +142 -0
- package/dist/controllers/masonry_controller.js.map +1 -0
- package/dist/controllers/menubar_controller.d.ts +62 -0
- package/dist/controllers/menubar_controller.js +433 -0
- package/dist/controllers/menubar_controller.js.map +1 -0
- package/dist/controllers/multi_select_controller.d.ts +115 -0
- package/dist/controllers/multi_select_controller.js +472 -0
- package/dist/controllers/multi_select_controller.js.map +1 -0
- package/dist/controllers/navigation_menu_controller.d.ts +99 -0
- package/dist/controllers/navigation_menu_controller.js +384 -0
- package/dist/controllers/navigation_menu_controller.js.map +1 -0
- package/dist/controllers/overflow_indicator_controller.d.ts +12 -4
- package/dist/controllers/overflow_indicator_controller.js +178 -27
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +49 -0
- package/dist/controllers/password_reveal_controller.js +117 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -0
- package/dist/controllers/range_slider_controller.d.ts +87 -0
- package/dist/controllers/range_slider_controller.js +166 -0
- package/dist/controllers/range_slider_controller.js.map +1 -0
- package/dist/controllers/read_more_controller.d.ts +52 -0
- package/dist/controllers/read_more_controller.js +194 -0
- package/dist/controllers/read_more_controller.js.map +1 -0
- package/dist/controllers/scroll_area_controller.js +15 -2
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +54 -0
- package/dist/controllers/scroll_restore_controller.js +93 -0
- package/dist/controllers/scroll_restore_controller.js.map +1 -0
- package/dist/controllers/scroll_visibility_controller.js +8 -4
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.js +33 -11
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -0
- package/dist/controllers/separator_controller.js +87 -0
- package/dist/controllers/separator_controller.js.map +1 -0
- package/dist/controllers/sidebar_controller.d.ts +107 -0
- package/dist/controllers/sidebar_controller.js +761 -0
- package/dist/controllers/sidebar_controller.js.map +1 -0
- package/dist/controllers/stepper_controller.d.ts +4 -0
- package/dist/controllers/stepper_controller.js +28 -12
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.js +8 -4
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +17 -4
- package/dist/controllers/sticky_observer_controller.js +88 -20
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +3 -2
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +91 -0
- package/dist/controllers/tags_input_controller.js +275 -0
- package/dist/controllers/tags_input_controller.js.map +1 -0
- package/dist/controllers/theme_controller.js +20 -10
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +67 -0
- package/dist/controllers/time_picker_controller.js +212 -0
- package/dist/controllers/time_picker_controller.js.map +1 -0
- package/dist/controllers/toast_controller.d.ts +1 -9
- package/dist/controllers/toast_controller.js +36 -9
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +14 -8
- package/dist/controllers/transition_controller.js +153 -38
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +54 -0
- package/dist/controllers/tree_view_controller.js +275 -0
- package/dist/controllers/tree_view_controller.js.map +1 -0
- package/dist/index.d.ts +27 -1818
- package/dist/index.js +811 -295
- package/dist/index.js.map +1 -1
- package/dist/inspector/examples.json +2 -2
- package/dist/inspector/manifest.json +15 -3
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
/** Establishes a single tab stop across all navigable cells/headers. */
|
|
56
|
+
connect(): void;
|
|
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(): void;
|
|
63
|
+
/** Cycles the activated column header's sort and emits `sort`. */
|
|
64
|
+
sort(event: Event): void;
|
|
65
|
+
/** Toggles selection of the row owning the event target. Bound optionally. */
|
|
66
|
+
toggleSelect(event: Event): void;
|
|
67
|
+
/** Grid navigation + sort/select activation. Bound to cells and headers. */
|
|
68
|
+
onKeydown(event: KeyboardEvent): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { DataGridController };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
// src/controllers/data_grid_controller.ts
|
|
4
|
+
var SORT_CYCLE = ["none", "ascending", "descending"];
|
|
5
|
+
function nextSortDirection(current) {
|
|
6
|
+
const index = SORT_CYCLE.indexOf(current);
|
|
7
|
+
const from = index < 0 ? 0 : index;
|
|
8
|
+
return SORT_CYCLE[(from + 1) % SORT_CYCLE.length] ?? "ascending";
|
|
9
|
+
}
|
|
10
|
+
var DataGridController = class extends Controller {
|
|
11
|
+
static targets = ["columnHeader", "row", "cell"];
|
|
12
|
+
static values = {
|
|
13
|
+
selection: { type: String, default: "none" }
|
|
14
|
+
};
|
|
15
|
+
static actions = ["onKeydown", "sort", "toggleSelect"];
|
|
16
|
+
static events = ["selectionchange", "sort"];
|
|
17
|
+
/** Establishes a single tab stop across all navigable cells/headers. */
|
|
18
|
+
connect() {
|
|
19
|
+
const cells = this.#navigableCells();
|
|
20
|
+
const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];
|
|
21
|
+
this.#setActiveCell(active, { focus: false });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Keeps `aria-multiselectable` in step with the `selection` Value. Fires on connect
|
|
25
|
+
* (so it self-heals after a Turbo morph) and on any runtime change, so the ARIA
|
|
26
|
+
* never drifts from the selection logic, which reads `selectionValue` live.
|
|
27
|
+
*/
|
|
28
|
+
selectionValueChanged() {
|
|
29
|
+
this.#syncSelectable();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Mirrors `selection="multiple"` onto `aria-multiselectable` (APG Grid) so SRs
|
|
33
|
+
* announce that more than one row can be selected; cleared for single/none so a
|
|
34
|
+
* grid never carries a misleading attribute.
|
|
35
|
+
*/
|
|
36
|
+
#syncSelectable() {
|
|
37
|
+
if (this.selectionValue === "multiple") {
|
|
38
|
+
this.element.setAttribute("aria-multiselectable", "true");
|
|
39
|
+
} else {
|
|
40
|
+
this.element.removeAttribute("aria-multiselectable");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Cycles the activated column header's sort and emits `sort`. */
|
|
44
|
+
sort(event) {
|
|
45
|
+
const header = event.currentTarget;
|
|
46
|
+
if (!this.columnHeaderTargets.includes(header)) return;
|
|
47
|
+
const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
|
|
48
|
+
for (const other of this.columnHeaderTargets) {
|
|
49
|
+
other.setAttribute("aria-sort", other === header ? direction : "none");
|
|
50
|
+
}
|
|
51
|
+
this.#setActiveCell(header, { focus: false });
|
|
52
|
+
this.dispatch("sort", { detail: { column: header, direction } });
|
|
53
|
+
}
|
|
54
|
+
/** Toggles selection of the row owning the event target. Bound optionally. */
|
|
55
|
+
toggleSelect(event) {
|
|
56
|
+
const row = event.currentTarget.closest("[role='row']");
|
|
57
|
+
if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
|
|
58
|
+
}
|
|
59
|
+
/** Grid navigation + sort/select activation. Bound to cells and headers. */
|
|
60
|
+
onKeydown(event) {
|
|
61
|
+
const cell = event.currentTarget;
|
|
62
|
+
const matrix = this.#matrix();
|
|
63
|
+
const position = this.#locate(matrix, cell);
|
|
64
|
+
if (!position) return;
|
|
65
|
+
const [row, col] = position;
|
|
66
|
+
const rowCells = matrix[row] ?? [];
|
|
67
|
+
let target;
|
|
68
|
+
switch (event.key) {
|
|
69
|
+
case "ArrowRight":
|
|
70
|
+
target = this.#cellInRow(matrix, row, col + 1);
|
|
71
|
+
break;
|
|
72
|
+
case "ArrowLeft":
|
|
73
|
+
target = this.#cellInRow(matrix, row, Math.max(col - 1, 0));
|
|
74
|
+
break;
|
|
75
|
+
case "ArrowDown":
|
|
76
|
+
target = this.#cellInRow(matrix, Math.min(row + 1, matrix.length - 1), col);
|
|
77
|
+
break;
|
|
78
|
+
case "ArrowUp":
|
|
79
|
+
target = this.#cellInRow(matrix, Math.max(row - 1, 0), col);
|
|
80
|
+
break;
|
|
81
|
+
case "Home":
|
|
82
|
+
target = event.ctrlKey ? this.#cellInRow(matrix, 0, 0) : rowCells[0];
|
|
83
|
+
break;
|
|
84
|
+
case "End":
|
|
85
|
+
target = event.ctrlKey ? this.#cellInRow(matrix, matrix.length - 1, Number.POSITIVE_INFINITY) : rowCells[rowCells.length - 1];
|
|
86
|
+
break;
|
|
87
|
+
case "Enter":
|
|
88
|
+
case " ":
|
|
89
|
+
this.#activate(cell);
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
return;
|
|
92
|
+
default:
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (target) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
this.#setActiveCell(target, { focus: true });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Performs a header's sort or a cell row's selection toggle on activation. */
|
|
101
|
+
#activate(cell) {
|
|
102
|
+
if (this.columnHeaderTargets.includes(cell)) {
|
|
103
|
+
this.#cycleSort(cell);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.selectionValue === "none") return;
|
|
107
|
+
const row = cell.closest("[role='row']");
|
|
108
|
+
if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
|
|
109
|
+
}
|
|
110
|
+
/** Shared sort logic for both click and keyboard activation. */
|
|
111
|
+
#cycleSort(header) {
|
|
112
|
+
const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
|
|
113
|
+
for (const other of this.columnHeaderTargets) {
|
|
114
|
+
other.setAttribute("aria-sort", other === header ? direction : "none");
|
|
115
|
+
}
|
|
116
|
+
this.dispatch("sort", { detail: { column: header, direction } });
|
|
117
|
+
}
|
|
118
|
+
/** Toggles a row's `aria-selected`, honoring single vs. multiple selection. */
|
|
119
|
+
#toggleRow(row) {
|
|
120
|
+
const selected = row.getAttribute("aria-selected") === "true";
|
|
121
|
+
if (this.selectionValue === "single" && !selected) {
|
|
122
|
+
for (const other of this.rowTargets) other.setAttribute("aria-selected", "false");
|
|
123
|
+
}
|
|
124
|
+
row.setAttribute("aria-selected", selected ? "false" : "true");
|
|
125
|
+
const rows = this.rowTargets.filter((r) => r.getAttribute("aria-selected") === "true");
|
|
126
|
+
this.dispatch("selectionchange", { detail: { rows } });
|
|
127
|
+
}
|
|
128
|
+
/** Makes `cell` the single tabbable cell (roving) and optionally focuses it. */
|
|
129
|
+
#setActiveCell(cell, { focus }) {
|
|
130
|
+
if (!cell) return;
|
|
131
|
+
for (const candidate of this.#navigableCells()) {
|
|
132
|
+
candidate.tabIndex = candidate === cell ? 0 : -1;
|
|
133
|
+
}
|
|
134
|
+
if (focus) cell.focus();
|
|
135
|
+
}
|
|
136
|
+
/** All navigable elements (headers + cells) in DOM order. */
|
|
137
|
+
#navigableCells() {
|
|
138
|
+
return this.#matrix().flat();
|
|
139
|
+
}
|
|
140
|
+
/** The grid as rows of navigable cells, derived from each `role="row"`. */
|
|
141
|
+
#matrix() {
|
|
142
|
+
const navigable = /* @__PURE__ */ new Set([...this.columnHeaderTargets, ...this.cellTargets]);
|
|
143
|
+
const rows = Array.from(this.element.querySelectorAll("[role='row']"));
|
|
144
|
+
return rows.map(
|
|
145
|
+
(row) => Array.from(row.children).filter(
|
|
146
|
+
(child) => navigable.has(child)
|
|
147
|
+
)
|
|
148
|
+
).filter((cells) => cells.length > 0);
|
|
149
|
+
}
|
|
150
|
+
/** Finds `[rowIndex, colIndex]` of `cell` within `matrix`, or null. */
|
|
151
|
+
#locate(matrix, cell) {
|
|
152
|
+
for (let row = 0; row < matrix.length; row++) {
|
|
153
|
+
const col = (matrix[row] ?? []).indexOf(cell);
|
|
154
|
+
if (col !== -1) return [row, col];
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/** The cell at `[row, col]`, clamping `col` to that row's last cell. */
|
|
159
|
+
#cellInRow(matrix, row, col) {
|
|
160
|
+
const cells = matrix[row];
|
|
161
|
+
if (!cells || cells.length === 0) return void 0;
|
|
162
|
+
return cells[Math.min(col, cells.length - 1)];
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export { DataGridController };
|
|
167
|
+
//# sourceMappingURL=data_grid_controller.js.map
|
|
168
|
+
//# sourceMappingURL=data_grid_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/controllers/data_grid_controller.ts"],"names":[],"mappings":"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","file":"data_grid_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\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 /** Establishes a single tab stop across all navigable cells/headers. */\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 }\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 }\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 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 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 switch (event.key) {\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 if (this.selectionValue === \"single\" && !selected) {\n for (const other of this.rowTargets) other.setAttribute(\"aria-selected\", \"false\");\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,87 @@
|
|
|
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 monthLabelTarget: HTMLElement;
|
|
59
|
+
readonly cellTargets: HTMLElement[];
|
|
60
|
+
readonly statusTarget: HTMLElement;
|
|
61
|
+
readonly startFieldTarget: HTMLInputElement;
|
|
62
|
+
readonly endFieldTarget: HTMLInputElement;
|
|
63
|
+
readonly hasMonthLabelTarget: boolean;
|
|
64
|
+
readonly hasStatusTarget: boolean;
|
|
65
|
+
readonly hasStartFieldTarget: boolean;
|
|
66
|
+
readonly hasEndFieldTarget: boolean;
|
|
67
|
+
minValue: string;
|
|
68
|
+
maxValue: string;
|
|
69
|
+
/** Seeds the range from any pre-filled hidden fields and renders the grid. */
|
|
70
|
+
connect(): void;
|
|
71
|
+
/** Cancels any pending deferred focus so it never fires on a detached element. */
|
|
72
|
+
disconnect(): void;
|
|
73
|
+
/** Navigates to the previous month. */
|
|
74
|
+
prev(event?: Event): void;
|
|
75
|
+
/** Navigates to the next month. */
|
|
76
|
+
next(event?: Event): void;
|
|
77
|
+
/** Confirms a range endpoint from a clicked cell. */
|
|
78
|
+
selectDate(event: Event): void;
|
|
79
|
+
/** Previews the range up to a hovered/focused cell while selecting. */
|
|
80
|
+
previewTo(event: Event): void;
|
|
81
|
+
/** Applies a named preset (`today` / `last7` / `last30` / `thisMonth`). */
|
|
82
|
+
applyPreset(event: Event): void;
|
|
83
|
+
/** Grid keyboard navigation, selection (Enter/Space), and Escape-to-cancel. */
|
|
84
|
+
onKeydown(event: KeyboardEvent): void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { DateRangePickerController };
|