@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.
93 lines (88 loc) • 3.94 kB
JavaScript
;
'use client';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFocusExtended = useFocusExtended;
var React = _interopRequireWildcard(require("react"));
var _dom = require("@floating-ui/utils/dom");
var _utils = require("@floating-ui/react/utils");
var _constants = require("./constants");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* @ignore - internal hook.
* Adds support for delay, since Floating UI's `useFocus` hook does not support it.
*/
function useFocusExtended(context, props = {}) {
const {
onOpenChange,
elements,
open,
dataRef
} = context;
const {
delay = _constants.OPEN_DELAY
} = props;
const timeoutRef = React.useRef(-1);
const blockFocusRef = React.useRef(false);
React.useEffect(() => {
const win = (0, _dom.getWindow)(elements.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() {
if (!open && (0, _dom.isHTMLElement)(elements.domReference) && elements.domReference === (0, _utils.activeElement)((0, _utils.getDocument)(elements.domReference))) {
blockFocusRef.current = true;
}
}
win.addEventListener('blur', handleBlur);
return () => {
win.removeEventListener('blur', handleBlur);
};
}, [elements.domReference, open]);
React.useEffect(() => {
return () => {
window.clearTimeout(timeoutRef.current);
};
}, []);
const reference = React.useMemo(() => ({
onFocus(event) {
const {
nativeEvent
} = event;
timeoutRef.current = window.setTimeout(() => {
onOpenChange(true, nativeEvent, 'focus');
}, delay);
},
onBlur(event) {
blockFocusRef.current = false;
const {
relatedTarget,
nativeEvent
} = event;
window.clearTimeout(timeoutRef.current);
// Wait for the window blur listener to fire.
timeoutRef.current = window.setTimeout(() => {
const activeEl = (0, _utils.activeElement)(elements.domReference ? elements.domReference.ownerDocument : document);
// Focus left the page, keep it open.
if (!relatedTarget && activeEl === elements.domReference) {
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)(elements.domReference, activeEl)) {
return;
}
onOpenChange(false, nativeEvent, 'focus');
});
}
}), [delay, onOpenChange, elements.domReference, dataRef]);
return React.useMemo(() => ({
reference
}), [reference]);
}