From e5f4b3b965aafef57aa02f4af657cef939e2dcbb Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 21:18:44 +0400 Subject: [PATCH] =?UTF-8?q?perf(table):=20ColumnResizer=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B0=D0=B5=D1=82=20MRT=20=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D0=B5=D1=86=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=82=D0=B0=D1=81=D0=BA=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=88=D0=B8=D1=80=D0=B8=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isResizingColumn выставляется на mousedown и снимается по mouseup, blur окна и размонтированию: пока тянут, MRT замораживает тело таблицы (design.md D3). Co-Authored-By: Claude Sonnet 5.5 --- .../RealtimeTable/TableHead/ColumnResizer.jsx | 39 +++++++++++++------ .../RealtimeTable/TableHead/TableHead.jsx | 8 +++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx b/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx index d16988e..9770f96 100644 --- a/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx +++ b/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx @@ -1,14 +1,28 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -const ColumnResizer = ({ reportDragDeltaX }) => { +const ColumnResizer = ({ columnId, reportDragDeltaX, onDragStart, onDragEnd }) => { const [isDragging, setIsDragging] = useState(false); + const isDraggingRef = useRef(false); const lastXRef = useRef(0); + // HeaderCell создаёт колбэки на каждый рендер: держим последний в ref. + const onDragEndRef = useRef(onDragEnd); + onDragEndRef.current = onDragEnd; + + // Единая точка завершения (mouseup, blur окна, размонтирование); идемпотентна. + const finishDrag = useCallback(() => { + if (!isDraggingRef.current) return; + isDraggingRef.current = false; + setIsDragging(false); + onDragEndRef.current?.(); + }, []); const handleMouseDown = (e) => { if (e.button !== 0) return; e.preventDefault(); + isDraggingRef.current = true; setIsDragging(true); lastXRef.current = e.clientX; + onDragStart?.(); }; const handleMouseMove = useCallback( @@ -25,24 +39,27 @@ const ColumnResizer = ({ reportDragDeltaX }) => { [isDragging, reportDragDeltaX], ); - const handleMouseUp = useCallback(() => { - setIsDragging(false); - }, []); - useEffect(() => { - if (isDragging) { - window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('mouseup', handleMouseUp); - } + if (!isDragging) return; + + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', finishDrag); + window.addEventListener('blur', finishDrag); return () => { window.removeEventListener('mousemove', handleMouseMove); - window.removeEventListener('mouseup', handleMouseUp); + window.removeEventListener('mouseup', finishDrag); + window.removeEventListener('blur', finishDrag); }; - }, [isDragging, handleMouseMove, handleMouseUp]); + }, [isDragging, handleMouseMove, finishDrag]); + + // Размонтирование посреди перетаскивания не должно оставить MRT с isResizingColumn. + // Отдельный эффект: очистка эффекта подписок срабатывает при каждой смене handleMouseMove. + useEffect(() => () => finishDrag(), [finishDrag]); return (
{ const pinningPosition = column.getIsPinned(); @@ -174,7 +175,12 @@ const HeaderCell = ({ header, table, pinningPosition, onClick, onChangeWidth, bu }}> {header.isPlaceholder ? null : formatHeaderYear(column.columnDef.header, budgetYear)}
- + table.setColumnSizingInfo(startColumnResize(column.id))} + onDragEnd={() => table.setColumnSizingInfo(endColumnResize)} + /> );