Skip to content
Open
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
7 changes: 7 additions & 0 deletions docs/assets/option/en/common/option-secondary.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,13 @@ Whether to adjust the column width, support configuring functions, the function
canResizeColumn?: (col: number, row: number, table: BaseTableAPI) => boolean;
```

##${prefix} realtime(boolean) = true

Whether to respond in real time when dragging to resize columns/rows.

- `true` (default): Column widths / row heights update in real time during dragging, keeping the historical behavior.
- `false`: Only the indicator line moves while dragging; the final size is applied when the mouse is released. Recommended for large datasets to significantly reduce lag while dragging.

#${prefix} dragOrder(Object)

#${prefix} dragOrder(Object)
Expand Down
7 changes: 7 additions & 0 deletions docs/assets/option/zh/common/option-secondary.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,13 @@ animationAppear?: boolean | {
canResizeColumn?: (col: number, row: number, table: BaseTableAPI) => boolean;
```

##${prefix} realtime(boolean) = true

拖拽调整行列尺寸时是否实时响应。

- `true`(默认):拖拽过程中实时更新列宽/行高,与历史行为一致。
- `false`:拖拽过程中仅移动指示线,鼠标释放后才应用最终尺寸变化。适用于大数据量场景,可显著缓解拖拽时的卡顿。

#${prefix} dragOrder(Object)

拖拽移动位置的配置。
Expand Down
2 changes: 2 additions & 0 deletions packages/vtable/__tests__/visual/cases/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export const cases = [
{ id: 'pivot-empty-string-dimension', purpose: '透视空字符串列维度与普通列并存', file: './pivot/empty-string-dimension.mjs', bundles: ['vtable'] },
{ id: 'pivot-chart-pie-multi-select', purpose: '透视饼图连续扇区多选', file: './pivot-chart/pie-multi-select.mjs', bundles: ['vchart'] },
{ id: 'resize-row-height', purpose: '列表选择后拖动行边界调整行高', file: './interaction/resize-row-height.mjs', bundles: ['vtable'] },
{ id: 'resize-column-non-realtime', purpose: 'resize.realtime=false 时列宽仅在鼠标松开后应用', file: './interaction/resize-column-non-realtime.mjs', bundles: ['vtable'] },
{ id: 'resize-column-realtime-default', purpose: 'resize.realtime 默认 true 时列宽实时随拖拽变化', file: './interaction/resize-column-realtime-default.mjs', bundles: ['vtable'] },
{ id: 'pivot-resize-indicator-row', purpose: '指标行上的透视表拖动行高', file: './pivot/resize-indicator-row.mjs', bundles: ['vtable'] },
{ id: 'pivot-corner-sort-one-dimension', purpose: '单级角头维度初始降序状态与排序图标', file: './pivot/corner-sort-one-dimension.mjs', bundles: ['vtable'] },
{ id: 'pivot-corner-sort-two-dimensions', purpose: '两级角头维度初始降序状态与排序图标', file: './pivot/corner-sort-two-dimensions.mjs', bundles: ['vtable'] },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* 验证目的:resize.realtime=false 时,列宽调整仅在鼠标松开后应用一次。
* 拖拽过程中列宽保持不变,松开鼠标时列宽等于累计偏移量。
*/
export default {
mount(container) {
return new window.VTable.ListTable(container, {
columns: [
{ field: 'name', title: 'Name', width: 150 },
{ field: 'value', title: 'Value', width: 150 }
],
records: Array.from({ length: 5 }, (_, index) => ({
name: `Item ${index + 1}`,
value: index + 1
})),
defaultRowHeight: 32,
resize: { columnResizeMode: 'all', realtime: false }
});
},
async exercise(page) {
const point = await page.evaluate(() => {
const table = window.__visualTable;
const b = table.getCellRect(0, 0).bounds;
const host = document.getElementById('table').getBoundingClientRect();
window.__colWidthBefore = table.getColWidth(0);
return {
cell: { x: host.x + (b.x1 + b.x2) / 2, y: host.y + (b.y1 + b.y2) / 2 },
edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 }
};
});
await page.mouse.move(point.edge.x, point.edge.y);
await page.mouse.down();
// 拖拽过程中每步都记录列宽,确认拖拽时列宽未变化
await page.mouse.move(point.edge.x + 40, point.edge.y, { steps: 10 });
const widthDuringDrag = await page.evaluate(() => window.__visualTable.getColWidth(0));
await page.mouse.move(point.edge.x + 80, point.edge.y, { steps: 10 });
const widthDuringDrag2 = await page.evaluate(() => window.__visualTable.getColWidth(0));
await page.mouse.up();
await page.evaluate(
({ w1, w2 }) => {
window.__widthDuringDrag = w1;
window.__widthDuringDrag2 = w2;
},
{ w1: widthDuringDrag, w2: widthDuringDrag2 }
);
},
async verify(page) {
await page.evaluate(() => {
const table = window.__visualTable;
const before = window.__colWidthBefore;
const during1 = window.__widthDuringDrag;
const during2 = window.__widthDuringDrag2;
const after = table.getColWidth(0);
// 拖拽中列宽不应变化
if (during1 !== before || during2 !== before) {
throw new Error(
`非实时响应模式下拖拽过程中列宽发生了变化: before=${before}, during1=${during1}, during2=${during2}, after=${after}`
);
}
// 松开鼠标后列宽应增加(累计偏移量 80px 应用)
if (after <= before + 40) {
throw new Error(`鼠标松开后列宽未按累计偏移量应用: before=${before}, after=${after}`);
}
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* 验证目的:resize.realtime=true(默认)时保持历史行为,拖拽过程中列宽实时变化。
* 作为 resize-column-non-realtime 的对照用例,防止改动回归默认行为。
*/
export default {
mount(container) {
return new window.VTable.ListTable(container, {
columns: [
{ field: 'name', title: 'Name', width: 150 },
{ field: 'value', title: 'Value', width: 150 }
],
records: Array.from({ length: 5 }, (_, index) => ({
name: `Item ${index + 1}`,
value: index + 1
})),
defaultRowHeight: 32,
resize: { columnResizeMode: 'all' }
});
},
async exercise(page) {
const point = await page.evaluate(() => {
const table = window.__visualTable;
const b = table.getCellRect(0, 0).bounds;
const host = document.getElementById('table').getBoundingClientRect();
window.__colWidthBefore = table.getColWidth(0);
return {
edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 }
};
});
await page.mouse.move(point.edge.x, point.edge.y);
await page.mouse.down();
await page.mouse.move(point.edge.x + 40, point.edge.y, { steps: 10 });
const widthDuringDrag = await page.evaluate(() => window.__visualTable.getColWidth(0));
await page.mouse.move(point.edge.x + 80, point.edge.y, { steps: 10 });
const widthDuringDrag2 = await page.evaluate(() => window.__visualTable.getColWidth(0));
await page.mouse.up();
await page.evaluate(
({ w1, w2 }) => {
window.__widthDuringDrag = w1;
window.__widthDuringDrag2 = w2;
},
{ w1: widthDuringDrag, w2: widthDuringDrag2 }
);
},
async verify(page) {
await page.evaluate(() => {
const before = window.__colWidthBefore;
const during1 = window.__widthDuringDrag;
const during2 = window.__widthDuringDrag2;
const after = window.__visualTable.getColWidth(0);
// 实时模式:拖拽中列宽应递增
if (during1 <= before || during2 <= during1) {
throw new Error(`实时模式下列宽未随拖拽变化: before=${before}, during1=${during1}, during2=${during2}`);
}
if (after <= before + 40) {
throw new Error(`鼠标松开后列宽未达到预期: before=${before}, after=${after}`);
}
});
}
};
73 changes: 73 additions & 0 deletions packages/vtable/examples/interactive/resize-realtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as VTable from '../../src';

const ListTable = VTable.ListTable;
const CONTAINER_ID = 'vTable';

export function createTable() {
// 生成较大数据量,便于对比实时与非实时响应下的拖拽流畅度
const records = Array.from({ length: 10000 }, (_, index) => ({
id: index + 1,
name: `记录 ${index + 1}`,
dept: ['研发', '产品', '运营', '销售'][index % 4],
city: ['杭州', '北京', '上海', '成都', '广州'][index % 5],
score: Math.round(Math.random() * 100),
salary: Math.round(4000 + Math.random() * 20000)
}));

const option: any = {
columns: [
{ field: 'id', title: 'ID', width: 80 },
{ field: 'name', title: '姓名', width: 160 },
{ field: 'dept', title: '部门', width: 120 },
{ field: 'city', title: '城市', width: 120 },
{ field: 'score', title: '评分', width: 120 },
{ field: 'salary', title: '薪资', width: 140 }
],
records,
defaultRowHeight: 32,
resize: {
columnResizeMode: 'all',
rowResizeMode: 'all',
// 关键:设为 false 时,拖拽过程只移动指示线,鼠标松开后才应用列宽/行高变化,
// 可显著缓解大数据量下的拖拽卡顿。默认为 true,保持原有行为。
realtime: false
}
};

const instance = new ListTable(document.getElementById(CONTAINER_ID)!, option);
(window as any).tableInstance = instance;

// 提供简单的运行时切换按钮,便于对比两种模式的手感
const bar = document.createElement('div');
bar.style.cssText = [
'position:fixed',
'top:10px',
'right:10px',
'z-index:9999',
'background:#fff',
'padding:8px 12px',
'border:1px solid #ddd',
'border-radius:4px',
'box-shadow:0 1px 4px rgba(0,0,0,.1)',
'font-family:sans-serif',
'font-size:12px'
].join(';');
bar.innerHTML = `
<div style="margin-bottom:6px;">当前 resize.realtime = <b id="__realtime_state">false</b></div>
<button id="__toggle_realtime">切换为 实时响应(true)</button>
`;
document.body.appendChild(bar);
let current = false;
bar.querySelector('#__toggle_realtime')!.addEventListener('click', () => {
current = !current;
// 通过 updateOption 重新应用配置
(instance as any).updateOption({
...option,
resize: { ...option.resize, realtime: current }
});
(bar.querySelector('#__realtime_state') as HTMLElement).textContent = String(current);
(bar.querySelector('#__toggle_realtime') as HTMLElement).textContent = `切换为 ${
current ? '非实时(false)' : '实时(true)'
}`;
});
}
4 changes: 4 additions & 0 deletions packages/vtable/examples/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,10 @@ export const menus = [
path: 'interactive',
name: 'row-resize'
},
{
path: 'interactive',
name: 'resize-realtime'
},
{
path: 'interactive',
name: 'resize-setRecords'
Expand Down
2 changes: 2 additions & 0 deletions packages/vtable/src/core/BaseTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
internalProps.columnResizeMode = resize?.columnResizeMode ?? columnResizeMode;
internalProps.canResizeColumn = resize?.canResizeColumn;
internalProps.rowResizeMode = resize?.rowResizeMode ?? rowResizeMode;
internalProps.resizeRealtime = resize?.realtime !== false;
internalProps.dragHeaderMode = dragOrder?.dragHeaderMode ?? dragHeaderMode ?? 'none';
internalProps.renderChartAsync = renderChartAsync;
setBatchRenderChartCount(renderChartAsyncBatchCount);
Expand Down Expand Up @@ -2911,6 +2912,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
internalProps.columnResizeMode = resize?.columnResizeMode ?? columnResizeMode;
internalProps.canResizeColumn = resize?.canResizeColumn;
internalProps.rowResizeMode = resize?.rowResizeMode ?? rowResizeMode;
internalProps.resizeRealtime = resize?.realtime !== false;
internalProps.dragHeaderMode = dragOrder?.dragHeaderMode ?? dragHeaderMode ?? 'none';
internalProps.renderChartAsync = renderChartAsync;
setBatchRenderChartCount(renderChartAsyncBatchCount);
Expand Down
19 changes: 13 additions & 6 deletions packages/vtable/src/scenegraph/component/table-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,12 @@ export class TableComponent {
* @param {number} y 标签显示的y坐标
* @return {*}
*/
updateResizeCol(col: number, y: number, isRightFrozen?: boolean) {
updateResizeCol(col: number, y: number, isRightFrozen?: boolean, previewDeltaX?: number) {
// 基准线
const colX = getColX(col, this.table, isRightFrozen);
const colBaseX = getColX(col, this.table, isRightFrozen);
const delta = previewDeltaX ?? 0;
// 非实时响应模式下指示线基于当前列右边界+累计偏移量渲染
const colX = isRightFrozen ? colBaseX - delta : colBaseX + delta;
// this.columnResizeLine.setAttribute('x', x);
this.columnResizeLine.setAttributes({
x: colX,
Expand All @@ -665,7 +668,8 @@ export class TableComponent {
x: colX,
y
});
(this.columnResizeLabel.lastChild as Text).setAttribute('text', `${Math.floor(this.table.getColWidth(col))}px`);
const previewWidth = Math.max(0, this.table.getColWidth(col) + delta);
(this.columnResizeLabel.lastChild as Text).setAttribute('text', `${Math.floor(previewWidth)}px`);
}
}

Expand Down Expand Up @@ -725,9 +729,11 @@ export class TableComponent {
* @param {number} y 标签显示的y坐标
* @return {*}
*/
updateResizeRow(row: number, x: number, isBottomFrozen?: boolean) {
updateResizeRow(row: number, x: number, isBottomFrozen?: boolean, previewDeltaY?: number) {
// 基准线
const rowY = getRowY(row, this.table, isBottomFrozen);
const rowBaseY = getRowY(row, this.table, isBottomFrozen);
const delta = previewDeltaY ?? 0;
const rowY = isBottomFrozen ? rowBaseY - delta : rowBaseY + delta;
// this.columnResizeLine.setAttribute('x', x);
this.rowResizeLine.setAttributes({
y: rowY,
Expand All @@ -750,7 +756,8 @@ export class TableComponent {
y: rowY,
x
});
(this.rowResizeLabel.lastChild as Text).setAttribute('text', `${Math.floor(this.table.getRowHeight(row))}px`);
const previewHeight = Math.max(0, this.table.getRowHeight(row) + delta);
(this.rowResizeLabel.lastChild as Text).setAttribute('text', `${Math.floor(previewHeight)}px`);
}
}

Expand Down
50 changes: 32 additions & 18 deletions packages/vtable/src/state/resize/update-resize-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,40 @@ export function updateResizeColumn(xInTable: number, yInTable: number, state: St
return;
}

// 非实时响应模式:仅累积偏移量并移动指示线,实际列宽在拖拽结束时统一应用
if (state.table.internalProps.resizeRealtime === false) {
state.columnResize.pendingDeltaX = (state.columnResize.pendingDeltaX ?? 0) + detaX;
state.columnResize.x = xInTable;
state.table.scenegraph.component.updateResizeCol(
state.columnResize.col,
yInTable,
state.columnResize.isRightFrozen,
state.columnResize.pendingDeltaX
);
state.table.scenegraph.updateNextFrame();
return;
}

applyColumnResizeDelta(detaX, xInTable, yInTable, state);
}

/** 非实时响应模式下,在拖拽结束时提交累计的宽度变化 */
export function commitPendingColumnResize(state: StateManager) {
const pending = state.columnResize.pendingDeltaX ?? 0;
state.columnResize.pendingDeltaX = 0;
if (Math.abs(pending) < 1) {
return;
}
// 用当前列坐标+累计偏移量作为最终位置,复用实时模式的处理逻辑
const isRightFrozen = state.columnResize.isRightFrozen;
const currentX = state.columnResize.x;
const targetX = isRightFrozen ? currentX - pending : currentX + pending;
applyColumnResizeDelta(pending, targetX, currentX, state);
}

function applyColumnResizeDelta(detaX: number, xInTable: number, yInTable: number, state: StateManager) {
// 检查minWidth/maxWidth
// getColWidth会进行Math.round,所以先从colWidthsMap获取:
// 如果是数值,直接使用;如果不是,则通过getColWidth获取像素值
// let widthCache = (state.table as any).colWidthsMap.get(state.columnResize.col);
// if (typeof widthCache === 'number') {
// widthCache = widthCache;
// } else {
const widthCache = state.table.getColWidth(state.columnResize.col);
// }
let width = widthCache;
width += detaX;
const minWidth = state.table.getMinColWidth(state.columnResize.col);
Expand Down Expand Up @@ -80,17 +105,6 @@ export function updateResizeColumn(xInTable: number, yInTable: number, state: St
updateResizeColForColumn(detaX, state);
}

// if (state.table.widthMode === 'adaptive' && state.columnResize.col < state.table.colCount - 1) {
// // in adaptive mode, the right column width can not be negative
// const rightColWidth = state.table.getColWidth(state.columnResize.col + 1);
// if (rightColWidth - detaX < 0) {
// detaX = rightColWidth;
// }
// state.table.scenegraph.updateColWidth(state.columnResize.col, detaX);
// state.table.scenegraph.updateColWidth(state.columnResize.col + 1, -detaX);
// } else {
// state.table.scenegraph.updateColWidth(state.columnResize.col, detaX);
// }
state.columnResize.x = xInTable;

// update resize column component
Expand Down
Loading
Loading