diff --git a/common/changes/@visactor/vtable/fix-issue-5027_2026-05-21-07-15.json b/common/changes/@visactor/vtable/fix-issue-5027_2026-05-21-07-15.json new file mode 100644 index 0000000000..7ee1609801 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-5027_2026-05-21-07-15.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: avoid selection gap with frozen rows\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file 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 efc31f54cd..7d6d6073a0 100644 --- a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts @@ -116,4 +116,30 @@ describe('listTable init test', () => { expect(listTable.cellIsInVisualView(3, 39)).toBe(true); expect(listTable.cellIsInVisualView(3, 38)).toBe(true); }); + + test('listTable body visible row range should ignore frozen rows offset duplication', () => { + const optionWithFrozenRows = { + ...option, + frozenRowCount: 5, + rightFrozenColCount: 0, + bottomFrozenRowCount: 0, + container: createDiv(), + records + }; + optionWithFrozenRows.container.style.position = 'relative'; + optionWithFrozenRows.container.style.width = '1000px'; + optionWithFrozenRows.container.style.height = '800px'; + + const frozenTable = new ListTable(optionWithFrozenRows); + frozenTable.scrollTop = 400; + + expect(frozenTable.getBodyVisibleRowRange()).toEqual({ + rowStart: 10, + rowEnd: 28 + }); + expect(frozenTable.getBodyVisibleCellRange()).toMatchObject({ + rowStart: 10, + rowEnd: 28 + }); + }); }); diff --git a/packages/vtable/examples/debug/issue-5027.ts b/packages/vtable/examples/debug/issue-5027.ts new file mode 100644 index 0000000000..f33e0f83ff --- /dev/null +++ b/packages/vtable/examples/debug/issue-5027.ts @@ -0,0 +1,81 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; + +function createRecords(count: number) { + return Array.from({ length: count }, (_, index) => ({ + name: `John ${index + 1}`, + age: 18 + (index % 10), + gender: index % 2 === 0 ? 'male' : 'female', + hobby: index % 3 === 0 ? '🏀' : index % 3 === 1 ? '🎸' : '📚' + })); +} + +export function createTable() { + const container = document.getElementById(CONTAINER_ID); + if (!container) { + throw new Error('Cannot find VTable container'); + } + const option: VTable.ListTableConstructorOptions = { + container, + columns: [ + { + field: 'name', + title: 'name', + width: 180 + }, + { + field: 'age', + title: 'age', + width: 120 + }, + { + field: 'gender', + title: 'gender', + width: 140 + }, + { + field: 'hobby', + title: 'hobby', + width: 140 + } + ], + records: createRecords(2000), + frozenRowCount: 5, + heightMode: 'standard', + widthMode: 'standard', + select: { + headerSelectMode: 'inline', + highlightMode: 'column' + }, + theme: VTable.themes.DEFAULT.extends({ + scrollStyle: { + visible: 'always', + hoverOn: false + } + }) + }; + + const tableInstance = new VTable.ListTable(option); + + // Preselect the age column so the scrollbar drag issue can be reproduced immediately. + tableInstance.selectCol(1); + + const info = document.createElement('div'); + info.style.cssText = ['margin: 8px 0', 'font-size: 12px', 'line-height: 18px', 'color: #333'].join(';'); + info.innerText = 'issue-5027: drag the vertical scrollbar while the age column is selected and frozenRowCount is 5.'; + container.parentElement?.insertBefore(info, container); + + const w = window as unknown as { + tableInstance?: VTable.ListTable; + issue5027?: { + table: VTable.ListTable; + selectAgeColumn: () => void; + }; + }; + w.tableInstance = tableInstance; + w.issue5027 = { + table: tableInstance, + selectAgeColumn: () => tableInstance.selectCol(1) + }; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 9b1f6fa603..fa2b3ed89c 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -33,6 +33,10 @@ export const menus = [ { path: 'debug', name: 'issue-5114' + }, + { + path: 'debug', + name: 'issue-5027' } ] }, diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 90b058be22..45c880566a 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2503,17 +2503,19 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { /** 获取表格body部分的显示单元格范围 */ getBodyVisibleCellRange() { const { scrollTop, scrollLeft } = this; - const frozenRowsHeight = this.getFrozenRowsHeight(); const frozenColsContentWidth = this.getFrozenColsContentWidth(); const frozenColsOffset = this.getFrozenColsOffset(); const bottomFrozenRowsHeight = this.getBottomFrozenRowsHeight(); const rightFrozenColsWidth = this.getRightFrozenColsWidth(); // 计算非冻结 - const { row: rowStart } = this.getRowAt(scrollTop + frozenRowsHeight + 1); + const rowStart = Math.max(this.getTargetRowAt(scrollTop + 1)?.row ?? -1, this.frozenRowCount); const { col: colStart } = this.getColAt(scrollLeft + frozenColsContentWidth + 1); const rowEnd = this.getAllRowsHeight() > this.tableNoFrameHeight - ? this.getRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight).row + ? Math.max( + this.getTargetRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight)?.row ?? -1, + rowStart + ) : this.rowCount - 1; const colEnd = this.getAllColsWidth() > this.tableNoFrameWidth @@ -2532,13 +2534,16 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { */ getBodyVisibleRowRange(start_deltaY: number = 0, end_deltaY: number = 0) { const { scrollTop } = this; - const frozenRowsHeight = this.getFrozenRowsHeight(); const bottomFrozenRowsHeight = this.getBottomFrozenRowsHeight(); // 计算非冻结 - const { row: rowStart } = this.getRowAt(scrollTop + frozenRowsHeight + 1 + start_deltaY); + const rowStart = Math.max(this.getTargetRowAt(scrollTop + 1 + start_deltaY)?.row ?? -1, this.frozenRowCount); const rowEnd = this.getAllRowsHeight() > this.tableNoFrameHeight - ? this.getRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight + end_deltaY).row + ? Math.max( + this.getTargetRowAt(scrollTop + this.tableNoFrameHeight - 1 - bottomFrozenRowsHeight + end_deltaY)?.row ?? + -1, + rowStart + ) : this.rowCount - 1; if (rowEnd < 0) { return null;