diff --git a/common/changes/@visactor/vtable/fix-issue-4984-group-sticky-collapse_2026-05-20-02-00.json b/common/changes/@visactor/vtable/fix-issue-4984-group-sticky-collapse_2026-05-20-02-00.json new file mode 100644 index 000000000..422bad3dc --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-4984-group-sticky-collapse_2026-05-20-02-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: preserve sticky group position after toggle\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/src/event/event.ts b/packages/vtable/src/event/event.ts index 198f5325a..bae7c2fd2 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -551,6 +551,9 @@ export class EventManager { cellIsHeaderCheck(eventArgsSet: SceneEvent, update?: boolean): boolean { const { eventArgs } = eventArgsSet; + if (!eventArgs) { + return false; + } const { col, row, target } = eventArgs; if (!this.table.isHeader(col, row)) { return false; diff --git a/packages/vtable/src/plugins/list-tree-stick-cell.ts b/packages/vtable/src/plugins/list-tree-stick-cell.ts index af05f3713..93c82918b 100644 --- a/packages/vtable/src/plugins/list-tree-stick-cell.ts +++ b/packages/vtable/src/plugins/list-tree-stick-cell.ts @@ -6,6 +6,8 @@ import type { Graphic } from '@src/vrender'; import { createRect } from '@src/vrender'; import { Factory } from '../core/factory'; import { getTargetCell } from '../event/util'; +import { getIconAndPositionFromTarget } from '../scenegraph/utils/icon'; +import { IconFuncTypeEnum } from '../ts-types'; export interface IListTreeStickCellPlugin { new (table: ListTable): ListTreeStickCellPlugin; @@ -247,7 +249,18 @@ function prepareShadowRoot(table: ListTable) { const titleRows = table.listTreeStickCellPlugin.titleRows; const { shadowTarget } = e.pickParams; const cellGroup = getTargetCell(shadowTarget); - const { col, row } = cellGroup; + if (!cellGroup) { + return; + } + const iconInfo = getIconAndPositionFromTarget(shadowTarget); + const funcType = iconInfo?.icon?.attribute?.funcType; + if (funcType === IconFuncTypeEnum.collapse || funcType === IconFuncTypeEnum.expand) { + const stickCellTop = getStickCellTop(cellGroup, table); + table.toggleHierarchyState(cellGroup.col, cellGroup.row); + keepRowAtVisiblePosition(cellGroup.col, cellGroup.row, stickCellTop, table); + return; + } + const { row } = cellGroup; const rowIndex = titleRows.indexOf(row); // table.scrollToCell({ col, row: row - rowIndex }); scrollToRow(row - rowIndex, table); @@ -271,7 +284,18 @@ function prepareShadowRoot(table: ListTable) { const titleRows = table.listTreeStickCellPlugin.titleRows; const { shadowTarget } = e.pickParams; const cellGroup = getTargetCell(shadowTarget); - const { col, row } = cellGroup; + if (!cellGroup) { + return; + } + const iconInfo = getIconAndPositionFromTarget(shadowTarget); + const funcType = iconInfo?.icon?.attribute?.funcType; + if (funcType === IconFuncTypeEnum.collapse || funcType === IconFuncTypeEnum.expand) { + const stickCellTop = getStickCellTop(cellGroup, table); + table.toggleHierarchyState(cellGroup.col, cellGroup.row); + keepRowAtVisiblePosition(cellGroup.col, cellGroup.row, stickCellTop, table); + return; + } + const { row } = cellGroup; const rowIndex = titleRows.indexOf(row); // table.scrollToCell({ col, row: row - rowIndex }); scrollToRow(row - rowIndex, table); @@ -297,6 +321,21 @@ function scrollToRow(row: number, table: ListTable) { table.scenegraph.updateNextFrame(); } +function getStickCellTop(cellGroup: Group, table: ListTable) { + return table.getFrozenRowsHeight() + (cellGroup.attribute?.y ?? 0); +} + +function keepRowAtVisiblePosition(col: number, row: number, visibleTop: number, table: ListTable) { + const drawRange = table.getDrawRange(); + // Keep the clicked sticky group row in roughly the same viewport position after collapse/expand. + const targetScrollTop = Math.max( + 0, + Math.min(table.getCellRect(col, row).top - visibleTop, table.getAllRowsHeight() - drawRange.height) + ); + table.scrollTop = targetScrollTop; + table.scenegraph.updateNextFrame(); +} + export const registerListTreeStickCellPlugin = () => { Factory.registerComponent('listTreeStickCellPlugin', ListTreeStickCellPlugin); };