@tamagui/focus-scope 1.0.1-beta.118 → 1.0.1-beta.119

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.
@@ -37,6 +37,8 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
37
37
  trapped = false,
38
38
  onMountAutoFocus: onMountAutoFocusProp,
39
39
  onUnmountAutoFocus: onUnmountAutoFocusProp,
40
+ children,
41
+ forceUnmount,
40
42
  ...scopeProps
41
43
  } = props;
42
44
  const [container, setContainer] = React.useState(null);
@@ -54,63 +56,63 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
54
56
  }
55
57
  }).current;
56
58
  React.useEffect(() => {
57
- if (trapped) {
58
- let handleFocusIn2 = function(event) {
59
- if (focusScope.paused || !container)
60
- return;
61
- const target = event.target;
62
- if (container.contains(target)) {
63
- lastFocusedElementRef.current = target;
64
- } else {
65
- focus(lastFocusedElementRef.current, { select: true });
66
- }
67
- }, handleFocusOut2 = function(event) {
68
- if (focusScope.paused || !container)
69
- return;
70
- if (!container.contains(event.relatedTarget)) {
71
- focus(lastFocusedElementRef.current, { select: true });
72
- }
73
- };
74
- var handleFocusIn = handleFocusIn2, handleFocusOut = handleFocusOut2;
75
- document.addEventListener("focusin", handleFocusIn2);
76
- document.addEventListener("focusout", handleFocusOut2);
77
- return () => {
78
- document.removeEventListener("focusin", handleFocusIn2);
79
- document.removeEventListener("focusout", handleFocusOut2);
80
- };
59
+ if (!trapped)
60
+ return;
61
+ function handleFocusIn(event) {
62
+ if (focusScope.paused || !container)
63
+ return;
64
+ const target = event.target;
65
+ if (container.contains(target)) {
66
+ lastFocusedElementRef.current = target;
67
+ } else {
68
+ focus(lastFocusedElementRef.current, { select: true });
69
+ }
81
70
  }
82
- }, [trapped, container, focusScope.paused]);
71
+ function handleFocusOut(event) {
72
+ if (focusScope.paused || !container)
73
+ return;
74
+ if (!container.contains(event.relatedTarget)) {
75
+ focus(lastFocusedElementRef.current, { select: true });
76
+ }
77
+ }
78
+ document.addEventListener("focusin", handleFocusIn);
79
+ document.addEventListener("focusout", handleFocusOut);
80
+ return () => {
81
+ document.removeEventListener("focusin", handleFocusIn);
82
+ document.removeEventListener("focusout", handleFocusOut);
83
+ };
84
+ }, [trapped, forceUnmount, container, focusScope.paused]);
83
85
  React.useEffect(() => {
84
- if (container) {
85
- focusScopesStack.add(focusScope);
86
- const previouslyFocusedElement = document.activeElement;
87
- const hasFocusedCandidate = container.contains(previouslyFocusedElement);
88
- if (!hasFocusedCandidate) {
89
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
90
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
91
- container.dispatchEvent(mountEvent);
92
- if (!mountEvent.defaultPrevented) {
93
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
94
- if (document.activeElement === previouslyFocusedElement) {
95
- focus(container);
96
- }
86
+ if (!container)
87
+ return;
88
+ if (forceUnmount)
89
+ return;
90
+ focusScopesStack.add(focusScope);
91
+ const previouslyFocusedElement = document.activeElement;
92
+ const hasFocusedCandidate = container.contains(previouslyFocusedElement);
93
+ if (!hasFocusedCandidate) {
94
+ const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
95
+ container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
96
+ container.dispatchEvent(mountEvent);
97
+ if (!mountEvent.defaultPrevented) {
98
+ focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
99
+ if (document.activeElement === previouslyFocusedElement) {
100
+ focus(container);
97
101
  }
98
102
  }
99
- return () => {
100
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
101
- setTimeout(() => {
102
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
103
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
104
- container.dispatchEvent(unmountEvent);
105
- if (!unmountEvent.defaultPrevented) {
106
- focus(previouslyFocusedElement ?? document.body, { select: true });
107
- }
108
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
109
- focusScopesStack.remove(focusScope);
110
- }, 0);
111
- };
112
103
  }
113
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
104
+ return () => {
105
+ container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
106
+ const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
107
+ container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
108
+ container.dispatchEvent(unmountEvent);
109
+ if (!unmountEvent.defaultPrevented) {
110
+ focus(previouslyFocusedElement ?? document.body, { select: true });
111
+ }
112
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
113
+ focusScopesStack.remove(focusScope);
114
+ };
115
+ }, [container, forceUnmount, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
114
116
  const handleKeyDown = React.useCallback((event) => {
115
117
  if (!loop && !trapped)
116
118
  return;
@@ -138,7 +140,7 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
138
140
  }
139
141
  }
140
142
  }, [loop, trapped, focusScope.paused]);
141
- const child = React.Children.only(props.children);
143
+ const child = React.Children.only(children);
142
144
  return React.cloneElement(child, {
143
145
  tabIndex: -1,
144
146
  ...scopeProps,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScope.tsx"],
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 ...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) {\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": "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",
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
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScopeProps.tsx"],
4
- "sourcesContent": ["import React from 'react'\n\nexport interface FocusScopeProps {\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 children?: React.ReactNode\n}\n"],
4
+ "sourcesContent": ["import React from 'react'\n\nexport interface FocusScopeProps {\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 /**\n * If unmount is animated, you want to force re-focus at start of animation not after\n */\n forceUnmount?: boolean\n\n children?: React.ReactNode\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,8 @@ 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);
@@ -28,63 +30,63 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
28
30
  }
29
31
  }).current;
30
32
  React.useEffect(() => {
31
- if (trapped) {
32
- let handleFocusIn2 = 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
- }, handleFocusOut2 = function(event) {
42
- if (focusScope.paused || !container)
43
- return;
44
- if (!container.contains(event.relatedTarget)) {
45
- focus(lastFocusedElementRef.current, { select: true });
46
- }
47
- };
48
- var handleFocusIn = handleFocusIn2, handleFocusOut = handleFocusOut2;
49
- document.addEventListener("focusin", handleFocusIn2);
50
- document.addEventListener("focusout", handleFocusOut2);
51
- return () => {
52
- document.removeEventListener("focusin", handleFocusIn2);
53
- document.removeEventListener("focusout", handleFocusOut2);
54
- };
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
+ }
55
44
  }
56
- }, [trapped, container, focusScope.paused]);
45
+ function handleFocusOut(event) {
46
+ if (focusScope.paused || !container)
47
+ return;
48
+ if (!container.contains(event.relatedTarget)) {
49
+ focus(lastFocusedElementRef.current, { select: true });
50
+ }
51
+ }
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]);
57
59
  React.useEffect(() => {
58
- if (container) {
59
- focusScopesStack.add(focusScope);
60
- const previouslyFocusedElement = document.activeElement;
61
- const hasFocusedCandidate = container.contains(previouslyFocusedElement);
62
- if (!hasFocusedCandidate) {
63
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
64
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
65
- container.dispatchEvent(mountEvent);
66
- if (!mountEvent.defaultPrevented) {
67
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
68
- if (document.activeElement === previouslyFocusedElement) {
69
- focus(container);
70
- }
60
+ if (!container)
61
+ return;
62
+ if (forceUnmount)
63
+ return;
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);
71
75
  }
72
76
  }
73
- return () => {
74
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
75
- setTimeout(() => {
76
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
77
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
78
- container.dispatchEvent(unmountEvent);
79
- if (!unmountEvent.defaultPrevented) {
80
- focus(previouslyFocusedElement ?? document.body, { select: true });
81
- }
82
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
83
- focusScopesStack.remove(focusScope);
84
- }, 0);
85
- };
86
77
  }
87
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
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]);
88
90
  const handleKeyDown = React.useCallback((event) => {
89
91
  if (!loop && !trapped)
90
92
  return;
@@ -112,7 +114,7 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
112
114
  }
113
115
  }
114
116
  }, [loop, trapped, focusScope.paused]);
115
- const child = React.Children.only(props.children);
117
+ const child = React.Children.only(children);
116
118
  return React.cloneElement(child, {
117
119
  tabIndex: -1,
118
120
  ...scopeProps,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScope.tsx"],
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 ...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) {\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": "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",
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": "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,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,OACG;AAAA,MACD;AACJ,QAAM,CAAC,WAAW,gBAAgB,MAAM,SAA6B,IAAI;AACzE,QAAM,mBAAmB,SAAS,oBAAoB;AACtD,QAAM,qBAAqB,SAAS,sBAAsB;AAC1D,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,CAAC;AAAS;AACd,2BAAuB,OAAmB;AACxC,UAAI,WAAW,UAAU,CAAC;AAAW;AACrC,YAAM,SAAS,MAAM;AACrB,UAAI,UAAU,SAAS,MAAM,GAAG;AAC9B,8BAAsB,UAAU;AAAA,MAClC,OAAO;AACL,cAAM,sBAAsB,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,MACvD;AAAA,IACF;AAEA,4BAAwB,OAAmB;AACzC,UAAI,WAAW,UAAU,CAAC;AAAW;AACrC,UAAI,CAAC,UAAU,SAAS,MAAM,aAAmC,GAAG;AAClE,cAAM,sBAAsB,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,MACvD;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,aAAa;AAClD,aAAS,iBAAiB,YAAY,cAAc;AACpD,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,aAAa;AACrD,eAAS,oBAAoB,YAAY,cAAc;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,cAAc,WAAW,WAAW,MAAM,CAAC;AAExD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC;AAAW;AAChB,QAAI;AAAc;AAClB,qBAAiB,IAAI,UAAU;AAC/B,UAAM,2BAA2B,SAAS;AAC1C,UAAM,sBAAsB,UAAU,SAAS,wBAAwB;AAEvE,QAAI,CAAC,qBAAqB;AACxB,YAAM,aAAa,IAAI,YAAY,oBAAoB,aAAa;AACpE,gBAAU,iBAAiB,oBAAoB,gBAAgB;AAC/D,gBAAU,cAAc,UAAU;AAClC,UAAI,CAAC,WAAW,kBAAkB;AAChC,mBAAW,YAAY,sBAAsB,SAAS,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AAC1E,YAAI,SAAS,kBAAkB,0BAA0B;AACvD,gBAAM,SAAS;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,MAAM;AACX,gBAAU,oBAAoB,oBAAoB,gBAAgB;AAElE,YAAM,eAAe,IAAI,YAAY,sBAAsB,aAAa;AACxE,gBAAU,iBAAiB,sBAAsB,kBAAkB;AACnE,gBAAU,cAAc,YAAY;AACpC,UAAI,CAAC,aAAa,kBAAkB;AAClC,cAAM,4BAA4B,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,MACnE;AAEA,gBAAU,oBAAoB,sBAAsB,kBAAkB;AAEtE,uBAAiB,OAAO,UAAU;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,WAAW,cAAc,kBAAkB,oBAAoB,UAAU,CAAC;AAG9E,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,QAAQ;AAE1C,SAAO,MAAM,aAAa,OAAc;AAAA,IACtC,UAAU;AAAA,IACV,GAAG;AAAA,IACH,KAAK;AAAA,IACL,WAAW;AAAA,EACb,CAAC;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;AA3QtC;AA4QM,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
6
  "names": []
7
7
  }
@@ -11,6 +11,8 @@ 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);
@@ -28,63 +30,63 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
28
30
  }
29
31
  }).current;
30
32
  React.useEffect(() => {
31
- if (trapped) {
32
- let handleFocusIn2 = 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
- }, handleFocusOut2 = function(event) {
42
- if (focusScope.paused || !container)
43
- return;
44
- if (!container.contains(event.relatedTarget)) {
45
- focus(lastFocusedElementRef.current, { select: true });
46
- }
47
- };
48
- var handleFocusIn = handleFocusIn2, handleFocusOut = handleFocusOut2;
49
- document.addEventListener("focusin", handleFocusIn2);
50
- document.addEventListener("focusout", handleFocusOut2);
51
- return () => {
52
- document.removeEventListener("focusin", handleFocusIn2);
53
- document.removeEventListener("focusout", handleFocusOut2);
54
- };
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
+ }
55
44
  }
56
- }, [trapped, container, focusScope.paused]);
45
+ function handleFocusOut(event) {
46
+ if (focusScope.paused || !container)
47
+ return;
48
+ if (!container.contains(event.relatedTarget)) {
49
+ focus(lastFocusedElementRef.current, { select: true });
50
+ }
51
+ }
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]);
57
59
  React.useEffect(() => {
58
- if (container) {
59
- focusScopesStack.add(focusScope);
60
- const previouslyFocusedElement = document.activeElement;
61
- const hasFocusedCandidate = container.contains(previouslyFocusedElement);
62
- if (!hasFocusedCandidate) {
63
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
64
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
65
- container.dispatchEvent(mountEvent);
66
- if (!mountEvent.defaultPrevented) {
67
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true });
68
- if (document.activeElement === previouslyFocusedElement) {
69
- focus(container);
70
- }
60
+ if (!container)
61
+ return;
62
+ if (forceUnmount)
63
+ return;
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);
71
75
  }
72
76
  }
73
- return () => {
74
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
75
- setTimeout(() => {
76
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
77
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
78
- container.dispatchEvent(unmountEvent);
79
- if (!unmountEvent.defaultPrevented) {
80
- focus(previouslyFocusedElement != null ? previouslyFocusedElement : document.body, { select: true });
81
- }
82
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
83
- focusScopesStack.remove(focusScope);
84
- }, 0);
85
- };
86
77
  }
87
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
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 != null ? previouslyFocusedElement : document.body, { select: true });
85
+ }
86
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
87
+ focusScopesStack.remove(focusScope);
88
+ };
89
+ }, [container, forceUnmount, onMountAutoFocus, onUnmountAutoFocus, focusScope]);
88
90
  const handleKeyDown = React.useCallback((event) => {
89
91
  if (!loop && !trapped)
90
92
  return;
@@ -112,7 +114,7 @@ const FocusScope = React.forwardRef((props, forwardedRef) => {
112
114
  }
113
115
  }
114
116
  }, [loop, trapped, focusScope.paused]);
115
- const child = React.Children.only(props.children);
117
+ const child = React.Children.only(children);
116
118
  return React.cloneElement(child, {
117
119
  tabIndex: -1,
118
120
  ...scopeProps,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/FocusScope.tsx"],
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 ...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) {\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": "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",
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
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/focus-scope",
3
- "version": "1.0.1-beta.118",
3
+ "version": "1.0.1-beta.119",
4
4
  "sideEffects": true,
5
5
  "source": "src/index.ts",
6
6
  "types": "./types/index.d.ts",
@@ -19,15 +19,15 @@
19
19
  "clean:build": "tamagui-build clean:build"
20
20
  },
21
21
  "dependencies": {
22
- "@tamagui/compose-refs": "^1.0.1-beta.118",
23
- "@tamagui/use-event": "^1.0.1-beta.118"
22
+ "@tamagui/compose-refs": "^1.0.1-beta.119",
23
+ "@tamagui/use-event": "^1.0.1-beta.119"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "react": "*",
27
27
  "react-dom": "*"
28
28
  },
29
29
  "devDependencies": {
30
- "@tamagui/build": "^1.0.1-beta.118",
30
+ "@tamagui/build": "^1.0.1-beta.119",
31
31
  "react": "*",
32
32
  "react-dom": "*"
33
33
  },
@@ -24,6 +24,8 @@ const FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props,
24
24
  trapped = false,
25
25
  onMountAutoFocus: onMountAutoFocusProp,
26
26
  onUnmountAutoFocus: onUnmountAutoFocusProp,
27
+ children,
28
+ forceUnmount,
27
29
  ...scopeProps
28
30
  } = props
29
31
  const [container, setContainer] = React.useState<HTMLElement | null>(null)
@@ -44,72 +46,66 @@ const FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props,
44
46
 
45
47
  // Takes care of trapping focus if focus is moved outside programmatically for example
46
48
  React.useEffect(() => {
47
- if (trapped) {
48
- function handleFocusIn(event: FocusEvent) {
49
- if (focusScope.paused || !container) return
50
- const target = event.target as HTMLElement | null
51
- if (container.contains(target)) {
52
- lastFocusedElementRef.current = target
53
- } else {
54
- focus(lastFocusedElementRef.current, { select: true })
55
- }
49
+ if (!trapped) return
50
+ function handleFocusIn(event: FocusEvent) {
51
+ if (focusScope.paused || !container) return
52
+ const target = event.target as HTMLElement | null
53
+ if (container.contains(target)) {
54
+ lastFocusedElementRef.current = target
55
+ } else {
56
+ focus(lastFocusedElementRef.current, { select: true })
56
57
  }
58
+ }
57
59
 
58
- function handleFocusOut(event: FocusEvent) {
59
- if (focusScope.paused || !container) return
60
- if (!container.contains(event.relatedTarget as HTMLElement | null)) {
61
- focus(lastFocusedElementRef.current, { select: true })
62
- }
60
+ function handleFocusOut(event: FocusEvent) {
61
+ if (focusScope.paused || !container) return
62
+ if (!container.contains(event.relatedTarget as HTMLElement | null)) {
63
+ focus(lastFocusedElementRef.current, { select: true })
63
64
  }
65
+ }
64
66
 
65
- document.addEventListener('focusin', handleFocusIn)
66
- document.addEventListener('focusout', handleFocusOut)
67
- return () => {
68
- document.removeEventListener('focusin', handleFocusIn)
69
- document.removeEventListener('focusout', handleFocusOut)
70
- }
67
+ document.addEventListener('focusin', handleFocusIn)
68
+ document.addEventListener('focusout', handleFocusOut)
69
+ return () => {
70
+ document.removeEventListener('focusin', handleFocusIn)
71
+ document.removeEventListener('focusout', handleFocusOut)
71
72
  }
72
- }, [trapped, container, focusScope.paused])
73
+ }, [trapped, forceUnmount, container, focusScope.paused])
73
74
 
74
75
  React.useEffect(() => {
75
- if (container) {
76
- focusScopesStack.add(focusScope)
77
- const previouslyFocusedElement = document.activeElement as HTMLElement | null
78
- const hasFocusedCandidate = container.contains(previouslyFocusedElement)
79
-
80
- if (!hasFocusedCandidate) {
81
- const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS)
82
- container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus)
83
- container.dispatchEvent(mountEvent)
84
- if (!mountEvent.defaultPrevented) {
85
- focusFirst(removeLinks(getTabbableCandidates(container)), { select: true })
86
- if (document.activeElement === previouslyFocusedElement) {
87
- focus(container)
88
- }
76
+ if (!container) return
77
+ if (forceUnmount) return
78
+ focusScopesStack.add(focusScope)
79
+ const previouslyFocusedElement = document.activeElement as HTMLElement | null
80
+ const hasFocusedCandidate = container.contains(previouslyFocusedElement)
81
+
82
+ if (!hasFocusedCandidate) {
83
+ const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS)
84
+ container.addEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus)
85
+ container.dispatchEvent(mountEvent)
86
+ if (!mountEvent.defaultPrevented) {
87
+ focusFirst(removeLinks(getTabbableCandidates(container)), { select: true })
88
+ if (document.activeElement === previouslyFocusedElement) {
89
+ focus(container)
89
90
  }
90
91
  }
92
+ }
91
93
 
92
- return () => {
93
- container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus)
94
-
95
- // We hit a react bug (fixed in v17) with focusing in unmount.
96
- // We need to delay the focus a little to get around it for now.
97
- // See: https://github.com/facebook/react/issues/17894
98
- setTimeout(() => {
99
- const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS)
100
- container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus)
101
- container.dispatchEvent(unmountEvent)
102
- if (!unmountEvent.defaultPrevented) {
103
- focus(previouslyFocusedElement ?? document.body, { select: true })
104
- }
105
- // we need to remove the listener after we `dispatchEvent`
106
- container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus)
94
+ return () => {
95
+ container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus)
107
96
 
108
- focusScopesStack.remove(focusScope)
109
- }, 0)
97
+ const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS)
98
+ container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus)
99
+ container.dispatchEvent(unmountEvent)
100
+ if (!unmountEvent.defaultPrevented) {
101
+ focus(previouslyFocusedElement ?? document.body, { select: true })
110
102
  }
103
+ // we need to remove the listener after we `dispatchEvent`
104
+ container.removeEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus)
105
+
106
+ focusScopesStack.remove(focusScope)
111
107
  }
112
- }, [container, onMountAutoFocus, onUnmountAutoFocus, focusScope])
108
+ }, [container, forceUnmount, onMountAutoFocus, onUnmountAutoFocus, focusScope])
113
109
 
114
110
  // Takes care of looping focus (when tabbing whilst at the edges)
115
111
  const handleKeyDown = React.useCallback(
@@ -142,7 +138,7 @@ const FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props,
142
138
  [loop, trapped, focusScope.paused]
143
139
  )
144
140
 
145
- const child = React.Children.only(props.children)
141
+ const child = React.Children.only(children)
146
142
 
147
143
  return React.cloneElement(child as any, {
148
144
  tabIndex: -1,
@@ -27,5 +27,10 @@ export interface FocusScopeProps {
27
27
  */
28
28
  onUnmountAutoFocus?: (event: Event) => void
29
29
 
30
+ /**
31
+ * If unmount is animated, you want to force re-focus at start of animation not after
32
+ */
33
+ forceUnmount?: boolean
34
+
30
35
  children?: React.ReactNode
31
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FocusScope.d.ts","sourceRoot":"","sources":["../src/FocusScope.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAgBnD,QAAA,MAAM,UAAU,wFAoId,CAAA;AA2IF,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB,YAAY,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"FocusScope.d.ts","sourceRoot":"","sources":["../src/FocusScope.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAgBnD,QAAA,MAAM,UAAU,wFAgId,CAAA;AA2IF,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB,YAAY,EAAE,eAAe,EAAE,CAAA"}
@@ -4,6 +4,7 @@ export interface FocusScopeProps {
4
4
  trapped?: boolean;
5
5
  onMountAutoFocus?: (event: Event) => void;
6
6
  onUnmountAutoFocus?: (event: Event) => void;
7
+ forceUnmount?: boolean;
7
8
  children?: React.ReactNode;
8
9
  }
9
10
  //# sourceMappingURL=FocusScopeProps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FocusScopeProps.d.ts","sourceRoot":"","sources":["../src/FocusScopeProps.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,WAAW,eAAe;IAM9B,IAAI,CAAC,EAAE,OAAO,CAAA;IAOd,OAAO,CAAC,EAAE,OAAO,CAAA;IAMjB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAMzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAE3C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B"}
1
+ {"version":3,"file":"FocusScopeProps.d.ts","sourceRoot":"","sources":["../src/FocusScopeProps.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,WAAW,eAAe;IAM9B,IAAI,CAAC,EAAE,OAAO,CAAA;IAOd,OAAO,CAAC,EAAE,OAAO,CAAA;IAMjB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAMzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAK3C,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B"}