Skip to content

Commit 2fd9a29

Browse files
authored
Merge pull request #5189 from VisActor/codex/fix-issue-5187-max-frozen-width
fix: handle scroll frozen columns width
2 parents 93dcf58 + 9c2c2ea commit 2fd9a29

8 files changed

Lines changed: 328 additions & 84 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "fix: handle scroll frozen columns width",
5+
"type": "patch",
6+
"packageName": "@visactor/vtable"
7+
}
8+
],
9+
"packageName": "@visactor/vtable",
10+
"email": "892739385@qq.com"
11+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// @ts-nocheck
2+
import { ListTable } from '../../src';
3+
import { createDiv } from '../dom';
4+
5+
global.__VERSION__ = 'none';
6+
7+
describe('listTable scroll frozen columns', () => {
8+
test('keeps body next to frozen viewport when horizontal layout tries to shift right', () => {
9+
const containerDom: HTMLElement = createDiv();
10+
containerDom.style.position = 'relative';
11+
containerDom.style.width = '1055px';
12+
containerDom.style.height = '520px';
13+
14+
const columns = [
15+
['orderDate', '订单日期', 91],
16+
['customerName', '客户名称', 76],
17+
['shipDate', '发货日期', 91],
18+
['deliveryDate', '交付日期', 91],
19+
['productDate', '产品日期', 91],
20+
['category', '类别', 76],
21+
['subCategory', '子类别', 76],
22+
['region', '区域', 76],
23+
['customerId', '客户编号', 110],
24+
['birthDate', '出生日期', 100],
25+
['renewDate', '续费日期', 100],
26+
['rate', '折扣', 76],
27+
['ratio', '占比', 76]
28+
].map(([field, title, width]) => ({
29+
field,
30+
title,
31+
width
32+
}));
33+
const records = Array.from({ length: 20 }, (_, index) => ({
34+
orderDate: `2016-01-${index + 1}`,
35+
customerName: `客户${index + 1}`,
36+
shipDate: `2016-02-${index + 1}`,
37+
deliveryDate: `2017-05-${index + 1}`,
38+
productDate: `2016-03-${index + 1}`,
39+
category: '家具',
40+
subCategory: '桌子',
41+
region: '中南',
42+
customerId: `customer-${index + 1}`,
43+
birthDate: '1975-07-10',
44+
renewDate: '2026-02-28',
45+
rate: `${(index % 5) / 10}`,
46+
ratio: `${(index % 4) / 4}`
47+
}));
48+
49+
const listTable = new ListTable(containerDom, {
50+
columns,
51+
records,
52+
frozenColCount: 4,
53+
maxFrozenWidth: 100,
54+
unfreezeAllOnExceedsMaxWidth: false,
55+
scrollFrozenCols: true
56+
});
57+
58+
expect(listTable.getFrozenColsWidth()).toBe(100);
59+
expect(listTable.getFrozenColsContentWidth()).toBeGreaterThan(100);
60+
61+
listTable.scenegraph.updateTableSize();
62+
expect(listTable.scenegraph.cornerHeaderGroup.attribute.width).toBe(100);
63+
expect(listTable.scenegraph.rowHeaderGroup.attribute.width).toBe(100);
64+
65+
listTable.scenegraph.setBodyAndColHeaderX(172);
66+
expect(listTable.scenegraph.bodyGroup.attribute.x).toBe(100);
67+
expect(listTable.scenegraph.colHeaderGroup.attribute.x).toBe(100);
68+
69+
listTable.scenegraph.component.updateScrollBar();
70+
expect(listTable.scenegraph.component.hScrollBar.attribute.visible).toBe(false);
71+
expect(listTable.scenegraph.component.hScrollBar.attribute.width).toBe(0);
72+
expect(listTable.scenegraph.component.frozenHScrollBar.attribute.width).toBe(100);
73+
expect(listTable.scenegraph.component.frozenHScrollBar.attribute.range[1]).toBeLessThan(1);
74+
75+
listTable.scenegraph.component.showHorizontalScrollBar('all');
76+
expect(listTable.scenegraph.component.hScrollBar.attribute.visible).toBe(false);
77+
expect(listTable.scenegraph.component.frozenHScrollBar.attribute.visible).toBe(true);
78+
79+
listTable.setScrollLeft(100);
80+
expect(listTable.scrollLeft).toBe(0);
81+
82+
listTable.release();
83+
});
84+
});
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as VTable from '../../src';
2+
3+
const CONTAINER_ID = 'vTable';
4+
5+
const fields = [
6+
['orderDate', '订单日期', 91],
7+
['customerName', '客户名称', 76],
8+
['shipDate', '发货日期', 91],
9+
['deliveryDate', '交付日期', 91],
10+
['productDate', '产品日期', 91],
11+
['category', '类别', 76],
12+
['subCategory', '子类别', 76],
13+
['region', '区域', 76],
14+
['customerId', '客户编号', 110],
15+
['birthDate', '出生日期', 100],
16+
['renewDate', '续费日期', 100],
17+
['rate', '折扣', 76],
18+
['ratio', '占比', 76]
19+
] as const;
20+
21+
const records = Array.from({ length: 80 }, (_, index) => {
22+
const day = `${(index % 28) + 1}`.padStart(2, '0');
23+
const names = ['邢宁', '俞毅', '麦虢', '牛惠', '陶丽雪', '徐虹'];
24+
25+
return {
26+
orderDate: `2016-01-${day}`,
27+
customerName: names[index % names.length],
28+
shipDate: `2016-02-${day}`,
29+
deliveryDate: `2017-05-${day}`,
30+
productDate: `2016-03-${day}`,
31+
category: ['家具', '技术', '办公用品'][index % 3],
32+
subCategory: ['桌子', '电话', '配件', '收纳具'][index % 4],
33+
region: index % 2 ? '分组1' : '中南',
34+
customerId: `${names[index % names.length]}-${10000 + index}`,
35+
birthDate: '1975-07-10',
36+
renewDate: '2026-02-28',
37+
rate: `${(index % 5) / 10}`,
38+
ratio: `${(index % 4) / 4}`
39+
};
40+
});
41+
42+
export function createTable() {
43+
const container = document.getElementById(CONTAINER_ID)!;
44+
document.getElementById('issue5187Toolbar')?.remove();
45+
46+
container.style.width = '640px';
47+
container.style.height = '520px';
48+
49+
const toolbar = document.createElement('div');
50+
toolbar.id = 'issue5187Toolbar';
51+
toolbar.style.cssText = 'height: 32px; display: flex; gap: 8px; align-items: center; font-size: 12px;';
52+
toolbar.innerHTML = `
53+
<button id="issue5187ScrollFrozen">scroll frozen cols</button>
54+
<button id="issue5187Wide">set container 1600px</button>
55+
<button id="issue5187Narrow">set container 1055px</button>
56+
<button id="issue5187Reproduce">reproduce 1600px -> 1055px</button>
57+
<span id="issue5187State"></span>
58+
`;
59+
container.before(toolbar);
60+
61+
const columns: VTable.ColumnsDefine = fields.map(([field, title, width]) => ({
62+
field,
63+
title,
64+
width,
65+
showSort: false
66+
}));
67+
68+
const tableInstance = new VTable.ListTable({
69+
container,
70+
columns,
71+
records,
72+
widthMode: 'standard',
73+
columnResizeMode: 'all',
74+
defaultRowHeight: 35.2,
75+
heightMode: 'autoHeight',
76+
defaultHeaderColWidth: 'auto',
77+
frozenColCount: 4,
78+
maxFrozenWidth: 100,
79+
unfreezeAllOnExceedsMaxWidth: false,
80+
scrollFrozenCols: true,
81+
theme: VTable.themes.DEFAULT.extends({
82+
scrollStyle: {
83+
visible: 'focus',
84+
width: 7,
85+
hoverOn: true
86+
},
87+
frozenColumnLine: {
88+
shadow: {
89+
width: 3,
90+
startColor: 'rgba(225, 228, 232, 0.6)',
91+
endColor: 'rgba(225, 228, 232, 0.6)'
92+
}
93+
}
94+
})
95+
});
96+
97+
const stateNode = document.getElementById('issue5187State')!;
98+
const updateState = () => {
99+
stateNode.textContent = [
100+
`container: ${container.style.width}`,
101+
`frozen width: ${tableInstance.getFrozenColsWidth()}`,
102+
`content: ${tableInstance.getFrozenColsContentWidth()}`,
103+
`offset: ${tableInstance.getFrozenColsOffset()}`,
104+
`scrollLeft: ${tableInstance.getFrozenColsScrollLeft()}`
105+
].join(' | ');
106+
};
107+
const resizeContainer = (width: number) => {
108+
container.style.width = `${width}px`;
109+
tableInstance.resize();
110+
updateState();
111+
};
112+
113+
document.getElementById('issue5187ScrollFrozen')?.addEventListener('click', () => {
114+
tableInstance.stateManager.setFrozenColsScrollLeft(tableInstance.getFrozenColsOffset());
115+
updateState();
116+
});
117+
document.getElementById('issue5187Wide')?.addEventListener('click', () => {
118+
resizeContainer(1600);
119+
});
120+
document.getElementById('issue5187Narrow')?.addEventListener('click', () => {
121+
resizeContainer(1055);
122+
});
123+
document.getElementById('issue5187Reproduce')?.addEventListener('click', () => {
124+
resizeContainer(1600);
125+
tableInstance.stateManager.setFrozenColsScrollLeft(tableInstance.getFrozenColsOffset());
126+
requestAnimationFrame(() => {
127+
resizeContainer(1055);
128+
});
129+
});
130+
131+
tableInstance.on(VTable.ListTable.EVENT_TYPE.RESIZE_COLUMN_END, updateState);
132+
updateState();
133+
134+
const release = tableInstance.release.bind(tableInstance);
135+
tableInstance.release = () => {
136+
document.getElementById('issue5187Toolbar')?.remove();
137+
release();
138+
};
139+
140+
(window as any).tableInstance = tableInstance;
141+
}

packages/vtable/examples/menu.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export const menus = [
4545
{
4646
path: 'debug',
4747
name: 'scroll-collapse-bottom'
48+
},
49+
{
50+
path: 'debug',
51+
name: 'issue-5187-max-frozen-width'
4852
}
4953
]
5054
},

0 commit comments

Comments
 (0)