748 lines
42 KiB
JavaScript
748 lines
42 KiB
JavaScript
import {useLayoutEffect as $cgawC$useLayoutEffect, getActiveElement as $cgawC$getActiveElement, getOwnerDocument as $cgawC$getOwnerDocument, getEventTarget as $cgawC$getEventTarget, isAndroid as $cgawC$isAndroid, isChrome as $cgawC$isChrome, isTabbable as $cgawC$isTabbable, isFocusable as $cgawC$isFocusable, createShadowTreeWalker as $cgawC$createShadowTreeWalker} from "@react-aria/utils";
|
|
import {getInteractionModality as $cgawC$getInteractionModality, focusSafely as $cgawC$focusSafely} from "@react-aria/interactions";
|
|
import $cgawC$react, {useRef as $cgawC$useRef, useContext as $cgawC$useContext, useMemo as $cgawC$useMemo, useEffect as $cgawC$useEffect} from "react";
|
|
|
|
/*
|
|
* Copyright 2020 Adobe. All rights reserved.
|
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
* governing permissions and limitations under the License.
|
|
*/
|
|
|
|
|
|
const $9bf71ea28793e738$var$FocusContext = /*#__PURE__*/ (0, $cgawC$react).createContext(null);
|
|
const $9bf71ea28793e738$var$RESTORE_FOCUS_EVENT = 'react-aria-focus-scope-restore';
|
|
let $9bf71ea28793e738$var$activeScope = null;
|
|
function $9bf71ea28793e738$export$20e40289641fbbb6(props) {
|
|
let { children: children, contain: contain, restoreFocus: restoreFocus, autoFocus: autoFocus } = props;
|
|
let startRef = (0, $cgawC$useRef)(null);
|
|
let endRef = (0, $cgawC$useRef)(null);
|
|
let scopeRef = (0, $cgawC$useRef)([]);
|
|
let { parentNode: parentNode } = (0, $cgawC$useContext)($9bf71ea28793e738$var$FocusContext) || {};
|
|
// Create a tree node here so we can add children to it even before it is added to the tree.
|
|
let node = (0, $cgawC$useMemo)(()=>new $9bf71ea28793e738$var$TreeNode({
|
|
scopeRef: scopeRef
|
|
}), [
|
|
scopeRef
|
|
]);
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
// If a new scope mounts outside the active scope, (e.g. DialogContainer launched from a menu),
|
|
// use the active scope as the parent instead of the parent from context. Layout effects run bottom
|
|
// up, so if the parent is not yet added to the tree, don't do this. Only the outer-most FocusScope
|
|
// that is being added should get the activeScope as its parent.
|
|
let parent = parentNode || $9bf71ea28793e738$export$d06fae2ee68b101e.root;
|
|
if ($9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(parent.scopeRef) && $9bf71ea28793e738$var$activeScope && !$9bf71ea28793e738$var$isAncestorScope($9bf71ea28793e738$var$activeScope, parent.scopeRef)) {
|
|
let activeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode($9bf71ea28793e738$var$activeScope);
|
|
if (activeNode) parent = activeNode;
|
|
}
|
|
// Add the node to the parent, and to the tree.
|
|
parent.addChild(node);
|
|
$9bf71ea28793e738$export$d06fae2ee68b101e.addNode(node);
|
|
}, [
|
|
node,
|
|
parentNode
|
|
]);
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
let node = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
|
|
if (node) node.contain = !!contain;
|
|
}, [
|
|
contain
|
|
]);
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
var _startRef_current;
|
|
// Find all rendered nodes between the sentinels and add them to the scope.
|
|
let node = (_startRef_current = startRef.current) === null || _startRef_current === void 0 ? void 0 : _startRef_current.nextSibling;
|
|
let nodes = [];
|
|
let stopPropagation = (e)=>e.stopPropagation();
|
|
while(node && node !== endRef.current){
|
|
nodes.push(node);
|
|
// Stop custom restore focus event from propagating to parent focus scopes.
|
|
node.addEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
|
|
node = node.nextSibling;
|
|
}
|
|
scopeRef.current = nodes;
|
|
return ()=>{
|
|
for (let node of nodes)node.removeEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
|
|
};
|
|
}, [
|
|
children
|
|
]);
|
|
$9bf71ea28793e738$var$useActiveScopeTracker(scopeRef, restoreFocus, contain);
|
|
$9bf71ea28793e738$var$useFocusContainment(scopeRef, contain);
|
|
$9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain);
|
|
$9bf71ea28793e738$var$useAutoFocus(scopeRef, autoFocus);
|
|
// This needs to be an effect so that activeScope is updated after the FocusScope tree is complete.
|
|
// It cannot be a useLayoutEffect because the parent of this node hasn't been attached in the tree yet.
|
|
(0, $cgawC$useEffect)(()=>{
|
|
const activeElement = (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined));
|
|
let scope = null;
|
|
if ($9bf71ea28793e738$var$isElementInScope(activeElement, scopeRef.current)) {
|
|
// We need to traverse the focusScope tree and find the bottom most scope that
|
|
// contains the active element and set that as the activeScope.
|
|
for (let node of $9bf71ea28793e738$export$d06fae2ee68b101e.traverse())if (node.scopeRef && $9bf71ea28793e738$var$isElementInScope(activeElement, node.scopeRef.current)) scope = node;
|
|
if (scope === $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef)) $9bf71ea28793e738$var$activeScope = scope.scopeRef;
|
|
}
|
|
}, [
|
|
scopeRef
|
|
]);
|
|
// This layout effect cleanup is so that the tree node is removed synchronously with react before the RAF
|
|
// in useRestoreFocus cleanup runs.
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
return ()=>{
|
|
var _focusScopeTree_getTreeNode_parent, _focusScopeTree_getTreeNode;
|
|
var _focusScopeTree_getTreeNode_parent_scopeRef;
|
|
// Scope may have been re-parented.
|
|
let parentScope = (_focusScopeTree_getTreeNode_parent_scopeRef = (_focusScopeTree_getTreeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef)) === null || _focusScopeTree_getTreeNode === void 0 ? void 0 : (_focusScopeTree_getTreeNode_parent = _focusScopeTree_getTreeNode.parent) === null || _focusScopeTree_getTreeNode_parent === void 0 ? void 0 : _focusScopeTree_getTreeNode_parent.scopeRef) !== null && _focusScopeTree_getTreeNode_parent_scopeRef !== void 0 ? _focusScopeTree_getTreeNode_parent_scopeRef : null;
|
|
if ((scopeRef === $9bf71ea28793e738$var$activeScope || $9bf71ea28793e738$var$isAncestorScope(scopeRef, $9bf71ea28793e738$var$activeScope)) && (!parentScope || $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(parentScope))) $9bf71ea28793e738$var$activeScope = parentScope;
|
|
$9bf71ea28793e738$export$d06fae2ee68b101e.removeTreeNode(scopeRef);
|
|
};
|
|
}, [
|
|
scopeRef
|
|
]);
|
|
let focusManager = (0, $cgawC$useMemo)(()=>$9bf71ea28793e738$var$createFocusManagerForScope(scopeRef), []);
|
|
let value = (0, $cgawC$useMemo)(()=>({
|
|
focusManager: focusManager,
|
|
parentNode: node
|
|
}), [
|
|
node,
|
|
focusManager
|
|
]);
|
|
return /*#__PURE__*/ (0, $cgawC$react).createElement($9bf71ea28793e738$var$FocusContext.Provider, {
|
|
value: value
|
|
}, /*#__PURE__*/ (0, $cgawC$react).createElement("span", {
|
|
"data-focus-scope-start": true,
|
|
hidden: true,
|
|
ref: startRef
|
|
}), children, /*#__PURE__*/ (0, $cgawC$react).createElement("span", {
|
|
"data-focus-scope-end": true,
|
|
hidden: true,
|
|
ref: endRef
|
|
}));
|
|
}
|
|
function $9bf71ea28793e738$export$10c5169755ce7bd7() {
|
|
var _useContext;
|
|
return (_useContext = (0, $cgawC$useContext)($9bf71ea28793e738$var$FocusContext)) === null || _useContext === void 0 ? void 0 : _useContext.focusManager;
|
|
}
|
|
function $9bf71ea28793e738$var$createFocusManagerForScope(scopeRef) {
|
|
return {
|
|
focusNext (opts = {}) {
|
|
let scope = scopeRef.current;
|
|
let { from: from, tabbable: tabbable, wrap: wrap, accept: accept } = opts;
|
|
var _scope_;
|
|
let node = from || (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)((_scope_ = scope[0]) !== null && _scope_ !== void 0 ? _scope_ : undefined));
|
|
let sentinel = scope[0].previousElementSibling;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
}, scope);
|
|
walker.currentNode = $9bf71ea28793e738$var$isElementInScope(node, scope) ? node : sentinel;
|
|
let nextNode = walker.nextNode();
|
|
if (!nextNode && wrap) {
|
|
walker.currentNode = sentinel;
|
|
nextNode = walker.nextNode();
|
|
}
|
|
if (nextNode) $9bf71ea28793e738$var$focusElement(nextNode, true);
|
|
return nextNode;
|
|
},
|
|
focusPrevious (opts = {}) {
|
|
let scope = scopeRef.current;
|
|
let { from: from, tabbable: tabbable, wrap: wrap, accept: accept } = opts;
|
|
var _scope_;
|
|
let node = from || (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)((_scope_ = scope[0]) !== null && _scope_ !== void 0 ? _scope_ : undefined));
|
|
let sentinel = scope[scope.length - 1].nextElementSibling;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
}, scope);
|
|
walker.currentNode = $9bf71ea28793e738$var$isElementInScope(node, scope) ? node : sentinel;
|
|
let previousNode = walker.previousNode();
|
|
if (!previousNode && wrap) {
|
|
walker.currentNode = sentinel;
|
|
previousNode = walker.previousNode();
|
|
}
|
|
if (previousNode) $9bf71ea28793e738$var$focusElement(previousNode, true);
|
|
return previousNode;
|
|
},
|
|
focusFirst (opts = {}) {
|
|
let scope = scopeRef.current;
|
|
let { tabbable: tabbable, accept: accept } = opts;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
}, scope);
|
|
walker.currentNode = scope[0].previousElementSibling;
|
|
let nextNode = walker.nextNode();
|
|
if (nextNode) $9bf71ea28793e738$var$focusElement(nextNode, true);
|
|
return nextNode;
|
|
},
|
|
focusLast (opts = {}) {
|
|
let scope = scopeRef.current;
|
|
let { tabbable: tabbable, accept: accept } = opts;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
}, scope);
|
|
walker.currentNode = scope[scope.length - 1].nextElementSibling;
|
|
let previousNode = walker.previousNode();
|
|
if (previousNode) $9bf71ea28793e738$var$focusElement(previousNode, true);
|
|
return previousNode;
|
|
}
|
|
};
|
|
}
|
|
function $9bf71ea28793e738$var$getScopeRoot(scope) {
|
|
return scope[0].parentElement;
|
|
}
|
|
function $9bf71ea28793e738$var$shouldContainFocus(scopeRef) {
|
|
let scope = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode($9bf71ea28793e738$var$activeScope);
|
|
while(scope && scope.scopeRef !== scopeRef){
|
|
if (scope.contain) return false;
|
|
scope = scope.parent;
|
|
}
|
|
return true;
|
|
}
|
|
function $9bf71ea28793e738$var$isTabbableRadio(element) {
|
|
if (element.checked) return true;
|
|
let radios = [];
|
|
if (!element.form) radios = [
|
|
...(0, $cgawC$getOwnerDocument)(element).querySelectorAll(`input[type="radio"][name="${CSS.escape(element.name)}"]`)
|
|
].filter((radio)=>!radio.form);
|
|
else {
|
|
var _element_form_elements, _element_form;
|
|
let radioList = (_element_form = element.form) === null || _element_form === void 0 ? void 0 : (_element_form_elements = _element_form.elements) === null || _element_form_elements === void 0 ? void 0 : _element_form_elements.namedItem(element.name);
|
|
radios = [
|
|
...radioList !== null && radioList !== void 0 ? radioList : []
|
|
];
|
|
}
|
|
if (!radios) return false;
|
|
let anyChecked = radios.some((radio)=>radio.checked);
|
|
return !anyChecked;
|
|
}
|
|
function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
|
|
let focusedNode = (0, $cgawC$useRef)(undefined);
|
|
let raf = (0, $cgawC$useRef)(undefined);
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
let scope = scopeRef.current;
|
|
if (!contain) {
|
|
// if contain was changed, then we should cancel any ongoing waits to pull focus back into containment
|
|
if (raf.current) {
|
|
cancelAnimationFrame(raf.current);
|
|
raf.current = undefined;
|
|
}
|
|
return;
|
|
}
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scope ? scope[0] : undefined);
|
|
// Handle the Tab key to contain focus within the scope
|
|
let onKeyDown = (e)=>{
|
|
if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
|
|
let focusedElement = (0, $cgawC$getActiveElement)(ownerDocument);
|
|
let scope = scopeRef.current;
|
|
if (!scope || !$9bf71ea28793e738$var$isElementInScope(focusedElement, scope)) return;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: true
|
|
}, scope);
|
|
if (!focusedElement) return;
|
|
walker.currentNode = focusedElement;
|
|
let nextElement = e.shiftKey ? walker.previousNode() : walker.nextNode();
|
|
if (!nextElement) {
|
|
walker.currentNode = e.shiftKey ? scope[scope.length - 1].nextElementSibling : scope[0].previousElementSibling;
|
|
nextElement = e.shiftKey ? walker.previousNode() : walker.nextNode();
|
|
}
|
|
e.preventDefault();
|
|
if (nextElement) $9bf71ea28793e738$var$focusElement(nextElement, true);
|
|
};
|
|
let onFocus = (e)=>{
|
|
// If focusing an element in a child scope of the currently active scope, the child becomes active.
|
|
// Moving out of the active scope to an ancestor is not allowed.
|
|
if ((!$9bf71ea28793e738$var$activeScope || $9bf71ea28793e738$var$isAncestorScope($9bf71ea28793e738$var$activeScope, scopeRef)) && $9bf71ea28793e738$var$isElementInScope((0, $cgawC$getEventTarget)(e), scopeRef.current)) {
|
|
$9bf71ea28793e738$var$activeScope = scopeRef;
|
|
focusedNode.current = (0, $cgawC$getEventTarget)(e);
|
|
} else if ($9bf71ea28793e738$var$shouldContainFocus(scopeRef) && !$9bf71ea28793e738$var$isElementInChildScope((0, $cgawC$getEventTarget)(e), scopeRef)) {
|
|
// If a focus event occurs outside the active scope (e.g. user tabs from browser location bar),
|
|
// restore focus to the previously focused node or the first tabbable element in the active scope.
|
|
if (focusedNode.current) focusedNode.current.focus();
|
|
else if ($9bf71ea28793e738$var$activeScope && $9bf71ea28793e738$var$activeScope.current) $9bf71ea28793e738$var$focusFirstInScope($9bf71ea28793e738$var$activeScope.current);
|
|
} else if ($9bf71ea28793e738$var$shouldContainFocus(scopeRef)) focusedNode.current = (0, $cgawC$getEventTarget)(e);
|
|
};
|
|
let onBlur = (e)=>{
|
|
// Firefox doesn't shift focus back to the Dialog properly without this
|
|
if (raf.current) cancelAnimationFrame(raf.current);
|
|
raf.current = requestAnimationFrame(()=>{
|
|
// Patches infinite focus coersion loop for Android Talkback where the user isn't able to move the virtual cursor
|
|
// if within a containing focus scope. Bug filed against Chrome: https://issuetracker.google.com/issues/384844019.
|
|
// Note that this means focus can leave focus containing modals due to this, but it is isolated to Chrome Talkback.
|
|
let modality = (0, $cgawC$getInteractionModality)();
|
|
let shouldSkipFocusRestore = (modality === 'virtual' || modality === null) && (0, $cgawC$isAndroid)() && (0, $cgawC$isChrome)();
|
|
// Use document.activeElement instead of e.relatedTarget so we can tell if user clicked into iframe
|
|
let activeElement = (0, $cgawC$getActiveElement)(ownerDocument);
|
|
if (!shouldSkipFocusRestore && activeElement && $9bf71ea28793e738$var$shouldContainFocus(scopeRef) && !$9bf71ea28793e738$var$isElementInChildScope(activeElement, scopeRef)) {
|
|
$9bf71ea28793e738$var$activeScope = scopeRef;
|
|
let target = (0, $cgawC$getEventTarget)(e);
|
|
if (target && target.isConnected) {
|
|
var _focusedNode_current;
|
|
focusedNode.current = target;
|
|
(_focusedNode_current = focusedNode.current) === null || _focusedNode_current === void 0 ? void 0 : _focusedNode_current.focus();
|
|
} else if ($9bf71ea28793e738$var$activeScope.current) $9bf71ea28793e738$var$focusFirstInScope($9bf71ea28793e738$var$activeScope.current);
|
|
}
|
|
});
|
|
};
|
|
ownerDocument.addEventListener('keydown', onKeyDown, false);
|
|
ownerDocument.addEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusout', onBlur, false));
|
|
return ()=>{
|
|
ownerDocument.removeEventListener('keydown', onKeyDown, false);
|
|
ownerDocument.removeEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusout', onBlur, false));
|
|
};
|
|
}, [
|
|
scopeRef,
|
|
contain
|
|
]);
|
|
// This is a useLayoutEffect so it is guaranteed to run before our async synthetic blur
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
return ()=>{
|
|
if (raf.current) cancelAnimationFrame(raf.current);
|
|
};
|
|
}, [
|
|
raf
|
|
]);
|
|
}
|
|
function $9bf71ea28793e738$var$isElementInAnyScope(element) {
|
|
return $9bf71ea28793e738$var$isElementInChildScope(element);
|
|
}
|
|
function $9bf71ea28793e738$var$isElementInScope(element, scope) {
|
|
if (!element) return false;
|
|
if (!scope) return false;
|
|
return scope.some((node)=>node.contains(element));
|
|
}
|
|
function $9bf71ea28793e738$var$isElementInChildScope(element, scope = null) {
|
|
// If the element is within a top layer element (e.g. toasts), always allow moving focus there.
|
|
if (element instanceof Element && element.closest('[data-react-aria-top-layer]')) return true;
|
|
// node.contains in isElementInScope covers child scopes that are also DOM children,
|
|
// but does not cover child scopes in portals.
|
|
for (let { scopeRef: s } of $9bf71ea28793e738$export$d06fae2ee68b101e.traverse($9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scope))){
|
|
if (s && $9bf71ea28793e738$var$isElementInScope(element, s.current)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
function $9bf71ea28793e738$export$1258395f99bf9cbf(element) {
|
|
return $9bf71ea28793e738$var$isElementInChildScope(element, $9bf71ea28793e738$var$activeScope);
|
|
}
|
|
function $9bf71ea28793e738$var$isAncestorScope(ancestor, scope) {
|
|
var _focusScopeTree_getTreeNode;
|
|
let parent = (_focusScopeTree_getTreeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scope)) === null || _focusScopeTree_getTreeNode === void 0 ? void 0 : _focusScopeTree_getTreeNode.parent;
|
|
while(parent){
|
|
if (parent.scopeRef === ancestor) return true;
|
|
parent = parent.parent;
|
|
}
|
|
return false;
|
|
}
|
|
function $9bf71ea28793e738$var$focusElement(element, scroll = false) {
|
|
if (element != null && !scroll) try {
|
|
(0, $cgawC$focusSafely)(element);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
else if (element != null) try {
|
|
element.focus();
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
function $9bf71ea28793e738$var$getFirstInScope(scope, tabbable = true) {
|
|
let sentinel = scope[0].previousElementSibling;
|
|
let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: tabbable
|
|
}, scope);
|
|
walker.currentNode = sentinel;
|
|
let nextNode = walker.nextNode();
|
|
// If the scope does not contain a tabbable element, use the first focusable element.
|
|
if (tabbable && !nextNode) {
|
|
scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
|
|
walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
|
|
tabbable: false
|
|
}, scope);
|
|
walker.currentNode = sentinel;
|
|
nextNode = walker.nextNode();
|
|
}
|
|
return nextNode;
|
|
}
|
|
function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
|
|
$9bf71ea28793e738$var$focusElement($9bf71ea28793e738$var$getFirstInScope(scope, tabbable));
|
|
}
|
|
function $9bf71ea28793e738$var$useAutoFocus(scopeRef, autoFocus) {
|
|
const autoFocusRef = (0, $cgawC$react).useRef(autoFocus);
|
|
(0, $cgawC$useEffect)(()=>{
|
|
if (autoFocusRef.current) {
|
|
$9bf71ea28793e738$var$activeScope = scopeRef;
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined);
|
|
if (!$9bf71ea28793e738$var$isElementInScope((0, $cgawC$getActiveElement)(ownerDocument), $9bf71ea28793e738$var$activeScope.current) && scopeRef.current) $9bf71ea28793e738$var$focusFirstInScope(scopeRef.current);
|
|
}
|
|
autoFocusRef.current = false;
|
|
}, [
|
|
scopeRef
|
|
]);
|
|
}
|
|
function $9bf71ea28793e738$var$useActiveScopeTracker(scopeRef, restore, contain) {
|
|
// tracks the active scope, in case restore and contain are both false.
|
|
// if either are true, this is tracked in useRestoreFocus or useFocusContainment.
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
if (restore || contain) return;
|
|
let scope = scopeRef.current;
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scope ? scope[0] : undefined);
|
|
let onFocus = (e)=>{
|
|
let target = (0, $cgawC$getEventTarget)(e);
|
|
if ($9bf71ea28793e738$var$isElementInScope(target, scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
|
|
else if (!$9bf71ea28793e738$var$isElementInAnyScope(target)) $9bf71ea28793e738$var$activeScope = null;
|
|
};
|
|
ownerDocument.addEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
|
|
return ()=>{
|
|
ownerDocument.removeEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
|
|
};
|
|
}, [
|
|
scopeRef,
|
|
restore,
|
|
contain
|
|
]);
|
|
}
|
|
function $9bf71ea28793e738$var$shouldRestoreFocus(scopeRef) {
|
|
let scope = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode($9bf71ea28793e738$var$activeScope);
|
|
while(scope && scope.scopeRef !== scopeRef){
|
|
if (scope.nodeToRestore) return false;
|
|
scope = scope.parent;
|
|
}
|
|
return (scope === null || scope === void 0 ? void 0 : scope.scopeRef) === scopeRef;
|
|
}
|
|
function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain) {
|
|
// create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
|
|
// eslint-disable-next-line no-restricted-globals
|
|
const nodeToRestoreRef = (0, $cgawC$useRef)(typeof document !== 'undefined' ? (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined)) : null);
|
|
// restoring scopes should all track if they are active regardless of contain, but contain already tracks it plus logic to contain the focus
|
|
// restoring-non-containing scopes should only care if they become active so they can perform the restore
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
let scope = scopeRef.current;
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scope ? scope[0] : undefined);
|
|
if (!restoreFocus || contain) return;
|
|
let onFocus = ()=>{
|
|
// If focusing an element in a child scope of the currently active scope, the child becomes active.
|
|
// Moving out of the active scope to an ancestor is not allowed.
|
|
if ((!$9bf71ea28793e738$var$activeScope || $9bf71ea28793e738$var$isAncestorScope($9bf71ea28793e738$var$activeScope, scopeRef)) && $9bf71ea28793e738$var$isElementInScope((0, $cgawC$getActiveElement)(ownerDocument), scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
|
|
};
|
|
ownerDocument.addEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
|
|
return ()=>{
|
|
ownerDocument.removeEventListener('focusin', onFocus, false);
|
|
scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
scopeRef,
|
|
contain
|
|
]);
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined);
|
|
if (!restoreFocus) return;
|
|
// Handle the Tab key so that tabbing out of the scope goes to the next element
|
|
// after the node that had focus when the scope mounted. This is important when
|
|
// using portals for overlays, so that focus goes to the expected element when
|
|
// tabbing out of the overlay.
|
|
let onKeyDown = (e)=>{
|
|
if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
|
|
let focusedElement = ownerDocument.activeElement;
|
|
if (!$9bf71ea28793e738$var$isElementInChildScope(focusedElement, scopeRef) || !$9bf71ea28793e738$var$shouldRestoreFocus(scopeRef)) return;
|
|
let treeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
|
|
if (!treeNode) return;
|
|
let nodeToRestore = treeNode.nodeToRestore;
|
|
// Create a DOM tree walker that matches all tabbable elements
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(ownerDocument.body, {
|
|
tabbable: true
|
|
});
|
|
// Find the next tabbable element after the currently focused element
|
|
walker.currentNode = focusedElement;
|
|
let nextElement = e.shiftKey ? walker.previousNode() : walker.nextNode();
|
|
if (!nodeToRestore || !nodeToRestore.isConnected || nodeToRestore === ownerDocument.body) {
|
|
nodeToRestore = undefined;
|
|
treeNode.nodeToRestore = undefined;
|
|
}
|
|
// If there is no next element, or it is outside the current scope, move focus to the
|
|
// next element after the node to restore to instead.
|
|
if ((!nextElement || !$9bf71ea28793e738$var$isElementInChildScope(nextElement, scopeRef)) && nodeToRestore) {
|
|
walker.currentNode = nodeToRestore;
|
|
// Skip over elements within the scope, in case the scope immediately follows the node to restore.
|
|
do nextElement = e.shiftKey ? walker.previousNode() : walker.nextNode();
|
|
while ($9bf71ea28793e738$var$isElementInChildScope(nextElement, scopeRef));
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (nextElement) $9bf71ea28793e738$var$focusElement(nextElement, true);
|
|
else // If there is no next element and the nodeToRestore isn't within a FocusScope (i.e. we are leaving the top level focus scope)
|
|
// then move focus to the body.
|
|
// Otherwise restore focus to the nodeToRestore (e.g menu within a popover -> tabbing to close the menu should move focus to menu trigger)
|
|
if (!$9bf71ea28793e738$var$isElementInAnyScope(nodeToRestore)) focusedElement.blur();
|
|
else $9bf71ea28793e738$var$focusElement(nodeToRestore, true);
|
|
}
|
|
};
|
|
if (!contain) ownerDocument.addEventListener('keydown', onKeyDown, true);
|
|
return ()=>{
|
|
if (!contain) ownerDocument.removeEventListener('keydown', onKeyDown, true);
|
|
};
|
|
}, [
|
|
scopeRef,
|
|
restoreFocus,
|
|
contain
|
|
]);
|
|
// useLayoutEffect instead of useEffect so the active element is saved synchronously instead of asynchronously.
|
|
(0, $cgawC$useLayoutEffect)(()=>{
|
|
const ownerDocument = (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined);
|
|
if (!restoreFocus) return;
|
|
let treeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
|
|
if (!treeNode) return;
|
|
var _nodeToRestoreRef_current;
|
|
treeNode.nodeToRestore = (_nodeToRestoreRef_current = nodeToRestoreRef.current) !== null && _nodeToRestoreRef_current !== void 0 ? _nodeToRestoreRef_current : undefined;
|
|
return ()=>{
|
|
let treeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
|
|
if (!treeNode) return;
|
|
let nodeToRestore = treeNode.nodeToRestore;
|
|
// if we already lost focus to the body and this was the active scope, then we should attempt to restore
|
|
let activeElement = (0, $cgawC$getActiveElement)(ownerDocument);
|
|
if (restoreFocus && nodeToRestore && (activeElement && $9bf71ea28793e738$var$isElementInChildScope(activeElement, scopeRef) || activeElement === ownerDocument.body && $9bf71ea28793e738$var$shouldRestoreFocus(scopeRef))) {
|
|
// freeze the focusScopeTree so it persists after the raf, otherwise during unmount nodes are removed from it
|
|
let clonedTree = $9bf71ea28793e738$export$d06fae2ee68b101e.clone();
|
|
requestAnimationFrame(()=>{
|
|
// Only restore focus if we've lost focus to the body, the alternative is that focus has been purposefully moved elsewhere
|
|
if (ownerDocument.activeElement === ownerDocument.body) {
|
|
// look up the tree starting with our scope to find a nodeToRestore still in the DOM
|
|
let treeNode = clonedTree.getTreeNode(scopeRef);
|
|
while(treeNode){
|
|
if (treeNode.nodeToRestore && treeNode.nodeToRestore.isConnected) {
|
|
$9bf71ea28793e738$var$restoreFocusToElement(treeNode.nodeToRestore);
|
|
return;
|
|
}
|
|
treeNode = treeNode.parent;
|
|
}
|
|
// If no nodeToRestore was found, focus the first element in the nearest
|
|
// ancestor scope that is still in the tree.
|
|
treeNode = clonedTree.getTreeNode(scopeRef);
|
|
while(treeNode){
|
|
if (treeNode.scopeRef && treeNode.scopeRef.current && $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(treeNode.scopeRef)) {
|
|
let node = $9bf71ea28793e738$var$getFirstInScope(treeNode.scopeRef.current, true);
|
|
$9bf71ea28793e738$var$restoreFocusToElement(node);
|
|
return;
|
|
}
|
|
treeNode = treeNode.parent;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}, [
|
|
scopeRef,
|
|
restoreFocus
|
|
]);
|
|
}
|
|
function $9bf71ea28793e738$var$restoreFocusToElement(node) {
|
|
// Dispatch a custom event that parent elements can intercept to customize focus restoration.
|
|
// For example, virtualized collection components reuse DOM elements, so the original element
|
|
// might still exist in the DOM but representing a different item.
|
|
if (node.dispatchEvent(new CustomEvent($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, {
|
|
bubbles: true,
|
|
cancelable: true
|
|
}))) $9bf71ea28793e738$var$focusElement(node);
|
|
}
|
|
function $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, opts, scope) {
|
|
let filter = (opts === null || opts === void 0 ? void 0 : opts.tabbable) ? (0, $cgawC$isTabbable) : (0, $cgawC$isFocusable);
|
|
// Ensure that root is an Element or fall back appropriately
|
|
let rootElement = (root === null || root === void 0 ? void 0 : root.nodeType) === Node.ELEMENT_NODE ? root : null;
|
|
// Determine the document to use
|
|
let doc = (0, $cgawC$getOwnerDocument)(rootElement);
|
|
// Create a TreeWalker, ensuring the root is an Element or Document
|
|
let walker = (0, $cgawC$createShadowTreeWalker)(doc, root || doc, NodeFilter.SHOW_ELEMENT, {
|
|
acceptNode (node) {
|
|
var _opts_from;
|
|
// Skip nodes inside the starting node.
|
|
if (opts === null || opts === void 0 ? void 0 : (_opts_from = opts.from) === null || _opts_from === void 0 ? void 0 : _opts_from.contains(node)) return NodeFilter.FILTER_REJECT;
|
|
if ((opts === null || opts === void 0 ? void 0 : opts.tabbable) && node.tagName === 'INPUT' && node.getAttribute('type') === 'radio') {
|
|
// If the radio is in a form, we can get all the other radios by name
|
|
if (!$9bf71ea28793e738$var$isTabbableRadio(node)) return NodeFilter.FILTER_REJECT;
|
|
// If the radio is in the same group as the current node and none are selected, we can skip it
|
|
if (walker.currentNode.tagName === 'INPUT' && walker.currentNode.type === 'radio' && walker.currentNode.name === node.name) return NodeFilter.FILTER_REJECT;
|
|
}
|
|
if (filter(node) && (!scope || $9bf71ea28793e738$var$isElementInScope(node, scope)) && (!(opts === null || opts === void 0 ? void 0 : opts.accept) || opts.accept(node))) return NodeFilter.FILTER_ACCEPT;
|
|
return NodeFilter.FILTER_SKIP;
|
|
}
|
|
});
|
|
if (opts === null || opts === void 0 ? void 0 : opts.from) walker.currentNode = opts.from;
|
|
return walker;
|
|
}
|
|
function $9bf71ea28793e738$export$c5251b9e124bf29(ref, defaultOptions = {}) {
|
|
return {
|
|
focusNext (opts = {}) {
|
|
let root = ref.current;
|
|
if (!root) return null;
|
|
let { from: from, tabbable: tabbable = defaultOptions.tabbable, wrap: wrap = defaultOptions.wrap, accept: accept = defaultOptions.accept } = opts;
|
|
let node = from || (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)(root));
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
});
|
|
if (root.contains(node)) walker.currentNode = node;
|
|
let nextNode = walker.nextNode();
|
|
if (!nextNode && wrap) {
|
|
walker.currentNode = root;
|
|
nextNode = walker.nextNode();
|
|
}
|
|
if (nextNode) $9bf71ea28793e738$var$focusElement(nextNode, true);
|
|
return nextNode;
|
|
},
|
|
focusPrevious (opts = defaultOptions) {
|
|
let root = ref.current;
|
|
if (!root) return null;
|
|
let { from: from, tabbable: tabbable = defaultOptions.tabbable, wrap: wrap = defaultOptions.wrap, accept: accept = defaultOptions.accept } = opts;
|
|
let node = from || (0, $cgawC$getActiveElement)((0, $cgawC$getOwnerDocument)(root));
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
});
|
|
if (root.contains(node)) walker.currentNode = node;
|
|
else {
|
|
let next = $9bf71ea28793e738$var$last(walker);
|
|
if (next) $9bf71ea28793e738$var$focusElement(next, true);
|
|
return next !== null && next !== void 0 ? next : null;
|
|
}
|
|
let previousNode = walker.previousNode();
|
|
if (!previousNode && wrap) {
|
|
walker.currentNode = root;
|
|
let lastNode = $9bf71ea28793e738$var$last(walker);
|
|
if (!lastNode) // couldn't wrap
|
|
return null;
|
|
previousNode = lastNode;
|
|
}
|
|
if (previousNode) $9bf71ea28793e738$var$focusElement(previousNode, true);
|
|
return previousNode !== null && previousNode !== void 0 ? previousNode : null;
|
|
},
|
|
focusFirst (opts = defaultOptions) {
|
|
let root = ref.current;
|
|
if (!root) return null;
|
|
let { tabbable: tabbable = defaultOptions.tabbable, accept: accept = defaultOptions.accept } = opts;
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
});
|
|
let nextNode = walker.nextNode();
|
|
if (nextNode) $9bf71ea28793e738$var$focusElement(nextNode, true);
|
|
return nextNode;
|
|
},
|
|
focusLast (opts = defaultOptions) {
|
|
let root = ref.current;
|
|
if (!root) return null;
|
|
let { tabbable: tabbable = defaultOptions.tabbable, accept: accept = defaultOptions.accept } = opts;
|
|
let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, {
|
|
tabbable: tabbable,
|
|
accept: accept
|
|
});
|
|
let next = $9bf71ea28793e738$var$last(walker);
|
|
if (next) $9bf71ea28793e738$var$focusElement(next, true);
|
|
return next !== null && next !== void 0 ? next : null;
|
|
}
|
|
};
|
|
}
|
|
function $9bf71ea28793e738$var$last(walker) {
|
|
let next = undefined;
|
|
let last;
|
|
do {
|
|
last = walker.lastChild();
|
|
if (last) next = last;
|
|
}while (last);
|
|
return next;
|
|
}
|
|
class $9bf71ea28793e738$var$Tree {
|
|
get size() {
|
|
return this.fastMap.size;
|
|
}
|
|
getTreeNode(data) {
|
|
return this.fastMap.get(data);
|
|
}
|
|
addTreeNode(scopeRef, parent, nodeToRestore) {
|
|
let parentNode = this.fastMap.get(parent !== null && parent !== void 0 ? parent : null);
|
|
if (!parentNode) return;
|
|
let node = new $9bf71ea28793e738$var$TreeNode({
|
|
scopeRef: scopeRef
|
|
});
|
|
parentNode.addChild(node);
|
|
node.parent = parentNode;
|
|
this.fastMap.set(scopeRef, node);
|
|
if (nodeToRestore) node.nodeToRestore = nodeToRestore;
|
|
}
|
|
addNode(node) {
|
|
this.fastMap.set(node.scopeRef, node);
|
|
}
|
|
removeTreeNode(scopeRef) {
|
|
// never remove the root
|
|
if (scopeRef === null) return;
|
|
let node = this.fastMap.get(scopeRef);
|
|
if (!node) return;
|
|
let parentNode = node.parent;
|
|
// when we remove a scope, check if any sibling scopes are trying to restore focus to something inside the scope we're removing
|
|
// if we are, then replace the siblings restore with the restore from the scope we're removing
|
|
for (let current of this.traverse())if (current !== node && node.nodeToRestore && current.nodeToRestore && node.scopeRef && node.scopeRef.current && $9bf71ea28793e738$var$isElementInScope(current.nodeToRestore, node.scopeRef.current)) current.nodeToRestore = node.nodeToRestore;
|
|
let children = node.children;
|
|
if (parentNode) {
|
|
parentNode.removeChild(node);
|
|
if (children.size > 0) children.forEach((child)=>parentNode && parentNode.addChild(child));
|
|
}
|
|
this.fastMap.delete(node.scopeRef);
|
|
}
|
|
// Pre Order Depth First
|
|
*traverse(node = this.root) {
|
|
if (node.scopeRef != null) yield node;
|
|
if (node.children.size > 0) for (let child of node.children)yield* this.traverse(child);
|
|
}
|
|
clone() {
|
|
var _node_parent;
|
|
let newTree = new $9bf71ea28793e738$var$Tree();
|
|
var _node_parent_scopeRef;
|
|
for (let node of this.traverse())newTree.addTreeNode(node.scopeRef, (_node_parent_scopeRef = (_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : _node_parent.scopeRef) !== null && _node_parent_scopeRef !== void 0 ? _node_parent_scopeRef : null, node.nodeToRestore);
|
|
return newTree;
|
|
}
|
|
constructor(){
|
|
this.fastMap = new Map();
|
|
this.root = new $9bf71ea28793e738$var$TreeNode({
|
|
scopeRef: null
|
|
});
|
|
this.fastMap.set(null, this.root);
|
|
}
|
|
}
|
|
class $9bf71ea28793e738$var$TreeNode {
|
|
addChild(node) {
|
|
this.children.add(node);
|
|
node.parent = this;
|
|
}
|
|
removeChild(node) {
|
|
this.children.delete(node);
|
|
node.parent = undefined;
|
|
}
|
|
constructor(props){
|
|
this.children = new Set();
|
|
this.contain = false;
|
|
this.scopeRef = props.scopeRef;
|
|
}
|
|
}
|
|
let $9bf71ea28793e738$export$d06fae2ee68b101e = new $9bf71ea28793e738$var$Tree();
|
|
|
|
|
|
export {$9bf71ea28793e738$export$20e40289641fbbb6 as FocusScope, $9bf71ea28793e738$export$d06fae2ee68b101e as focusScopeTree, $9bf71ea28793e738$export$10c5169755ce7bd7 as useFocusManager, $9bf71ea28793e738$export$2d6ec8fc375ceafa as getFocusableTreeWalker, $9bf71ea28793e738$export$1258395f99bf9cbf as isElementInChildOfActiveScope, $9bf71ea28793e738$export$c5251b9e124bf29 as createFocusManager};
|
|
//# sourceMappingURL=FocusScope.module.js.map
|