Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: reset all interaction states on data update to avoid stale resize indicators",
"type": "patch",
"packageName": "@visactor/vtable"
}
],
"packageName": "@visactor/vtable",
"email": "2779428708@qq.com"
}
126 changes: 126 additions & 0 deletions packages/vtable/examples/interactive/resize-setRecords.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as VTable from '../../src';
import { bindDebugTool } from '../../src/scenegraph/debug-tool';
const ListTable = VTable.ListTable;
const CONTAINER_ID = 'vTable';

/**
* 验证场景:列宽/行高调整过程中调用 setRecords 或 updateOption 时,
* 调整指示线应被正确清除,不会残留在表格上。
* 对应 issue: https://github.com/VisActor/VTable/issues/4120
*
* 复现步骤:
* 1. 点击"自动定时刷新"按钮开启每3秒自动 setRecords
* 2. 拖动列头边界开始调整列宽(鼠标按住不放)
* 3. 等待自动刷新触发(或手动点击按钮)
* 4. 修复前:指示线会卡在表格上无法消失
* 5. 修复后:指示线在 setRecords/updateOption 调用时被正确清除
*/
export function createTable() {
const generatePersons = (count: number) => {
return Array.from(new Array(count)).map((_, i) => ({
id: i + 1,
name: `员工${i + 1}`,
email: `user${i + 1}@example.com`,
department: ['研发部', '市场部', '设计部', '产品部', '运营部'][i % 5],
salary: Math.round(Math.random() * 10000 + 5000),
city: ['北京', '上海', '广州', '深圳', '杭州'][i % 5]
}));
};

let records = generatePersons(100);

const columns: VTable.ColumnsDefine = [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'name', title: '姓名', width: 120 },
{ field: 'email', title: '邮箱', width: 220 },
{ field: 'department', title: '部门', width: 120 },
{ field: 'salary', title: '薪资', width: 120 },
{ field: 'city', title: '城市', width: 120 }
];

const option: VTable.ListTableConstructorOptions = {
container: document.getElementById(CONTAINER_ID),
records,
columns,
widthMode: 'standard',
defaultRowHeight: 40,
defaultHeaderRowHeight: 50,
theme: VTable.themes.ARCO
};

const btnContainer = document.createElement('div');
btnContainer.style.cssText = 'padding: 10px 0; display: flex; gap: 10px; align-items: center; flex-wrap: wrap;';

const tip = document.createElement('span');
tip.style.cssText = 'color: #666; font-size: 13px;';
tip.textContent = '操作:先拖动列边界调整列宽,拖动过程中点击下方按钮';
btnContainer.appendChild(tip);

const btnSetRecords = document.createElement('button');
btnSetRecords.textContent = 'setRecords (刷新数据)';
btnSetRecords.style.cssText =
'padding: 6px 16px; cursor: pointer; background: #416EFF; color: #fff; border: none; border-radius: 4px;';
btnSetRecords.addEventListener('click', () => {
records = generatePersons(100);
instance.setRecords(records);
console.log('setRecords called');
});
btnContainer.appendChild(btnSetRecords);

const btnUpdateOption = document.createElement('button');
btnUpdateOption.textContent = 'updateOption (更新配置)';
btnUpdateOption.style.cssText =
'padding: 6px 16px; cursor: pointer; background: #52C41A; color: #fff; border: none; border-radius: 4px;';
btnUpdateOption.addEventListener('click', () => {
records = generatePersons(100);
instance.updateOption({
...option,
records
});
console.log('updateOption called');
});
btnContainer.appendChild(btnUpdateOption);

let timer: any = null;
const btnAutoTest = document.createElement('button');
btnAutoTest.textContent = '自动定时刷新 (每3秒)';
btnAutoTest.style.cssText =
'padding: 6px 16px; cursor: pointer; background: #FA8C16; color: #fff; border: none; border-radius: 4px;';
btnAutoTest.addEventListener('click', () => {
if (timer) {
clearInterval(timer);
timer = null;
btnAutoTest.textContent = '自动定时刷新 (每3秒)';
btnAutoTest.style.background = '#FA8C16';
} else {
timer = setInterval(() => {
records = generatePersons(100);
instance.setRecords(records);
console.log('auto setRecords triggered');
}, 3000);
btnAutoTest.textContent = '停止自动刷新';
btnAutoTest.style.background = '#FF4D4F';
}
});
btnContainer.appendChild(btnAutoTest);

document.getElementById(CONTAINER_ID)?.before(btnContainer);

const instance = new ListTable(option);

bindDebugTool(instance.scenegraph.stage as any, {
customGrapicKeys: ['role', '_updateTag']
});

const originalRelease = instance.release.bind(instance);
instance.release = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
btnContainer.remove();
originalRelease();
};

(window as any).tableInstance = instance;
}
4 changes: 4 additions & 0 deletions packages/vtable/examples/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,10 @@ export const menus = [
path: 'interactive',
name: 'row-resize'
},
{
path: 'interactive',
name: 'resize-setRecords'
},
{
path: 'interactive',
name: 'pre-sort'
Expand Down
1 change: 1 addition & 0 deletions packages/vtable/src/ListTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ export class ListTable extends BaseTable implements ListTableAPI {
* @param option 附近参数,其中的sortState为排序状态,如果设置null 将清除目前的排序状态
*/
setRecords(records: Array<any>, option?: { sortState?: SortState | SortState[] | null }): void {
this.stateManager.endResizeIfResizing();
clearChartRenderQueue();
// 释放事件 及 对象
this.internalProps.dataSource?.release();
Expand Down
1 change: 1 addition & 0 deletions packages/vtable/src/PivotChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI {
* @param sort
*/
setRecords(records: Array<any>): void {
this.stateManager.endResizeIfResizing();
this.internalProps.layoutMap.release();
clearChartRenderQueue();
this.scenegraph.updateChartState(null, undefined);
Expand Down
1 change: 1 addition & 0 deletions packages/vtable/src/PivotTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI {
* @param sort
*/
setRecords(records: Array<any>): void {
this.stateManager.endResizeIfResizing();
clearChartRenderQueue();
const oldHoverState = { col: this.stateManager.hover.cellPos.col, row: this.stateManager.hover.cellPos.row };
this.options.records = this.internalProps.records = records;
Expand Down
14 changes: 14 additions & 0 deletions packages/vtable/src/state/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,21 @@ export class StateManager {
this.setSelectState();
this.setFrozenState();
}
endResizeIfResizing() {
if (this.columnResize.resizing) {
this.table.scenegraph.component.hideResizeCol();
this.columnResize.resizing = false;
}
if (this.rowResize.resizing) {
this.table.scenegraph.component.hideResizeRow();
this.rowResize.resizing = false;
}
if (this.interactionState === InteractionState.grabing) {
this.interactionState = InteractionState.default;
}
}
_updateOptionSetState() {
this.endResizeIfResizing();
this.interactionState = InteractionState.default;
// this.select = {
// highlightScope: HighlightScope.single,
Expand Down
Loading