diff --git a/apps/client/src/features/editor/components/table/handle/column-handle.tsx b/apps/client/src/features/editor/components/table/handle/column-handle.tsx index ccc459740..a46ac50d5 100644 --- a/apps/client/src/features/editor/components/table/handle/column-handle.tsx +++ b/apps/client/src/features/editor/components/table/handle/column-handle.tsx @@ -31,7 +31,12 @@ export const ColumnHandle = React.memo(function ColumnHandle({ // (the plugin re-emits `hoveringCell` with the mapped pos a tick later); // unmounting the source element here would make pragmatic-dnd silently // abort the active drag. - const lookupCellDom = editor.view.nodeDOM(anchorPos) as HTMLElement | null; + // `nodeDOM` is typed as `Node | null` — when `anchorPos` goes stale (e.g. + // an external drop reflows the doc before the plugin re-emits + // hoveringCell), it can resolve to a Text node, on which `.closest` is + // undefined. Filter to HTMLElement so downstream consumers stay safe. + const lookupDom = editor.view.nodeDOM(anchorPos); + const lookupCellDom = lookupDom instanceof HTMLElement ? lookupDom : null; const [cellDom, setCellDom] = useState(lookupCellDom); const lastCellDomRef = useRef(lookupCellDom); useEffect(() => { diff --git a/apps/client/src/features/editor/components/table/handle/row-handle.tsx b/apps/client/src/features/editor/components/table/handle/row-handle.tsx index 7a5483558..1f3e3cc51 100644 --- a/apps/client/src/features/editor/components/table/handle/row-handle.tsx +++ b/apps/client/src/features/editor/components/table/handle/row-handle.tsx @@ -29,7 +29,12 @@ export const RowHandle = React.memo(function RowHandle({ // See ColumnHandle for the rationale: keep the last valid cell DOM cached // so the handle div stays mounted across stale-anchor renders, otherwise // pragmatic-dnd silently aborts an in-flight drag. - const lookupCellDom = editor.view.nodeDOM(anchorPos) as HTMLElement | null; + // `nodeDOM` is typed as `Node | null` — when `anchorPos` goes stale (e.g. + // an external drop reflows the doc before the plugin re-emits + // hoveringCell), it can resolve to a Text node, on which `.closest` is + // undefined. Filter to HTMLElement so downstream consumers stay safe. + const lookupDom = editor.view.nodeDOM(anchorPos); + const lookupCellDom = lookupDom instanceof HTMLElement ? lookupDom : null; const [cellDom, setCellDom] = useState(lookupCellDom); const lastCellDomRef = useRef(lookupCellDom); useEffect(() => {