diff --git a/apps/web/core/components/workspace/confirm-workspace-member-remove.tsx b/apps/web/core/components/workspace/confirm-workspace-member-remove.tsx index 1eb29a0ef7d..8e38061a7d7 100644 --- a/apps/web/core/components/workspace/confirm-workspace-member-remove.tsx +++ b/apps/web/core/components/workspace/confirm-workspace-member-remove.tsx @@ -1,4 +1,5 @@ -import { useState } from "react"; import { observer } from "mobx-react"; +import { useState } from "react"; +import { observer } from "mobx-react"; import { AlertTriangle } from "lucide-react"; // ui import { useTranslation } from "@plane/i18n"; 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 0dba2b1e354..ed344a0703f 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 e0039ecd25e..b425a1c782c 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 1d9ef420af2..3425d0cdea2 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 18ac677c46c..71b7e1c1b16 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); + } + }); + }, });