diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx index 0dba2b1e3..ed344a070 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx @@ -12,7 +12,7 @@ import { } from "@floating-ui/react"; import type { Editor } from "@tiptap/core"; import { Ellipsis } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; // plane imports import { cn } from "@plane/utils"; // constants @@ -49,6 +49,25 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) { const { col, editor } = props; // states const [isDropdownOpen, setIsDropdownOpen] = useState(false); + // Track active event listeners for cleanup + const activeListenersRef = useRef<{ + mouseup?: (e: MouseEvent) => void; + mousemove?: (e: MouseEvent) => void; + }>({}); + + // Cleanup window event listeners on unmount + useEffect(() => { + const listenersRef = activeListenersRef.current; + return () => { + // Remove any lingering window event listeners when component unmounts + if (listenersRef.mouseup) { + window.removeEventListener("mouseup", listenersRef.mouseup); + } + if (listenersRef.mousemove) { + window.removeEventListener("mousemove", listenersRef.mousemove); + } + }; + }, []); // floating ui const { refs, floatingStyles, context } = useFloating({ placement: "bottom-start", @@ -94,6 +113,17 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) { e.stopPropagation(); e.preventDefault(); + // Prevent multiple simultaneous drag operations + // If there are already listeners attached, remove them first + if (activeListenersRef.current.mouseup) { + window.removeEventListener("mouseup", activeListenersRef.current.mouseup); + } + if (activeListenersRef.current.mousemove) { + window.removeEventListener("mousemove", activeListenersRef.current.mousemove); + } + activeListenersRef.current.mouseup = undefined; + activeListenersRef.current.mousemove = undefined; + const table = findTable(editor.state.selection); if (!table) return; @@ -133,6 +163,9 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) { } window.removeEventListener("mouseup", handleFinish); window.removeEventListener("mousemove", handleMove); + // Clear the ref + activeListenersRef.current.mouseup = undefined; + activeListenersRef.current.mousemove = undefined; }; let pseudoColumn: HTMLElement | undefined; @@ -169,6 +202,9 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) { }; try { + // Store references for cleanup + activeListenersRef.current.mouseup = handleFinish; + activeListenersRef.current.mousemove = handleMove; window.addEventListener("mouseup", handleFinish); window.addEventListener("mousemove", handleMove); } catch (error) { diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/plugin.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/plugin.ts index e0039ecd2..b425a1c78 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/plugin.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/plugin.ts @@ -18,6 +18,8 @@ type TableColumnDragHandlePluginState = { // track table structure to detect changes tableWidth?: number; tableNodePos?: number; + // track renderers for cleanup + renderers?: ReactRenderer[]; }; const TABLE_COLUMN_DRAG_HANDLE_PLUGIN_KEY = new PluginKey("tableColumnHandlerDecorationPlugin"); @@ -58,11 +60,22 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin { + try { + renderer.destroy(); + } catch (error) { + console.error("Error destroying renderer:", error); + } + }); + // recreate all decorations const decorations: Decoration[] = []; + const renderers: ReactRenderer[] = []; for (let col = 0; col < tableMap.width; col++) { const pos = getTableCellWidgetDecorationPos(table, tableMap, col); @@ -75,6 +88,7 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin dragHandleComponent.element)); } @@ -82,12 +96,27 @@ export const TableColumnDragHandlePlugin = (editor: Editor): Plugin { + try { + renderer.destroy(); + } catch (error) { + console.error("Error destroying renderer:", error); + } + }); + }, }); diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx index 1d9ef420a..3425d0cde 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx @@ -12,7 +12,7 @@ import { } from "@floating-ui/react"; import type { Editor } from "@tiptap/core"; import { Ellipsis } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; // plane imports import { cn } from "@plane/utils"; // constants @@ -49,6 +49,25 @@ export function RowDragHandle(props: RowDragHandleProps) { const { editor, row } = props; // states const [isDropdownOpen, setIsDropdownOpen] = useState(false); + // Track active event listeners for cleanup + const activeListenersRef = useRef<{ + mouseup?: (e: MouseEvent) => void; + mousemove?: (e: MouseEvent) => void; + }>({}); + + // Cleanup window event listeners on unmount + useEffect(() => { + const listenersRef = activeListenersRef.current; + return () => { + // Remove any lingering window event listeners when component unmounts + if (listenersRef.mouseup) { + window.removeEventListener("mouseup", listenersRef.mouseup); + } + if (listenersRef.mousemove) { + window.removeEventListener("mousemove", listenersRef.mousemove); + } + }; + }, []); // floating ui const { refs, floatingStyles, context } = useFloating({ placement: "bottom-start", @@ -94,6 +113,17 @@ export function RowDragHandle(props: RowDragHandleProps) { e.stopPropagation(); e.preventDefault(); + // Prevent multiple simultaneous drag operations + // If there are already listeners attached, remove them first + if (activeListenersRef.current.mouseup) { + window.removeEventListener("mouseup", activeListenersRef.current.mouseup); + } + if (activeListenersRef.current.mousemove) { + window.removeEventListener("mousemove", activeListenersRef.current.mousemove); + } + activeListenersRef.current.mouseup = undefined; + activeListenersRef.current.mousemove = undefined; + const table = findTable(editor.state.selection); if (!table) return; @@ -133,6 +163,9 @@ export function RowDragHandle(props: RowDragHandleProps) { } window.removeEventListener("mouseup", handleFinish); window.removeEventListener("mousemove", handleMove); + // Clear the ref + activeListenersRef.current.mouseup = undefined; + activeListenersRef.current.mousemove = undefined; }; let pseudoRow: HTMLElement | undefined; @@ -168,6 +201,9 @@ export function RowDragHandle(props: RowDragHandleProps) { }; try { + // Store references for cleanup + activeListenersRef.current.mouseup = handleFinish; + activeListenersRef.current.mousemove = handleMove; window.addEventListener("mouseup", handleFinish); window.addEventListener("mousemove", handleMove); } catch (error) { diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/plugin.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/plugin.ts index 18ac677c4..71b7e1c1b 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/plugin.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/plugin.ts @@ -18,6 +18,8 @@ type TableRowDragHandlePluginState = { // track table structure to detect changes tableHeight?: number; tableNodePos?: number; + // track renderers for cleanup + renderers?: ReactRenderer[]; }; const TABLE_ROW_DRAG_HANDLE_PLUGIN_KEY = new PluginKey("tableRowDragHandlePlugin"); @@ -58,11 +60,22 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin { + try { + renderer.destroy(); + } catch (error) { + console.error("Error destroying renderer:", error); + } + }); + // recreate all decorations const decorations: Decoration[] = []; + const renderers: ReactRenderer[] = []; for (let row = 0; row < tableMap.height; row++) { const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width); @@ -75,6 +88,7 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin dragHandleComponent.element)); } @@ -82,12 +96,27 @@ export const TableRowDragHandlePlugin = (editor: Editor): Plugin { + try { + renderer.destroy(); + } catch (error) { + console.error("Error destroying renderer:", error); + } + }); + }, });