From 28d5709035ecded03c43c4f36436182bc48d51d4 Mon Sep 17 00:00:00 2001 From: biubiukam Date: Wed, 15 Jul 2026 05:18:14 +0800 Subject: [PATCH 1/3] fix: delete reverse-selected rows from context menu --- .../context-menu/handle-menu-helper.test.ts | 65 +++++++++++++++++++ .../src/contextmenu/handle-menu-helper.ts | 4 +- .../vtable-plugins/src/table-series-number.ts | 6 +- 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts diff --git a/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts b/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts new file mode 100644 index 0000000000..1c777b352a --- /dev/null +++ b/packages/vtable-plugins/__tests__/context-menu/handle-menu-helper.test.ts @@ -0,0 +1,65 @@ +// @ts-nocheck +import { ListTable } from '@visactor/vtable'; +import { createDiv } from '../../../vtable/__tests__/dom'; +import { MenuHandler } from '../../src/contextmenu/handle-menu-helper'; +import { TableSeriesNumber } from '../../src/table-series-number'; + +global.__VERSION__ = 'none'; + +describe('Context menu row deletion', () => { + let table: ListTable; + + afterEach(() => { + table?.release(); + document.body.innerHTML = ''; + }); + + test('deletes all rows in a reverse-dragged row selection', () => { + const container = createDiv(); + container.style.width = '600px'; + container.style.height = '400px'; + + const seriesNumberPlugin = new TableSeriesNumber({ + rowCount: 5, + colCount: 2 + }); + table = new ListTable({ + container, + showHeader: false, + columns: [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' } + ], + records: [ + { id: 0, name: 'A' }, + { id: 1, name: 'B' }, + { id: 2, name: 'C' }, + { id: 3, name: 'D' }, + { id: 4, name: 'E' } + ], + syncRecordOperationsToSourceRecords: true, + plugins: [seriesNumberPlugin] + }); + + table.stateManager.select.ranges = [ + { + start: { col: 0, row: 3 }, + end: { col: table.colCount - 1, row: 1 } + } + ]; + + const selectCells = jest.spyOn(table, 'selectCells'); + seriesNumberPlugin['handleSeriesNumberCellRightClick']({ + detail: { + seriesNumberCell: { id: 2, name: 'row-series-number-cell' }, + event: new MouseEvent('contextmenu') + } + }); + + expect(selectCells).not.toHaveBeenCalled(); + + new MenuHandler().handleDeleteRow(table); + + expect(table.records.map(record => record.id)).toEqual([0, 4]); + }); +}); diff --git a/packages/vtable-plugins/src/contextmenu/handle-menu-helper.ts b/packages/vtable-plugins/src/contextmenu/handle-menu-helper.ts index 1528a775e9..94fdf081e4 100644 --- a/packages/vtable-plugins/src/contextmenu/handle-menu-helper.ts +++ b/packages/vtable-plugins/src/contextmenu/handle-menu-helper.ts @@ -105,7 +105,9 @@ export class MenuHandler { const deleteRowIndexs: number[] = []; for (let i = 0; i < selectRanges.length; i++) { const range = selectRanges[i]; - for (let j = range.start.row; j <= range.end.row; j++) { + const startRow = Math.min(range.start.row, range.end.row); + const endRow = Math.max(range.start.row, range.end.row); + for (let j = startRow; j <= endRow; j++) { if (!deleteRowIndexs.includes(j)) { deleteRowIndexs.push(j); } diff --git a/packages/vtable-plugins/src/table-series-number.ts b/packages/vtable-plugins/src/table-series-number.ts index 8919a1cded..4d581d8c9b 100644 --- a/packages/vtable-plugins/src/table-series-number.ts +++ b/packages/vtable-plugins/src/table-series-number.ts @@ -289,9 +289,11 @@ export class TableSeriesNumber implements pluginsDefinition.IVTablePlugin { const rowIndex = seriesNumberCell.id; //判断rowIndex整行是否被选中 const isRowSelected = this.table.stateManager.select.ranges.some(range => { + const startRow = Math.min(range.start.row, range.end.row); + const endRow = Math.max(range.start.row, range.end.row); return ( - range.start.row <= rowIndex && - rowIndex <= range.end.row && + startRow <= rowIndex && + rowIndex <= endRow && range.start.col === 0 && range.end.col === this.table.colCount - 1 ); From 8fcb48adb4be26fbab793e0524340d6a89a79e30 Mon Sep 17 00:00:00 2001 From: biubiukam Date: Wed, 15 Jul 2026 05:22:29 +0800 Subject: [PATCH 2/3] docs: update changlog of rush --- ...ue-5214-delete-selected-rows_2026-07-14-21-21.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/fix-issue-5214-delete-selected-rows_2026-07-14-21-21.json diff --git a/common/changes/@visactor/vtable/fix-issue-5214-delete-selected-rows_2026-07-14-21-21.json b/common/changes/@visactor/vtable/fix-issue-5214-delete-selected-rows_2026-07-14-21-21.json new file mode 100644 index 0000000000..3857992a67 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-5214-delete-selected-rows_2026-07-14-21-21.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: delete reverse-selected rows from context menu", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "biukam.w@gmail.com" +} \ No newline at end of file From 078bee6c21be1da423799db82e9dfab3e1a05143 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 20 Jul 2026 11:58:40 +0800 Subject: [PATCH 3/3] demo: add issue 5214 reproduction --- .../issue-5214-reverse-selected-row-delete.ts | 111 ++++++++++++++++++ packages/vtable-plugins/demo/menu.ts | 4 + 2 files changed, 115 insertions(+) create mode 100644 packages/vtable-plugins/demo/context-menu/issue-5214-reverse-selected-row-delete.ts diff --git a/packages/vtable-plugins/demo/context-menu/issue-5214-reverse-selected-row-delete.ts b/packages/vtable-plugins/demo/context-menu/issue-5214-reverse-selected-row-delete.ts new file mode 100644 index 0000000000..b0cdfcc0d7 --- /dev/null +++ b/packages/vtable-plugins/demo/context-menu/issue-5214-reverse-selected-row-delete.ts @@ -0,0 +1,111 @@ +import * as VTable from '@visactor/vtable'; +import { MenuHandler } from '../../src/contextmenu/handle-menu-helper'; +import { TableSeriesNumber } from '../../src/table-series-number'; + +const CONTAINER_ID = 'vTable'; + +const initialRecords = [ + { id: 0, name: 'A' }, + { id: 1, name: 'B' }, + { id: 2, name: 'C' }, + { id: 3, name: 'D' }, + { id: 4, name: 'E' } +]; + +const removeDemoToolbar = () => { + document.getElementById('issue5214Toolbar')?.remove(); +}; + +const setStatus = (message: string, pass: boolean) => { + const statusNode = document.getElementById('issue5214Status'); + if (!statusNode) { + return; + } + statusNode.textContent = message; + statusNode.style.color = pass ? '#237804' : '#cf1322'; +}; + +export function createTable() { + removeDemoToolbar(); + + const container = document.getElementById(CONTAINER_ID)!; + container.style.width = '640px'; + container.style.height = '360px'; + + const toolbar = document.createElement('div'); + toolbar.id = 'issue5214Toolbar'; + toolbar.style.cssText = [ + 'display: flex', + 'gap: 8px', + 'align-items: center', + 'height: 48px', + 'font-size: 12px' + ].join(';'); + toolbar.innerHTML = ` + + + 预期:删除 id=1,2,3,仅保留 0 和 4。 + + `; + container.before(toolbar); + + const seriesNumberPlugin = new TableSeriesNumber({ + rowCount: 5, + colCount: 2 + }); + + const tableInstance = new VTable.ListTable({ + container, + showHeader: false, + columns: [ + { field: 'id', title: 'ID', width: 120 }, + { field: 'name', title: 'Name', width: 160 } + ], + records: initialRecords.map(record => ({ ...record })), + syncRecordOperationsToSourceRecords: true, + plugins: [seriesNumberPlugin], + defaultRowHeight: 36 + }); + + const getRecordIds = () => tableInstance.records.map((record: { id: number }) => record.id); + const updateStatusFromRecords = (prefix: string, pass: boolean) => { + setStatus(`${prefix} | records=[${getRecordIds().join(',')}]`, pass); + }; + + document.getElementById('issue5214Reset')?.addEventListener('click', () => { + tableInstance.setRecords(initialRecords.map(record => ({ ...record }))); + updateStatusFromRecords('RESET', true); + }); + + document.getElementById('issue5214Reproduce')?.addEventListener('click', () => { + tableInstance.stateManager.select.ranges = [ + { + start: { col: 0, row: 3 }, + end: { col: tableInstance.colCount - 1, row: 1 } + } + ]; + + seriesNumberPlugin['handleSeriesNumberCellRightClick']({ + detail: { + seriesNumberCell: { id: 2, name: 'row-series-number-cell' }, + event: new MouseEvent('contextmenu') + } + }); + + new MenuHandler().handleDeleteRow(tableInstance); + + const ids = getRecordIds(); + const pass = ids.length === 2 && ids[0] === 0 && ids[1] === 4; + updateStatusFromRecords(pass ? 'PASS' : 'FAIL', pass); + }); + + updateStatusFromRecords('READY', true); + + const release = tableInstance.release.bind(tableInstance); + tableInstance.release = () => { + removeDemoToolbar(); + release(); + }; + + (window as any).tableInstance = tableInstance; +} diff --git a/packages/vtable-plugins/demo/menu.ts b/packages/vtable-plugins/demo/menu.ts index f5e19298ca..91bd566855 100644 --- a/packages/vtable-plugins/demo/menu.ts +++ b/packages/vtable-plugins/demo/menu.ts @@ -140,6 +140,10 @@ export const menus = [ path: 'context-menu', name: 'context-menu' }, + { + path: 'context-menu', + name: 'issue-5214-reverse-selected-row-delete' + }, { path: 'table-export', name: 'table-export'