@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.
@@ -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 };
@@ -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": ";;;AAAA,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAwM3B;AAtMJ,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,gBAAgB,EAAE,SAAS,OAAO,YAAY,KAAK;AAQzD,IAAM,mBAAmB;AAgCzB,IAAM,aAAmB,iBAA+C,CAAC,OAAO,iBAAiB;AAC/F,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,CAAC,WAAW,YAAY,IAAU,eAA6B,IAAI;AACzE,QAAM,mBAAmB,eAAe,oBAAoB;AAC5D,QAAM,qBAAqB,eAAe,sBAAsB;AAChE,QAAM,wBAA8B,aAA2B,IAAI;AACnE,QAAM,eAAe,gBAAgB,cAAc,CAAC,SAAS,aAAa,IAAI,CAAC;AAE/E,QAAM,aAAmB,aAAO;AAAA,IAC9B,QAAQ;AAAA,IACR,QAAQ;AACN,WAAK,SAAS;AAAA,IAChB;AAAA,IACA,SAAS;AACP,WAAK,SAAS;AAAA,IAChB;AAAA,EACF,CAAC,EAAE;AAGH,EAAM,gBAAU,MAAM;AACpB,QAAI,SAAS;AACX,UAASA,iBAAT,SAAuB,OAAmB;AACxC,YAAI,WAAW,UAAU,CAAC,UAAW;AACrC,cAAM,SAAS,MAAM;AACrB,YAAI,UAAU,SAAS,MAAM,GAAG;AAC9B,gCAAsB,UAAU;AAAA,QAClC,OAAO;AACL,gBAAM,sBAAsB,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,QACvD;AAAA,MACF,GAESC,kBAAT,SAAwB,OAAmB;AACzC,YAAI,WAAW,UAAU,CAAC,UAAW;AACrC,cAAM,gBAAgB,MAAM;AAY5B,YAAI,kBAAkB,KAAM;AAI5B,YAAI,CAAC,UAAU,SAAS,aAAa,GAAG;AACtC,gBAAM,sBAAsB,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,QACvD;AAAA,MACF,GAKSC,mBAAT,SAAyB,WAA6B;AACpD,cAAM,iBAAiB,SAAS;AAChC,YAAI,mBAAmB,SAAS,KAAM;AACtC,mBAAW,YAAY,WAAW;AAChC,cAAI,SAAS,aAAa,SAAS,EAAG,OAAM,SAAS;AAAA,QACvD;AAAA,MACF;AA1CS,0BAAAF,gBAUA,iBAAAC,iBA0BA,kBAAAC;AAQT,eAAS,iBAAiB,WAAWF,cAAa;AAClD,eAAS,iBAAiB,YAAYC,eAAc;AACpD,YAAM,mBAAmB,IAAI,iBAAiBC,gBAAe;AAC7D,UAAI,UAAW,kBAAiB,QAAQ,WAAW,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAErF,aAAO,MAAM;AACX,iBAAS,oBAAoB,WAAWF,cAAa;AACrD,iBAAS,oBAAoB,YAAYC,eAAc;AACvD,yBAAiB,WAAW;AAAA,MAC9B;AAAA,IACF;AAAA,EACF,GAAG,CAAC,SAAS,WAAW,WAAW,MAAM,CAAC;AAE1C,EAAM,gBAAU,MAAM;AACpB,QAAI,WAAW;AACb,uBAAiB,IAAI,UAAU;AAC/B,YAAM,2BAA2B,SAAS;AAC1C,YAAM,sBAAsB,UAAU,SAAS,wBAAwB;AAEvE,UAAI,CAAC,qBAAqB;AACxB,cAAM,aAAa,IAAI,YAAY,oBAAoB,aAAa;AACpE,kBAAU,iBAAiB,oBAAoB,gBAAgB;AAC/D,kBAAU,cAAc,UAAU;AAClC,YAAI,CAAC,WAAW,kBAAkB;AAChC,qBAAW,YAAY,sBAAsB,SAAS,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AAC1E,cAAI,SAAS,kBAAkB,0BAA0B;AACvD,kBAAM,SAAS;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAEA,aAAO,MAAM;AACX,kBAAU,oBAAoB,oBAAoB,gBAAgB;AAKlE,mBAAW,MAAM;AACf,gBAAM,eAAe,IAAI,YAAY,sBAAsB,aAAa;AACxE,oBAAU,iBAAiB,sBAAsB,kBAAkB;AACnE,oBAAU,cAAc,YAAY;AACpC,cAAI,CAAC,aAAa,kBAAkB;AAClC,kBAAM,4BAA4B,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,UACnE;AAEA,oBAAU,oBAAoB,sBAAsB,kBAAkB;AAEtE,2BAAiB,OAAO,UAAU;AAAA,QACpC,GAAG,CAAC;AAAA,MACN;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,kBAAkB,oBAAoB,UAAU,CAAC;AAGhE,QAAM,gBAAsB;AAAA,IAC1B,CAAC,UAA+B;AAC9B,UAAI,CAAC,QAAQ,CAAC,QAAS;AACvB,UAAI,WAAW,OAAQ;AAEvB,YAAM,WAAW,MAAM,QAAQ,SAAS,CAAC,MAAM,UAAU,CAAC,MAAM,WAAW,CAAC,MAAM;AAClF,YAAM,iBAAiB,SAAS;AAEhC,UAAI,YAAY,gBAAgB;AAC9B,cAAME,aAAY,MAAM;AACxB,cAAM,CAAC,OAAO,IAAI,IAAI,iBAAiBA,UAAS;AAChD,cAAM,4BAA4B,SAAS;AAG3C,YAAI,CAAC,2BAA2B;AAC9B,cAAI,mBAAmBA,WAAW,OAAM,eAAe;AAAA,QACzD,OAAO;AACL,cAAI,CAAC,MAAM,YAAY,mBAAmB,MAAM;AAC9C,kBAAM,eAAe;AACrB,gBAAI,KAAM,OAAM,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,UACzC,WAAW,MAAM,YAAY,mBAAmB,OAAO;AACrD,kBAAM,eAAe;AACrB,gBAAI,KAAM,OAAM,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,MAAM,SAAS,WAAW,MAAM;AAAA,EACnC;AAEA,SACE,oBAAC,UAAU,KAAV,EAAc,UAAU,IAAK,GAAG,YAAY,KAAK,cAAc,WAAW,eAAe;AAE9F,CAAC;AAED,WAAW,cAAc;AAUzB,SAAS,WAAW,YAA2B,EAAE,SAAS,MAAM,IAAI,CAAC,GAAG;AACtE,QAAM,2BAA2B,SAAS;AAC1C,aAAW,aAAa,YAAY;AAClC,UAAM,WAAW,EAAE,OAAO,CAAC;AAC3B,QAAI,SAAS,kBAAkB,yBAA0B;AAAA,EAC3D;AACF;AAKA,SAAS,iBAAiB,WAAwB;AAChD,QAAM,aAAa,sBAAsB,SAAS;AAClD,QAAM,QAAQ,YAAY,YAAY,SAAS;AAC/C,QAAM,OAAO,YAAY,WAAW,QAAQ,GAAG,SAAS;AACxD,SAAO,CAAC,OAAO,IAAI;AACrB;AAYA,SAAS,sBAAsB,WAAwB;AACrD,QAAM,QAAuB,CAAC;AAC9B,QAAM,SAAS,SAAS,iBAAiB,WAAW,WAAW,cAAc;AAAA,IAC3E,YAAY,CAAC,SAAc;AACzB,YAAM,gBAAgB,KAAK,YAAY,WAAW,KAAK,SAAS;AAChE,UAAI,KAAK,YAAY,KAAK,UAAU,cAAe,QAAO,WAAW;AAIrE,aAAO,KAAK,YAAY,IAAI,WAAW,gBAAgB,WAAW;AAAA,IACpE;AAAA,EACF,CAAC;AACD,SAAO,OAAO,SAAS,EAAG,OAAM,KAAK,OAAO,WAA0B;AAGtE,SAAO;AACT;AAMA,SAAS,YAAY,UAAyB,WAAwB;AACpE,aAAW,WAAW,UAAU;AAE9B,QAAI,CAAC,SAAS,SAAS,EAAE,MAAM,UAAU,CAAC,EAAG,QAAO;AAAA,EACtD;AACF;AAEA,SAAS,SAAS,MAAmB,EAAE,KAAK,GAA2B;AACrE,MAAI,iBAAiB,IAAI,EAAE,eAAe,SAAU,QAAO;AAC3D,SAAO,MAAM;AAEX,QAAI,SAAS,UAAa,SAAS,KAAM,QAAO;AAChD,QAAI,iBAAiB,IAAI,EAAE,YAAY,OAAQ,QAAO;AACtD,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAmE;AAC5F,SAAO,mBAAmB,oBAAoB,YAAY;AAC5D;AAEA,SAAS,MAAM,SAAkC,EAAE,SAAS,MAAM,IAAI,CAAC,GAAG;AAExE,MAAI,WAAW,QAAQ,OAAO;AAC5B,UAAM,2BAA2B,SAAS;AAE1C,YAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AAErC,QAAI,YAAY,4BAA4B,kBAAkB,OAAO,KAAK;AACxE,cAAQ,OAAO;AAAA,EACnB;AACF;AAOA,IAAM,mBAAmB,uBAAuB;AAEhD,SAAS,yBAAyB;AAEhC,MAAI,QAAyB,CAAC;AAE9B,SAAO;AAAA,IACL,IAAI,YAA2B;AAE7B,YAAM,mBAAmB,MAAM,CAAC;AAChC,UAAI,eAAe,kBAAkB;AACnC,0BAAkB,MAAM;AAAA,MAC1B;AAEA,cAAQ,YAAY,OAAO,UAAU;AACrC,YAAM,QAAQ,UAAU;AAAA,IAC1B;AAAA,IAEA,OAAO,YAA2B;AAChC,cAAQ,YAAY,OAAO,UAAU;AACrC,YAAM,CAAC,GAAG,OAAO;AAAA,IACnB;AAAA,EACF;AACF;AAEA,SAAS,YAAe,OAAY,MAAS;AAC3C,QAAM,eAAe,CAAC,GAAG,KAAK;AAC9B,QAAM,QAAQ,aAAa,QAAQ,IAAI;AACvC,MAAI,UAAU,IAAI;AAChB,iBAAa,OAAO,OAAO,CAAC;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAsB;AACzC,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,YAAY,GAAG;AACpD;AAEA,IAAM,OAAO;",
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.1",
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": "workspace:*",
32
- "@huin-core/react-primitive": "workspace:*",
33
- "@huin-core/react-use-callback-ref": "workspace:*"
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": "*",