@tamagui/focus-scope 2.7.7 → 3.0.0-beta.637.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/dist/cjs/FocusScope.cjs +346 -351
- package/dist/cjs/FocusScope.native.cjs +37 -0
- package/dist/cjs/FocusScope.native.js +21 -27
- package/dist/cjs/FocusScope.native.js.map +1 -1
- package/dist/cjs/FocusScopeController.cjs +48 -54
- package/dist/cjs/FocusScopeController.native.cjs +76 -0
- package/dist/cjs/FocusScopeController.native.js +50 -55
- package/dist/cjs/FocusScopeController.native.js.map +1 -1
- package/dist/cjs/index.cjs +10 -11
- package/dist/cjs/index.native.cjs +22 -0
- package/dist/cjs/index.native.js +10 -10
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/types.cjs +10 -11
- package/dist/cjs/types.native.cjs +19 -0
- package/dist/cjs/types.native.js +10 -10
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/FocusScope.mjs +331 -328
- package/dist/esm/FocusScope.mjs.map +1 -1
- package/dist/esm/FocusScope.native.js +6 -3
- package/dist/esm/FocusScope.native.js.map +1 -1
- package/dist/esm/FocusScopeController.mjs +29 -27
- package/dist/esm/FocusScopeController.mjs.map +1 -1
- package/dist/esm/FocusScopeController.native.js +32 -30
- package/dist/esm/FocusScopeController.native.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.mjs +3 -3
- package/dist/esm/index.native.js +3 -3
- package/dist/esm/types.mjs +0 -2
- package/dist/esm/types.native.js +0 -2
- package/dist/jsx/FocusScope.mjs +331 -328
- package/dist/jsx/FocusScope.mjs.map +1 -1
- package/dist/jsx/FocusScope.native.cjs +37 -0
- package/dist/jsx/FocusScope.native.js +21 -27
- package/dist/jsx/FocusScope.native.js.map +1 -1
- package/dist/jsx/FocusScopeController.mjs +29 -27
- package/dist/jsx/FocusScopeController.mjs.map +1 -1
- package/dist/jsx/FocusScopeController.native.cjs +76 -0
- package/dist/jsx/FocusScopeController.native.js +50 -55
- package/dist/jsx/FocusScopeController.native.js.map +1 -1
- package/dist/jsx/index.js +3 -3
- package/dist/jsx/index.mjs +3 -3
- package/dist/jsx/index.native.cjs +22 -0
- package/dist/jsx/index.native.js +10 -10
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/types.mjs +0 -2
- package/dist/jsx/types.native.cjs +19 -0
- package/dist/jsx/types.native.js +10 -10
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +9 -8
- package/src/FocusScope.native.tsx +2 -1
- package/src/FocusScope.tsx +62 -28
- package/src/FocusScopeController.tsx +3 -0
- package/src/types.tsx +16 -11
- package/types/FocusScope.d.ts +3 -4
- package/types/FocusScope.d.ts.map +1 -1
- package/types/FocusScope.native.d.ts +1 -2
- package/types/FocusScope.native.d.ts.map +1 -1
- package/types/FocusScopeController.d.ts +1 -1
- package/types/FocusScopeController.d.ts.map +1 -1
- package/types/types.d.ts +15 -9
- package/types/types.d.ts.map +1 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/index.native.js.map +0 -1
- package/dist/esm/types.mjs.map +0 -1
- package/dist/esm/types.native.js.map +0 -1
- package/dist/jsx/index.js.map +0 -1
- package/dist/jsx/index.mjs.map +0 -1
- package/dist/jsx/types.mjs.map +0 -1
package/dist/esm/FocusScope.mjs
CHANGED
|
@@ -1,358 +1,361 @@
|
|
|
1
|
-
import { useComposedRefs } from "@tamagui/compose-refs";
|
|
1
|
+
import { createRefComponent, useComposedRefs } from "@tamagui/compose-refs";
|
|
2
2
|
import { isWeb, useIsomorphicLayoutEffect } from "@tamagui/constants";
|
|
3
|
+
import { createChangeEventDetails } from "@tamagui/core";
|
|
3
4
|
import { idle, useAsyncEffect } from "@tamagui/use-async";
|
|
4
5
|
import { useEvent } from "@tamagui/use-event";
|
|
5
6
|
import * as React from "react";
|
|
6
7
|
import { useFocusScopeControllerContext } from "./FocusScopeController.mjs";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
children: mergedProps.children(childProps)
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return React.cloneElement(React.Children.only(mergedProps.children), childProps);
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
const FOCUS_SCOPE_STYLE = { display: "contents" };
|
|
11
|
+
const FocusScope = createRefComponent(function FocusScope2({ __scopeFocusScope, ...props }, forwardedRef) {
|
|
12
|
+
const context = useFocusScopeControllerContext("FocusScope", __scopeFocusScope, {
|
|
13
|
+
warn: false,
|
|
14
|
+
fallback: {}
|
|
15
|
+
});
|
|
16
|
+
const mergedProps = {
|
|
17
|
+
...props,
|
|
18
|
+
enabled: context.enabled ?? props.enabled,
|
|
19
|
+
loop: context.loop ?? props.loop,
|
|
20
|
+
trapped: context.trapped ?? props.trapped,
|
|
21
|
+
noFocus: context.noFocus ?? props.noFocus,
|
|
22
|
+
onMountAutoFocus: context.onMountAutoFocus ?? props.onMountAutoFocus,
|
|
23
|
+
onUnmountAutoFocus: context.onUnmountAutoFocus ?? props.onUnmountAutoFocus,
|
|
24
|
+
forceUnmount: context.forceUnmount ?? props.forceUnmount,
|
|
25
|
+
focusOnIdle: context.focusOnIdle ?? props.focusOnIdle
|
|
26
|
+
};
|
|
27
|
+
const childProps = useFocusScope(mergedProps, forwardedRef);
|
|
28
|
+
const { ref, style, ...scopeProps } = childProps;
|
|
29
|
+
const children = mergedProps.children;
|
|
30
|
+
return /* @__PURE__ */ jsx("div", {
|
|
31
|
+
...scopeProps,
|
|
32
|
+
ref,
|
|
33
|
+
style: getFocusScopeStyle(style),
|
|
34
|
+
children
|
|
35
|
+
});
|
|
39
36
|
});
|
|
37
|
+
function getFocusScopeStyle(style) {
|
|
38
|
+
if (!style) return FOCUS_SCOPE_STYLE;
|
|
39
|
+
return {
|
|
40
|
+
...style,
|
|
41
|
+
display: "contents"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
40
44
|
function setupFocusTrap(container, lastFocusedElementRef, focusScope) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
document.removeEventListener("focusout", handleFocusOut);
|
|
89
|
-
};
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
let rafId = null;
|
|
47
|
+
function scheduleRefocus() {
|
|
48
|
+
if (rafId) return;
|
|
49
|
+
const elementToFocus = lastFocusedElementRef.current;
|
|
50
|
+
rafId = requestAnimationFrame(() => {
|
|
51
|
+
rafId = null;
|
|
52
|
+
if (focusScope.paused) return;
|
|
53
|
+
if (focusScope.stopped) return;
|
|
54
|
+
if (!container.isConnected) return;
|
|
55
|
+
if (!container.contains(document.activeElement)) {
|
|
56
|
+
focus(elementToFocus);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function handleFocusIn(event) {
|
|
61
|
+
if (focusScope.paused) return;
|
|
62
|
+
const target = event.target;
|
|
63
|
+
if (container.contains(target)) {
|
|
64
|
+
target?.addEventListener("blur", handleBlur, { signal: controller.signal });
|
|
65
|
+
lastFocusedElementRef.current = target;
|
|
66
|
+
} else if (target?.contains(container)) {} else {
|
|
67
|
+
scheduleRefocus();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function handleFocusOut(event) {
|
|
71
|
+
controller.abort();
|
|
72
|
+
if (focusScope.paused) return;
|
|
73
|
+
const relatedTarget = event.relatedTarget;
|
|
74
|
+
if (relatedTarget === null) return;
|
|
75
|
+
if (relatedTarget.contains(container)) return;
|
|
76
|
+
if (!container.contains(relatedTarget)) {
|
|
77
|
+
scheduleRefocus();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function handleBlur() {}
|
|
81
|
+
document.addEventListener("focusin", handleFocusIn);
|
|
82
|
+
document.addEventListener("focusout", handleFocusOut);
|
|
83
|
+
return () => {
|
|
84
|
+
if (rafId) {
|
|
85
|
+
cancelAnimationFrame(rafId);
|
|
86
|
+
rafId = null;
|
|
87
|
+
}
|
|
88
|
+
controller.abort();
|
|
89
|
+
document.removeEventListener("focusin", handleFocusIn);
|
|
90
|
+
document.removeEventListener("focusout", handleFocusOut);
|
|
91
|
+
};
|
|
90
92
|
}
|
|
91
93
|
function useFocusScope(props, forwardedRef) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
94
|
+
const { loop = false, enabled = true, trapped: trappedProp = false, noFocus = false, onMountAutoFocus: onMountAutoFocusProp, onUnmountAutoFocus: onUnmountAutoFocusProp, forceUnmount, focusOnIdle = true, children: _children, asChild: _asChild, ...scopeProps } = props;
|
|
95
|
+
const trapped = noFocus ? false : trappedProp;
|
|
96
|
+
const [container, setContainer] = React.useState(null);
|
|
97
|
+
const containerRef = React.useRef(null);
|
|
98
|
+
const onMountAutoFocus = useEvent(onMountAutoFocusProp);
|
|
99
|
+
const onUnmountAutoFocus = useEvent(onUnmountAutoFocusProp);
|
|
100
|
+
const lastFocusedElementRef = React.useRef(null);
|
|
101
|
+
const focusScopeRef = React.useRef({
|
|
102
|
+
paused: false,
|
|
103
|
+
stopped: false,
|
|
104
|
+
pause() {
|
|
105
|
+
this.paused = true;
|
|
106
|
+
},
|
|
107
|
+
resume() {
|
|
108
|
+
this.paused = false;
|
|
109
|
+
},
|
|
110
|
+
stop() {
|
|
111
|
+
this.stopped = true;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const focusScope = focusScopeRef.current;
|
|
115
|
+
const trapCleanupRef = React.useRef(null);
|
|
116
|
+
const setContainerRef = React.useCallback((node) => {
|
|
117
|
+
if (trapCleanupRef.current) {
|
|
118
|
+
trapCleanupRef.current();
|
|
119
|
+
trapCleanupRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
containerRef.current = node;
|
|
122
|
+
if (isWeb && node && enabled && trapped) {
|
|
123
|
+
trapCleanupRef.current = setupFocusTrap(node, lastFocusedElementRef, focusScopeRef.current);
|
|
124
|
+
}
|
|
125
|
+
setContainer(node);
|
|
126
|
+
}, [enabled, trapped]);
|
|
127
|
+
const composedRefs = useComposedRefs(forwardedRef, setContainerRef);
|
|
128
|
+
useIsomorphicLayoutEffect(() => {
|
|
129
|
+
if (!trapped) {
|
|
130
|
+
focusScope.stopped = true;
|
|
131
|
+
if (trapCleanupRef.current) {
|
|
132
|
+
trapCleanupRef.current();
|
|
133
|
+
trapCleanupRef.current = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return () => {
|
|
137
|
+
if (trapCleanupRef.current) {
|
|
138
|
+
trapCleanupRef.current();
|
|
139
|
+
trapCleanupRef.current = null;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}, [trapped, focusScope]);
|
|
143
|
+
useIsomorphicLayoutEffect(() => {
|
|
144
|
+
if (!isWeb) return;
|
|
145
|
+
if (!enabled || !noFocus || !container) return;
|
|
146
|
+
const blurActiveElement = () => {
|
|
147
|
+
const active = document.activeElement;
|
|
148
|
+
if (active && active !== document.body && typeof active.blur === "function") {
|
|
149
|
+
active.blur();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
blurActiveElement();
|
|
153
|
+
document.addEventListener("focusin", blurActiveElement, true);
|
|
154
|
+
return () => {
|
|
155
|
+
document.removeEventListener("focusin", blurActiveElement, true);
|
|
156
|
+
};
|
|
157
|
+
}, [
|
|
158
|
+
enabled,
|
|
159
|
+
noFocus,
|
|
160
|
+
container
|
|
161
|
+
]);
|
|
162
|
+
useAsyncEffect(async (signal) => {
|
|
163
|
+
if (!enabled) return;
|
|
164
|
+
if (!container) return;
|
|
165
|
+
if (forceUnmount) return;
|
|
166
|
+
if (noFocus) return;
|
|
167
|
+
if (trapped) {
|
|
168
|
+
focusScope.stopped = false;
|
|
169
|
+
}
|
|
170
|
+
focusScopesStack.add(focusScope);
|
|
171
|
+
const previouslyFocusedElement = document.activeElement;
|
|
172
|
+
const hasFocusedCandidate = container.contains(previouslyFocusedElement) && previouslyFocusedElement !== null && !isHidden(previouslyFocusedElement, { upTo: container });
|
|
173
|
+
if (!hasFocusedCandidate) {
|
|
174
|
+
const details = createChangeEventDetails("trigger-focus");
|
|
175
|
+
onMountAutoFocus?.(details);
|
|
176
|
+
if (!details.isCanceled) {
|
|
177
|
+
if (focusOnIdle) {
|
|
178
|
+
await idle(signal, typeof focusOnIdle == "object" ? focusOnIdle : {
|
|
179
|
+
max: 200,
|
|
180
|
+
min: typeof focusOnIdle == "number" ? focusOnIdle : 16
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const allCandidates = getTabbableCandidates(container);
|
|
184
|
+
const linkedRemoved = removeLinks(allCandidates);
|
|
185
|
+
const visibleCandidates = linkedRemoved.filter((candidate) => !isHidden(candidate, { upTo: container }));
|
|
186
|
+
focusFirst(visibleCandidates, { select: true });
|
|
187
|
+
if (visibleCandidates.length > 0) {
|
|
188
|
+
lastFocusedElementRef.current = visibleCandidates[0];
|
|
189
|
+
} else {
|
|
190
|
+
lastFocusedElementRef.current = container;
|
|
191
|
+
}
|
|
192
|
+
if (document.activeElement === previouslyFocusedElement && visibleCandidates.length === 0) {
|
|
193
|
+
focus(container);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return () => {
|
|
198
|
+
focusScope.stop();
|
|
199
|
+
const details = createChangeEventDetails("focus-out");
|
|
200
|
+
onUnmountAutoFocus?.(details);
|
|
201
|
+
if (!details.isCanceled) {
|
|
202
|
+
const currentFocus = document.activeElement;
|
|
203
|
+
const focusHasMovedOutside = currentFocus && currentFocus !== document.body && currentFocus !== container && !container.contains(currentFocus);
|
|
204
|
+
if (!focusHasMovedOutside) {
|
|
205
|
+
focus(previouslyFocusedElement ?? document.body, { select: true });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
focusScopesStack.remove(focusScope);
|
|
209
|
+
};
|
|
210
|
+
}, [
|
|
211
|
+
enabled,
|
|
212
|
+
container,
|
|
213
|
+
forceUnmount,
|
|
214
|
+
noFocus,
|
|
215
|
+
onMountAutoFocus,
|
|
216
|
+
onUnmountAutoFocus,
|
|
217
|
+
focusScope,
|
|
218
|
+
focusOnIdle
|
|
219
|
+
]);
|
|
220
|
+
const handleKeyDown = React.useCallback((event) => {
|
|
221
|
+
if (!trapped) return;
|
|
222
|
+
if (!loop) return;
|
|
223
|
+
if (focusScope.paused) return;
|
|
224
|
+
if (!enabled) return;
|
|
225
|
+
if (!container) return;
|
|
226
|
+
const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
227
|
+
const focusedElement = document.activeElement;
|
|
228
|
+
if (isTabKey && focusedElement) {
|
|
229
|
+
const [first, last] = getTabbableEdges(container);
|
|
230
|
+
const hasTabbableElementsInside = first && last;
|
|
231
|
+
if (!hasTabbableElementsInside) {
|
|
232
|
+
if (focusedElement === container) event.preventDefault();
|
|
233
|
+
} else {
|
|
234
|
+
if (!event.shiftKey && focusedElement === last) {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
if (loop) focus(first, { select: true });
|
|
237
|
+
} else if (event.shiftKey && focusedElement === first) {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
if (loop) focus(last, { select: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}, [
|
|
244
|
+
loop,
|
|
245
|
+
trapped,
|
|
246
|
+
focusScope.paused,
|
|
247
|
+
enabled,
|
|
248
|
+
container
|
|
249
|
+
]);
|
|
250
|
+
React.useEffect(() => {
|
|
251
|
+
if (!container) return;
|
|
252
|
+
if (!trapped) return;
|
|
253
|
+
if (!loop) return;
|
|
254
|
+
if (!enabled) return;
|
|
255
|
+
const handleKeyDownCapture = (event) => {
|
|
256
|
+
if (event.key === "Tab") {
|
|
257
|
+
handleKeyDown(event);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
container.addEventListener("keydown", handleKeyDownCapture, true);
|
|
261
|
+
return () => {
|
|
262
|
+
container.removeEventListener("keydown", handleKeyDownCapture, true);
|
|
263
|
+
};
|
|
264
|
+
}, [
|
|
265
|
+
container,
|
|
266
|
+
trapped,
|
|
267
|
+
loop,
|
|
268
|
+
enabled,
|
|
269
|
+
handleKeyDown
|
|
270
|
+
]);
|
|
271
|
+
const existingOnKeyDown = scopeProps.onKeyDown;
|
|
272
|
+
const composedOnKeyDown = React.useCallback((event) => {
|
|
273
|
+
existingOnKeyDown?.(event);
|
|
274
|
+
}, [existingOnKeyDown]);
|
|
275
|
+
return {
|
|
276
|
+
...scopeProps,
|
|
277
|
+
ref: composedRefs,
|
|
278
|
+
onKeyDown: composedOnKeyDown
|
|
279
|
+
};
|
|
264
280
|
}
|
|
265
|
-
function focusFirst(candidates, {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
select
|
|
272
|
-
});
|
|
273
|
-
if (document.activeElement !== previouslyFocusedElement) return;
|
|
274
|
-
}
|
|
281
|
+
function focusFirst(candidates, { select = false } = {}) {
|
|
282
|
+
const previouslyFocusedElement = document.activeElement;
|
|
283
|
+
for (const candidate of candidates) {
|
|
284
|
+
focus(candidate, { select });
|
|
285
|
+
if (document.activeElement !== previouslyFocusedElement) return;
|
|
286
|
+
}
|
|
275
287
|
}
|
|
276
288
|
function getTabbableEdges(container) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
const candidates = getTabbableCandidates(container);
|
|
290
|
+
const first = findVisible(candidates, container);
|
|
291
|
+
const last = findVisible(candidates.reverse(), container);
|
|
292
|
+
return [first, last];
|
|
281
293
|
}
|
|
282
294
|
function getTabbableCandidates(container) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
292
|
-
return nodes;
|
|
295
|
+
const nodes = [];
|
|
296
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, { acceptNode: (node) => {
|
|
297
|
+
const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
|
|
298
|
+
if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;
|
|
299
|
+
return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
300
|
+
} });
|
|
301
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
302
|
+
return nodes;
|
|
293
303
|
}
|
|
294
304
|
function findVisible(elements, container) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
})) return element;
|
|
299
|
-
}
|
|
305
|
+
for (const element of elements) {
|
|
306
|
+
if (!isHidden(element, { upTo: container })) return element;
|
|
307
|
+
}
|
|
300
308
|
}
|
|
301
|
-
function isHidden(node, {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
return false;
|
|
309
|
+
function isHidden(node, { upTo }) {
|
|
310
|
+
if (getComputedStyle(node).visibility === "hidden") return true;
|
|
311
|
+
while (node) {
|
|
312
|
+
if (upTo !== void 0 && node === upTo) return false;
|
|
313
|
+
if (getComputedStyle(node).display === "none") return true;
|
|
314
|
+
node = node.parentElement;
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
311
317
|
}
|
|
312
318
|
function isSelectableInput(element) {
|
|
313
|
-
|
|
319
|
+
return element instanceof HTMLInputElement && "select" in element;
|
|
314
320
|
}
|
|
315
|
-
function focus(element, {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
});
|
|
324
|
-
if (element !== previouslyFocusedElement && isSelectableInput(element) && select) element.select();
|
|
325
|
-
} catch (error) {}
|
|
326
|
-
}
|
|
321
|
+
function focus(element, { select = false } = {}) {
|
|
322
|
+
if (element?.focus) {
|
|
323
|
+
const previouslyFocusedElement = document.activeElement;
|
|
324
|
+
try {
|
|
325
|
+
element.focus({ preventScroll: true });
|
|
326
|
+
if (element !== previouslyFocusedElement && isSelectableInput(element) && select) element.select();
|
|
327
|
+
} catch (error) {}
|
|
328
|
+
}
|
|
327
329
|
}
|
|
328
330
|
const focusScopesStack = createFocusScopesStack();
|
|
329
331
|
function createFocusScopesStack() {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
332
|
+
let stack = [];
|
|
333
|
+
return {
|
|
334
|
+
add(focusScope) {
|
|
335
|
+
const activeFocusScope = stack[0];
|
|
336
|
+
if (focusScope !== activeFocusScope) {
|
|
337
|
+
activeFocusScope?.pause();
|
|
338
|
+
}
|
|
339
|
+
stack = arrayRemove(stack, focusScope);
|
|
340
|
+
stack.unshift(focusScope);
|
|
341
|
+
},
|
|
342
|
+
remove(focusScope) {
|
|
343
|
+
stack = arrayRemove(stack, focusScope);
|
|
344
|
+
stack[0]?.resume();
|
|
345
|
+
}
|
|
346
|
+
};
|
|
345
347
|
}
|
|
346
348
|
function arrayRemove(array, item) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
const updatedArray = [...array];
|
|
350
|
+
const index = updatedArray.indexOf(item);
|
|
351
|
+
if (index !== -1) {
|
|
352
|
+
updatedArray.splice(index, 1);
|
|
353
|
+
}
|
|
354
|
+
return updatedArray;
|
|
353
355
|
}
|
|
354
356
|
function removeLinks(items) {
|
|
355
|
-
|
|
357
|
+
return items.filter((item) => item.tagName !== "A");
|
|
356
358
|
}
|
|
359
|
+
|
|
357
360
|
export { FocusScope, useFocusScope };
|
|
358
361
|
//# sourceMappingURL=FocusScope.mjs.map
|