@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
JavaScript
;
'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]);
}