@tamagui/focus-scope 1.0.1-beta.99 → 1.0.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,56 +1,23 @@
1
- var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- }
17
- return a;
18
- };
19
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
- var __objRest = (source, exclude) => {
21
- var target = {};
22
- for (var prop in source)
23
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
- target[prop] = source[prop];
25
- if (source != null && __getOwnPropSymbols)
26
- for (var prop of __getOwnPropSymbols(source)) {
27
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
- target[prop] = source[prop];
29
- }
30
- return target;
31
- };
32
- import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
33
1
  import { useComposedRefs } from "@tamagui/compose-refs";
2
+ import { useEvent } from "@tamagui/use-event";
34
3
  import * as React from "react";
35
4
  const AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
36
5
  const AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
37
6
  const EVENT_OPTIONS = { bubbles: false, cancelable: true };
38
7
  const FOCUS_SCOPE_NAME = "FocusScope";
39
8
  const FocusScope = React.forwardRef((props, forwardedRef) => {
40
- const _a = props, {
9
+ const {
41
10
  loop = false,
42
11
  trapped = false,
43
12
  onMountAutoFocus: onMountAutoFocusProp,
44
- onUnmountAutoFocus: onUnmountAutoFocusProp
45
- } = _a, scopeProps = __objRest(_a, [
46
- "loop",
47
- "trapped",
48
- "onMountAutoFocus",
49
- "onUnmountAutoFocus"
50
- ]);
13
+ onUnmountAutoFocus: onUnmountAutoFocusProp,
14
+ children,
15
+ forceUnmount,
16
+ ...scopeProps
17
+ } = props;
51
18
  const [container, setContainer] = React.useState(null);
52
- const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);
53
- const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);
19
+ const onMountAutoFocus = useEvent(onMountAutoFocusProp);
20
+ const onUnmountAutoFocus = useEvent(onUnmountAutoFocusProp);
54
21
  const lastFocusedElementRef = React.useRef(null);
55
22
  const composedRefs = useComposedRefs(forwardedRef, (node) => setContainer(node));
56
23
  const focusScope = React.useRef({
@@ -63,96 +30,100 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
63
30
  }
64
31
  }).current;
65
32
  React.useEffect(() => {
66
- if (trapped) {
67
- let handleFocusIn = function(event) {
68
- if (focusScope.paused || !container)
69
- return;
70
- const target = event.target;
71
- if (container.contains(target)) {
72
- lastFocusedElementRef.current = target;
73
- } else {
74
- focus(lastFocusedElementRef.current, { select: true });
75
- }
76
- }, handleFocusOut = function(event) {
77
- if (focusScope.paused || !container)
78
- return;
79
- if (!container.contains(event.relatedTarget)) {
80
- focus(lastFocusedElementRef.current, { select: true });
81
- }
82
- };
83
- document.addEventListener("focusin", handleFocusIn);
84
- document.addEventListener("focusout", handleFocusOut);
85
- return () => {
86
- document.removeEventListener("focusin", handleFocusIn);
87
- document.removeEventListener("focusout", handleFocusOut);
88
- };
33
+ if (!trapped)
34
+ return;
35
+ function handleFocusIn(event) {
36
+ if (focusScope.paused || !container)
37
+ return;
38
+ const target = event.target;
39
+ if (container.contains(target)) {
40
+ lastFocusedElementRef.current = target;
41
+ } else {
42
+ focus(lastFocusedElementRef.current, { select: true });
43
+ }
89
44
  }
90
- }, [trapped, container, focusScope.paused]);
91
- React.useEffect(() => {
92
- if (container) {
93
- focusScopesStack.add(focusScope);
94
- const previouslyFocusedElement = document.activeElement;
95
- const hasFocusedCandidate = container.contains(previouslyFocusedElement);
96
- if (!hasFocusedCandidate) {
97
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
98
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
99
- container.dispatchEvent(mountEvent);
100
- if (!mountEvent.defaultPrevented) {
101
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
102
- if (document.activeElement === previouslyFocusedElement) {
103
- focus(container);
104
- }
105
- }
45
+ function handleFocusOut(event) {
46
+ if (focusScope.paused || !container)
47
+ return;
48
+ if (!container.contains(event.relatedTarget)) {
49
+ focus(lastFocusedElementRef.current, { select: true });
106
50
  }
107
- return () => {
108
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
109
- setTimeout(() => {
110
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
111
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
112
- container.dispatchEvent(unmountEvent);
113
- if (!unmountEvent.defaultPrevented) {
114
- focus(previouslyFocusedElement ?? document.body, { select: true });
115
- }
116
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
117
- focusScopesStack.remove(focusScope);
118
- }, 0);
119
- };
120
51
  }
121
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
122
- const handleKeyDown = React.useCallback((event) => {
123
- if (!loop && !trapped)
52
+ document.addEventListener("focusin", handleFocusIn);
53
+ document.addEventListener("focusout", handleFocusOut);
54
+ return () => {
55
+ document.removeEventListener("focusin", handleFocusIn);
56
+ document.removeEventListener("focusout", handleFocusOut);
57
+ };
58
+ }, [trapped, forceUnmount, container, focusScope.paused]);
59
+ React.useEffect(() => {
60
+ if (!container)
124
61
  return;
125
- if (focusScope.paused)
62
+ if (forceUnmount)
126
63
  return;
127
- const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
128
- const focusedElement = document.activeElement;
129
- if (isTabKey && focusedElement) {
130
- const container2 = event.currentTarget;
131
- const [first, last] = getTabbableEdges(container2);
132
- const hasTabbableElementsInside = first && last;
133
- if (!hasTabbableElementsInside) {
134
- if (focusedElement === container2)
135
- event.preventDefault();
136
- } else {
137
- if (!event.shiftKey && focusedElement === last) {
138
- event.preventDefault();
139
- if (loop)
140
- focus(first, { select: true });
141
- } else if (event.shiftKey && focusedElement === first) {
142
- event.preventDefault();
143
- if (loop)
144
- focus(last, { select: true });
64
+ focusScopesStack.add(focusScope);
65
+ const previouslyFocusedElement = document.activeElement;
66
+ const hasFocusedCandidate = container.contains(previouslyFocusedElement);
67
+ if (!hasFocusedCandidate) {
68
+ const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
69
+ container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
70
+ container.dispatchEvent(mountEvent);
71
+ if (!mountEvent.defaultPrevented) {
72
+ focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
73
+ if (document.activeElement === previouslyFocusedElement) {
74
+ focus(container);
145
75
  }
146
76
  }
147
77
  }
148
- }, [loop, trapped, focusScope.paused]);
149
- const child = React.Children.only(props.children);
150
- return React.cloneElement(child, __spreadProps(__spreadValues({
151
- tabIndex: -1
152
- }, scopeProps), {
78
+ return () => {
79
+ container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
80
+ const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
81
+ container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
82
+ container.dispatchEvent(unmountEvent);
83
+ if (!unmountEvent.defaultPrevented) {
84
+ focus(previouslyFocusedElement ?? document.body, { select: true });
85
+ }
86
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
87
+ focusScopesStack.remove(focusScope);
88
+ };
89
+ }, [container, forceUnmount, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
90
+ const handleKeyDown = React.useCallback(
91
+ (event) => {
92
+ if (!loop && !trapped)
93
+ return;
94
+ if (focusScope.paused)
95
+ return;
96
+ const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
97
+ const focusedElement = document.activeElement;
98
+ if (isTabKey && focusedElement) {
99
+ const container2 = event.currentTarget;
100
+ const [first, last] = getTabbableEdges(container2);
101
+ const hasTabbableElementsInside = first && last;
102
+ if (!hasTabbableElementsInside) {
103
+ if (focusedElement === container2)
104
+ event.preventDefault();
105
+ } else {
106
+ if (!event.shiftKey && focusedElement === last) {
107
+ event.preventDefault();
108
+ if (loop)
109
+ focus(first, { select: true });
110
+ } else if (event.shiftKey && focusedElement === first) {
111
+ event.preventDefault();
112
+ if (loop)
113
+ focus(last, { select: true });
114
+ }
115
+ }
116
+ }
117
+ },
118
+ [loop, trapped, focusScope.paused]
119
+ );
120
+ const child = React.Children.only(children);
121
+ return React.cloneElement(child, {
122
+ tabIndex: -1,
123
+ ...scopeProps,
153
124
  ref: composedRefs,
154
125
  onKeyDown: handleKeyDown
155
- }));
126
+ });
156
127
  });
157
128
  FocusScope.displayName = FOCUS_SCOPE_NAME;
158
129
  function focusFirst(candidates, { select = false } = {}) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScope.tsx"],
4
- "sourcesContent": ["import { useCallbackRef } from '@radix-ui/react-use-callback-ref'\nimport { useComposedRefs } from '@tamagui/compose-refs'\nimport * as React from 'react'\n\nimport { FocusScopeProps } from './FocusScopeProps'\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 = HTMLDivElement\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 if (!container.contains(event.relatedTarget as HTMLElement | null)) {\n focus(lastFocusedElementRef.current, { select: true })\n }\n }\n\n document.addEventListener('focusin', handleFocusIn)\n document.addEventListener('focusout', handleFocusOut)\n return () => {\n document.removeEventListener('focusin', handleFocusIn)\n document.removeEventListener('focusout', handleFocusOut)\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 const child = React.Children.only(props.children)\n\n return React.cloneElement(child as any, {\n tabIndex: -1,\n ...scopeProps,\n ref: composedRefs,\n 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\nexport { FocusScope }\n\nexport type { FocusScopeProps }\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AAIA,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAC7B,MAAM,gBAAgB,EAAE,SAAS,OAAO,YAAY,KAAK;AAQzD,MAAM,mBAAmB;AAIzB,MAAM,aAAa,MAAM,WAA+C,CAAC,OAAO,iBAAiB;AAC/F,QAMI,YALF;AAAA,WAAO;AAAA,IACP,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,MAElB,IADC,uBACD,IADC;AAAA,IAJH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAGF,QAAM,CAAC,WAAW,gBAAgB,MAAM,SAA6B,IAAI;AACzE,QAAM,mBAAmB,eAAe,oBAAoB;AAC5D,QAAM,qBAAqB,eAAe,sBAAsB;AAChE,QAAM,wBAAwB,MAAM,OAA2B,IAAI;AACnE,QAAM,eAAe,gBAAgB,cAAc,CAAC,SAAS,aAAa,IAAI,CAAC;AAE/E,QAAM,aAAa,MAAM,OAAO;AAAA,IAC9B,QAAQ;AAAA,IACR,QAAQ;AACN,WAAK,SAAS;AAAA,IAChB;AAAA,IACA,SAAS;AACP,WAAK,SAAS;AAAA,IAChB;AAAA,EACF,CAAC,EAAE;AAGH,QAAM,UAAU,MAAM;AACpB,QAAI,SAAS;AACX,UAAS,gBAAT,SAAuB,OAAmB;AACxC,YAAI,WAAW,UAAU,CAAC;AAAW;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,GAES,iBAAT,SAAwB,OAAmB;AACzC,YAAI,WAAW,UAAU,CAAC;AAAW;AACrC,YAAI,CAAC,UAAU,SAAS,MAAM,aAAmC,GAAG;AAClE,gBAAM,sBAAsB,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,QACvD;AAAA,MACF;AAEA,eAAS,iBAAiB,WAAW,aAAa;AAClD,eAAS,iBAAiB,YAAY,cAAc;AACpD,aAAO,MAAM;AACX,iBAAS,oBAAoB,WAAW,aAAa;AACrD,iBAAS,oBAAoB,YAAY,cAAc;AAAA,MACzD;AAAA,IACF;AAAA,EACF,GAAG,CAAC,SAAS,WAAW,WAAW,MAAM,CAAC;AAE1C,QAAM,UAAU,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,gBAAgB,MAAM,YAC1B,CAAC,UAA+B;AAC9B,QAAI,CAAC,QAAQ,CAAC;AAAS;AACvB,QAAI,WAAW;AAAQ;AAEvB,UAAM,WAAW,MAAM,QAAQ,SAAS,CAAC,MAAM,UAAU,CAAC,MAAM,WAAW,CAAC,MAAM;AAClF,UAAM,iBAAiB,SAAS;AAEhC,QAAI,YAAY,gBAAgB;AAC9B,YAAM,aAAY,MAAM;AACxB,YAAM,CAAC,OAAO,QAAQ,iBAAiB,UAAS;AAChD,YAAM,4BAA4B,SAAS;AAG3C,UAAI,CAAC,2BAA2B;AAC9B,YAAI,mBAAmB;AAAW,gBAAM,eAAe;AAAA,MACzD,OAAO;AACL,YAAI,CAAC,MAAM,YAAY,mBAAmB,MAAM;AAC9C,gBAAM,eAAe;AACrB,cAAI;AAAM,kBAAM,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,QACzC,WAAW,MAAM,YAAY,mBAAmB,OAAO;AACrD,gBAAM,eAAe;AACrB,cAAI;AAAM,kBAAM,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF,GACA,CAAC,MAAM,SAAS,WAAW,MAAM,CACnC;AAEA,QAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,QAAQ;AAEhD,SAAO,MAAM,aAAa,OAAc;AAAA,IACtC,UAAU;AAAA,KACP,aAFmC;AAAA,IAGtC,KAAK;AAAA,IACL,WAAW;AAAA,EACb,EAAC;AACH,CAAC;AAED,WAAW,cAAc;AAUzB,oBAAoB,YAA2B,EAAE,SAAS,UAAU,CAAC,GAAG;AACtE,QAAM,2BAA2B,SAAS;AAC1C,aAAW,aAAa,YAAY;AAClC,UAAM,WAAW,EAAE,OAAO,CAAC;AAC3B,QAAI,SAAS,kBAAkB;AAA0B;AAAA,EAC3D;AACF;AAKA,0BAA0B,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,+BAA+B,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;AAAe,eAAO,WAAW;AAIrE,aAAO,KAAK,YAAY,IAAI,WAAW,gBAAgB,WAAW;AAAA,IACpE;AAAA,EACF,CAAC;AACD,SAAO,OAAO,SAAS;AAAG,UAAM,KAAK,OAAO,WAA0B;AAGtE,SAAO;AACT;AAMA,qBAAqB,UAAyB,WAAwB;AACpE,aAAW,WAAW,UAAU;AAE9B,QAAI,CAAC,SAAS,SAAS,EAAE,MAAM,UAAU,CAAC;AAAG,aAAO;AAAA,EACtD;AACF;AAEA,kBAAkB,MAAmB,EAAE,QAAgC;AACrE,MAAI,iBAAiB,IAAI,EAAE,eAAe;AAAU,WAAO;AAC3D,SAAO,MAAM;AAEX,QAAI,SAAS,UAAa,SAAS;AAAM,aAAO;AAChD,QAAI,iBAAiB,IAAI,EAAE,YAAY;AAAQ,aAAO;AACtD,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAEA,2BAA2B,SAAmE;AAC5F,SAAO,mBAAmB,oBAAoB,YAAY;AAC5D;AAEA,eAAe,SAAkC,EAAE,SAAS,UAAU,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,MAAM,mBAAmB,uBAAuB;AAEhD,kCAAkC;AAEhC,MAAI,QAAyB,CAAC;AAE9B,SAAO;AAAA,IACL,IAAI,YAA2B;AAE7B,YAAM,mBAAmB,MAAM;AAC/B,UAAI,eAAe,kBAAkB;AACnC,6DAAkB;AAAA,MACpB;AAEA,cAAQ,YAAY,OAAO,UAAU;AACrC,YAAM,QAAQ,UAAU;AAAA,IAC1B;AAAA,IAEA,OAAO,YAA2B;AA/QtC;AAgRM,cAAQ,YAAY,OAAO,UAAU;AACrC,kBAAM,OAAN,mBAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,qBAAwB,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,qBAAqB,OAAsB;AACzC,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,YAAY,GAAG;AACpD;",
6
- "names": []
4
+ "sourcesContent": ["import { useComposedRefs } from '@tamagui/compose-refs'\nimport { useEvent } from '@tamagui/use-event'\nimport * as React from 'react'\n\nimport { FocusScopeProps } from './FocusScopeProps'\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 = HTMLDivElement\n\nconst FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props, forwardedRef) => {\n const {\n loop = false,\n trapped = false,\n onMountAutoFocus: onMountAutoFocusProp,\n onUnmountAutoFocus: onUnmountAutoFocusProp,\n children,\n forceUnmount,\n ...scopeProps\n } = props\n const [container, setContainer] = React.useState<HTMLElement | null>(null)\n const onMountAutoFocus = useEvent(onMountAutoFocusProp)\n const onUnmountAutoFocus = useEvent(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) return\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 if (!container.contains(event.relatedTarget as HTMLElement | null)) {\n focus(lastFocusedElementRef.current, { select: true })\n }\n }\n\n document.addEventListener('focusin', handleFocusIn)\n document.addEventListener('focusout', handleFocusOut)\n return () => {\n document.removeEventListener('focusin', handleFocusIn)\n document.removeEventListener('focusout', handleFocusOut)\n }\n }, [trapped, forceUnmount, container, focusScope.paused])\n\n React.useEffect(() => {\n if (!container) return\n if (forceUnmount) return\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 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 }\n }, [container, forceUnmount, 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 const child = React.Children.only(children)\n\n return React.cloneElement(child as any, {\n tabIndex: -1,\n ...scopeProps,\n ref: composedRefs,\n 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\nexport { FocusScope }\n\nexport type { FocusScopeProps }\n"],
5
+ "mappings": "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",
6
+ "names": ["container"]
7
7
  }
File without changes
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScope.native.tsx"],
4
4
  "sourcesContent": ["import { forwardRef } from 'react'\n\nimport { FocusScopeProps } from './FocusScopeProps'\n\nexport const FocusScope = forwardRef((props: FocusScopeProps, _ref) => {\n return props.children as any\n})\n"],
5
- "mappings": "AAAA;AAIO,MAAM,aAAa,WAAW,CAAC,OAAwB,SAAS;AACrE,SAAO,MAAM;AACf,CAAC;",
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAIpB,MAAM,aAAa,WAAW,CAAC,OAAwB,SAAS;AACrE,SAAO,MAAM;AACf,CAAC;",
6
6
  "names": []
7
7
  }
File without changes
File without changes
package/dist/esm/index.js CHANGED
File without changes
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.tsx"],
4
4
  "sourcesContent": ["export * from './FocusScope'\n"],
5
- "mappings": "AAAA;",
5
+ "mappings": "AAAA,cAAc;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
- import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
2
1
  import { useComposedRefs } from "@tamagui/compose-refs";
2
+ import { useEvent } from "@tamagui/use-event";
3
3
  import * as React from "react";
4
4
  const AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
5
5
  const AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
@@ -11,11 +11,13 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
11
11
  trapped = false,
12
12
  onMountAutoFocus: onMountAutoFocusProp,
13
13
  onUnmountAutoFocus: onUnmountAutoFocusProp,
14
+ children,
15
+ forceUnmount,
14
16
  ...scopeProps
15
17
  } = props;
16
18
  const [container, setContainer] = React.useState(null);
17
- const onMountAutoFocus = useCallbackRef(onMountAutoFocusProp);
18
- const onUnmountAutoFocus = useCallbackRef(onUnmountAutoFocusProp);
19
+ const onMountAutoFocus = useEvent(onMountAutoFocusProp);
20
+ const onUnmountAutoFocus = useEvent(onUnmountAutoFocusProp);
19
21
  const lastFocusedElementRef = React.useRef(null);
20
22
  const composedRefs = useComposedRefs(forwardedRef, (node) => setContainer(node));
21
23
  const focusScope = React.useRef({
@@ -28,90 +30,94 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
28
30
  }
29
31
  }).current;
30
32
  React.useEffect(() => {
31
- if (trapped) {
32
- let handleFocusIn = function(event) {
33
- if (focusScope.paused || !container)
34
- return;
35
- const target = event.target;
36
- if (container.contains(target)) {
37
- lastFocusedElementRef.current = target;
38
- } else {
39
- focus(lastFocusedElementRef.current, { select: true });
40
- }
41
- }, handleFocusOut = function(event) {
42
- if (focusScope.paused || !container)
43
- return;
44
- if (!container.contains(event.relatedTarget)) {
45
- focus(lastFocusedElementRef.current, { select: true });
46
- }
47
- };
48
- document.addEventListener("focusin", handleFocusIn);
49
- document.addEventListener("focusout", handleFocusOut);
50
- return () => {
51
- document.removeEventListener("focusin", handleFocusIn);
52
- document.removeEventListener("focusout", handleFocusOut);
53
- };
33
+ if (!trapped)
34
+ return;
35
+ function handleFocusIn(event) {
36
+ if (focusScope.paused || !container)
37
+ return;
38
+ const target = event.target;
39
+ if (container.contains(target)) {
40
+ lastFocusedElementRef.current = target;
41
+ } else {
42
+ focus(lastFocusedElementRef.current, { select: true });
43
+ }
54
44
  }
55
- }, [trapped, container, focusScope.paused]);
56
- React.useEffect(() => {
57
- if (container) {
58
- focusScopesStack.add(focusScope);
59
- const previouslyFocusedElement = document.activeElement;
60
- const hasFocusedCandidate = container.contains(previouslyFocusedElement);
61
- if (!hasFocusedCandidate) {
62
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
63
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
64
- container.dispatchEvent(mountEvent);
65
- if (!mountEvent.defaultPrevented) {
66
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
67
- if (document.activeElement === previouslyFocusedElement) {
68
- focus(container);
69
- }
70
- }
45
+ function handleFocusOut(event) {
46
+ if (focusScope.paused || !container)
47
+ return;
48
+ if (!container.contains(event.relatedTarget)) {
49
+ focus(lastFocusedElementRef.current, { select: true });
71
50
  }
72
- return () => {
73
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
74
- setTimeout(() => {
75
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
76
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
77
- container.dispatchEvent(unmountEvent);
78
- if (!unmountEvent.defaultPrevented) {
79
- focus(previouslyFocusedElement != null ? previouslyFocusedElement : document.body, { select: true });
80
- }
81
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
82
- focusScopesStack.remove(focusScope);
83
- }, 0);
84
- };
85
51
  }
86
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
87
- const handleKeyDown = React.useCallback((event) => {
88
- if (!loop && !trapped)
52
+ document.addEventListener("focusin", handleFocusIn);
53
+ document.addEventListener("focusout", handleFocusOut);
54
+ return () => {
55
+ document.removeEventListener("focusin", handleFocusIn);
56
+ document.removeEventListener("focusout", handleFocusOut);
57
+ };
58
+ }, [trapped, forceUnmount, container, focusScope.paused]);
59
+ React.useEffect(() => {
60
+ if (!container)
89
61
  return;
90
- if (focusScope.paused)
62
+ if (forceUnmount)
91
63
  return;
92
- const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
93
- const focusedElement = document.activeElement;
94
- if (isTabKey && focusedElement) {
95
- const container2 = event.currentTarget;
96
- const [first, last] = getTabbableEdges(container2);
97
- const hasTabbableElementsInside = first && last;
98
- if (!hasTabbableElementsInside) {
99
- if (focusedElement === container2)
100
- event.preventDefault();
101
- } else {
102
- if (!event.shiftKey && focusedElement === last) {
103
- event.preventDefault();
104
- if (loop)
105
- focus(first, { select: true });
106
- } else if (event.shiftKey && focusedElement === first) {
107
- event.preventDefault();
108
- if (loop)
109
- focus(last, { select: true });
64
+ focusScopesStack.add(focusScope);
65
+ const previouslyFocusedElement = document.activeElement;
66
+ const hasFocusedCandidate = container.contains(previouslyFocusedElement);
67
+ if (!hasFocusedCandidate) {
68
+ const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
69
+ container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
70
+ container.dispatchEvent(mountEvent);
71
+ if (!mountEvent.defaultPrevented) {
72
+ focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
73
+ if (document.activeElement === previouslyFocusedElement) {
74
+ focus(container);
110
75
  }
111
76
  }
112
77
  }
113
- }, [loop, trapped, focusScope.paused]);
114
- const child = React.Children.only(props.children);
78
+ return () => {
79
+ container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
80
+ const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
81
+ container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
82
+ container.dispatchEvent(unmountEvent);
83
+ if (!unmountEvent.defaultPrevented) {
84
+ focus(previouslyFocusedElement ?? document.body, { select: true });
85
+ }
86
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
87
+ focusScopesStack.remove(focusScope);
88
+ };
89
+ }, [container, forceUnmount, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
90
+ const handleKeyDown = React.useCallback(
91
+ (event) => {
92
+ if (!loop && !trapped)
93
+ return;
94
+ if (focusScope.paused)
95
+ return;
96
+ const isTabKey = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
97
+ const focusedElement = document.activeElement;
98
+ if (isTabKey && focusedElement) {
99
+ const container2 = event.currentTarget;
100
+ const [first, last] = getTabbableEdges(container2);
101
+ const hasTabbableElementsInside = first && last;
102
+ if (!hasTabbableElementsInside) {
103
+ if (focusedElement === container2)
104
+ event.preventDefault();
105
+ } else {
106
+ if (!event.shiftKey && focusedElement === last) {
107
+ event.preventDefault();
108
+ if (loop)
109
+ focus(first, { select: true });
110
+ } else if (event.shiftKey && focusedElement === first) {
111
+ event.preventDefault();
112
+ if (loop)
113
+ focus(last, { select: true });
114
+ }
115
+ }
116
+ }
117
+ },
118
+ [loop, trapped, focusScope.paused]
119
+ );
120
+ const child = React.Children.only(children);
115
121
  return React.cloneElement(child, {
116
122
  tabIndex: -1,
117
123
  ...scopeProps,
@@ -184,15 +190,14 @@ function createFocusScopesStack() {
184
190
  add(focusScope) {
185
191
  const activeFocusScope = stack[0];
186
192
  if (focusScope !== activeFocusScope) {
187
- activeFocusScope == null ? void 0 : activeFocusScope.pause();
193
+ activeFocusScope?.pause();
188
194
  }
189
195
  stack = arrayRemove(stack, focusScope);
190
196
  stack.unshift(focusScope);
191
197
  },
192
198
  remove(focusScope) {
193
- var _a;
194
199
  stack = arrayRemove(stack, focusScope);
195
- (_a = stack[0]) == null ? void 0 : _a.resume();
200
+ stack[0]?.resume();
196
201
  }
197
202
  };
198
203
  }