fix(table): ресайз завершается при потерянном mouseup; тест-характеристики table-core; комментарии

- ColumnResizer: handleMouseMove завершает перетаскивание, если левая кнопка не зажата
- cellMemoReset.test: убран дубль теста columnSizing, добавлены характеристики закрепления второй колонки и скрытия закреплённой
- комментарии к columnFilters (триггер React.memo(TableHead)) и ссылки на архивную спеку

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 21:49:03 +04:00
parent 651d71f7c0
commit e0e460b6c2
5 changed files with 38 additions and 19 deletions

View File

@ -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}

View File

@ -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(() => {

View File

@ -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) =>

View File

@ -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'));
});

View File

@ -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,