fix(markdown-editor): write the table-grip open ref synchronously to close a race

openRef was mirrored from the `open` state through a passive effect, but the
document-level mousemove handler and the stale-target droppers read
openRef.current on continuous events. Between a grip click (setOpen) and the
effect flush, a mousemove saw the stale null and retargeted the grip — and the
about-to-open menu — onto a neighbouring cell. Set the ref synchronously inside
onMenuChange before setOpen and drop the mirroring effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-07 19:54:30 +07:00
co-authored by Claude Opus 4.8
parent b4fb572efa
commit 418b1eae65
@@ -248,10 +248,6 @@ function useTableHandles(editor: Editor): TableHandlesController {
const openRef = useRef<OpenMenu>(null);
const cellPosRef = useRef<null | number>(null);
useEffect(() => {
openRef.current = open;
}, [open]);
useEffect(() => {
const dom = editor.view.dom;
@@ -367,7 +363,13 @@ function useTableHandles(editor: Editor): TableHandlesController {
}, [editor]);
const onMenuChange = (menu: 'column' | 'row') => (isOpen: boolean) => {
setOpen(isOpen ? menu : null);
const next = isOpen ? menu : null;
// Write the ref synchronously, before setOpen. handleMove/dropStaleTarget are continuous-event handlers
// reading openRef.current; mirroring `open` through a passive effect flushes a tick late, so a mousemove
// in that window would retarget the grip to a neighbouring cell just as a menu opens on this one.
openRef.current = next;
setOpen(next);
if (!isOpen) {
cellPosRef.current = null;