ExperimentalA stable anchor (the same object on every render): its virtualRef goes to
PopoverAnchor, and its setSource points it at what to follow.
This export is unstable and may change shape or disappear without notice
const anchor = useLivePopoverAnchor();
const [isOpen, setIsOpen] = useState(false);
const openAtSelection = () => {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || !editorRef.current) return;
// Clone the range: the live selection keeps moving while the popover is open.
const range = selection.getRangeAt(0).cloneRange();
// Point the anchor at its source BEFORE opening, so the popover never opens at a stale rect.
anchor.setSource({
measure: () => {
const rect = measureBox(range);
return rect && leftEdgeRect(rect);
},
contextElement: editorRef.current,
});
setIsOpen(true);
};
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverAnchor virtualRef={anchor.virtualRef} />
<PopoverContent>…</PopoverContent>
</Popover>
);
A popover anchor that follows its text instead of keeping the rect it had when the popover opened. The popover's own positioning (floating-ui's auto-update, run by Radix while the popover is open) re-reads the rect on scroll of the text's scroll container, on resize, and when the text reflows under a zoom change, so the popover stays beside its caller or selection.
Hidden case: handled by holding the last usable rect. A popover can be open while its pane is hidden — the Scripture editor's footnote popover survives Escape and an outside click — and a hidden pane has no layout, so a source measures nothing there. The anchor keeps the last rect it had rather than collapsing to the pane's corner, and the next frame after the tab is shown measures again and catches up. Sources report "no measurement" by returning
undefined; see measureBox.