UNPKG

@base-ui/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.

60 lines (58 loc) 2.66 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.useTriggerFocusGuards = useTriggerFocusGuards; var React = _interopRequireWildcard(require("react")); var ReactDOM = _interopRequireWildcard(require("react-dom")); var _useStableCallback = require("@base-ui/utils/useStableCallback"); var _utils = require("../../floating-ui-react/utils"); var _createBaseUIEventDetails = require("../../internals/createBaseUIEventDetails"); var _reasons = require("../../internals/reasons"); /** * Minimal store interface required by the focus guard hook. * Both PopoverStore and MenuStore satisfy this interface. */ /** * Provides focus guard handlers for popup triggers (Popover, Menu). * * When the popup is open, invisible focus guard elements are placed before and after * the trigger. These handlers close the popup and move focus to the appropriate * tabbable element when the guards receive focus (i.e. when the user tabs out). */ function useTriggerFocusGuards(store, triggerElementRef) { const preFocusGuardRef = React.useRef(null); const handlePreFocusGuardFocus = (0, _useStableCallback.useStableCallback)(event => { ReactDOM.flushSync(() => { store.setOpen(false, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.focusOut, event.nativeEvent, event.currentTarget)); }); const previousTabbable = (0, _utils.getTabbableBeforeElement)(preFocusGuardRef.current); previousTabbable?.focus(); }); const handleFocusTargetFocus = (0, _useStableCallback.useStableCallback)(event => { const positionerElement = store.select('positionerElement'); if (positionerElement && (0, _utils.isOutsideEvent)(event, positionerElement)) { store.context.beforeContentFocusGuardRef.current?.focus(); } else { ReactDOM.flushSync(() => { store.setOpen(false, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.focusOut, event.nativeEvent, event.currentTarget)); }); let nextTabbable = (0, _utils.getTabbableAfterElement)(store.context.triggerFocusTargetRef.current || triggerElementRef.current); while (nextTabbable !== null && (0, _utils.contains)(positionerElement, nextTabbable)) { const prevTabbable = nextTabbable; nextTabbable = (0, _utils.getNextTabbable)(nextTabbable); if (nextTabbable === prevTabbable) { break; } } nextTabbable?.focus(); } }); return { preFocusGuardRef, handlePreFocusGuardFocus, handleFocusTargetFocus }; }