UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

71 lines (66 loc) • 2.67 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import { FocusTrapExtension } from '@lexical/a11y'; import { getExtensionDependencyFromEditor } from '@lexical/extension/getExtensionDependencyFromEditor'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useRef, useCallback } from 'react'; /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ /** * Returns a `RefCallback` that registers the attached DOM element as a * focus-trap container with {@link FocusTrapExtension}. The trap * activates when `isActive` is `true` and the element is mounted, and is * released when the element detaches or `isActive` becomes `false`. * * ```tsx * const trapRef = useLexicalFocusTrapRef(true, 'container'); * return <div ref={trapRef} tabIndex={-1}>…</div>; * ``` * * `allowOutside` is the escape hatch for descendants that portal outside the * container (autocomplete popups, tooltips): return `true` for those targets so * the trap lets them keep focus instead of pulling it back. It is held in a ref * and read at event time, so passing an inline lambda is fine — it does not * re-create the trap on every render. * * Multiple elements can use this hook simultaneously — each gets its * own independent focus trap. * * Requires `FocusTrapExtension` in the editor's extension tree. */ function useLexicalFocusTrapRef(isActive, initialFocus = 'firstFocusable', allowOutside) { const [editor] = useLexicalComposerContext(); const disposeRef = useRef(null); // Keep the latest predicate in a ref so an inline lambda doesn't change the // RefCallback identity (which would tear down and rebuild the trap every // render); the registered trap reads it at event time. const allowOutsideRef = useRef(allowOutside); allowOutsideRef.current = allowOutside; return useCallback(node => { if (disposeRef.current !== null) { disposeRef.current(); disposeRef.current = null; } if (node !== null && isActive) { const dep = getExtensionDependencyFromEditor(editor, FocusTrapExtension); disposeRef.current = dep.output.register(node, { allowOutside: target => { const fn = allowOutsideRef.current; return fn ? fn(target) : false; }, initialFocus }); } }, [editor, isActive, initialFocus]); } export { useLexicalFocusTrapRef };