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 4a243da287..c6081a0e07 100644 --- a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts @@ -143,6 +143,168 @@ describe('listTable init test', () => { }); }); + test('listTable bottom frozen rows should stay connected after short body content', () => { + const optionWithBottomFrozenRows = { + ...option, + frozenRowCount: 5, + rightFrozenColCount: 0, + bottomFrozenRowCount: 2, + container: createDiv(), + records: records.slice(0, 10) + }; + optionWithBottomFrozenRows.container.style.position = 'relative'; + optionWithBottomFrozenRows.container.style.width = '1000px'; + optionWithBottomFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithBottomFrozenRows); + const { scenegraph } = frozenTable; + + expect(scenegraph.bottomFrozenGroup.attribute.y).toBe( + scenegraph.bodyGroup.attribute.y + scenegraph.bodyGroup.attribute.height + ); + expect(scenegraph.rightBottomCornerGroup.attribute.visible).toBe(false); + + frozenTable.release(); + }); + + test('listTable bottom left corner should be hidden without frozen columns', () => { + const optionWithOnlyBottomFrozenRows = { + ...option, + frozenColCount: 0, + rightFrozenColCount: 0, + bottomFrozenRowCount: 2, + container: createDiv(), + records: records.slice(0, 10) + }; + optionWithOnlyBottomFrozenRows.container.style.position = 'relative'; + optionWithOnlyBottomFrozenRows.container.style.width = '1000px'; + optionWithOnlyBottomFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithOnlyBottomFrozenRows); + + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.visible).toBe(false); + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.width).toBe(0); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.visible).toBe(false); + + frozenTable.release(); + }); + + test('listTable bottom right corner should stay connected after short content', () => { + const shortColumns = columns.slice(0, 5).map(column => ({ + ...column, + width: 100 + })); + const optionWithBottomRightFrozen = { + ...option, + columns: shortColumns, + defaultColWidth: 100, + frozenColCount: 2, + frozenRowCount: 5, + rightFrozenColCount: 2, + bottomFrozenRowCount: 2, + containerFit: true, + container: createDiv(), + records: records.slice(0, 10) + }; + optionWithBottomRightFrozen.container.style.position = 'relative'; + optionWithBottomRightFrozen.container.style.width = '1000px'; + optionWithBottomRightFrozen.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithBottomRightFrozen); + const { scenegraph } = frozenTable; + + expect(scenegraph.rightFrozenGroup.attribute.x).toBe( + scenegraph.bodyGroup.attribute.x + scenegraph.bodyGroup.attribute.width + ); + expect(scenegraph.rightBottomCornerGroup.attribute.x).toBe(scenegraph.rightFrozenGroup.attribute.x); + expect(scenegraph.rightBottomCornerGroup.attribute.y).toBe( + scenegraph.bodyGroup.attribute.y + scenegraph.bodyGroup.attribute.height + ); + + frozenTable.release(); + }); + + test('listTable right frozen header should stay on the right side without records', () => { + const optionWithRightFrozenHeader = { + ...option, + frozenColCount: 2, + rightFrozenColCount: 2, + bottomFrozenRowCount: 0, + container: createDiv(), + records: [] + }; + optionWithRightFrozenHeader.container.style.position = 'relative'; + optionWithRightFrozenHeader.container.style.width = '1000px'; + optionWithRightFrozenHeader.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithRightFrozenHeader); + const { scenegraph } = frozenTable; + const rightFrozenColsWidth = frozenTable.getRightFrozenColsWidth(); + + expect(scenegraph.rightTopCornerGroup.attribute.visible).toBe(true); + expect(scenegraph.rightFrozenGroup.attribute.x).toBe(scenegraph.tableGroup.attribute.width - rightFrozenColsWidth); + expect(scenegraph.rightTopCornerGroup.attribute.x).toBe(scenegraph.rightFrozenGroup.attribute.x); + + frozenTable.release(); + }); + + test('listTable bottom corner rows should stay below header when body is empty', () => { + const twoColumns = columns.slice(0, 2).map(column => ({ + ...column, + width: 150 + })); + const optionWithOnlyCornerRows = { + ...option, + columns: twoColumns, + frozenColCount: 1, + rightFrozenColCount: 1, + bottomFrozenRowCount: 1, + container: createDiv(), + records: records.slice(0, 1) + }; + optionWithOnlyCornerRows.container.style.position = 'relative'; + optionWithOnlyCornerRows.container.style.width = '1000px'; + optionWithOnlyCornerRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithOnlyCornerRows); + const { scenegraph } = frozenTable; + const headerBottom = Math.max( + scenegraph.cornerHeaderGroup.attribute.y + scenegraph.cornerHeaderGroup.attribute.height, + scenegraph.rightTopCornerGroup.attribute.y + scenegraph.rightTopCornerGroup.attribute.height + ); + + expect(scenegraph.bodyGroup.attribute.height).toBe(0); + expect(scenegraph.leftBottomCornerGroup.attribute.y).toBe(headerBottom); + expect(scenegraph.rightBottomCornerGroup.attribute.y).toBe(headerBottom); + + frozenTable.release(); + }); + + test('listTable bottom corner rows should stay below right frozen body when only right frozen column exists', () => { + const optionWithOnlyRightFrozenColumn = { + ...option, + columns: columns.slice(0, 1), + frozenColCount: 0, + rightFrozenColCount: 1, + bottomFrozenRowCount: 3, + container: createDiv(), + records: records.slice(0, 4) + }; + optionWithOnlyRightFrozenColumn.container.style.position = 'relative'; + optionWithOnlyRightFrozenColumn.container.style.width = '1000px'; + optionWithOnlyRightFrozenColumn.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithOnlyRightFrozenColumn); + const { scenegraph } = frozenTable; + const rightFrozenBottom = scenegraph.rightFrozenGroup.attribute.y + scenegraph.rightFrozenGroup.attribute.height; + + expect(scenegraph.bodyGroup.attribute.height).toBe(0); + expect(scenegraph.rightFrozenGroup.attribute.height).toBeGreaterThan(0); + expect(scenegraph.rightBottomCornerGroup.attribute.y).toBe(rightFrozenBottom); + + frozenTable.release(); + }); + test('listTable should support decreasing rightFrozenColCount by setter with row series number', () => { const optionWithRightFrozen = { ...option, @@ -165,5 +327,62 @@ describe('listTable init test', () => { rightFrozenTable.rightFrozenColCount = 1; }).not.toThrow(); expect(rightFrozenTable.rightFrozenColCount).toBe(1); + expect(rightFrozenTable.scenegraph.rightBottomCornerGroup.attribute.visible).toBe(false); + expect(rightFrozenTable.scenegraph.rightBottomCornerGroup.attribute.width).toBe(0); + }); + + test('listTable bottom corner groups should be reset after clearing bottom frozen rows', () => { + const optionWithBottomFrozenRows = { + ...option, + frozenColCount: 2, + rightFrozenColCount: 2, + bottomFrozenRowCount: 2, + container: createDiv(), + records: records.slice(0, 10) + }; + optionWithBottomFrozenRows.container.style.position = 'relative'; + optionWithBottomFrozenRows.container.style.width = '1000px'; + optionWithBottomFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithBottomFrozenRows); + + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.visible).toBe(true); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.visible).toBe(true); + + frozenTable.bottomFrozenRowCount = 0; + + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.visible).toBe(false); + expect(frozenTable.scenegraph.leftBottomCornerGroup.attribute.height).toBe(0); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.visible).toBe(false); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.width).toBe(0); + expect(frozenTable.scenegraph.rightBottomCornerGroup.attribute.height).toBe(0); + + frozenTable.release(); + }); + + test('listTable right top corner group should be reset after clearing frozen rows', () => { + const optionWithRightFrozenRows = { + ...option, + frozenRowCount: 5, + bottomFrozenRowCount: 0, + rightFrozenColCount: 2, + container: createDiv(), + records + }; + optionWithRightFrozenRows.container.style.position = 'relative'; + optionWithRightFrozenRows.container.style.width = '1000px'; + optionWithRightFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithRightFrozenRows); + + expect(frozenTable.scenegraph.rightTopCornerGroup.attribute.visible).toBe(true); + + frozenTable.frozenRowCount = 0; + + expect(frozenTable.scenegraph.rightTopCornerGroup.attribute.visible).toBe(false); + expect(frozenTable.scenegraph.rightTopCornerGroup.attribute.width).toBe(0); + expect(frozenTable.scenegraph.rightTopCornerGroup.attribute.height).toBe(0); + + frozenTable.release(); }); }); diff --git a/packages/vtable/examples/debug/issue-4904-frozen-row-gap.ts b/packages/vtable/examples/debug/issue-4904-frozen-row-gap.ts new file mode 100644 index 0000000000..22884137d5 --- /dev/null +++ b/packages/vtable/examples/debug/issue-4904-frozen-row-gap.ts @@ -0,0 +1,90 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; + +const generatePersons = (count: number) => { + return Array.from(new Array(count)).map((_, i) => ({ + id: i + 1, + email1: `${i + 1}@xxx.com`, + name: `小明${i + 1}`, + lastName: '王', + date1: '2022年9月1日', + tel: '000-0000-0000', + sex: i % 2 === 0 ? 'boy' : 'girl', + work: i % 2 === 0 ? 'back-end engineer' : 'front-end engineer', + city: 'beijing' + })); +}; + +export function createTable() { + const container = document.getElementById(CONTAINER_ID)!; + container.style.width = '800px'; + container.style.height = '800px'; + + const records = generatePersons(10); + const columns: VTable.ColumnsDefine = [ + { + field: 'id', + title: 'ID', + width: 120, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'lastName', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + } + ]; + const option: VTable.ListTableConstructorOptions = { + container, + records, + columns, + frozenRowCount: 5, + bottomFrozenRowCount: 2, + allowFrozenColCount: 2 + }; + + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; +} diff --git a/packages/vtable/examples/debug/issue-5115-auto-height-zero-row.ts b/packages/vtable/examples/debug/issue-5115-auto-height-zero-row.ts index 88b1877e06..ca1cde9a6c 100644 --- a/packages/vtable/examples/debug/issue-5115-auto-height-zero-row.ts +++ b/packages/vtable/examples/debug/issue-5115-auto-height-zero-row.ts @@ -73,7 +73,9 @@ export function createTable() { const proxy = tableInstance.scenegraph.proxy; const bodyStart = tableInstance.frozenRowCount; const bodyHeight = - tableInstance.tableNoFrameHeight - tableInstance.getFrozenRowsHeight() - tableInstance.getBottomFrozenRowsHeight(); + tableInstance.tableNoFrameHeight - + tableInstance.getFrozenRowsHeight() - + tableInstance.getBottomFrozenRowsHeight(); const renderedHeight = tableInstance.getRowsHeight(bodyStart, proxy.rowEnd); const firstFilteredRowHeight = tableInstance.getRowHeight(tableInstance.columnHeaderLevelCount + 1); const proxyRowsSynced = diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 71d7f7a744..99ce817057 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -74,6 +74,10 @@ export const menus = [ path: 'debug', name: 'issue-4816-functional-icons-theme' }, + { + path: 'debug', + name: 'issue-4904-frozen-row-gap' + }, { path: 'debug', name: 'issue-4798-sort-icon-visible-time' diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 0330409fa0..f3dc9a32a8 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1435,36 +1435,78 @@ export class Scenegraph { } } - if (this.table.bottomFrozenRowCount > 0) { - this.bottomFrozenGroup.setAttribute( - 'y', - this.tableGroup.attribute.height - this.table.getBottomFrozenRowsHeight() + const hasFrozenCols = this.table.frozenColCount > 0; + const hasRightFrozenCols = this.table.rightFrozenColCount > 0; + const hasFrozenRows = this.table.frozenRowCount > 0; + const hasBottomFrozenRows = this.table.bottomFrozenRowCount > 0; + + if (hasBottomFrozenRows) { + const bottomFrozenRowsHeight = this.table.getBottomFrozenRowsHeight(); + const topFrozenBottom = Math.max( + this.colHeaderGroup.attribute.y + this.colHeaderGroup.attribute.height, + this.cornerHeaderGroup.attribute.y + this.cornerHeaderGroup.attribute.height, + this.rightTopCornerGroup.attribute.y + this.rightTopCornerGroup.attribute.height + ); + const middleContentBottom = Math.max( + this.rowHeaderGroup.attribute.y + this.rowHeaderGroup.attribute.height, + this.bodyGroup.attribute.y + this.bodyGroup.attribute.height, + this.rightFrozenGroup.attribute.y + this.rightFrozenGroup.attribute.height, + topFrozenBottom ); + const bottomFrozenY = Math.min(this.tableGroup.attribute.height - bottomFrozenRowsHeight, middleContentBottom); + this.bottomFrozenGroup.setAttribute('y', bottomFrozenY); + this.leftBottomCornerGroup.setAttributes({ + visible: hasFrozenCols, + y: bottomFrozenY, + height: bottomFrozenRowsHeight, + width: hasFrozenCols ? this.table.getFrozenColsWidth() : 0 + }); + this.rightBottomCornerGroup.setAttributes({ + visible: hasRightFrozenCols, + x: 0, + y: bottomFrozenY, + width: 0, + height: bottomFrozenRowsHeight + }); + } else { this.leftBottomCornerGroup.setAttributes({ - visible: true, - y: this.tableGroup.attribute.height - this.table.getBottomFrozenRowsHeight(), - height: this.table.getBottomFrozenRowsHeight(), - width: this.table.getFrozenColsWidth() + visible: false, + width: 0, + height: 0 }); this.rightBottomCornerGroup.setAttributes({ - visible: true, - y: this.tableGroup.attribute.height - this.table.getBottomFrozenRowsHeight(), - height: this.table.getBottomFrozenRowsHeight() + visible: false, + width: 0, + height: 0 }); } - if (this.table.rightFrozenColCount > 0) { - this.rightFrozenGroup.setAttribute('x', this.tableGroup.attribute.width - this.table.getRightFrozenColsWidth()); + if (hasRightFrozenCols) { + const rightFrozenColsWidth = this.table.getRightFrozenColsWidth(); + const middleContentRight = Math.max( + this.colHeaderGroup.attribute.x + this.colHeaderGroup.attribute.width, + this.bodyGroup.attribute.x + this.bodyGroup.attribute.width, + this.bottomFrozenGroup.attribute.x + this.bottomFrozenGroup.attribute.width + ); + const rightFrozenX = Math.min(this.tableGroup.attribute.width - rightFrozenColsWidth, middleContentRight); + this.rightFrozenGroup.setAttribute('x', rightFrozenX); this.rightTopCornerGroup.setAttributes({ - visible: true, - x: this.tableGroup.attribute.width - this.table.getRightFrozenColsWidth(), - width: this.table.getRightFrozenColsWidth(), - height: this.table.getFrozenRowsHeight() + visible: hasFrozenRows, + x: rightFrozenX, + width: hasFrozenRows ? rightFrozenColsWidth : 0, + height: hasFrozenRows ? this.table.getFrozenRowsHeight() : 0 }); this.rightBottomCornerGroup.setAttributes({ - visible: true, - x: this.tableGroup.attribute.width - this.table.getRightFrozenColsWidth(), - width: this.table.getRightFrozenColsWidth() + visible: hasBottomFrozenRows, + x: rightFrozenX, + width: hasBottomFrozenRows ? rightFrozenColsWidth : 0, + height: hasBottomFrozenRows ? this.table.getBottomFrozenRowsHeight() : 0 + }); + } else { + this.rightTopCornerGroup.setAttributes({ + visible: false, + width: 0, + height: 0 }); }