From d39495184c96d25f3e7b00bc51fb96753d327ceb Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 22 Dec 2025 14:22:38 +0530 Subject: [PATCH 1/3] refactor: editor table performance --- .../drag-handles/column/drag-handle.tsx | 27 +++++++++++++++- .../plugins/drag-handles/column/plugin.ts | 31 ++++++++++++++++++- .../plugins/drag-handles/row/drag-handle.tsx | 27 +++++++++++++++- .../table/plugins/drag-handles/row/plugin.ts | 31 ++++++++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) 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..e65162bbd45 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", @@ -133,6 +152,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 +191,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..e7a0d74c9ca 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", @@ -133,6 +152,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 +190,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); + } + }); + }, }); From 2b4df1123742b4787469c0a48cb24e85bb967b07 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 22 Dec 2025 14:36:10 +0530 Subject: [PATCH 2/3] fix: potential memory leak --- .../table/plugins/drag-handles/column/drag-handle.tsx | 11 +++++++++++ .../table/plugins/drag-handles/row/drag-handle.tsx | 11 +++++++++++ 2 files changed, 22 insertions(+) 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 e65162bbd45..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 @@ -113,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; 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 e7a0d74c9ca..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 @@ -113,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; From e285ccc354060158b6f0d694cd760bce19e4e56b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 22 Dec 2025 14:48:03 +0530 Subject: [PATCH 3/3] fix: formatting --- .../components/workspace/confirm-workspace-member-remove.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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";