UNPKG

@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.

87 lines (82 loc) 3.62 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.useFocusWithDelay = useFocusWithDelay; var React = _interopRequireWildcard(require("react")); var _dom = require("@floating-ui/utils/dom"); var _useTimeout = require("@base-ui-components/utils/useTimeout"); var _createBaseUIEventDetails = require("../createBaseUIEventDetails"); var _reasons = require("../reasons"); var _utils = require("../../floating-ui-react/utils"); /** * Adds support for delay, since Floating UI's `useFocus` hook does not support it. */ function useFocusWithDelay(store, props = {}) { const domReference = store.useState('domReferenceElement'); const dataRef = store.context.dataRef; const { delay } = props; const timeout = (0, _useTimeout.useTimeout)(); const blockFocusRef = React.useRef(false); React.useEffect(() => { // TODO: remove domReference from dependencies or split this hook into trigger/popup hooks. const win = (0, _dom.getWindow)(domReference); // If the reference was focused and the user left the tab/window, and the preview card was not // open, the focus should be blocked when they return to the tab/window. function handleBlur() { const currentDomReference = store.select('domReferenceElement'); if (!store.select('open') && (0, _dom.isHTMLElement)(currentDomReference) && currentDomReference === (0, _utils.activeElement)((0, _utils.getDocument)(currentDomReference))) { blockFocusRef.current = true; } } win.addEventListener('blur', handleBlur); return () => { win.removeEventListener('blur', handleBlur); }; }, [store, domReference]); const reference = React.useMemo(() => ({ onFocus(event) { const { nativeEvent } = event; const delayValue = typeof delay === 'function' ? delay() : delay; timeout.start(delayValue ?? 0, () => { store.setOpen(true, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.triggerFocus, nativeEvent)); }); }, onBlur(event) { blockFocusRef.current = false; const { relatedTarget, nativeEvent } = event; // Wait for the window blur listener to fire. timeout.start(0, () => { const currentDomReference = store.select('domReferenceElement'); const activeEl = (0, _utils.activeElement)(currentDomReference ? currentDomReference.ownerDocument : document); // Focus left the page, keep it open. if (!relatedTarget && activeEl === currentDomReference) { return; } // When focusing the reference element (e.g. regular click), then // clicking into the floating element, prevent it from hiding. // Note: it must be focusable, e.g. `tabindex="-1"`. // We can not rely on relatedTarget to point to the correct element // as it will only point to the shadow host of the newly focused element // and not the element that actually has received focus if it is located // inside a shadow root. if ((0, _utils.contains)(dataRef.current.floatingContext?.refs.floating.current, activeEl) || (0, _utils.contains)(currentDomReference, activeEl)) { return; } store.setOpen(false, (0, _createBaseUIEventDetails.createChangeEventDetails)(_reasons.REASONS.triggerFocus, nativeEvent)); }); } }), [delay, store, dataRef, timeout]); return React.useMemo(() => ({ reference }), [reference]); }