@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
583 lines (571 loc) • 27.5 kB
JavaScript
;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FloatingFocusManager = FloatingFocusManager;
var React = _interopRequireWildcard(require("react"));
var _tabbable = require("tabbable");
var _dom = require("@floating-ui/utils/dom");
var _useMergedRefs = require("@base-ui-components/utils/useMergedRefs");
var _useValueAsRef = require("@base-ui-components/utils/useValueAsRef");
var _useStableCallback = require("@base-ui-components/utils/useStableCallback");
var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect");
var _visuallyHidden = require("@base-ui-components/utils/visuallyHidden");
var _useTimeout = require("@base-ui-components/utils/useTimeout");
var _useAnimationFrame = require("@base-ui-components/utils/useAnimationFrame");
var _owner = require("@base-ui-components/utils/owner");
var _FocusGuard = require("../../utils/FocusGuard");
var _utils = require("../utils");
var _createBaseUIEventDetails = require("../../utils/createBaseUIEventDetails");
var _reasons = require("../../utils/reasons");
var _createAttribute = require("../utils/createAttribute");
var _enqueueFocus = require("../utils/enqueueFocus");
var _markOthers = require("../utils/markOthers");
var _FloatingPortal = require("./FloatingPortal");
var _FloatingTree = require("./FloatingTree");
var _constants = require("../../utils/constants");
var _resolveRef = require("../../utils/resolveRef");
var _jsxRuntime = require("react/jsx-runtime");
function getEventType(event, lastInteractionType) {
const win = (0, _owner.ownerWindow)(event.target);
if (event instanceof win.KeyboardEvent) {
return 'keyboard';
}
if (event instanceof win.FocusEvent) {
// Focus events can be caused by a preceding pointer interaction (e.g., focusout on outside press).
// Prefer the last known pointer type if provided, else treat as keyboard.
return lastInteractionType || 'keyboard';
}
if ('pointerType' in event) {
return event.pointerType || 'keyboard';
}
if ('touches' in event) {
return 'touch';
}
if (event instanceof win.MouseEvent) {
// onClick events may not contain pointer events, and will fall through to here
return lastInteractionType || (event.detail === 0 ? 'keyboard' : 'mouse');
}
return '';
}
const LIST_LIMIT = 20;
let previouslyFocusedElements = [];
function clearDisconnectedPreviouslyFocusedElements() {
previouslyFocusedElements = previouslyFocusedElements.filter(el => el.isConnected);
}
function addPreviouslyFocusedElement(element) {
clearDisconnectedPreviouslyFocusedElements();
if (element && (0, _dom.getNodeName)(element) !== 'body') {
previouslyFocusedElements.push(element);
if (previouslyFocusedElements.length > LIST_LIMIT) {
previouslyFocusedElements = previouslyFocusedElements.slice(-LIST_LIMIT);
}
}
}
function getPreviouslyFocusedElement() {
clearDisconnectedPreviouslyFocusedElements();
return previouslyFocusedElements[previouslyFocusedElements.length - 1];
}
function getFirstTabbableElement(container) {
if (!container) {
return null;
}
const tabbableOptions = (0, _utils.getTabbableOptions)();
if ((0, _tabbable.isTabbable)(container, tabbableOptions)) {
return container;
}
return (0, _tabbable.tabbable)(container, tabbableOptions)[0] || container;
}
function isFocusable(element) {
if (!element || !element.isConnected) {
return false;
}
if (typeof element.checkVisibility === 'function') {
return element.checkVisibility();
}
return (0, _dom.getComputedStyle)(element).display !== 'none';
}
function handleTabIndex(floatingFocusElement, orderRef) {
if (!orderRef.current.includes('floating') && !floatingFocusElement.getAttribute('role')?.includes('dialog')) {
return;
}
const options = (0, _utils.getTabbableOptions)();
const focusableElements = (0, _tabbable.focusable)(floatingFocusElement, options);
const tabbableContent = focusableElements.filter(element => {
const dataTabIndex = element.getAttribute('data-tabindex') || '';
return (0, _tabbable.isTabbable)(element, options) || element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-');
});
const tabIndex = floatingFocusElement.getAttribute('tabindex');
if (orderRef.current.includes('floating') || tabbableContent.length === 0) {
if (tabIndex !== '0') {
floatingFocusElement.setAttribute('tabindex', '0');
}
} else if (tabIndex !== '-1' || floatingFocusElement.hasAttribute('data-tabindex') && floatingFocusElement.getAttribute('data-tabindex') !== '-1') {
floatingFocusElement.setAttribute('tabindex', '-1');
floatingFocusElement.setAttribute('data-tabindex', '-1');
}
}
/**
* Provides focus management for the floating element.
* @see https://floating-ui.com/docs/FloatingFocusManager
* @internal
*/
function FloatingFocusManager(props) {
const {
context,
children,
disabled = false,
order = ['content'],
initialFocus = true,
returnFocus = true,
restoreFocus = false,
modal = true,
closeOnFocusOut = true,
openInteractionType = '',
getInsideElements: getInsideElementsProp = () => [],
nextFocusableElement,
previousFocusableElement,
beforeContentFocusGuardRef,
externalTree
} = props;
const store = 'rootStore' in context ? context.rootStore : context;
const open = store.useState('open');
const domReference = store.useState('domReferenceElement');
const floating = store.useState('floatingElement');
const {
events,
dataRef
} = store.context;
const getNodeId = (0, _useStableCallback.useStableCallback)(() => dataRef.current.floatingContext?.nodeId);
const getInsideElements = (0, _useStableCallback.useStableCallback)(getInsideElementsProp);
const ignoreInitialFocus = initialFocus === false;
// If the reference is a combobox and is typeable (e.g. input/textarea),
// there are different focus semantics. The guards should not be rendered, but
// aria-hidden should be applied to all nodes still. Further, the visually
// hidden dismiss button should only appear at the end of the list, not the
// start.
const isUntrappedTypeableCombobox = (0, _utils.isTypeableCombobox)(domReference) && ignoreInitialFocus;
const orderRef = (0, _useValueAsRef.useValueAsRef)(order);
const initialFocusRef = (0, _useValueAsRef.useValueAsRef)(initialFocus);
const returnFocusRef = (0, _useValueAsRef.useValueAsRef)(returnFocus);
const openInteractionTypeRef = (0, _useValueAsRef.useValueAsRef)(openInteractionType);
const tree = (0, _FloatingTree.useFloatingTree)(externalTree);
const portalContext = (0, _FloatingPortal.usePortalContext)();
const startDismissButtonRef = React.useRef(null);
const endDismissButtonRef = React.useRef(null);
const preventReturnFocusRef = React.useRef(false);
const isPointerDownRef = React.useRef(false);
const tabbableIndexRef = React.useRef(-1);
const closeTypeRef = React.useRef('');
const lastInteractionTypeRef = React.useRef('');
const blurTimeout = (0, _useTimeout.useTimeout)();
const pointerDownTimeout = (0, _useTimeout.useTimeout)();
const restoreFocusFrame = (0, _useAnimationFrame.useAnimationFrame)();
const isInsidePortal = portalContext != null;
const floatingFocusElement = (0, _utils.getFloatingFocusElement)(floating);
const getTabbableContent = (0, _useStableCallback.useStableCallback)((container = floatingFocusElement) => {
return container ? (0, _tabbable.tabbable)(container, (0, _utils.getTabbableOptions)()) : [];
});
const getTabbableElements = (0, _useStableCallback.useStableCallback)(container => {
const content = getTabbableContent(container);
return orderRef.current.map(() => content).filter(Boolean).flat();
});
React.useEffect(() => {
if (disabled) {
return undefined;
}
if (!modal) {
return undefined;
}
function onKeyDown(event) {
if (event.key === 'Tab') {
// The focus guards have nothing to focus, so we need to stop the event.
if ((0, _utils.contains)(floatingFocusElement, (0, _utils.activeElement)((0, _utils.getDocument)(floatingFocusElement))) && getTabbableContent().length === 0 && !isUntrappedTypeableCombobox) {
(0, _utils.stopEvent)(event);
}
}
}
const doc = (0, _utils.getDocument)(floatingFocusElement);
doc.addEventListener('keydown', onKeyDown);
return () => {
doc.removeEventListener('keydown', onKeyDown);
};
}, [disabled, domReference, floatingFocusElement, modal, orderRef, isUntrappedTypeableCombobox, getTabbableContent, getTabbableElements]);
React.useEffect(() => {
if (disabled) {
return undefined;
}
if (!floating) {
return undefined;
}
function handleFocusIn(event) {
const target = (0, _utils.getTarget)(event);
const tabbableContent = getTabbableContent();
const tabbableIndex = tabbableContent.indexOf(target);
if (tabbableIndex !== -1) {
tabbableIndexRef.current = tabbableIndex;
}
}
floating.addEventListener('focusin', handleFocusIn);
return () => {
floating.removeEventListener('focusin', handleFocusIn);
};
}, [disabled, floating, getTabbableContent]);
// Track the last interaction type at the document level to disambiguate focus events
React.useEffect(() => {
if (disabled || !open) {
return undefined;
}
const doc = (0, _utils.getDocument)(floatingFocusElement);
function onPointerDown(event) {
lastInteractionTypeRef.current = event.pointerType || 'keyboard';
}
function onKeyDown() {
lastInteractionTypeRef.current = 'keyboard';
}
doc.addEventListener('pointerdown', onPointerDown, true);
doc.addEventListener('keydown', onKeyDown, true);
return () => {
doc.removeEventListener('pointerdown', onPointerDown, true);
doc.removeEventListener('keydown', onKeyDown, true);
};
}, [disabled, floating, domReference, floatingFocusElement, open]);
React.useEffect(() => {
if (disabled) {
return undefined;
}
if (!closeOnFocusOut) {
return undefined;
}
// In Safari, buttons lose focus when pressing them.
function handlePointerDown() {
isPointerDownRef.current = true;
pointerDownTimeout.start(0, () => {
isPointerDownRef.current = false;
});
}
function handleFocusOutside(event) {
const relatedTarget = event.relatedTarget;
const currentTarget = event.currentTarget;
const target = (0, _utils.getTarget)(event);
queueMicrotask(() => {
const nodeId = getNodeId();
const triggers = store.context.triggerElements;
const movedToUnrelatedNode = !((0, _utils.contains)(domReference, relatedTarget) || (0, _utils.contains)(floating, relatedTarget) || (0, _utils.contains)(relatedTarget, floating) || (0, _utils.contains)(portalContext?.portalNode, relatedTarget) || relatedTarget != null && triggers.hasElement(relatedTarget) || triggers.hasMatchingElement(trigger => (0, _utils.contains)(trigger, relatedTarget)) || relatedTarget?.hasAttribute((0, _createAttribute.createAttribute)('focus-guard')) || tree && ((0, _utils.getNodeChildren)(tree.nodesRef.current, nodeId).find(node => (0, _utils.contains)(node.context?.elements.floating, relatedTarget) || (0, _utils.contains)(node.context?.elements.domReference, relatedTarget)) || (0, _utils.getNodeAncestors)(tree.nodesRef.current, nodeId).find(node => [node.context?.elements.floating, (0, _utils.getFloatingFocusElement)(node.context?.elements.floating)].includes(relatedTarget) || node.context?.elements.domReference === relatedTarget)));
if (currentTarget === domReference && floatingFocusElement) {
handleTabIndex(floatingFocusElement, orderRef);
}
// Restore focus to the previous tabbable element index to prevent
// focus from being lost outside the floating tree.
if (restoreFocus && currentTarget !== domReference && !isFocusable(target) && (0, _utils.activeElement)((0, _utils.getDocument)(floatingFocusElement)) === (0, _utils.getDocument)(floatingFocusElement).body) {
// Let `FloatingPortal` effect knows that focus is still inside the
// floating tree.
if ((0, _dom.isHTMLElement)(floatingFocusElement)) {
floatingFocusElement.focus();
// If explicitly requested to restore focus to the popup container, do not search
// for the next/previous tabbable element.
if (restoreFocus === 'popup') {
// If the element is removed on pointerdown, focus tries to move it,
// but since it's removed at the same time, focus gets lost as it
// happens after the .focus() call above.
// In this case, focus needs to be moved asynchronously.
restoreFocusFrame.request(() => {
floatingFocusElement.focus();
});
return;
}
}
const prevTabbableIndex = tabbableIndexRef.current;
const tabbableContent = getTabbableContent();
const nodeToFocus = tabbableContent[prevTabbableIndex] || tabbableContent[tabbableContent.length - 1] || floatingFocusElement;
if ((0, _dom.isHTMLElement)(nodeToFocus)) {
nodeToFocus.focus();
}
}
// https://github.com/floating-ui/floating-ui/issues/3060
if (dataRef.current.insideReactTree) {
dataRef.current.insideReactTree = false;
return;
}
// Focus did not move inside the floating tree, and there are no tabbable
// portal guards to handle closing.
if ((isUntrappedTypeableCombobox ? true : !modal) && relatedTarget && movedToUnrelatedNode && (
// Fix React 18 Strict Mode returnFocus due to double rendering.
// For an "untrapped" typeable combobox (input role=combobox with
// initialFocus=false), re-opening the popup and tabbing out should still close it even
// when the previously focused element (e.g. the next tabbable outside the popup) is
// focused again. Otherwise, the popup remains open on the second Tab sequence:
// click input -> Tab (closes) -> click input -> Tab.
// Allow closing when `isUntrappedTypeableCombobox` regardless of the previously focused element.
isUntrappedTypeableCombobox || relatedTarget !== getPreviouslyFocusedElement())) {
preventReturnFocusRef.current = true;
store.setOpen(false, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.focusOut, event));
}
});
}
function markInsideReactTree() {
dataRef.current.insideReactTree = true;
blurTimeout.start(0, () => {
dataRef.current.insideReactTree = false;
});
}
const domReferenceElement = (0, _dom.isHTMLElement)(domReference) ? domReference : null;
const cleanups = [];
if (!floating && !domReferenceElement) {
return undefined;
}
if (domReferenceElement) {
domReferenceElement.addEventListener('focusout', handleFocusOutside);
domReferenceElement.addEventListener('pointerdown', handlePointerDown);
cleanups.push(() => {
domReferenceElement.removeEventListener('focusout', handleFocusOutside);
domReferenceElement.removeEventListener('pointerdown', handlePointerDown);
});
}
if (floating) {
floating.addEventListener('focusout', handleFocusOutside);
if (portalContext) {
floating.addEventListener('focusout', markInsideReactTree, true);
cleanups.push(() => {
floating.removeEventListener('focusout', markInsideReactTree, true);
});
}
cleanups.push(() => {
floating.removeEventListener('focusout', handleFocusOutside);
});
}
return () => {
cleanups.forEach(cleanup => {
cleanup();
});
};
}, [disabled, domReference, floating, floatingFocusElement, modal, tree, portalContext, store, closeOnFocusOut, restoreFocus, getTabbableContent, isUntrappedTypeableCombobox, getNodeId, orderRef, dataRef, blurTimeout, pointerDownTimeout, restoreFocusFrame]);
const beforeGuardRef = React.useRef(null);
const afterGuardRef = React.useRef(null);
const mergedBeforeGuardRef = (0, _useMergedRefs.useMergedRefs)(beforeGuardRef, beforeContentFocusGuardRef, portalContext?.beforeInsideRef);
const mergedAfterGuardRef = (0, _useMergedRefs.useMergedRefs)(afterGuardRef, portalContext?.afterInsideRef);
React.useEffect(() => {
if (disabled || !floating || !open) {
return undefined;
}
// Don't hide portals nested within the parent portal.
const portalNodes = Array.from(portalContext?.portalNode?.querySelectorAll(`[${(0, _createAttribute.createAttribute)('portal')}]`) || []);
const ancestors = tree ? (0, _utils.getNodeAncestors)(tree.nodesRef.current, getNodeId()) : [];
const rootAncestorComboboxDomReference = ancestors.find(node => (0, _utils.isTypeableCombobox)(node.context?.elements.domReference || null))?.context?.elements.domReference;
const insideElements = [floating, rootAncestorComboboxDomReference, ...portalNodes, ...getInsideElements(), startDismissButtonRef.current, endDismissButtonRef.current, beforeGuardRef.current, afterGuardRef.current, portalContext?.beforeOutsideRef.current, portalContext?.afterOutsideRef.current, (0, _resolveRef.resolveRef)(previousFocusableElement), (0, _resolveRef.resolveRef)(nextFocusableElement), isUntrappedTypeableCombobox ? domReference : null].filter(x => x != null);
const cleanup = (0, _markOthers.markOthers)(insideElements, modal || isUntrappedTypeableCombobox);
return () => {
cleanup();
};
}, [open, disabled, domReference, floating, modal, orderRef, portalContext, isUntrappedTypeableCombobox, tree, getNodeId, getInsideElements, nextFocusableElement, previousFocusableElement]);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (!open || disabled || !(0, _dom.isHTMLElement)(floatingFocusElement)) {
return;
}
const doc = (0, _utils.getDocument)(floatingFocusElement);
const previouslyFocusedElement = (0, _utils.activeElement)(doc);
// Wait for any layout effect state setters to execute to set `tabIndex`.
queueMicrotask(() => {
const focusableElements = getTabbableElements(floatingFocusElement);
const initialFocusValueOrFn = initialFocusRef.current;
const resolvedInitialFocus = typeof initialFocusValueOrFn === 'function' ? initialFocusValueOrFn(openInteractionTypeRef.current || '') : initialFocusValueOrFn;
// `null` should fallback to default behavior in case of an empty ref.
if (resolvedInitialFocus === undefined || resolvedInitialFocus === false) {
return;
}
let elToFocus;
if (resolvedInitialFocus === true || resolvedInitialFocus === null) {
elToFocus = focusableElements[0] || floatingFocusElement;
} else {
elToFocus = (0, _resolveRef.resolveRef)(resolvedInitialFocus);
}
elToFocus = elToFocus || focusableElements[0] || floatingFocusElement;
const focusAlreadyInsideFloatingEl = (0, _utils.contains)(floatingFocusElement, previouslyFocusedElement);
if (focusAlreadyInsideFloatingEl) {
return;
}
(0, _enqueueFocus.enqueueFocus)(elToFocus, {
preventScroll: elToFocus === floatingFocusElement
});
});
}, [disabled, open, floatingFocusElement, ignoreInitialFocus, getTabbableElements, initialFocusRef, openInteractionTypeRef]);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (disabled || !floatingFocusElement) {
return undefined;
}
const doc = (0, _utils.getDocument)(floatingFocusElement);
const previouslyFocusedElement = (0, _utils.activeElement)(doc);
addPreviouslyFocusedElement(previouslyFocusedElement);
// Dismissing via outside press should always ignore `returnFocus` to
// prevent unwanted scrolling.
function onOpenChangeLocal(details) {
if (!details.open) {
closeTypeRef.current = getEventType(details.nativeEvent, lastInteractionTypeRef.current);
}
if (details.reason === _reasons.REASONS.triggerHover && details.nativeEvent.type === 'mouseleave') {
preventReturnFocusRef.current = true;
}
if (details.reason !== _reasons.REASONS.outsidePress) {
return;
}
if (details.nested) {
preventReturnFocusRef.current = false;
} else if ((0, _utils.isVirtualClick)(details.nativeEvent) || (0, _utils.isVirtualPointerEvent)(details.nativeEvent)) {
preventReturnFocusRef.current = false;
} else {
let isPreventScrollSupported = false;
document.createElement('div').focus({
get preventScroll() {
isPreventScrollSupported = true;
return false;
}
});
if (isPreventScrollSupported) {
preventReturnFocusRef.current = false;
} else {
preventReturnFocusRef.current = true;
}
}
}
events.on('openchange', onOpenChangeLocal);
const fallbackEl = doc.createElement('span');
fallbackEl.setAttribute('tabindex', '-1');
fallbackEl.setAttribute('aria-hidden', 'true');
Object.assign(fallbackEl.style, _visuallyHidden.visuallyHidden);
if (isInsidePortal && domReference) {
domReference.insertAdjacentElement('afterend', fallbackEl);
}
function getReturnElement() {
const returnFocusValueOrFn = returnFocusRef.current;
let resolvedReturnFocusValue = typeof returnFocusValueOrFn === 'function' ? returnFocusValueOrFn(closeTypeRef.current) : returnFocusValueOrFn;
// `null` should fallback to default behavior in case of an empty ref.
if (resolvedReturnFocusValue === undefined || resolvedReturnFocusValue === false) {
return null;
}
if (resolvedReturnFocusValue === null) {
resolvedReturnFocusValue = true;
}
if (typeof resolvedReturnFocusValue === 'boolean') {
const el = domReference || getPreviouslyFocusedElement();
return el && el.isConnected ? el : fallbackEl;
}
const fallback = domReference || getPreviouslyFocusedElement() || fallbackEl;
return (0, _resolveRef.resolveRef)(resolvedReturnFocusValue) || fallback;
}
return () => {
events.off('openchange', onOpenChangeLocal);
const activeEl = (0, _utils.activeElement)(doc);
const isFocusInsideFloatingTree = (0, _utils.contains)(floating, activeEl) || tree && (0, _utils.getNodeChildren)(tree.nodesRef.current, getNodeId(), false).some(node => (0, _utils.contains)(node.context?.elements.floating, activeEl));
const returnElement = getReturnElement();
queueMicrotask(() => {
// This is `returnElement`, if it's tabbable, or its first tabbable child.
const tabbableReturnElement = getFirstTabbableElement(returnElement);
const hasExplicitReturnFocus = typeof returnFocusRef.current !== 'boolean';
if (
// eslint-disable-next-line react-hooks/exhaustive-deps
returnFocusRef.current && !preventReturnFocusRef.current && (0, _dom.isHTMLElement)(tabbableReturnElement) && (
// If the focus moved somewhere else after mount, avoid returning focus
// since it likely entered a different element which should be
// respected: https://github.com/floating-ui/floating-ui/issues/2607
!hasExplicitReturnFocus && tabbableReturnElement !== activeEl && activeEl !== doc.body ? isFocusInsideFloatingTree : true)) {
tabbableReturnElement.focus({
preventScroll: true
});
}
fallbackEl.remove();
});
};
}, [disabled, floating, floatingFocusElement, returnFocusRef, dataRef, events, tree, isInsidePortal, domReference, getNodeId]);
React.useEffect(() => {
// The `returnFocus` cleanup behavior is inside a microtask; ensure we
// wait for it to complete before resetting the flag.
queueMicrotask(() => {
preventReturnFocusRef.current = false;
});
}, [disabled]);
React.useEffect(() => {
if (disabled || !open) {
return undefined;
}
function handlePointerDown(event) {
const target = (0, _utils.getTarget)(event);
if (target?.closest(`[${_constants.CLICK_TRIGGER_IDENTIFIER}]`)) {
isPointerDownRef.current = true;
}
}
const doc = (0, _utils.getDocument)(floatingFocusElement);
doc.addEventListener('pointerdown', handlePointerDown, true);
return () => {
doc.removeEventListener('pointerdown', handlePointerDown, true);
};
}, [disabled, open, floatingFocusElement]);
// Synchronize the `context` & `modal` value to the FloatingPortal context.
// It will decide whether or not it needs to render its own guards.
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (disabled) {
return undefined;
}
if (!portalContext) {
return undefined;
}
portalContext.setFocusManagerState({
modal,
closeOnFocusOut,
open,
onOpenChange: store.setOpen,
domReference
});
return () => {
portalContext.setFocusManagerState(null);
};
}, [disabled, portalContext, modal, open, store, closeOnFocusOut, domReference]);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
if (disabled || !floatingFocusElement) {
return undefined;
}
handleTabIndex(floatingFocusElement, orderRef);
return () => {
queueMicrotask(clearDisconnectedPreviouslyFocusedElements);
};
}, [disabled, floatingFocusElement, orderRef]);
const shouldRenderGuards = !disabled && (modal ? !isUntrappedTypeableCombobox : true) && (isInsidePortal || modal);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, {
children: [shouldRenderGuards && /*#__PURE__*/(0, _jsxRuntime.jsx)(_FocusGuard.FocusGuard, {
"data-type": "inside",
ref: mergedBeforeGuardRef,
onFocus: event => {
if (modal) {
const els = getTabbableElements();
(0, _enqueueFocus.enqueueFocus)(els[els.length - 1]);
} else if (portalContext?.portalNode) {
preventReturnFocusRef.current = false;
if ((0, _utils.isOutsideEvent)(event, portalContext.portalNode)) {
const nextTabbable = (0, _utils.getNextTabbable)(domReference);
nextTabbable?.focus();
} else {
(0, _resolveRef.resolveRef)(previousFocusableElement ?? portalContext.beforeOutsideRef)?.focus();
}
}
}
}), children, shouldRenderGuards && /*#__PURE__*/(0, _jsxRuntime.jsx)(_FocusGuard.FocusGuard, {
"data-type": "inside",
ref: mergedAfterGuardRef,
onFocus: event => {
if (modal) {
(0, _enqueueFocus.enqueueFocus)(getTabbableElements()[0]);
} else if (portalContext?.portalNode) {
if (closeOnFocusOut) {
preventReturnFocusRef.current = true;
}
if ((0, _utils.isOutsideEvent)(event, portalContext.portalNode)) {
const prevTabbable = (0, _utils.getPreviousTabbable)(domReference);
prevTabbable?.focus();
} else {
(0, _resolveRef.resolveRef)(nextFocusableElement ?? portalContext.afterOutsideRef)?.focus();
}
}
}
})]
});
}