UNPKG

@nex-ui/react

Version:

🎉 A beautiful, modern, and reliable React component library.

119 lines (115 loc) • 4.34 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var utils = require('@nex-ui/utils'); var react = require('react'); var hooks = require('@nex-ui/hooks'); var getTabbable = require('./getTabbable.cjs'); const FocusTrap = ({ children, active, paused, restoreFocus = true })=>{ const rootRef = react.useRef(null); const sentinelStartRef = react.useRef(null); const sentinelEndRef = react.useRef(null); const lastKeydownRef = react.useRef(null); const pausedRef = hooks.useLatest(paused); const restoredNode = react.useRef(null); const ignoreNextFocus = react.useRef(false); const mergedRefs = utils.mergeRefs(rootRef, children?.props.ref); react.useEffect(()=>{ if (!active || !rootRef.current) { return; } if (!rootRef.current.contains(document.activeElement)) { // If the focus is not inside the focus trap, focus the root element rootRef.current?.focus(); } return ()=>{ const node = restoredNode.current; if (restoreFocus && node) { ignoreNextFocus.current = true; node.focus(); } }; }, [ active, restoreFocus ]); react.useEffect(()=>{ if (!active || !rootRef.current) { return; } const trapFocus = ()=>{ const rootElement = rootRef.current; if (!rootElement || pausedRef.current) return; if (ignoreNextFocus.current) { ignoreNextFocus.current = false; return; } // The focus is already inside if (rootElement.contains(document.activeElement)) return; if (document.activeElement !== sentinelEndRef.current && document.activeElement !== sentinelStartRef.current) { return; } const tabbable = getTabbable.getTabbable(rootElement); // one of the sentinel nodes was focused, so move the focus // to the first/last tabbable element inside the focus trap. if (tabbable.length && lastKeydownRef.current) { const shiftPressed = lastKeydownRef.current.key === 'Tab' && lastKeydownRef.current.shiftKey; const focusNext = tabbable[0]; const focusPrevious = tabbable[tabbable.length - 1]; if (shiftPressed) { focusPrevious.focus(); } else { focusNext.focus(); } } else { // no tabbable elements in the trap focus rootElement.focus(); } }; const removeFocusEventListener = utils.addEventListener(document.body, 'focus', trapFocus, true); const removeKeydownEventListener = utils.addEventListener(document.body, 'keydown', (e)=>{ lastKeydownRef.current = e; }, true); return ()=>{ removeFocusEventListener(); removeKeydownEventListener(); }; }, [ active, pausedRef ]); if (!/*#__PURE__*/ react.isValidElement(children)) { return null; } if (utils.__DEV__ && children.type === react.Fragment) { console.error('[Nex UI] FocusTrap: FocusTrap cannot use a Fragment as its child container.'); return null; } const handleFocus = (e)=>{ if (restoredNode.current === null) { restoredNode.current = e.relatedTarget; } const childrenPropsHandler = children.props.onFocus; if (childrenPropsHandler) { childrenPropsHandler(e); } }; return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /*#__PURE__*/ jsxRuntime.jsx("div", { tabIndex: active && !paused ? 0 : -1, ref: sentinelStartRef }), /*#__PURE__*/ react.cloneElement(children, { ...children.props, ref: mergedRefs, onFocus: handleFocus }), /*#__PURE__*/ jsxRuntime.jsx("div", { tabIndex: active && !paused ? 0 : -1, ref: sentinelEndRef }) ] }); }; FocusTrap.displayName = 'FocusTrap'; exports.FocusTrap = FocusTrap;