From e0e460b6c2125bf707277f4eca73aeeff875539d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 21:49:03 +0400 Subject: [PATCH] =?UTF-8?q?fix(table):=20=D1=80=D0=B5=D1=81=D0=B0=D0=B9?= =?UTF-8?q?=D0=B7=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D0=BD=D0=BE=D0=BC=20mouseup;=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82-=D1=85=D0=B0=D1=80=D0=B0=D0=BA=D1=82=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8=20table-core;=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ColumnResizer: handleMouseMove завершает перетаскивание, если левая кнопка не зажата - cellMemoReset.test: убран дубль теста columnSizing, добавлены характеристики закрепления второй колонки и скрытия закреплённой - комментарии к columnFilters (триггер React.memo(TableHead)) и ссылки на архивную спеку Co-Authored-By: Claude Sonnet 5.5 --- .../RealtimeTable/RealtimeTable.jsx | 1 + .../RealtimeTable/TableHead/ColumnResizer.jsx | 7 ++- .../RealtimeTable/utils/cellMemoReset.js | 2 +- .../RealtimeTable/utils/cellMemoReset.test.js | 45 ++++++++++++------- .../RealtimeTable/utils/columnResizeInfo.js | 2 +- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 3079179..078e908 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -847,6 +847,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { columnPinning={columnPinning} columnSizing={columnSizing} columnVisibility={columnVisibility} + // Проп нужен как триггер React.memo(TableHead): фильтры шапка читает из table.getState(). columnFilters={columnFilters} showColumnFilters={showColumnFilters} selectedColumnId={selectedColumnId} diff --git a/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx b/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx index 9770f96..99407ff 100644 --- a/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx +++ b/web/src/components/RealtimeTable/TableHead/ColumnResizer.jsx @@ -28,6 +28,11 @@ const ColumnResizer = ({ columnId, reportDragDeltaX, onDragStart, onDragEnd }) = const handleMouseMove = useCallback( (e) => { if (!isDragging) return; + // Потерянный mouseup (отпустили вне окна без blur): кнопка уже не зажата. + if ((e.buttons & 1) === 0) { + finishDrag(); + return; + } const deltaX = e.clientX - lastXRef.current; @@ -36,7 +41,7 @@ const ColumnResizer = ({ columnId, reportDragDeltaX, onDragStart, onDragEnd }) = lastXRef.current = e.clientX; } }, - [isDragging, reportDragDeltaX], + [isDragging, reportDragDeltaX, finishDrag], ); useEffect(() => { diff --git a/web/src/components/RealtimeTable/utils/cellMemoReset.js b/web/src/components/RealtimeTable/utils/cellMemoReset.js index 137b690..770b5e9 100644 --- a/web/src/components/RealtimeTable/utils/cellMemoReset.js +++ b/web/src/components/RealtimeTable/utils/cellMemoReset.js @@ -1,5 +1,5 @@ // Ключ явного сброса мемоизации ячеек MRT (memoMode: 'cells') в таблице листа. -// Обоснование и контракт: openspec/changes/aurora-1839-cell-memo/ (design.md D1, D2; specs/sheet-table). +// Обоснование и контракт: openspec/changes/aurora-1839-cell-memo/ (design.md D1, D2; specs/sheet-table; после архивации — openspec/specs/sheet-table/spec.md). // Новое состояние, которое меняет определения колонок или стили закрепления, добавляется сюда и в cellMemoReset.test.js. const getPinnedSide = (ids, columnVisibility, columnSizing) => diff --git a/web/src/components/RealtimeTable/utils/cellMemoReset.test.js b/web/src/components/RealtimeTable/utils/cellMemoReset.test.js index 764635f..039eada 100644 --- a/web/src/components/RealtimeTable/utils/cellMemoReset.test.js +++ b/web/src/components/RealtimeTable/utils/cellMemoReset.test.js @@ -110,7 +110,9 @@ test('пока ширину тянут, ключ прежний; после от // Допущение @tanstack/table-core 8.20.5, на котором стоит явный сброс // (openspec/changes/aurora-1839-cell-memo/design.md, «Контекст», п. 3; D1). -// Первые два теста держат допущение: ячейки пересоздаются при смене массива columns и не пересоздаются при смене ширин. +// Тесты ниже фиксируют характеристику table-core 8.20.5 (упадут при обновлении библиотеки): +// новый массив columns даёт новые ячейки; смена columnSizing ячейки не пересоздаёт (поэтому ключ нужен для ширины +// закреплённой колонки); смена columnPinning пересоздаёт ячейки закреплённых сторон, центральные те же. const createFixture = () => createTable({ data: [{ a: 1, b: 2, c: 3 }], @@ -131,13 +133,6 @@ const cellsById = (table) => .map((cell) => [cell.id, cell]), ); -test('table-core: при прежних columns и data объекты ячеек те же', () => { - const table = createFixture(); - const before = cellsById(table); - updateState(table, { columnSizing: { a: 80 } }); - for (const [id, cell] of cellsById(table)) assert.equal(cell, before.get(id)); -}); - test('table-core: новый массив columns с теми же определениями даёт новые ячейки', () => { const table = createFixture(); const before = cellsById(table); @@ -147,12 +142,11 @@ test('table-core: новый массив columns с теми же опреде for (const [id, cell] of after) assert.notEqual(cell, before.get(id)); }); -// Допущение дизайна «ячейки пересоздаются только при смене columns/data» неверно для columnPinning: -// в table-core 8.20.5 getLeftVisibleCells/getRightVisibleCells (features/ColumnPinning.js) возвращают -// поверхностные копии `{...cell, position}`, поэтому при смене закрепления объект ячейки закрепляемой колонки -// новый при прежнем массиве columns. Ячейки остальных колонок (центр) остаются теми же объектами. -// Ключ сброса по закреплению остаётся как защита: он нужен для ячеек, которые table-core не пересоздаёт, -// и сработает как страховка, если поведение сменится. Тест фиксирует факт и упадёт при обновлении table-core. +// В table-core 8.20.5 getLeftVisibleCells/getRightVisibleCells (features/ColumnPinning.js) возвращают +// поверхностные копии `{...cell, position}`, поэтому при смене закрепления пересоздаются ячейки закреплённых +// колонок при прежнем массиве columns; центральные остаются теми же объектами. +// Ключ сброса по закреплению нужен из-за ширины закреплённой колонки (смена columnSizing ячейки не пересоздаёт) +// и как страховка на случай смены поведения библиотеки. test('table-core: смена columnPinning при прежнем массиве columns пересоздаёт ячейку закреплённой колонки, остальные те же', () => { const table = createFixture(); const before = cellsById(table); @@ -163,8 +157,6 @@ test('table-core: смена columnPinning при прежнем массиве for (const id of ['0_a', '0_b']) assert.equal(after.get(id), before.get(id)); }); -// Характеристика, а не ожидание дизайна: фиксирует поведение table-core 8.20.5 при смене columnSizing, -// чтобы обновление библиотеки показало смену поведения. test('table-core: смена columnSizing при прежнем массиве columns оставляет те же ячейки', () => { const table = createFixture(); const before = cellsById(table); @@ -173,3 +165,24 @@ test('table-core: смена columnSizing при прежнем массиве c assert.equal(after.size, before.size); for (const [id, cell] of after) assert.equal(cell, before.get(id)); }); + +test('table-core: закрепление второй колонки слева пересоздаёт все закреплённые ячейки, центральная та же', () => { + const table = createFixture(); + updateState(table, { columnPinning: { left: ['a'], right: [] } }); + const before = cellsById(table); + updateState(table, { columnPinning: { left: ['a', 'c'], right: [] } }); + const after = cellsById(table); + assert.notEqual(after.get('0_a'), before.get('0_a')); + assert.notEqual(after.get('0_c'), before.get('0_c')); + assert.equal(after.get('0_b'), before.get('0_b')); +}); + +test('table-core: скрытие закреплённой колонки пересоздаёт ячейки оставшейся закреплённой стороны', () => { + const table = createFixture(); + updateState(table, { columnPinning: { left: ['a', 'c'], right: [] } }); + const before = cellsById(table); + updateState(table, { columnVisibility: { a: false } }); + const after = cellsById(table); + assert.equal(after.has('0_a'), false); + assert.notEqual(after.get('0_c'), before.get('0_c')); +}); diff --git a/web/src/components/RealtimeTable/utils/columnResizeInfo.js b/web/src/components/RealtimeTable/utils/columnResizeInfo.js index d948b3b..83528ba 100644 --- a/web/src/components/RealtimeTable/utils/columnResizeInfo.js +++ b/web/src/components/RealtimeTable/utils/columnResizeInfo.js @@ -1,6 +1,6 @@ // columnSizingInfo для MRT/TanStack при собственном перетаскивании ширины (TableHead/ColumnResizer). // Пока isResizingColumn !== false, MRT 3.2.1 не пересоздаёт колонки и замораживает тело таблицы -// (openspec/changes/aurora-1839-cell-memo/design.md, «Контекст» п. 3–4, D3). +// (openspec/changes/aurora-1839-cell-memo/design.md, «Контекст» п. 3–4, D3; после архивации — openspec/specs/sheet-table/spec.md). export const DEFAULT_COLUMN_SIZING_INFO = { startOffset: null,