UNPKG

@blocknote/react

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

43 lines (39 loc) 1.78 kB
import { RefObject, useEffect, useRef } from "react"; /** * BlockNote's autofocus for form inputs: focuses the element when * `autoFocus` is true, without scrolling. * * Why not the native `autofocus` attribute (or React's `autoFocus` prop, * which is a bare `.focus()` at commit): these inputs live in popovers that * floating-ui positions *after* mount, so the browser's scroll-into-view * would run while the popover is still at its pre-positioned spot and yank * the page (on mobile, right out from under the block being edited). * * The shape matches the official popover-autofocus implementations * (Mantine: setTimeout + preventScroll; floating-ui: microtask + rAF + * preventScroll), minus their deferral layers: there is nothing here to * wait for, and added hops erode the user-gesture window in which iOS * Safari lets a programmatic focus open the keyboard (validated on real * iOS). `preventScroll` also makes the timing not load-bearing: no * ordering relative to floating-ui's positioning can scroll the page. * * This runs at mount, so the popover content must mount when the popover * opens: Mantine and Base UI do that by default, Ariakit needs * `unmountOnHide` (see the ariakit Popover). The skins keep their * libraries' own autofocus off; Ariakit's is a bare `focus()` plus a Safari * `scrollIntoView`, exactly the scroll this hook avoids. * * Returns the ref to attach; merge it with a forwarded ref via * `useMergeRefs`. */ export function useAutoFocus<T extends HTMLElement>( autoFocus: boolean | undefined, ): RefObject<T | null> { const elementRef = useRef<T | null>(null); useEffect(() => { if (autoFocus) { elementRef.current?.focus({ preventScroll: true }); } }, [autoFocus]); return elementRef; }