@mortenbrudvik/waveui 0.9.0 → 0.10.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.
- package/README.md +425 -104
- package/dist/components/input/Calendar.cjs +2 -0
- package/dist/components/input/Calendar.cjs.map +1 -0
- package/dist/components/input/Calendar.mjs +352 -0
- package/dist/components/input/Calendar.mjs.map +1 -0
- package/dist/components/input/Calendar.views.cjs +2 -0
- package/dist/components/input/Calendar.views.cjs.map +1 -0
- package/dist/components/input/Calendar.views.mjs +194 -0
- package/dist/components/input/Calendar.views.mjs.map +1 -0
- package/dist/components/input/ColorArea.cjs +1 -1
- package/dist/components/input/ColorArea.cjs.map +1 -1
- package/dist/components/input/ColorArea.mjs +18 -18
- package/dist/components/input/ColorArea.mjs.map +1 -1
- package/dist/components/input/Combobox.cjs +1 -1
- package/dist/components/input/Combobox.cjs.map +1 -1
- package/dist/components/input/Combobox.mjs +232 -156
- package/dist/components/input/Combobox.mjs.map +1 -1
- package/dist/components/input/Combobox.text.cjs +2 -0
- package/dist/components/input/Combobox.text.cjs.map +1 -0
- package/dist/components/input/Combobox.text.mjs +24 -0
- package/dist/components/input/Combobox.text.mjs.map +1 -0
- package/dist/components/input/DatePicker.cjs +1 -1
- package/dist/components/input/DatePicker.cjs.map +1 -1
- package/dist/components/input/DatePicker.mjs +203 -288
- package/dist/components/input/DatePicker.mjs.map +1 -1
- package/dist/components/input/Dropdown.cjs +1 -1
- package/dist/components/input/Dropdown.cjs.map +1 -1
- package/dist/components/input/Dropdown.mjs +135 -109
- package/dist/components/input/Dropdown.mjs.map +1 -1
- package/dist/components/input/Listbox.cjs +2 -0
- package/dist/components/input/Listbox.cjs.map +1 -0
- package/dist/components/input/Listbox.mjs +134 -0
- package/dist/components/input/Listbox.mjs.map +1 -0
- package/dist/components/input/Option.cjs +1 -1
- package/dist/components/input/Option.cjs.map +1 -1
- package/dist/components/input/Option.mjs +158 -97
- package/dist/components/input/Option.mjs.map +1 -1
- package/dist/components/input/SwatchPicker.cjs +1 -1
- package/dist/components/input/SwatchPicker.cjs.map +1 -1
- package/dist/components/input/SwatchPicker.context.cjs +2 -0
- package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
- package/dist/components/input/SwatchPicker.context.mjs +35 -0
- package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
- package/dist/components/input/SwatchPicker.mjs +131 -103
- package/dist/components/input/SwatchPicker.mjs.map +1 -1
- package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
- package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
- package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
- package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
- package/dist/components/input/TagPicker.cjs +1 -1
- package/dist/components/input/TagPicker.cjs.map +1 -1
- package/dist/components/input/TagPicker.mjs +144 -139
- package/dist/components/input/TagPicker.mjs.map +1 -1
- package/dist/components/input/TimePicker.cjs +1 -1
- package/dist/components/input/TimePicker.cjs.map +1 -1
- package/dist/components/input/TimePicker.mjs +77 -77
- package/dist/components/input/TimePicker.mjs.map +1 -1
- package/dist/components/input/dateUtils.cjs +1 -1
- package/dist/components/input/dateUtils.cjs.map +1 -1
- package/dist/components/input/dateUtils.mjs +114 -96
- package/dist/components/input/dateUtils.mjs.map +1 -1
- package/dist/components/input/pickerLabels.cjs +2 -0
- package/dist/components/input/pickerLabels.cjs.map +1 -0
- package/dist/components/input/pickerLabels.mjs +16 -0
- package/dist/components/input/pickerLabels.mjs.map +1 -0
- package/dist/components/input/pickerValues.cjs +2 -0
- package/dist/components/input/pickerValues.cjs.map +1 -0
- package/dist/components/input/pickerValues.mjs +24 -0
- package/dist/components/input/pickerValues.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +1 -1
- package/dist/components/table/useGridNavigation.cjs.map +1 -1
- package/dist/components/table/useGridNavigation.mjs +67 -70
- package/dist/components/table/useGridNavigation.mjs.map +1 -1
- package/dist/hooks/useActiveDescendant.cjs +2 -0
- package/dist/hooks/useActiveDescendant.cjs.map +1 -0
- package/dist/hooks/useActiveDescendant.mjs +56 -0
- package/dist/hooks/useActiveDescendant.mjs.map +1 -0
- package/dist/hooks/useListbox.cjs +1 -1
- package/dist/hooks/useListbox.cjs.map +1 -1
- package/dist/hooks/useListbox.mjs +250 -210
- package/dist/hooks/useListbox.mjs.map +1 -1
- package/dist/hooks/useRovingGrid.cjs +2 -0
- package/dist/hooks/useRovingGrid.cjs.map +1 -0
- package/dist/hooks/useRovingGrid.mjs +69 -0
- package/dist/hooks/useRovingGrid.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2147 -155
- package/dist/index.d.ts +2147 -155
- package/dist/index.mjs +107 -101
- package/dist/lib/classNames.cjs +1 -1
- package/dist/lib/classNames.cjs.map +1 -1
- package/dist/lib/classNames.mjs +84 -48
- package/dist/lib/classNames.mjs.map +1 -1
- package/dist/lib/date.cjs +2 -0
- package/dist/lib/date.cjs.map +1 -0
- package/dist/lib/date.mjs +44 -0
- package/dist/lib/date.mjs.map +1 -0
- package/dist/lib/focus.cjs +1 -1
- package/dist/lib/focus.cjs.map +1 -1
- package/dist/lib/focus.mjs +5 -2
- package/dist/lib/focus.mjs.map +1 -1
- package/dist/lib/layers.cjs.map +1 -1
- package/dist/lib/layers.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { getArrowIntent as e, getDirection as t } from "../../lib/direction.mjs";
|
|
3
|
-
import { FOCUSABLE_SELECTOR as n, getFirstTabbable as r, isFocusable as i } from "../../lib/focus.mjs";
|
|
4
|
-
import * as
|
|
3
|
+
import { FOCUSABLE_SELECTOR as n, getFirstTabbable as r, isFocusable as i, isFocused as a } from "../../lib/focus.mjs";
|
|
4
|
+
import * as o from "react";
|
|
5
5
|
//#region src/components/table/useGridNavigation.ts
|
|
6
|
-
var
|
|
6
|
+
var s = /* @__PURE__ */ new Set([
|
|
7
7
|
"checkbox",
|
|
8
8
|
"radio",
|
|
9
9
|
"button",
|
|
@@ -12,13 +12,13 @@ var o = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"image",
|
|
13
13
|
"file",
|
|
14
14
|
"color"
|
|
15
|
-
]),
|
|
15
|
+
]), c = /* @__PURE__ */ new Set([
|
|
16
16
|
"textbox",
|
|
17
17
|
"searchbox",
|
|
18
18
|
"combobox",
|
|
19
19
|
"spinbutton",
|
|
20
20
|
"slider"
|
|
21
|
-
]),
|
|
21
|
+
]), l = ["[data-roving-container]", ...[
|
|
22
22
|
"toolbar",
|
|
23
23
|
"radiogroup",
|
|
24
24
|
"listbox",
|
|
@@ -28,7 +28,7 @@ var o = /* @__PURE__ */ new Set([
|
|
|
28
28
|
"menu",
|
|
29
29
|
"menubar",
|
|
30
30
|
"tree"
|
|
31
|
-
].map((e) => `[role="${e}"]`)].join(", "),
|
|
31
|
+
].map((e) => `[role="${e}"]`)].join(", "), u = `${n}, ${l}`, d = [
|
|
32
32
|
"disabled",
|
|
33
33
|
"tabindex",
|
|
34
34
|
"hidden",
|
|
@@ -39,31 +39,31 @@ var o = /* @__PURE__ */ new Set([
|
|
|
39
39
|
"data-roving-container",
|
|
40
40
|
"aria-selected"
|
|
41
41
|
];
|
|
42
|
-
function
|
|
42
|
+
function f(e) {
|
|
43
43
|
let t = e.localName;
|
|
44
44
|
if (t === "textarea" || t === "select") return !0;
|
|
45
|
-
if (t === "input") return !
|
|
45
|
+
if (t === "input") return !s.has(e.type);
|
|
46
46
|
let n = e.getAttribute("contenteditable");
|
|
47
47
|
if (n !== null && n !== "false") return !0;
|
|
48
48
|
let r = e.getAttribute("role");
|
|
49
|
-
return r !== null &&
|
|
49
|
+
return r !== null && c.has(r);
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
return e.matches(
|
|
51
|
+
function p(e) {
|
|
52
|
+
return e.matches(l);
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function m(e, t) {
|
|
55
55
|
if (e === t) return !1;
|
|
56
|
-
for (let n = e.parentElement; n && n !== t; n = n.parentElement) if (
|
|
56
|
+
for (let n = e.parentElement; n && n !== t; n = n.parentElement) if (p(n)) return !0;
|
|
57
57
|
return !1;
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
function h(e) {
|
|
60
60
|
let t = e.querySelector("[tabindex=\"0\"]");
|
|
61
61
|
return t && i(t) ? t : r(e, { includeContainer: !0 });
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function g(e) {
|
|
64
64
|
return e.localName === "input" && e.type === "hidden" || e.matches(":disabled") ? !1 : e.closest("[hidden], [inert]") === null;
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function _(e) {
|
|
67
67
|
if (!e.name) return !1;
|
|
68
68
|
let t = e.getRootNode();
|
|
69
69
|
if (typeof t.querySelectorAll != "function") return !1;
|
|
@@ -71,16 +71,13 @@ function g(e) {
|
|
|
71
71
|
for (let t of Array.from(n)) if (t !== e && t.name === e.name && t.form === e.form) return !0;
|
|
72
72
|
return !1;
|
|
73
73
|
}
|
|
74
|
-
function _(e) {
|
|
75
|
-
return e.localName === "td" || e.localName === "th";
|
|
76
|
-
}
|
|
77
74
|
function v(e) {
|
|
78
|
-
return e.
|
|
75
|
+
return e.localName === "td" || e.localName === "th";
|
|
79
76
|
}
|
|
80
77
|
function y(e, t) {
|
|
81
78
|
let n = t instanceof Element ? t : null;
|
|
82
79
|
for (; n && n !== e;) {
|
|
83
|
-
if (
|
|
80
|
+
if (v(n) && n.closest("table") === e) return n;
|
|
84
81
|
n = n.parentElement;
|
|
85
82
|
}
|
|
86
83
|
return null;
|
|
@@ -119,52 +116,52 @@ function C(e, t) {
|
|
|
119
116
|
}
|
|
120
117
|
function w(e, t, n) {
|
|
121
118
|
if (t instanceof Element) {
|
|
122
|
-
|
|
119
|
+
v(t) && t.closest("table") === e && n.add(t);
|
|
123
120
|
for (let r of Array.from(t.querySelectorAll("td, th"))) r.closest("table") === e && n.add(r);
|
|
124
121
|
}
|
|
125
122
|
}
|
|
126
123
|
function T(n = {}) {
|
|
127
|
-
let r = Math.max(1, n.pageSize ?? 10), [
|
|
128
|
-
|
|
124
|
+
let r = Math.max(1, n.pageSize ?? 10), [s, c] = o.useState(null), l = o.useRef(null), x = o.useRef(null), [T] = o.useState(() => /* @__PURE__ */ new WeakSet()), [E] = o.useState(() => /* @__PURE__ */ new WeakMap()), [D] = o.useState(() => /* @__PURE__ */ new WeakSet()), O = o.useRef(r);
|
|
125
|
+
o.useLayoutEffect(() => {
|
|
129
126
|
O.current = r;
|
|
130
127
|
}, [r]);
|
|
131
|
-
let k =
|
|
128
|
+
let k = o.useCallback((e, t) => {
|
|
132
129
|
let n = [], r = null;
|
|
133
|
-
for (let a of Array.from(e.querySelectorAll(
|
|
130
|
+
for (let a of Array.from(e.querySelectorAll(u))) {
|
|
134
131
|
if (r && r.contains(a)) continue;
|
|
135
|
-
if (
|
|
136
|
-
r = a, (t ?
|
|
132
|
+
if (p(a)) {
|
|
133
|
+
r = a, (t ? h(a) !== null : g(a)) && n.push({
|
|
137
134
|
element: a,
|
|
138
135
|
composite: !0
|
|
139
136
|
});
|
|
140
137
|
continue;
|
|
141
138
|
}
|
|
142
139
|
let e = a.getAttribute("tabindex");
|
|
143
|
-
e !== null && Number(e) < 0 && !T.has(a) || (t ? i(a) :
|
|
140
|
+
e !== null && Number(e) < 0 && !T.has(a) || (t ? i(a) : g(a)) && n.push({
|
|
144
141
|
element: a,
|
|
145
142
|
composite: !1
|
|
146
143
|
});
|
|
147
144
|
}
|
|
148
145
|
return n;
|
|
149
|
-
}, [T]), A =
|
|
146
|
+
}, [T]), A = o.useCallback((e, t, n) => {
|
|
150
147
|
if (t.length !== 1) return e;
|
|
151
148
|
let [{ element: r, composite: i }] = t;
|
|
152
|
-
return i ||
|
|
153
|
-
}, []), j =
|
|
149
|
+
return i || f(r) || n && r instanceof HTMLInputElement && r.type === "radio" && !r.checked && _(r) ? e : r;
|
|
150
|
+
}, []), j = o.useCallback((e, t) => {
|
|
154
151
|
let n = String(t);
|
|
155
152
|
e.getAttribute("tabindex") !== n && (E.set(e, n), e.setAttribute("tabindex", n)), T.add(e);
|
|
156
|
-
}, [T, E]), M =
|
|
153
|
+
}, [T, E]), M = o.useCallback((e, t, n) => {
|
|
157
154
|
let r = k(e, t);
|
|
158
155
|
if (n) {
|
|
159
156
|
D.delete(e), j(e, -1);
|
|
160
157
|
for (let e of r) e.composite || j(e.element, 0);
|
|
161
158
|
return;
|
|
162
159
|
}
|
|
163
|
-
let i = A(e, r, t),
|
|
164
|
-
i === e ? (D.delete(e), j(e, t ? 0 : -1)) : T.has(e) &&
|
|
160
|
+
let i = A(e, r, t), o = !1;
|
|
161
|
+
i === e ? (D.delete(e), j(e, t ? 0 : -1)) : T.has(e) && a(e) ? (o = !0, D.add(e), j(e, t ? 0 : -1)) : T.has(e) && (D.delete(e), T.delete(e), e.hasAttribute("tabindex") && (E.set(e, null), e.removeAttribute("tabindex")));
|
|
165
162
|
for (let e of r) {
|
|
166
163
|
if (e.composite) continue;
|
|
167
|
-
let n = e.element === i && t && !
|
|
164
|
+
let n = e.element === i && t && !o;
|
|
168
165
|
j(e.element, n ? 0 : -1);
|
|
169
166
|
}
|
|
170
167
|
}, [
|
|
@@ -174,9 +171,9 @@ function T(n = {}) {
|
|
|
174
171
|
j,
|
|
175
172
|
T,
|
|
176
173
|
E
|
|
177
|
-
]), N =
|
|
178
|
-
let n = b(e), r =
|
|
179
|
-
if ((!r || !r.isConnected || !n.includes(r.parentElement)) && (r = n[0]?.cells[0] ?? null,
|
|
174
|
+
]), N = o.useCallback((e, t) => {
|
|
175
|
+
let n = b(e), r = l.current, i = /* @__PURE__ */ new Set();
|
|
176
|
+
if ((!r || !r.isConnected || !n.includes(r.parentElement)) && (r = n[0]?.cells[0] ?? null, l.current = r, r && i.add(r)), x.current !== null && x.current !== r) {
|
|
180
177
|
let e = x.current;
|
|
181
178
|
x.current = null, i.add(e);
|
|
182
179
|
}
|
|
@@ -185,55 +182,55 @@ function T(n = {}) {
|
|
|
185
182
|
else for (let e of t) e && i.add(e);
|
|
186
183
|
for (let t of i) t.isConnected && y(e, t) === t && M(t, t === r, t === a);
|
|
187
184
|
}, [M]);
|
|
188
|
-
|
|
189
|
-
if (!
|
|
185
|
+
o.useLayoutEffect(() => {
|
|
186
|
+
if (!s || (N(s), typeof MutationObserver > "u")) return;
|
|
190
187
|
let e = new MutationObserver((e) => {
|
|
191
188
|
let t = !1, n = !1, r = /* @__PURE__ */ new Set();
|
|
192
189
|
for (let i of e) {
|
|
193
190
|
let e = i.target;
|
|
194
|
-
if (!(e instanceof Element) ||
|
|
191
|
+
if (!(e instanceof Element) || m(e, s)) continue;
|
|
195
192
|
if (i.type === "attributes") {
|
|
196
193
|
if (i.attributeName === "tabindex" && E.has(e) && E.get(e) === e.getAttribute("tabindex")) continue;
|
|
197
194
|
t = !0;
|
|
198
|
-
let a = y(
|
|
199
|
-
a ? r.add(a) : e.localName === "tr" ? w(
|
|
195
|
+
let a = y(s, e);
|
|
196
|
+
a ? r.add(a) : e.localName === "tr" ? w(s, e, r) : n = !0;
|
|
200
197
|
continue;
|
|
201
198
|
}
|
|
202
199
|
t = !0;
|
|
203
|
-
let a = y(
|
|
200
|
+
let a = y(s, e);
|
|
204
201
|
if (a) {
|
|
205
202
|
r.add(a);
|
|
206
203
|
continue;
|
|
207
204
|
}
|
|
208
|
-
for (let e of Array.from(i.addedNodes)) w(
|
|
205
|
+
for (let e of Array.from(i.addedNodes)) w(s, e, r);
|
|
209
206
|
}
|
|
210
|
-
t && (
|
|
207
|
+
t && (l.current && r.add(l.current), N(s, n ? void 0 : r));
|
|
211
208
|
});
|
|
212
|
-
return e.observe(
|
|
209
|
+
return e.observe(s, {
|
|
213
210
|
childList: !0,
|
|
214
211
|
subtree: !0,
|
|
215
212
|
attributes: !0,
|
|
216
|
-
attributeFilter:
|
|
213
|
+
attributeFilter: d
|
|
217
214
|
}), () => e.disconnect();
|
|
218
215
|
}, [
|
|
219
|
-
|
|
216
|
+
s,
|
|
220
217
|
N,
|
|
221
218
|
E
|
|
222
219
|
]);
|
|
223
|
-
let P =
|
|
224
|
-
let r =
|
|
225
|
-
|
|
220
|
+
let P = o.useCallback((e, t, n) => {
|
|
221
|
+
let r = l.current, i = x.current;
|
|
222
|
+
l.current = t, x.current = n ? t : null, N(e, [
|
|
226
223
|
r,
|
|
227
224
|
i,
|
|
228
225
|
t
|
|
229
226
|
]);
|
|
230
|
-
}, [N]), F =
|
|
227
|
+
}, [N]), F = o.useCallback((e, t) => {
|
|
231
228
|
P(e, t, !1), A(t, k(t, !0), !0).focus();
|
|
232
229
|
}, [
|
|
233
230
|
P,
|
|
234
231
|
A,
|
|
235
232
|
k
|
|
236
|
-
]), I =
|
|
233
|
+
]), I = o.useCallback((n) => {
|
|
237
234
|
let r = n.currentTarget;
|
|
238
235
|
if (n.defaultPrevented) return;
|
|
239
236
|
let i = y(r, n.target);
|
|
@@ -246,14 +243,14 @@ function T(n = {}) {
|
|
|
246
243
|
if (n.altKey || n.metaKey) return;
|
|
247
244
|
if ((n.key === "Enter" || n.key === "F2") && s) {
|
|
248
245
|
let e = null;
|
|
249
|
-
for (let t of a) if (e = t.composite ?
|
|
246
|
+
for (let t of a) if (e = t.composite ? h(t.element) : t.element, e) break;
|
|
250
247
|
if (!e) return;
|
|
251
248
|
n.preventDefault(), P(r, i, !0), e.focus();
|
|
252
249
|
return;
|
|
253
250
|
}
|
|
254
251
|
let c = b(r), l = c.findIndex((e) => e === i.parentElement);
|
|
255
252
|
if (l < 0) return;
|
|
256
|
-
let u = Array.from(c[l].cells).indexOf(i), d = c.length - 1, f = n.ctrlKey, p = null,
|
|
253
|
+
let u = Array.from(c[l].cells).indexOf(i), d = c.length - 1, f = n.ctrlKey, p = null, m = null;
|
|
257
254
|
switch (n.key) {
|
|
258
255
|
case "ArrowLeft":
|
|
259
256
|
case "ArrowRight":
|
|
@@ -264,10 +261,10 @@ function T(n = {}) {
|
|
|
264
261
|
}) === "next" ? 1 : -1)];
|
|
265
262
|
break;
|
|
266
263
|
case "ArrowDown":
|
|
267
|
-
f || (
|
|
264
|
+
f || (m = [l + 1, 1]);
|
|
268
265
|
break;
|
|
269
266
|
case "ArrowUp":
|
|
270
|
-
f || (
|
|
267
|
+
f || (m = [l - 1, -1]);
|
|
271
268
|
break;
|
|
272
269
|
case "Home":
|
|
273
270
|
p = f ? [0, 0] : [l, 0];
|
|
@@ -276,16 +273,16 @@ function T(n = {}) {
|
|
|
276
273
|
p = f ? [d, Infinity] : [l, Infinity];
|
|
277
274
|
break;
|
|
278
275
|
case "PageDown":
|
|
279
|
-
f || (
|
|
276
|
+
f || (m = [Math.min(l + O.current, d), 1]);
|
|
280
277
|
break;
|
|
281
|
-
case "PageUp": f || (
|
|
278
|
+
case "PageUp": f || (m = [Math.max(l - O.current, 0), -1]);
|
|
282
279
|
}
|
|
283
|
-
if (!p && !
|
|
280
|
+
if (!p && !m) return;
|
|
284
281
|
n.preventDefault();
|
|
285
282
|
let g = null;
|
|
286
|
-
if (
|
|
283
|
+
if (m) {
|
|
287
284
|
let e = S(c), t = Math.max(e[l].indexOf(i), 0);
|
|
288
|
-
for (let n =
|
|
285
|
+
for (let n = m[0]; n >= 0 && n <= d; n += m[1]) {
|
|
289
286
|
let r = C(e[n], t);
|
|
290
287
|
if (r && r !== i) {
|
|
291
288
|
g = r;
|
|
@@ -302,7 +299,7 @@ function T(n = {}) {
|
|
|
302
299
|
F,
|
|
303
300
|
A,
|
|
304
301
|
k
|
|
305
|
-
]), L =
|
|
302
|
+
]), L = o.useCallback((e) => {
|
|
306
303
|
let t = e.currentTarget, n = e.target, r = y(t, n);
|
|
307
304
|
if (!r) return;
|
|
308
305
|
let i = n !== r && n !== A(r, k(r, !0), !1);
|
|
@@ -311,15 +308,15 @@ function T(n = {}) {
|
|
|
311
308
|
P,
|
|
312
309
|
A,
|
|
313
310
|
k
|
|
314
|
-
]), R =
|
|
311
|
+
]), R = o.useCallback((e) => {
|
|
315
312
|
let t = e.currentTarget, n = e.target, r = [];
|
|
316
|
-
n instanceof Element &&
|
|
313
|
+
n instanceof Element && v(n) && D.has(n) && r.push(n);
|
|
317
314
|
let i = e.relatedTarget, a = x.current;
|
|
318
315
|
a !== null && !(i instanceof Node && t.contains(i)) && (x.current = null, r.push(a)), r.length > 0 && N(t, r);
|
|
319
316
|
}, [D, N]);
|
|
320
317
|
return {
|
|
321
|
-
gridRef:
|
|
322
|
-
gridProps:
|
|
318
|
+
gridRef: c,
|
|
319
|
+
gridProps: o.useMemo(() => ({
|
|
323
320
|
onKeyDown: I,
|
|
324
321
|
onFocus: L,
|
|
325
322
|
onBlur: R
|
|
@@ -331,6 +328,6 @@ function T(n = {}) {
|
|
|
331
328
|
};
|
|
332
329
|
}
|
|
333
330
|
//#endregion
|
|
334
|
-
export {
|
|
331
|
+
export { f as isTextEntryElement, T as useGridNavigation };
|
|
335
332
|
|
|
336
333
|
//# sourceMappingURL=useGridNavigation.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGridNavigation.mjs","names":["React","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","FOCUSABLE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isFocusable","getFirstTabbable","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","isFocused","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","getArrowIntent","getDirection","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** Whether `el` is the focused element of its document (or shadow root). */\nfunction isFocused(el: Element): boolean {\n return (el.getRootNode() as Partial<DocumentOrShadowRoot>).activeElement === el;\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useGridNavigation.mjs","names":["React","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","FOCUSABLE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isFocusable","getFirstTabbable","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isFocused","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","getArrowIntent","getDirection","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable, isFocused } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./useEventCallback.cjs");let n=require("react");n=e.__toESM(n,1);function r(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function i(e,t,n,r){let i=e.length;if(i===0)return null;let a=t===null?-1:e.indexOf(t);if(a===-1)return n>0?e[0]:e[i-1];let o=a+n;return(o<0||o>=i)&&(o=r&&Math.abs(n)===1?(o+i)%i:Math.max(0,Math.min(i-1,o))),e[o]}function a(e){let{items:a,getId:o,enabled:s=!0,fallback:c=null,loop:l=!1,activateFirstOnChange:u=!1,getElement:d,onActiveValueChange:f}=e,p=n.useMemo(()=>new Set(a),[a]),[m,h]=n.useState(null),g=s?m:null,[_,v]=n.useState(()=>({enabled:s,items:a}));if(u){let e=!r(_.items,a);(e||_.enabled!==s)&&(v({enabled:s,items:a}),s&&e&&(g=a[0]??null))}g!==null&&!p.has(g)&&(g=null),g!==m&&h(g);let y=s?g??(c!==null&&p.has(c)?c:null):null,b=y===null?void 0:o(y),x=n.useRef(null),S=t.useEventCallback((e,t)=>{x.current=t?.scroll===!1?e:null,h(e)}),C=t.useEventCallback(e=>{s&&p.has(e)&&e!==y&&(x.current=e,h(e))}),w=t.useEventCallback(()=>S(a[0]??null)),T=t.useEventCallback(()=>S(a[a.length-1]??null)),E=t.useEventCallback(()=>S(i(a,y,1,l))),D=t.useEventCallback(()=>S(i(a,y,-1,l))),O=t.useEventCallback(e=>S(i(a,y,e,!1))),k=t.useEventCallback(e=>d?.(e)||(typeof document>`u`?null:document.getElementById(o(e))));n.useLayoutEffect(()=>{let e=y!==null&&y===x.current;x.current=null,!(y===null||e)&&k(y)?.scrollIntoView?.({block:`nearest`})},[y,k]);let A=n.useRef(null),j=t.useEventCallback(e=>f?.(e));return n.useEffect(()=>{A.current!==y&&(A.current=y,j(y))},[y,j]),{activeValue:y,activeDescendantId:b,setActiveValue:S,highlight:C,first:w,last:T,next:E,prev:D,move:O}}exports.useActiveDescendant=a;
|
|
2
|
+
//# sourceMappingURL=useActiveDescendant.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActiveDescendant.cjs","names":["React","useEventCallback","require_runtime","require_useEventCallback","react","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEventCallback as e } from "./useEventCallback.mjs";
|
|
3
|
+
import * as t from "react";
|
|
4
|
+
//#region src/hooks/useActiveDescendant.ts
|
|
5
|
+
function n(e, t) {
|
|
6
|
+
return e === t || e.length === t.length && e.every((e, n) => e === t[n]);
|
|
7
|
+
}
|
|
8
|
+
function r(e, t, n, r) {
|
|
9
|
+
let i = e.length;
|
|
10
|
+
if (i === 0) return null;
|
|
11
|
+
let a = t === null ? -1 : e.indexOf(t);
|
|
12
|
+
if (a === -1) return n > 0 ? e[0] : e[i - 1];
|
|
13
|
+
let o = a + n;
|
|
14
|
+
return (o < 0 || o >= i) && (o = r && Math.abs(n) === 1 ? (o + i) % i : Math.max(0, Math.min(i - 1, o))), e[o];
|
|
15
|
+
}
|
|
16
|
+
function i(i) {
|
|
17
|
+
let { items: a, getId: o, enabled: s = !0, fallback: c = null, loop: l = !1, activateFirstOnChange: u = !1, getElement: d, onActiveValueChange: f } = i, p = t.useMemo(() => new Set(a), [a]), [m, h] = t.useState(null), g = s ? m : null, [_, v] = t.useState(() => ({
|
|
18
|
+
enabled: s,
|
|
19
|
+
items: a
|
|
20
|
+
}));
|
|
21
|
+
if (u) {
|
|
22
|
+
let e = !n(_.items, a);
|
|
23
|
+
(e || _.enabled !== s) && (v({
|
|
24
|
+
enabled: s,
|
|
25
|
+
items: a
|
|
26
|
+
}), s && e && (g = a[0] ?? null));
|
|
27
|
+
}
|
|
28
|
+
g !== null && !p.has(g) && (g = null), g !== m && h(g);
|
|
29
|
+
let y = s ? g ?? (c !== null && p.has(c) ? c : null) : null, b = y === null ? void 0 : o(y), x = t.useRef(null), S = e((e, t) => {
|
|
30
|
+
x.current = t?.scroll === !1 ? e : null, h(e);
|
|
31
|
+
}), C = e((e) => {
|
|
32
|
+
s && p.has(e) && e !== y && (x.current = e, h(e));
|
|
33
|
+
}), w = e(() => S(a[0] ?? null)), T = e(() => S(a[a.length - 1] ?? null)), E = e(() => S(r(a, y, 1, l))), D = e(() => S(r(a, y, -1, l))), O = e((e) => S(r(a, y, e, !1))), k = e((e) => d?.(e) || (typeof document > "u" ? null : document.getElementById(o(e))));
|
|
34
|
+
t.useLayoutEffect(() => {
|
|
35
|
+
let e = y !== null && y === x.current;
|
|
36
|
+
x.current = null, !(y === null || e) && k(y)?.scrollIntoView?.({ block: "nearest" });
|
|
37
|
+
}, [y, k]);
|
|
38
|
+
let A = t.useRef(null), j = e((e) => f?.(e));
|
|
39
|
+
return t.useEffect(() => {
|
|
40
|
+
A.current !== y && (A.current = y, j(y));
|
|
41
|
+
}, [y, j]), {
|
|
42
|
+
activeValue: y,
|
|
43
|
+
activeDescendantId: b,
|
|
44
|
+
setActiveValue: S,
|
|
45
|
+
highlight: C,
|
|
46
|
+
first: w,
|
|
47
|
+
last: T,
|
|
48
|
+
next: E,
|
|
49
|
+
prev: D,
|
|
50
|
+
move: O
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { i as useActiveDescendant };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=useActiveDescendant.mjs.map
|