diff --git a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts index 29b93d49c..98b2dfe97 100644 --- a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts @@ -280,6 +280,29 @@ describe('listTable init test', () => { frozenTable.release(); }); + test('listTable should remove all records with a bottom frozen row', () => { + const optionWithBottomFrozenRow = { + ...option, + bottomFrozenRowCount: 1, + container: createDiv(), + records: records.slice(0, 2) + }; + optionWithBottomFrozenRow.container.style.position = 'relative'; + optionWithBottomFrozenRow.container.style.width = '1000px'; + optionWithBottomFrozenRow.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithBottomFrozenRow); + + expect(() => frozenTable.deleteRecords([0, 1])).not.toThrow(); + expect(frozenTable.records).toHaveLength(0); + expect(frozenTable.bottomFrozenRowCount).toBe(0); + expect(frozenTable.scenegraph.bottomFrozenGroup.attribute.height).toBe(0); + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.height).toBe(0); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.height).toBe(0); + + frozenTable.release(); + }); + test('listTable bottom corner rows should stay below right frozen body when only right frozen column exists', () => { const optionWithOnlyRightFrozenColumn = { ...option, diff --git a/packages/vtable/examples/debug/issue-3903-delete-all-bottom-frozen.ts b/packages/vtable/examples/debug/issue-3903-delete-all-bottom-frozen.ts new file mode 100644 index 000000000..707aeebc7 --- /dev/null +++ b/packages/vtable/examples/debug/issue-3903-delete-all-bottom-frozen.ts @@ -0,0 +1,63 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; +const STATUS_ID = 'issue3903Status'; + +const records = [ + { id: 1, name: 'Alice', total: 120 }, + { id: 2, name: 'Bob', total: 180 } +]; + +export function createTable() { + document.getElementById(STATUS_ID)?.remove(); + + const container = document.getElementById(CONTAINER_ID)!; + container.style.width = '720px'; + container.style.height = '320px'; + + const status = document.createElement('strong'); + status.id = STATUS_ID; + status.style.cssText = 'display: block; margin-bottom: 8px; font: 14px/1.5 sans-serif;'; + status.textContent = 'RUNNING'; + container.before(status); + + const tableInstance = new VTable.ListTable(container, { + records: records.slice(), + columns: [ + { field: 'id', title: 'ID', width: 120 }, + { field: 'name', title: 'Name', width: 300 }, + { field: 'total', title: 'Total', width: 180 } + ], + frozenColCount: 1, + rightFrozenColCount: 1, + bottomFrozenRowCount: 1, + widthMode: 'standard' + }); + + try { + tableInstance.deleteRecords([0, 1]); + const pass = + tableInstance.records.length === 0 && + tableInstance.bottomFrozenRowCount === 0 && + tableInstance.scenegraph.bottomFrozenGroup.attribute.height === 0; + status.textContent = `${pass ? 'PASS' : 'FAIL'} | records=${tableInstance.records.length}, bottomFrozenRows=${ + tableInstance.bottomFrozenRowCount + }`; + status.style.color = pass ? '#237804' : '#a8071a'; + } catch (error) { + status.textContent = `FAIL | ${error instanceof Error ? error.message : String(error)}`; + status.style.color = '#a8071a'; + } + + requestAnimationFrame(() => { + (window as any).BUGSERVER_SCREENSHOT?.(); + }); + + const release = tableInstance.release.bind(tableInstance); + tableInstance.release = () => { + document.getElementById(STATUS_ID)?.remove(); + release(); + }; + + (window as any).tableInstance = tableInstance; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 0376eaecd..b30a28fde 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -70,6 +70,10 @@ export const menus = [ path: 'debug', name: 'issue-5277-frozen-row-border' }, + { + path: 'debug', + name: 'issue-3903-delete-all-bottom-frozen' + }, { path: 'debug', name: 'issue-3752-large-merge-auto-height' diff --git a/packages/vtable/src/scenegraph/layout/frozen.ts b/packages/vtable/src/scenegraph/layout/frozen.ts index 5d0aadfd9..aef72065c 100644 --- a/packages/vtable/src/scenegraph/layout/frozen.ts +++ b/packages/vtable/src/scenegraph/layout/frozen.ts @@ -361,6 +361,9 @@ export function dealBottomFrozen(distBottomFrozenRow: number, scene: Scenegraph) // row header -> left bottom for (let col = 0; col < table.frozenColCount; col++) { const bottomFrozenColumnGroup = scene.getColGroupInLeftBottomCorner(col); + if (!bottomFrozenColumnGroup) { + continue; + } // move cell for (let row = table.rowCount - currentBottomFrozenRow - 1; row >= table.rowCount - distBottomFrozenRow; row--) { const cellGroup = scene.getCell(col, row, true); @@ -377,6 +380,9 @@ export function dealBottomFrozen(distBottomFrozenRow: number, scene: Scenegraph) // body -> bottom for (let col = proxy.colStart; col <= proxy.colEnd; col++) { const bottomFrozenColumnGroup = scene.getColGroupInBottom(col); + if (!bottomFrozenColumnGroup) { + continue; + } // move cell for (let row = table.rowCount - currentBottomFrozenRow - 1; row >= table.rowCount - distBottomFrozenRow; row--) { const cellGroup = scene.getCell(col, row, true); @@ -394,6 +400,9 @@ export function dealBottomFrozen(distBottomFrozenRow: number, scene: Scenegraph) // right -> right bottom for (let col = table.colCount - table.rightFrozenColCount; col < table.colCount; col++) { const bottomFrozenColumnGroup = scene.getColGroupInRightBottomCorner(col); + if (!bottomFrozenColumnGroup) { + continue; + } // move cell for ( let row = table.rowCount - currentBottomFrozenRow - 1;