@huin-core/react-focus-scope 1.0.1 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +246 -0
- package/dist/index.js.map +7 -0
- package/dist/index.mjs +214 -0
- package/dist/index.mjs.map +7 -0
- package/package.json +4 -4
package/dist/index.d.mts
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import { Primitive } from '@huin-core/react-primitive';
|
3
|
+
|
4
|
+
type PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Primitive.div>;
|
5
|
+
interface FocusScopeProps extends PrimitiveDivProps {
|
6
|
+
/**
|
7
|
+
* When `true`, tabbing from last item will focus first tabbable
|
8
|
+
* and shift+tab from first item will focus last tababble.
|
9
|
+
* @defaultValue false
|
10
|
+
*/
|
11
|
+
loop?: boolean;
|
12
|
+
/**
|
13
|
+
* When `true`, focus cannot escape the focus scope via keyboard,
|
14
|
+
* pointer, or a programmatic focus.
|
15
|
+
* @defaultValue false
|
16
|
+
*/
|
17
|
+
trapped?: boolean;
|
18
|
+
/**
|
19
|
+
* Event handler called when auto-focusing on mount.
|
20
|
+
* Can be prevented.
|
21
|
+
*/
|
22
|
+
onMountAutoFocus?: (event: Event) => void;
|
23
|
+
/**
|
24
|
+
* Event handler called when auto-focusing on unmount.
|
25
|
+
* Can be prevented.
|
26
|
+
*/
|
27
|
+
onUnmountAutoFocus?: (event: Event) => void;
|
28
|
+
}
|
29
|
+
declare const FocusScope: React.ForwardRefExoticComponent<FocusScopeProps & React.RefAttributes<HTMLDivElement>>;
|
30
|
+
declare const Root: React.ForwardRefExoticComponent<FocusScopeProps & React.RefAttributes<HTMLDivElement>>;
|
31
|
+
|
32
|
+
export { FocusScope, type FocusScopeProps, Root };
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import { Primitive } from '@huin-core/react-primitive';
|
3
|
+
|
4
|
+
type PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Primitive.div>;
|
5
|
+
interface FocusScopeProps extends PrimitiveDivProps {
|
6
|
+
/**
|
7
|
+
* When `true`, tabbing from last item will focus first tabbable
|
8
|
+
* and shift+tab from first item will focus last tababble.
|
9
|
+
* @defaultValue false
|
10
|
+
*/
|
11
|
+
loop?: boolean;
|
12
|
+
/**
|
13
|
+
* When `true`, focus cannot escape the focus scope via keyboard,
|
14
|
+
* pointer, or a programmatic focus.
|
15
|
+
* @defaultValue false
|
16
|
+
*/
|
17
|
+
trapped?: boolean;
|
18
|
+
/**
|
19
|
+
* Event handler called when auto-focusing on mount.
|
20
|
+
* Can be prevented.
|
21
|
+
*/
|
22
|
+
onMountAutoFocus?: (event: Event) => void;
|
23
|
+
/**
|
24
|
+
* Event handler called when auto-focusing on unmount.
|
25
|
+
* Can be prevented.
|
26
|
+
*/
|
27
|
+
onUnmountAutoFocus?: (event: Event) => void;
|
28
|
+
}
|
29
|
+
declare const FocusScope: React.ForwardRefExoticComponent<FocusScopeProps & React.RefAttributes<HTMLDivElement>>;
|
30
|
+
declare const Root: React.ForwardRefExoticComponent<FocusScopeProps & React.RefAttributes<HTMLDivElement>>;
|
31
|
+
|
32
|
+
export { FocusScope, type FocusScopeProps, Root };
|
package/dist/index.js
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use client";
|
3
|
+
var __create = Object.create;
|
4
|
+
var __defProp = Object.defineProperty;
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
9
|
+
var __export = (target, all) => {
|
10
|
+
for (var name in all)
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
12
|
+
};
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
15
|
+
for (let key of __getOwnPropNames(from))
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
18
|
+
}
|
19
|
+
return to;
|
20
|
+
};
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
27
|
+
mod
|
28
|
+
));
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
30
|
+
|
31
|
+
// packages/react/focus-scope/src/index.ts
|
32
|
+
var src_exports = {};
|
33
|
+
__export(src_exports, {
|
34
|
+
FocusScope: () => FocusScope,
|
35
|
+
Root: () => Root
|
36
|
+
});
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
38
|
+
|
39
|
+
// packages/react/focus-scope/src/FocusScope.tsx
|
40
|
+
var React = __toESM(require("react"));
|
41
|
+
var import_react_compose_refs = require("@huin-core/react-compose-refs");
|
42
|
+
var import_react_primitive = require("@huin-core/react-primitive");
|
43
|
+
var import_react_use_callback_ref = require("@huin-core/react-use-callback-ref");
|
44
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
45
|
+
var AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
|
46
|
+
var AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
|
47
|
+
var EVENT_OPTIONS = { bubbles: false, cancelable: true };
|
48
|
+
var FOCUS_SCOPE_NAME = "FocusScope";
|
49
|
+
var FocusScope = React.forwardRef((props, forwardedRef) => {
|
50
|
+
const {
|
51
|
+
loop = false,
|
52
|
+
trapped = false,
|
53
|
+
onMountAutoFocus: onMountAutoFocusProp,
|
54
|
+
onUnmountAutoFocus: onUnmountAutoFocusProp,
|
55
|
+
...scopeProps
|
56
|
+
} = props;
|
57
|
+
const [container, setContainer] = React.useState(null);
|
58
|
+
const onMountAutoFocus = (0, import_react_use_callback_ref.useCallbackRef)(onMountAutoFocusProp);
|
59
|
+
const onUnmountAutoFocus = (0, import_react_use_callback_ref.useCallbackRef)(onUnmountAutoFocusProp);
|
60
|
+
const lastFocusedElementRef = React.useRef(null);
|
61
|
+
const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, (node) => setContainer(node));
|
62
|
+
const focusScope = React.useRef({
|
63
|
+
paused: false,
|
64
|
+
pause() {
|
65
|
+
this.paused = true;
|
66
|
+
},
|
67
|
+
resume() {
|
68
|
+
this.paused = false;
|
69
|
+
}
|
70
|
+
}).current;
|
71
|
+
React.useEffect(() => {
|
72
|
+
if (trapped) {
|
73
|
+
let handleFocusIn2 = function(event) {
|
74
|
+
if (focusScope.paused || !container) return;
|
75
|
+
const target = event.target;
|
76
|
+
if (container.contains(target)) {
|
77
|
+
lastFocusedElementRef.current = target;
|
78
|
+
} else {
|
79
|
+
focus(lastFocusedElementRef.current, { select: true });
|
80
|
+
}
|
81
|
+
}, handleFocusOut2 = function(event) {
|
82
|
+
if (focusScope.paused || !container) return;
|
83
|
+
const relatedTarget = event.relatedTarget;
|
84
|
+
if (relatedTarget === null) return;
|
85
|
+
if (!container.contains(relatedTarget)) {
|
86
|
+
focus(lastFocusedElementRef.current, { select: true });
|
87
|
+
}
|
88
|
+
}, handleMutations2 = function(mutations) {
|
89
|
+
const focusedElement = document.activeElement;
|
90
|
+
if (focusedElement !== document.body) return;
|
91
|
+
for (const mutation of mutations) {
|
92
|
+
if (mutation.removedNodes.length > 0) focus(container);
|
93
|
+
}
|
94
|
+
};
|
95
|
+
var handleFocusIn = handleFocusIn2, handleFocusOut = handleFocusOut2, handleMutations = handleMutations2;
|
96
|
+
document.addEventListener("focusin", handleFocusIn2);
|
97
|
+
document.addEventListener("focusout", handleFocusOut2);
|
98
|
+
const mutationObserver = new MutationObserver(handleMutations2);
|
99
|
+
if (container) mutationObserver.observe(container, { childList: true, subtree: true });
|
100
|
+
return () => {
|
101
|
+
document.removeEventListener("focusin", handleFocusIn2);
|
102
|
+
document.removeEventListener("focusout", handleFocusOut2);
|
103
|
+
mutationObserver.disconnect();
|
104
|
+
};
|
105
|
+
}
|
106
|
+
}, [trapped, container, focusScope.paused]);
|
107
|
+
React.useEffect(() => {
|
108
|
+
if (container) {
|
109
|
+
focusScopesStack.add(focusScope);
|
110
|
+
const previouslyFocusedElement = document.activeElement;
|
111
|
+
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
|
112
|
+
if (!hasFocusedCandidate) {
|
113
|
+
const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
|
114
|
+
container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
|
115
|
+
container.dispatchEvent(mountEvent);
|
116
|
+
if (!mountEvent.defaultPrevented) {
|
117
|
+
focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
|
118
|
+
if (document.activeElement === previouslyFocusedElement) {
|
119
|
+
focus(container);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
return () => {
|
124
|
+
container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
|
125
|
+
setTimeout(() => {
|
126
|
+
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
|
127
|
+
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
|
128
|
+
container.dispatchEvent(unmountEvent);
|
129
|
+
if (!unmountEvent.defaultPrevented) {
|
130
|
+
focus(previouslyFocusedElement ?? document.body, { select: true });
|
131
|
+
}
|
132
|
+
container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
|
133
|
+
focusScopesStack.remove(focusScope);
|
134
|
+
}, 0);
|
135
|
+
};
|
136
|
+
}
|
137
|
+
}, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
|
138
|
+
const handleKeyDown = React.useCallback(
|
139
|
+
(event) => {
|
140
|
+
if (!loop && !trapped) return;
|
141
|
+
if (focusScope.paused) return;
|
142
|
+
const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
|
143
|
+
const focusedElement = document.activeElement;
|
144
|
+
if (isTabKey && focusedElement) {
|
145
|
+
const container2 = event.currentTarget;
|
146
|
+
const [first, last] = getTabbableEdges(container2);
|
147
|
+
const hasTabbableElementsInside = first && last;
|
148
|
+
if (!hasTabbableElementsInside) {
|
149
|
+
if (focusedElement === container2) event.preventDefault();
|
150
|
+
} else {
|
151
|
+
if (!event.shiftKey && focusedElement === last) {
|
152
|
+
event.preventDefault();
|
153
|
+
if (loop) focus(first, { select: true });
|
154
|
+
} else if (event.shiftKey && focusedElement === first) {
|
155
|
+
event.preventDefault();
|
156
|
+
if (loop) focus(last, { select: true });
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
},
|
161
|
+
[loop, trapped, focusScope.paused]
|
162
|
+
);
|
163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_primitive.Primitive.div, { tabIndex: -1, ...scopeProps, ref: composedRefs, onKeyDown: handleKeyDown });
|
164
|
+
});
|
165
|
+
FocusScope.displayName = FOCUS_SCOPE_NAME;
|
166
|
+
function focusFirst(candidates, { select = false } = {}) {
|
167
|
+
const previouslyFocusedElement = document.activeElement;
|
168
|
+
for (const candidate of candidates) {
|
169
|
+
focus(candidate, { select });
|
170
|
+
if (document.activeElement !== previouslyFocusedElement) return;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
function getTabbableEdges(container) {
|
174
|
+
const candidates = getTabbableCandidates(container);
|
175
|
+
const first = findVisible(candidates, container);
|
176
|
+
const last = findVisible(candidates.reverse(), container);
|
177
|
+
return [first, last];
|
178
|
+
}
|
179
|
+
function getTabbableCandidates(container) {
|
180
|
+
const nodes = [];
|
181
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
|
182
|
+
acceptNode: (node) => {
|
183
|
+
const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
|
184
|
+
if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;
|
185
|
+
return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
186
|
+
}
|
187
|
+
});
|
188
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
189
|
+
return nodes;
|
190
|
+
}
|
191
|
+
function findVisible(elements, container) {
|
192
|
+
for (const element of elements) {
|
193
|
+
if (!isHidden(element, { upTo: container })) return element;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
function isHidden(node, { upTo }) {
|
197
|
+
if (getComputedStyle(node).visibility === "hidden") return true;
|
198
|
+
while (node) {
|
199
|
+
if (upTo !== void 0 && node === upTo) return false;
|
200
|
+
if (getComputedStyle(node).display === "none") return true;
|
201
|
+
node = node.parentElement;
|
202
|
+
}
|
203
|
+
return false;
|
204
|
+
}
|
205
|
+
function isSelectableInput(element) {
|
206
|
+
return element instanceof HTMLInputElement && "select" in element;
|
207
|
+
}
|
208
|
+
function focus(element, { select = false } = {}) {
|
209
|
+
if (element && element.focus) {
|
210
|
+
const previouslyFocusedElement = document.activeElement;
|
211
|
+
element.focus({ preventScroll: true });
|
212
|
+
if (element !== previouslyFocusedElement && isSelectableInput(element) && select)
|
213
|
+
element.select();
|
214
|
+
}
|
215
|
+
}
|
216
|
+
var focusScopesStack = createFocusScopesStack();
|
217
|
+
function createFocusScopesStack() {
|
218
|
+
let stack = [];
|
219
|
+
return {
|
220
|
+
add(focusScope) {
|
221
|
+
const activeFocusScope = stack[0];
|
222
|
+
if (focusScope !== activeFocusScope) {
|
223
|
+
activeFocusScope?.pause();
|
224
|
+
}
|
225
|
+
stack = arrayRemove(stack, focusScope);
|
226
|
+
stack.unshift(focusScope);
|
227
|
+
},
|
228
|
+
remove(focusScope) {
|
229
|
+
stack = arrayRemove(stack, focusScope);
|
230
|
+
stack[0]?.resume();
|
231
|
+
}
|
232
|
+
};
|
233
|
+
}
|
234
|
+
function arrayRemove(array, item) {
|
235
|
+
const updatedArray = [...array];
|
236
|
+
const index = updatedArray.indexOf(item);
|
237
|
+
if (index !== -1) {
|
238
|
+
updatedArray.splice(index, 1);
|
239
|
+
}
|
240
|
+
return updatedArray;
|
241
|
+
}
|
242
|
+
function removeLinks(items) {
|
243
|
+
return items.filter((item) => item.tagName !== "A");
|
244
|
+
}
|
245
|
+
var Root = FocusScope;
|
246
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"version": 3,
|
3
|
+
"sources": ["../src/index.ts", "../src/FocusScope.tsx"],
|
4
|
+
"sourcesContent": ["'use client';\nexport {\n FocusScope,\n //\n Root,\n} from './FocusScope';\nexport type { FocusScopeProps } from './FocusScope';\n", "import * as React from 'react';\nimport { useComposedRefs } from '@huin-core/react-compose-refs';\nimport { Primitive } from '@huin-core/react-primitive';\nimport { useCallbackRef } from '@huin-core/react-use-callback-ref';\n\nconst AUTOFOCUS_ON_MOUNT = 'focusScope.autoFocusOnMount';\nconst AUTOFOCUS_ON_UNMOUNT = 'focusScope.autoFocusOnUnmount';\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\ntype FocusableTarget = HTMLElement | { focus(): void };\n\n/* -------------------------------------------------------------------------------------------------\n * FocusScope\n * -----------------------------------------------------------------------------------------------*/\n\nconst FOCUS_SCOPE_NAME = 'FocusScope';\n\ntype FocusScopeElement = React.ElementRef<typeof Primitive.div>;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Primitive.div>;\ninterface FocusScopeProps extends PrimitiveDivProps {\n /**\n * When `true`, tabbing from last item will focus first tabbable\n * and shift+tab from first item will focus last tababble.\n * @defaultValue false\n */\n loop?: boolean;\n\n /**\n * When `true`, focus cannot escape the focus scope via keyboard,\n * pointer, or a programmatic focus.\n * @defaultValue false\n */\n trapped?: boolean;\n\n /**\n * Event handler called when auto-focusing on mount.\n * Can be prevented.\n */\n onMountAutoFocus?: (event: Event) => void;\n\n /**\n * Event handler called when auto-focusing on unmount.\n * Can be prevented.\n */\n onUnmountAutoFocus?: (event: Event) => void;\n}\n\nconst FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props, forwardedRef) => {\n const {\n loop = false,\n trapped = false,\n onMountAutoFocus: onMountAutoFocusProp,\n onUnmountAutoFocus: onUnmountAutoFocusProp,\n ...scopeProps\n } = props;\n const [container, setContainer] = React.useState<HTMLElement | null>(null);\n const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);\n const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);\n const lastFocusedElementRef = React.useRef<HTMLElement | null>(null);\n const composedRefs = useComposedRefs(forwardedRef, (node) => setContainer(node));\n\n const focusScope = React.useRef({\n paused: false,\n pause() {\n this.paused = true;\n },\n resume() {\n this.paused = false;\n },\n }).current;\n\n // Takes care of trapping focus if focus is moved outside programmatically for example\n React.useEffect(() => {\n if (trapped) {\n function handleFocusIn(event: FocusEvent) {\n if (focusScope.paused || !container) return;\n const target = event.target as HTMLElement | null;\n if (container.contains(target)) {\n lastFocusedElementRef.current = target;\n } else {\n focus(lastFocusedElementRef.current, { select: true });\n }\n }\n\n function handleFocusOut(event: FocusEvent) {\n if (focusScope.paused || !container) return;\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n // A `focusout` event with a `null` `relatedTarget` will happen in at least two cases:\n //\n // 1. When the user switches app/tabs/windows/the browser itself loses focus.\n // 2. In Google Chrome, when the focused element is removed from the DOM.\n //\n // We let the browser do its thing here because:\n //\n // 1. The browser already keeps a memory of what's focused for when the page gets refocused.\n // 2. In Google Chrome, if we try to focus the deleted focused element (as per below), it\n // throws the CPU to 100%, so we avoid doing anything for this reason here too.\n if (relatedTarget === null) return;\n\n // If the focus has moved to an actual legitimate element (`relatedTarget !== null`)\n // that is outside the container, we move focus to the last valid focused element inside.\n if (!container.contains(relatedTarget)) {\n focus(lastFocusedElementRef.current, { select: true });\n }\n }\n\n // When the focused element gets removed from the DOM, browsers move focus\n // back to the document.body. In this case, we move focus to the container\n // to keep focus trapped correctly.\n function handleMutations(mutations: MutationRecord[]) {\n const focusedElement = document.activeElement as HTMLElement | null;\n if (focusedElement !== document.body) return;\n for (const mutation of mutations) {\n if (mutation.removedNodes.length > 0) focus(container);\n }\n }\n\n document.addEventListener('focusin', handleFocusIn);\n document.addEventListener('focusout', handleFocusOut);\n const mutationObserver = new MutationObserver(handleMutations);\n if (container) mutationObserver.observe(container, { childList: true, subtree: true });\n\n return () => {\n document.removeEventListener('focusin', handleFocusIn);\n document.removeEventListener('focusout', handleFocusOut);\n mutationObserver.disconnect();\n };\n }\n }, [trapped, container, focusScope.paused]);\n\n React.useEffect(() => {\n if (container) {\n focusScopesStack.add(focusScope);\n const previouslyFocusedElement = document.activeElement as HTMLElement | null;\n const hasFocusedCandidate = container.contains(previouslyFocusedElement);\n\n if (!hasFocusedCandidate) {\n const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);\n container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);\n container.dispatchEvent(mountEvent);\n if (!mountEvent.defaultPrevented) {\n focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });\n if (document.activeElement === previouslyFocusedElement) {\n focus(container);\n }\n }\n }\n\n return () => {\n container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);\n\n // We hit a react bug (fixed in v17) with focusing in unmount.\n // We need to delay the focus a little to get around it for now.\n // See: https://github.com/facebook/react/issues/17894\n setTimeout(() => {\n const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);\n container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);\n container.dispatchEvent(unmountEvent);\n if (!unmountEvent.defaultPrevented) {\n focus(previouslyFocusedElement ?? document.body, { select: true });\n }\n // we need to remove the listener after we `dispatchEvent`\n container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);\n\n focusScopesStack.remove(focusScope);\n }, 0);\n };\n }\n }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);\n\n // Takes care of looping focus (when tabbing whilst at the edges)\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (!loop && !trapped) return;\n if (focusScope.paused) return;\n\n const isTabKey = event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey;\n const focusedElement = document.activeElement as HTMLElement | null;\n\n if (isTabKey && focusedElement) {\n const container = event.currentTarget as HTMLElement;\n const [first, last] = getTabbableEdges(container);\n const hasTabbableElementsInside = first && last;\n\n // we can only wrap focus if we have tabbable edges\n if (!hasTabbableElementsInside) {\n if (focusedElement === container) event.preventDefault();\n } else {\n if (!event.shiftKey && focusedElement === last) {\n event.preventDefault();\n if (loop) focus(first, { select: true });\n } else if (event.shiftKey && focusedElement === first) {\n event.preventDefault();\n if (loop) focus(last, { select: true });\n }\n }\n }\n },\n [loop, trapped, focusScope.paused]\n );\n\n return (\n <Primitive.div tabIndex={-1} {...scopeProps} ref={composedRefs} onKeyDown={handleKeyDown} />\n );\n});\n\nFocusScope.displayName = FOCUS_SCOPE_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * Utils\n * -----------------------------------------------------------------------------------------------*/\n\n/**\n * Attempts focusing the first element in a list of candidates.\n * Stops when focus has actually moved.\n */\nfunction focusFirst(candidates: HTMLElement[], { select = false } = {}) {\n const previouslyFocusedElement = document.activeElement;\n for (const candidate of candidates) {\n focus(candidate, { select });\n if (document.activeElement !== previouslyFocusedElement) return;\n }\n}\n\n/**\n * Returns the first and last tabbable elements inside a container.\n */\nfunction getTabbableEdges(container: HTMLElement) {\n const candidates = getTabbableCandidates(container);\n const first = findVisible(candidates, container);\n const last = findVisible(candidates.reverse(), container);\n return [first, last] as const;\n}\n\n/**\n * Returns a list of potential tabbable candidates.\n *\n * NOTE: This is only a close approximation. For example it doesn't take into account cases like when\n * elements are not visible. This cannot be worked out easily by just reading a property, but rather\n * necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker\n * Credit: https://github.com/discord/focus-layers/blob/master/src/util/wrapFocus.tsx#L1\n */\nfunction getTabbableCandidates(container: HTMLElement) {\n const nodes: HTMLElement[] = [];\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node: any) => {\n const isHiddenInput = node.tagName === 'INPUT' && node.type === 'hidden';\n if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;\n // `.tabIndex` is not the same as the `tabindex` attribute. It works on the\n // runtime's understanding of tabbability, so this automatically accounts\n // for any kind of element that could be tabbed to.\n return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n },\n });\n while (walker.nextNode()) nodes.push(walker.currentNode as HTMLElement);\n // we do not take into account the order of nodes with positive `tabIndex` as it\n // hinders accessibility to have tab order different from visual order.\n return nodes;\n}\n\n/**\n * Returns the first visible element in a list.\n * NOTE: Only checks visibility up to the `container`.\n */\nfunction findVisible(elements: HTMLElement[], container: HTMLElement) {\n for (const element of elements) {\n // we stop checking if it's hidden at the `container` level (excluding)\n if (!isHidden(element, { upTo: container })) return element;\n }\n}\n\nfunction isHidden(node: HTMLElement, { upTo }: { upTo?: HTMLElement }) {\n if (getComputedStyle(node).visibility === 'hidden') return true;\n while (node) {\n // we stop at `upTo` (excluding it)\n if (upTo !== undefined && node === upTo) return false;\n if (getComputedStyle(node).display === 'none') return true;\n node = node.parentElement as HTMLElement;\n }\n return false;\n}\n\nfunction isSelectableInput(element: any): element is FocusableTarget & { select: () => void } {\n return element instanceof HTMLInputElement && 'select' in element;\n}\n\nfunction focus(element?: FocusableTarget | null, { select = false } = {}) {\n // only focus if that element is focusable\n if (element && element.focus) {\n const previouslyFocusedElement = document.activeElement;\n // NOTE: we prevent scrolling on focus, to minimize jarring transitions for users\n element.focus({ preventScroll: true });\n // only select if its not the same element, it supports selection and we need to select\n if (element !== previouslyFocusedElement && isSelectableInput(element) && select)\n element.select();\n }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * FocusScope stack\n * -----------------------------------------------------------------------------------------------*/\n\ntype FocusScopeAPI = { paused: boolean; pause(): void; resume(): void };\nconst focusScopesStack = createFocusScopesStack();\n\nfunction createFocusScopesStack() {\n /** A stack of focus scopes, with the active one at the top */\n let stack: FocusScopeAPI[] = [];\n\n return {\n add(focusScope: FocusScopeAPI) {\n // pause the currently active focus scope (at the top of the stack)\n const activeFocusScope = stack[0];\n if (focusScope !== activeFocusScope) {\n activeFocusScope?.pause();\n }\n // remove in case it already exists (because we'll re-add it at the top of the stack)\n stack = arrayRemove(stack, focusScope);\n stack.unshift(focusScope);\n },\n\n remove(focusScope: FocusScopeAPI) {\n stack = arrayRemove(stack, focusScope);\n stack[0]?.resume();\n },\n };\n}\n\nfunction arrayRemove<T>(array: T[], item: T) {\n const updatedArray = [...array];\n const index = updatedArray.indexOf(item);\n if (index !== -1) {\n updatedArray.splice(index, 1);\n }\n return updatedArray;\n}\n\nfunction removeLinks(items: HTMLElement[]) {\n return items.filter((item) => item.tagName !== 'A');\n}\n\nconst Root = FocusScope;\n\nexport {\n FocusScope,\n //\n Root,\n};\nexport type { FocusScopeProps };\n"],
|
5
|
+
"mappings": "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",
|
6
|
+
"names": ["handleFocusIn", "handleFocusOut", "handleMutations", "container"]
|
7
|
+
}
|
package/dist/index.mjs
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
"use client";
|
2
|
+
|
3
|
+
// packages/react/focus-scope/src/FocusScope.tsx
|
4
|
+
import * as React from "react";
|
5
|
+
import { useComposedRefs } from "@huin-core/react-compose-refs";
|
6
|
+
import { Primitive } from "@huin-core/react-primitive";
|
7
|
+
import { useCallbackRef } from "@huin-core/react-use-callback-ref";
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
9
|
+
var AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
|
10
|
+
var AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
|
11
|
+
var EVENT_OPTIONS = { bubbles: false, cancelable: true };
|
12
|
+
var FOCUS_SCOPE_NAME = "FocusScope";
|
13
|
+
var FocusScope = React.forwardRef((props, forwardedRef) => {
|
14
|
+
const {
|
15
|
+
loop = false,
|
16
|
+
trapped = false,
|
17
|
+
onMountAutoFocus: onMountAutoFocusProp,
|
18
|
+
onUnmountAutoFocus: onUnmountAutoFocusProp,
|
19
|
+
...scopeProps
|
20
|
+
} = props;
|
21
|
+
const [container, setContainer] = React.useState(null);
|
22
|
+
const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);
|
23
|
+
const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);
|
24
|
+
const lastFocusedElementRef = React.useRef(null);
|
25
|
+
const composedRefs = useComposedRefs(forwardedRef, (node) => setContainer(node));
|
26
|
+
const focusScope = React.useRef({
|
27
|
+
paused: false,
|
28
|
+
pause() {
|
29
|
+
this.paused = true;
|
30
|
+
},
|
31
|
+
resume() {
|
32
|
+
this.paused = false;
|
33
|
+
}
|
34
|
+
}).current;
|
35
|
+
React.useEffect(() => {
|
36
|
+
if (trapped) {
|
37
|
+
let handleFocusIn2 = function(event) {
|
38
|
+
if (focusScope.paused || !container) return;
|
39
|
+
const target = event.target;
|
40
|
+
if (container.contains(target)) {
|
41
|
+
lastFocusedElementRef.current = target;
|
42
|
+
} else {
|
43
|
+
focus(lastFocusedElementRef.current, { select: true });
|
44
|
+
}
|
45
|
+
}, handleFocusOut2 = function(event) {
|
46
|
+
if (focusScope.paused || !container) return;
|
47
|
+
const relatedTarget = event.relatedTarget;
|
48
|
+
if (relatedTarget === null) return;
|
49
|
+
if (!container.contains(relatedTarget)) {
|
50
|
+
focus(lastFocusedElementRef.current, { select: true });
|
51
|
+
}
|
52
|
+
}, handleMutations2 = function(mutations) {
|
53
|
+
const focusedElement = document.activeElement;
|
54
|
+
if (focusedElement !== document.body) return;
|
55
|
+
for (const mutation of mutations) {
|
56
|
+
if (mutation.removedNodes.length > 0) focus(container);
|
57
|
+
}
|
58
|
+
};
|
59
|
+
var handleFocusIn = handleFocusIn2, handleFocusOut = handleFocusOut2, handleMutations = handleMutations2;
|
60
|
+
document.addEventListener("focusin", handleFocusIn2);
|
61
|
+
document.addEventListener("focusout", handleFocusOut2);
|
62
|
+
const mutationObserver = new MutationObserver(handleMutations2);
|
63
|
+
if (container) mutationObserver.observe(container, { childList: true, subtree: true });
|
64
|
+
return () => {
|
65
|
+
document.removeEventListener("focusin", handleFocusIn2);
|
66
|
+
document.removeEventListener("focusout", handleFocusOut2);
|
67
|
+
mutationObserver.disconnect();
|
68
|
+
};
|
69
|
+
}
|
70
|
+
}, [trapped, container, focusScope.paused]);
|
71
|
+
React.useEffect(() => {
|
72
|
+
if (container) {
|
73
|
+
focusScopesStack.add(focusScope);
|
74
|
+
const previouslyFocusedElement = document.activeElement;
|
75
|
+
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
|
76
|
+
if (!hasFocusedCandidate) {
|
77
|
+
const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
|
78
|
+
container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
|
79
|
+
container.dispatchEvent(mountEvent);
|
80
|
+
if (!mountEvent.defaultPrevented) {
|
81
|
+
focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
|
82
|
+
if (document.activeElement === previouslyFocusedElement) {
|
83
|
+
focus(container);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
return () => {
|
88
|
+
container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
|
89
|
+
setTimeout(() => {
|
90
|
+
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
|
91
|
+
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
|
92
|
+
container.dispatchEvent(unmountEvent);
|
93
|
+
if (!unmountEvent.defaultPrevented) {
|
94
|
+
focus(previouslyFocusedElement ?? document.body, { select: true });
|
95
|
+
}
|
96
|
+
container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
|
97
|
+
focusScopesStack.remove(focusScope);
|
98
|
+
}, 0);
|
99
|
+
};
|
100
|
+
}
|
101
|
+
}, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
|
102
|
+
const handleKeyDown = React.useCallback(
|
103
|
+
(event) => {
|
104
|
+
if (!loop && !trapped) return;
|
105
|
+
if (focusScope.paused) return;
|
106
|
+
const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
|
107
|
+
const focusedElement = document.activeElement;
|
108
|
+
if (isTabKey && focusedElement) {
|
109
|
+
const container2 = event.currentTarget;
|
110
|
+
const [first, last] = getTabbableEdges(container2);
|
111
|
+
const hasTabbableElementsInside = first && last;
|
112
|
+
if (!hasTabbableElementsInside) {
|
113
|
+
if (focusedElement === container2) event.preventDefault();
|
114
|
+
} else {
|
115
|
+
if (!event.shiftKey && focusedElement === last) {
|
116
|
+
event.preventDefault();
|
117
|
+
if (loop) focus(first, { select: true });
|
118
|
+
} else if (event.shiftKey && focusedElement === first) {
|
119
|
+
event.preventDefault();
|
120
|
+
if (loop) focus(last, { select: true });
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
},
|
125
|
+
[loop, trapped, focusScope.paused]
|
126
|
+
);
|
127
|
+
return /* @__PURE__ */ jsx(Primitive.div, { tabIndex: -1, ...scopeProps, ref: composedRefs, onKeyDown: handleKeyDown });
|
128
|
+
});
|
129
|
+
FocusScope.displayName = FOCUS_SCOPE_NAME;
|
130
|
+
function focusFirst(candidates, { select = false } = {}) {
|
131
|
+
const previouslyFocusedElement = document.activeElement;
|
132
|
+
for (const candidate of candidates) {
|
133
|
+
focus(candidate, { select });
|
134
|
+
if (document.activeElement !== previouslyFocusedElement) return;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
function getTabbableEdges(container) {
|
138
|
+
const candidates = getTabbableCandidates(container);
|
139
|
+
const first = findVisible(candidates, container);
|
140
|
+
const last = findVisible(candidates.reverse(), container);
|
141
|
+
return [first, last];
|
142
|
+
}
|
143
|
+
function getTabbableCandidates(container) {
|
144
|
+
const nodes = [];
|
145
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
|
146
|
+
acceptNode: (node) => {
|
147
|
+
const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
|
148
|
+
if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;
|
149
|
+
return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
150
|
+
}
|
151
|
+
});
|
152
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
153
|
+
return nodes;
|
154
|
+
}
|
155
|
+
function findVisible(elements, container) {
|
156
|
+
for (const element of elements) {
|
157
|
+
if (!isHidden(element, { upTo: container })) return element;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
function isHidden(node, { upTo }) {
|
161
|
+
if (getComputedStyle(node).visibility === "hidden") return true;
|
162
|
+
while (node) {
|
163
|
+
if (upTo !== void 0 && node === upTo) return false;
|
164
|
+
if (getComputedStyle(node).display === "none") return true;
|
165
|
+
node = node.parentElement;
|
166
|
+
}
|
167
|
+
return false;
|
168
|
+
}
|
169
|
+
function isSelectableInput(element) {
|
170
|
+
return element instanceof HTMLInputElement && "select" in element;
|
171
|
+
}
|
172
|
+
function focus(element, { select = false } = {}) {
|
173
|
+
if (element && element.focus) {
|
174
|
+
const previouslyFocusedElement = document.activeElement;
|
175
|
+
element.focus({ preventScroll: true });
|
176
|
+
if (element !== previouslyFocusedElement && isSelectableInput(element) && select)
|
177
|
+
element.select();
|
178
|
+
}
|
179
|
+
}
|
180
|
+
var focusScopesStack = createFocusScopesStack();
|
181
|
+
function createFocusScopesStack() {
|
182
|
+
let stack = [];
|
183
|
+
return {
|
184
|
+
add(focusScope) {
|
185
|
+
const activeFocusScope = stack[0];
|
186
|
+
if (focusScope !== activeFocusScope) {
|
187
|
+
activeFocusScope?.pause();
|
188
|
+
}
|
189
|
+
stack = arrayRemove(stack, focusScope);
|
190
|
+
stack.unshift(focusScope);
|
191
|
+
},
|
192
|
+
remove(focusScope) {
|
193
|
+
stack = arrayRemove(stack, focusScope);
|
194
|
+
stack[0]?.resume();
|
195
|
+
}
|
196
|
+
};
|
197
|
+
}
|
198
|
+
function arrayRemove(array, item) {
|
199
|
+
const updatedArray = [...array];
|
200
|
+
const index = updatedArray.indexOf(item);
|
201
|
+
if (index !== -1) {
|
202
|
+
updatedArray.splice(index, 1);
|
203
|
+
}
|
204
|
+
return updatedArray;
|
205
|
+
}
|
206
|
+
function removeLinks(items) {
|
207
|
+
return items.filter((item) => item.tagName !== "A");
|
208
|
+
}
|
209
|
+
var Root = FocusScope;
|
210
|
+
export {
|
211
|
+
FocusScope,
|
212
|
+
Root
|
213
|
+
};
|
214
|
+
//# sourceMappingURL=index.mjs.map
|
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"version": 3,
|
3
|
+
"sources": ["../src/FocusScope.tsx"],
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nimport { useComposedRefs } from '@huin-core/react-compose-refs';\nimport { Primitive } from '@huin-core/react-primitive';\nimport { useCallbackRef } from '@huin-core/react-use-callback-ref';\n\nconst AUTOFOCUS_ON_MOUNT = 'focusScope.autoFocusOnMount';\nconst AUTOFOCUS_ON_UNMOUNT = 'focusScope.autoFocusOnUnmount';\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\ntype FocusableTarget = HTMLElement | { focus(): void };\n\n/* -------------------------------------------------------------------------------------------------\n * FocusScope\n * -----------------------------------------------------------------------------------------------*/\n\nconst FOCUS_SCOPE_NAME = 'FocusScope';\n\ntype FocusScopeElement = React.ElementRef<typeof Primitive.div>;\ntype PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Primitive.div>;\ninterface FocusScopeProps extends PrimitiveDivProps {\n /**\n * When `true`, tabbing from last item will focus first tabbable\n * and shift+tab from first item will focus last tababble.\n * @defaultValue false\n */\n loop?: boolean;\n\n /**\n * When `true`, focus cannot escape the focus scope via keyboard,\n * pointer, or a programmatic focus.\n * @defaultValue false\n */\n trapped?: boolean;\n\n /**\n * Event handler called when auto-focusing on mount.\n * Can be prevented.\n */\n onMountAutoFocus?: (event: Event) => void;\n\n /**\n * Event handler called when auto-focusing on unmount.\n * Can be prevented.\n */\n onUnmountAutoFocus?: (event: Event) => void;\n}\n\nconst FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props, forwardedRef) => {\n const {\n loop = false,\n trapped = false,\n onMountAutoFocus: onMountAutoFocusProp,\n onUnmountAutoFocus: onUnmountAutoFocusProp,\n ...scopeProps\n } = props;\n const [container, setContainer] = React.useState<HTMLElement | null>(null);\n const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);\n const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);\n const lastFocusedElementRef = React.useRef<HTMLElement | null>(null);\n const composedRefs = useComposedRefs(forwardedRef, (node) => setContainer(node));\n\n const focusScope = React.useRef({\n paused: false,\n pause() {\n this.paused = true;\n },\n resume() {\n this.paused = false;\n },\n }).current;\n\n // Takes care of trapping focus if focus is moved outside programmatically for example\n React.useEffect(() => {\n if (trapped) {\n function handleFocusIn(event: FocusEvent) {\n if (focusScope.paused || !container) return;\n const target = event.target as HTMLElement | null;\n if (container.contains(target)) {\n lastFocusedElementRef.current = target;\n } else {\n focus(lastFocusedElementRef.current, { select: true });\n }\n }\n\n function handleFocusOut(event: FocusEvent) {\n if (focusScope.paused || !container) return;\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n // A `focusout` event with a `null` `relatedTarget` will happen in at least two cases:\n //\n // 1. When the user switches app/tabs/windows/the browser itself loses focus.\n // 2. In Google Chrome, when the focused element is removed from the DOM.\n //\n // We let the browser do its thing here because:\n //\n // 1. The browser already keeps a memory of what's focused for when the page gets refocused.\n // 2. In Google Chrome, if we try to focus the deleted focused element (as per below), it\n // throws the CPU to 100%, so we avoid doing anything for this reason here too.\n if (relatedTarget === null) return;\n\n // If the focus has moved to an actual legitimate element (`relatedTarget !== null`)\n // that is outside the container, we move focus to the last valid focused element inside.\n if (!container.contains(relatedTarget)) {\n focus(lastFocusedElementRef.current, { select: true });\n }\n }\n\n // When the focused element gets removed from the DOM, browsers move focus\n // back to the document.body. In this case, we move focus to the container\n // to keep focus trapped correctly.\n function handleMutations(mutations: MutationRecord[]) {\n const focusedElement = document.activeElement as HTMLElement | null;\n if (focusedElement !== document.body) return;\n for (const mutation of mutations) {\n if (mutation.removedNodes.length > 0) focus(container);\n }\n }\n\n document.addEventListener('focusin', handleFocusIn);\n document.addEventListener('focusout', handleFocusOut);\n const mutationObserver = new MutationObserver(handleMutations);\n if (container) mutationObserver.observe(container, { childList: true, subtree: true });\n\n return () => {\n document.removeEventListener('focusin', handleFocusIn);\n document.removeEventListener('focusout', handleFocusOut);\n mutationObserver.disconnect();\n };\n }\n }, [trapped, container, focusScope.paused]);\n\n React.useEffect(() => {\n if (container) {\n focusScopesStack.add(focusScope);\n const previouslyFocusedElement = document.activeElement as HTMLElement | null;\n const hasFocusedCandidate = container.contains(previouslyFocusedElement);\n\n if (!hasFocusedCandidate) {\n const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);\n container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);\n container.dispatchEvent(mountEvent);\n if (!mountEvent.defaultPrevented) {\n focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });\n if (document.activeElement === previouslyFocusedElement) {\n focus(container);\n }\n }\n }\n\n return () => {\n container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);\n\n // We hit a react bug (fixed in v17) with focusing in unmount.\n // We need to delay the focus a little to get around it for now.\n // See: https://github.com/facebook/react/issues/17894\n setTimeout(() => {\n const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);\n container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);\n container.dispatchEvent(unmountEvent);\n if (!unmountEvent.defaultPrevented) {\n focus(previouslyFocusedElement ?? document.body, { select: true });\n }\n // we need to remove the listener after we `dispatchEvent`\n container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);\n\n focusScopesStack.remove(focusScope);\n }, 0);\n };\n }\n }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);\n\n // Takes care of looping focus (when tabbing whilst at the edges)\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (!loop && !trapped) return;\n if (focusScope.paused) return;\n\n const isTabKey = event.key === 'Tab' && !event.altKey && !event.ctrlKey && !event.metaKey;\n const focusedElement = document.activeElement as HTMLElement | null;\n\n if (isTabKey && focusedElement) {\n const container = event.currentTarget as HTMLElement;\n const [first, last] = getTabbableEdges(container);\n const hasTabbableElementsInside = first && last;\n\n // we can only wrap focus if we have tabbable edges\n if (!hasTabbableElementsInside) {\n if (focusedElement === container) event.preventDefault();\n } else {\n if (!event.shiftKey && focusedElement === last) {\n event.preventDefault();\n if (loop) focus(first, { select: true });\n } else if (event.shiftKey && focusedElement === first) {\n event.preventDefault();\n if (loop) focus(last, { select: true });\n }\n }\n }\n },\n [loop, trapped, focusScope.paused]\n );\n\n return (\n <Primitive.div tabIndex={-1} {...scopeProps} ref={composedRefs} onKeyDown={handleKeyDown} />\n );\n});\n\nFocusScope.displayName = FOCUS_SCOPE_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * Utils\n * -----------------------------------------------------------------------------------------------*/\n\n/**\n * Attempts focusing the first element in a list of candidates.\n * Stops when focus has actually moved.\n */\nfunction focusFirst(candidates: HTMLElement[], { select = false } = {}) {\n const previouslyFocusedElement = document.activeElement;\n for (const candidate of candidates) {\n focus(candidate, { select });\n if (document.activeElement !== previouslyFocusedElement) return;\n }\n}\n\n/**\n * Returns the first and last tabbable elements inside a container.\n */\nfunction getTabbableEdges(container: HTMLElement) {\n const candidates = getTabbableCandidates(container);\n const first = findVisible(candidates, container);\n const last = findVisible(candidates.reverse(), container);\n return [first, last] as const;\n}\n\n/**\n * Returns a list of potential tabbable candidates.\n *\n * NOTE: This is only a close approximation. For example it doesn't take into account cases like when\n * elements are not visible. This cannot be worked out easily by just reading a property, but rather\n * necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.\n *\n * See: https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker\n * Credit: https://github.com/discord/focus-layers/blob/master/src/util/wrapFocus.tsx#L1\n */\nfunction getTabbableCandidates(container: HTMLElement) {\n const nodes: HTMLElement[] = [];\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {\n acceptNode: (node: any) => {\n const isHiddenInput = node.tagName === 'INPUT' && node.type === 'hidden';\n if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;\n // `.tabIndex` is not the same as the `tabindex` attribute. It works on the\n // runtime's understanding of tabbability, so this automatically accounts\n // for any kind of element that could be tabbed to.\n return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n },\n });\n while (walker.nextNode()) nodes.push(walker.currentNode as HTMLElement);\n // we do not take into account the order of nodes with positive `tabIndex` as it\n // hinders accessibility to have tab order different from visual order.\n return nodes;\n}\n\n/**\n * Returns the first visible element in a list.\n * NOTE: Only checks visibility up to the `container`.\n */\nfunction findVisible(elements: HTMLElement[], container: HTMLElement) {\n for (const element of elements) {\n // we stop checking if it's hidden at the `container` level (excluding)\n if (!isHidden(element, { upTo: container })) return element;\n }\n}\n\nfunction isHidden(node: HTMLElement, { upTo }: { upTo?: HTMLElement }) {\n if (getComputedStyle(node).visibility === 'hidden') return true;\n while (node) {\n // we stop at `upTo` (excluding it)\n if (upTo !== undefined && node === upTo) return false;\n if (getComputedStyle(node).display === 'none') return true;\n node = node.parentElement as HTMLElement;\n }\n return false;\n}\n\nfunction isSelectableInput(element: any): element is FocusableTarget & { select: () => void } {\n return element instanceof HTMLInputElement && 'select' in element;\n}\n\nfunction focus(element?: FocusableTarget | null, { select = false } = {}) {\n // only focus if that element is focusable\n if (element && element.focus) {\n const previouslyFocusedElement = document.activeElement;\n // NOTE: we prevent scrolling on focus, to minimize jarring transitions for users\n element.focus({ preventScroll: true });\n // only select if its not the same element, it supports selection and we need to select\n if (element !== previouslyFocusedElement && isSelectableInput(element) && select)\n element.select();\n }\n}\n\n/* -------------------------------------------------------------------------------------------------\n * FocusScope stack\n * -----------------------------------------------------------------------------------------------*/\n\ntype FocusScopeAPI = { paused: boolean; pause(): void; resume(): void };\nconst focusScopesStack = createFocusScopesStack();\n\nfunction createFocusScopesStack() {\n /** A stack of focus scopes, with the active one at the top */\n let stack: FocusScopeAPI[] = [];\n\n return {\n add(focusScope: FocusScopeAPI) {\n // pause the currently active focus scope (at the top of the stack)\n const activeFocusScope = stack[0];\n if (focusScope !== activeFocusScope) {\n activeFocusScope?.pause();\n }\n // remove in case it already exists (because we'll re-add it at the top of the stack)\n stack = arrayRemove(stack, focusScope);\n stack.unshift(focusScope);\n },\n\n remove(focusScope: FocusScopeAPI) {\n stack = arrayRemove(stack, focusScope);\n stack[0]?.resume();\n },\n };\n}\n\nfunction arrayRemove<T>(array: T[], item: T) {\n const updatedArray = [...array];\n const index = updatedArray.indexOf(item);\n if (index !== -1) {\n updatedArray.splice(index, 1);\n }\n return updatedArray;\n}\n\nfunction removeLinks(items: HTMLElement[]) {\n return items.filter((item) => item.tagName !== 'A');\n}\n\nconst Root = FocusScope;\n\nexport {\n FocusScope,\n //\n Root,\n};\nexport type { FocusScopeProps };\n"],
|
5
|
+
"mappings": "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",
|
6
|
+
"names": ["handleFocusIn", "handleFocusOut", "handleMutations", "container"]
|
7
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@huin-core/react-focus-scope",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.3",
|
4
4
|
"license": "MIT",
|
5
5
|
"exports": {
|
6
6
|
".": {
|
@@ -28,9 +28,9 @@
|
|
28
28
|
"version": "yarn version"
|
29
29
|
},
|
30
30
|
"dependencies": {
|
31
|
-
"@huin-core/react-compose-refs": "
|
32
|
-
"@huin-core/react-primitive": "
|
33
|
-
"@huin-core/react-use-callback-ref": "
|
31
|
+
"@huin-core/react-compose-refs": "latest",
|
32
|
+
"@huin-core/react-primitive": "latest",
|
33
|
+
"@huin-core/react-use-callback-ref": "latest"
|
34
34
|
},
|
35
35
|
"peerDependencies": {
|
36
36
|
"@types/react": "*",
|