@rainbow-oh/yee-c 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Button/demo/icon.js +1 -1
- package/dist/cjs/Input/style/index.less +1 -0
- package/dist/cjs/Table/demo/resizable.d.ts +3 -0
- package/dist/cjs/Table/demo/resizable.js +143 -0
- package/dist/cjs/Table/hooks/useColumnResize.d.ts +30 -0
- package/dist/cjs/Table/hooks/useColumnResize.js +217 -0
- package/dist/cjs/Table/hooks/useColumns.d.ts +3 -1
- package/dist/cjs/Table/hooks/useColumns.js +14 -3
- package/dist/cjs/Table/interface.d.ts +49 -0
- package/dist/cjs/Table/style/index.less +42 -0
- package/dist/cjs/Table/table.js +62 -9
- package/dist/cjs/Table/theader/cell.js +46 -4
- package/dist/cjs/Table/util/index.d.ts +19 -1
- package/dist/cjs/Table/util/index.js +22 -0
- package/dist/cjs/Tabs/demo/overflow-center.d.ts +3 -0
- package/dist/cjs/Tabs/demo/overflow-center.js +54 -0
- package/dist/cjs/Tabs/hooks/useNavListMove.d.ts +2 -1
- package/dist/cjs/Tabs/hooks/useNavListMove.js +51 -16
- package/dist/cjs/Tabs/hooks/useTabScroll.d.ts +1 -0
- package/dist/cjs/Tabs/hooks/useTabScroll.js +5 -1
- package/dist/cjs/Tabs/interface.d.ts +10 -1
- package/dist/cjs/Tabs/style/index.less +5 -0
- package/dist/cjs/Tabs/tab-header/index.d.ts +1 -0
- package/dist/cjs/Tabs/tab-header/index.js +11 -3
- package/dist/cjs/Tabs/tabs.js +4 -1
- package/dist/cjs/Upload/demo/auto-upload.d.ts +3 -0
- package/dist/cjs/Upload/demo/auto-upload.js +65 -0
- package/dist/cjs/Upload/demo/basic.js +5 -10
- package/dist/cjs/Upload/demo/controlled.js +4 -4
- package/dist/cjs/Upload/demo/pictureWall.js +14 -13
- package/dist/cjs/Upload/dragger.d.ts +2 -1
- package/dist/cjs/Upload/dragger.js +32 -21
- package/dist/cjs/Upload/index.d.ts +1 -1
- package/dist/cjs/Upload/interface.d.ts +67 -120
- package/dist/cjs/Upload/style/index.less +40 -13
- package/dist/cjs/Upload/upload.d.ts +2 -2
- package/dist/cjs/Upload/upload.js +435 -185
- package/dist/cjs/Upload/uploaded-list/index.d.ts +2 -1
- package/dist/cjs/Upload/uploaded-list/index.js +6 -3
- package/dist/cjs/Upload/uploaded-list/uploaded-item.d.ts +6 -3
- package/dist/cjs/Upload/uploaded-list/uploaded-item.js +127 -138
- package/dist/cjs/locale/interface.d.ts +1 -0
- package/dist/cjs/locale/lang/en_US.js +2 -1
- package/dist/cjs/locale/lang/ja_JP.js +2 -1
- package/dist/cjs/locale/lang/zh_CN.js +2 -1
- package/dist/cjs/locale/lang/zh_TW.js +2 -1
- package/dist/cjs/style/size.css +2 -1
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +3 -2
- package/dist/cjs/utils/ax/core.d.ts +3 -1
- package/dist/cjs/utils/ax/core.js +24 -37
- package/dist/cjs/utils/ax/index.d.ts +3 -1
- package/dist/cjs/utils/folder-scanner.d.ts +1 -1
- package/dist/cjs/utils/folder-scanner.js +43 -34
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Button/demo/icon.js +7 -4
- package/dist/esm/Input/style/index.less +1 -0
- package/dist/esm/Table/demo/resizable.d.ts +3 -0
- package/dist/esm/Table/demo/resizable.js +110 -0
- package/dist/esm/Table/hooks/useColumnResize.d.ts +30 -0
- package/dist/esm/Table/hooks/useColumnResize.js +206 -0
- package/dist/esm/Table/hooks/useColumns.d.ts +3 -1
- package/dist/esm/Table/hooks/useColumns.js +9 -4
- package/dist/esm/Table/interface.d.ts +49 -0
- package/dist/esm/Table/style/index.less +42 -0
- package/dist/esm/Table/table.js +83 -13
- package/dist/esm/Table/theader/cell.js +38 -3
- package/dist/esm/Table/util/index.d.ts +19 -1
- package/dist/esm/Table/util/index.js +37 -0
- package/dist/esm/Tabs/demo/overflow-center.d.ts +3 -0
- package/dist/esm/Tabs/demo/overflow-center.js +61 -0
- package/dist/esm/Tabs/hooks/useNavListMove.d.ts +2 -1
- package/dist/esm/Tabs/hooks/useNavListMove.js +62 -25
- package/dist/esm/Tabs/hooks/useTabScroll.d.ts +1 -0
- package/dist/esm/Tabs/hooks/useTabScroll.js +9 -0
- package/dist/esm/Tabs/interface.d.ts +10 -1
- package/dist/esm/Tabs/style/index.less +5 -0
- package/dist/esm/Tabs/tab-header/index.d.ts +1 -0
- package/dist/esm/Tabs/tab-header/index.js +15 -3
- package/dist/esm/Tabs/tabs.js +4 -2
- package/dist/esm/Upload/demo/auto-upload.d.ts +3 -0
- package/dist/esm/Upload/demo/auto-upload.js +42 -0
- package/dist/esm/Upload/demo/basic.js +5 -11
- package/dist/esm/Upload/demo/controlled.js +8 -6
- package/dist/esm/Upload/demo/pictureWall.js +17 -14
- package/dist/esm/Upload/dragger.d.ts +2 -1
- package/dist/esm/Upload/dragger.js +10 -5
- package/dist/esm/Upload/index.d.ts +1 -1
- package/dist/esm/Upload/interface.d.ts +67 -120
- package/dist/esm/Upload/style/index.less +40 -13
- package/dist/esm/Upload/upload.d.ts +2 -2
- package/dist/esm/Upload/upload.js +573 -271
- package/dist/esm/Upload/uploaded-list/index.d.ts +2 -1
- package/dist/esm/Upload/uploaded-list/index.js +9 -4
- package/dist/esm/Upload/uploaded-list/uploaded-item.d.ts +6 -3
- package/dist/esm/Upload/uploaded-list/uploaded-item.js +145 -150
- package/dist/esm/locale/interface.d.ts +1 -0
- package/dist/esm/locale/lang/en_US.js +2 -1
- package/dist/esm/locale/lang/ja_JP.js +2 -1
- package/dist/esm/locale/lang/zh_CN.js +2 -1
- package/dist/esm/locale/lang/zh_TW.js +2 -1
- package/dist/esm/style/size.css +2 -1
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +3 -2
- package/dist/esm/utils/ax/core.d.ts +3 -1
- package/dist/esm/utils/ax/core.js +32 -40
- package/dist/esm/utils/ax/index.d.ts +3 -1
- package/dist/esm/utils/folder-scanner.d.ts +1 -1
- package/dist/esm/utils/folder-scanner.js +76 -75
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +2 -2
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Button/demo/icon.tsx +5 -3
- package/src/Input/style/index.less +1 -0
- package/src/Table/demo/resizable.tsx +106 -0
- package/src/Table/hooks/useColumnResize.ts +224 -0
- package/src/Table/hooks/useColumns.ts +18 -3
- package/src/Table/index.en-US.md +7 -0
- package/src/Table/index.zh-CN.md +7 -0
- package/src/Table/interface.ts +52 -0
- package/src/Table/style/index.less +42 -0
- package/src/Table/table-resize.test.tsx +234 -0
- package/src/Table/table.tsx +90 -13
- package/src/Table/theader/cell.tsx +52 -2
- package/src/Table/util/index.ts +44 -1
- package/src/Tabs/demo/overflow-center.tsx +35 -0
- package/src/Tabs/hooks/useNavListMove.ts +60 -18
- package/src/Tabs/hooks/useTabScroll.ts +7 -1
- package/src/Tabs/index.en-US.md +33 -31
- package/src/Tabs/index.zh-CN.md +33 -31
- package/src/Tabs/interface.ts +10 -1
- package/src/Tabs/style/index.less +5 -0
- package/src/Tabs/tab-header/index.tsx +14 -3
- package/src/Tabs/tabs.tsx +2 -0
- package/src/Upload/demo/auto-upload.tsx +35 -0
- package/src/Upload/demo/basic.tsx +7 -11
- package/src/Upload/demo/controlled.tsx +12 -8
- package/src/Upload/demo/directory.tsx +3 -4
- package/src/Upload/demo/drag.tsx +5 -12
- package/src/Upload/demo/multiple.tsx +3 -4
- package/src/Upload/demo/pictureWall.tsx +22 -21
- package/src/Upload/dragger.tsx +47 -34
- package/src/Upload/index.en-US.md +63 -43
- package/src/Upload/index.ts +8 -1
- package/src/Upload/index.zh-CN.md +62 -42
- package/src/Upload/interface.ts +68 -120
- package/src/Upload/style/index.less +40 -13
- package/src/Upload/upload.tsx +546 -215
- package/src/Upload/uploaded-list/index.tsx +9 -5
- package/src/Upload/uploaded-list/uploaded-item.tsx +199 -228
- package/src/locale/interface.ts +1 -0
- package/src/locale/lang/en_US.ts +1 -0
- package/src/locale/lang/ja_JP.ts +1 -0
- package/src/locale/lang/zh_CN.ts +1 -0
- package/src/locale/lang/zh_TW.ts +1 -0
- package/src/style/size.css +2 -1
- package/src/utils/ax/core.ts +28 -41
- package/src/utils/folder-scanner.ts +51 -37
- package/src/version.ts +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, cleanup, render } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import Table from './table';
|
|
6
|
+
|
|
7
|
+
import type { ColumnProps } from './interface';
|
|
8
|
+
|
|
9
|
+
const columns: ColumnProps[] = [
|
|
10
|
+
{ title: 'Name', dataIndex: 'name', key: 'name', width: 100, minWidth: 60 },
|
|
11
|
+
{ title: 'Age', dataIndex: 'age', key: 'age', width: 80, sorter: true },
|
|
12
|
+
{ title: 'Act', key: 'act', width: 120, resizable: false },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const dataSource = [{ key: '1', name: 'a', age: 1 }];
|
|
16
|
+
|
|
17
|
+
/** jsdom has no layout, so feed the hook the widths it would have measured. */
|
|
18
|
+
const stubSyncRaf = () =>
|
|
19
|
+
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
|
|
20
|
+
cb(0);
|
|
21
|
+
return 1;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const stubColWidths = (widths: number[]) => {
|
|
25
|
+
let index = 0;
|
|
26
|
+
const spy = vi
|
|
27
|
+
.spyOn(HTMLTableColElement.prototype, 'getBoundingClientRect')
|
|
28
|
+
.mockImplementation(function () {
|
|
29
|
+
const width = widths[index % widths.length];
|
|
30
|
+
index += 1;
|
|
31
|
+
return { width } as DOMRect;
|
|
32
|
+
});
|
|
33
|
+
return spy;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const pointer = (type: string, clientX: number) => {
|
|
37
|
+
const event = new MouseEvent(type, { bubbles: true, clientX, button: 0 });
|
|
38
|
+
Object.defineProperty(event, 'clientX', { value: clientX });
|
|
39
|
+
return event;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
cleanup();
|
|
44
|
+
vi.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Table column resize', () => {
|
|
48
|
+
it('renders a handle only on columns that opted in', () => {
|
|
49
|
+
const { container } = render(
|
|
50
|
+
<Table resizable columns={columns} dataSource={dataSource} />,
|
|
51
|
+
);
|
|
52
|
+
const handles = container.querySelectorAll('.yee-table-resize-handle');
|
|
53
|
+
expect(handles.length).toBe(2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders no handle when resizing is off', () => {
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<Table columns={columns} dataSource={dataSource} />,
|
|
59
|
+
);
|
|
60
|
+
expect(container.querySelectorAll('.yee-table-resize-handle').length).toBe(
|
|
61
|
+
0,
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('never puts a handle on a group header cell', () => {
|
|
66
|
+
const { container } = render(
|
|
67
|
+
<Table
|
|
68
|
+
resizable
|
|
69
|
+
columns={[
|
|
70
|
+
{
|
|
71
|
+
title: 'Group',
|
|
72
|
+
key: 'group',
|
|
73
|
+
children: [
|
|
74
|
+
{ title: 'A', dataIndex: 'name', key: 'name', width: 100 },
|
|
75
|
+
{ title: 'B', dataIndex: 'age', key: 'age', width: 100 },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
]}
|
|
79
|
+
dataSource={dataSource}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
const rows = container.querySelectorAll('thead tr');
|
|
83
|
+
expect(rows.length).toBe(2);
|
|
84
|
+
expect(rows[0].querySelectorAll('.yee-table-resize-handle').length).toBe(0);
|
|
85
|
+
expect(rows[1].querySelectorAll('.yee-table-resize-handle').length).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('widens the dragged column and freezes the rest at their measured width', () => {
|
|
89
|
+
stubSyncRaf();
|
|
90
|
+
stubColWidths([100, 80, 120]);
|
|
91
|
+
const onColumnResizeEnd = vi.fn();
|
|
92
|
+
const { container } = render(
|
|
93
|
+
<Table
|
|
94
|
+
resizable
|
|
95
|
+
columns={columns}
|
|
96
|
+
dataSource={dataSource}
|
|
97
|
+
onColumnResizeEnd={onColumnResizeEnd}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const handle = container.querySelector(
|
|
102
|
+
'.yee-table-resize-handle',
|
|
103
|
+
) as HTMLElement;
|
|
104
|
+
|
|
105
|
+
act(() => {
|
|
106
|
+
handle.dispatchEvent(pointer('pointerdown', 200));
|
|
107
|
+
});
|
|
108
|
+
act(() => {
|
|
109
|
+
window.dispatchEvent(pointer('pointermove', 260));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const cols = container.querySelectorAll('colgroup col');
|
|
113
|
+
expect((cols[0] as HTMLElement).style.width).toBe('160px');
|
|
114
|
+
expect((cols[1] as HTMLElement).style.width).toBe('80px');
|
|
115
|
+
expect((cols[2] as HTMLElement).style.width).toBe('120px');
|
|
116
|
+
|
|
117
|
+
const table = container.querySelector('table') as HTMLElement;
|
|
118
|
+
expect(table.style.tableLayout).toBe('fixed');
|
|
119
|
+
expect(table.style.width).toBe('360px');
|
|
120
|
+
|
|
121
|
+
act(() => {
|
|
122
|
+
window.dispatchEvent(pointer('pointerup', 260));
|
|
123
|
+
});
|
|
124
|
+
expect(onColumnResizeEnd).toHaveBeenCalledWith(
|
|
125
|
+
160,
|
|
126
|
+
expect.objectContaining({ key: 'name' }),
|
|
127
|
+
);
|
|
128
|
+
expect(document.body.style.cursor).toBe('');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('shifts the sticky offset of following fixed columns', () => {
|
|
132
|
+
stubSyncRaf();
|
|
133
|
+
stubColWidths([100, 80, 120]);
|
|
134
|
+
const { container } = render(
|
|
135
|
+
<Table
|
|
136
|
+
resizable
|
|
137
|
+
columns={[
|
|
138
|
+
{
|
|
139
|
+
title: 'Name',
|
|
140
|
+
dataIndex: 'name',
|
|
141
|
+
key: 'name',
|
|
142
|
+
width: 100,
|
|
143
|
+
fixed: 'left',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title: 'Age',
|
|
147
|
+
dataIndex: 'age',
|
|
148
|
+
key: 'age',
|
|
149
|
+
width: 80,
|
|
150
|
+
fixed: 'left',
|
|
151
|
+
},
|
|
152
|
+
{ title: 'Act', key: 'act', width: 120 },
|
|
153
|
+
]}
|
|
154
|
+
dataSource={dataSource}
|
|
155
|
+
/>,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const secondFixedTh = container.querySelectorAll(
|
|
159
|
+
'thead th',
|
|
160
|
+
)[1] as HTMLElement;
|
|
161
|
+
expect(secondFixedTh.style.left).toBe('100px');
|
|
162
|
+
|
|
163
|
+
const handle = container.querySelector(
|
|
164
|
+
'.yee-table-resize-handle',
|
|
165
|
+
) as HTMLElement;
|
|
166
|
+
act(() => {
|
|
167
|
+
handle.dispatchEvent(pointer('pointerdown', 200));
|
|
168
|
+
});
|
|
169
|
+
act(() => {
|
|
170
|
+
window.dispatchEvent(pointer('pointermove', 250));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(
|
|
174
|
+
(container.querySelectorAll('thead th')[1] as HTMLElement).style.left,
|
|
175
|
+
).toBe('150px');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('drops the snapshot when the column set changes', () => {
|
|
179
|
+
stubSyncRaf();
|
|
180
|
+
stubColWidths([100, 80, 120]);
|
|
181
|
+
const { container, rerender } = render(
|
|
182
|
+
<Table resizable columns={columns} dataSource={dataSource} />,
|
|
183
|
+
);
|
|
184
|
+
const handle = container.querySelector(
|
|
185
|
+
'.yee-table-resize-handle',
|
|
186
|
+
) as HTMLElement;
|
|
187
|
+
act(() => {
|
|
188
|
+
handle.dispatchEvent(pointer('pointerdown', 200));
|
|
189
|
+
});
|
|
190
|
+
act(() => {
|
|
191
|
+
window.dispatchEvent(pointer('pointermove', 260));
|
|
192
|
+
});
|
|
193
|
+
act(() => {
|
|
194
|
+
window.dispatchEvent(pointer('pointerup', 260));
|
|
195
|
+
});
|
|
196
|
+
expect((container.querySelector('table') as HTMLElement).style.width).toBe(
|
|
197
|
+
'360px',
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// A brand new column has no snapshotted width, so the px controlled layout
|
|
201
|
+
// would have a hole in it; the table must fall back to natural sizing.
|
|
202
|
+
rerender(
|
|
203
|
+
<Table
|
|
204
|
+
resizable
|
|
205
|
+
columns={[...columns, { title: 'New', dataIndex: 'x', key: 'x' }]}
|
|
206
|
+
dataSource={dataSource}
|
|
207
|
+
/>,
|
|
208
|
+
);
|
|
209
|
+
const table = container.querySelector('table') as HTMLElement;
|
|
210
|
+
expect(table.style.width).toBe('');
|
|
211
|
+
expect(table.style.tableLayout).toBe('');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('clamps to minWidth', () => {
|
|
215
|
+
stubSyncRaf();
|
|
216
|
+
stubColWidths([100, 80, 120]);
|
|
217
|
+
const { container } = render(
|
|
218
|
+
<Table resizable columns={columns} dataSource={dataSource} />,
|
|
219
|
+
);
|
|
220
|
+
const handle = container.querySelector(
|
|
221
|
+
'.yee-table-resize-handle',
|
|
222
|
+
) as HTMLElement;
|
|
223
|
+
|
|
224
|
+
act(() => {
|
|
225
|
+
handle.dispatchEvent(pointer('pointerdown', 200));
|
|
226
|
+
});
|
|
227
|
+
act(() => {
|
|
228
|
+
window.dispatchEvent(pointer('pointermove', 0));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const cols = container.querySelectorAll('colgroup col');
|
|
232
|
+
expect((cols[0] as HTMLElement).style.width).toBe('60px');
|
|
233
|
+
});
|
|
234
|
+
});
|
package/src/Table/table.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import Spin from '../Spin';
|
|
|
4
4
|
import ColGroup from './col-group';
|
|
5
5
|
import Footer from './footer';
|
|
6
6
|
import Header from './header';
|
|
7
|
+
import useColumnResize from './hooks/useColumnResize';
|
|
7
8
|
import useColumns from './hooks/useColumns';
|
|
8
9
|
import useExpand from './hooks/useExpand';
|
|
9
10
|
import useSelection from './hooks/useSelection';
|
|
@@ -12,7 +13,6 @@ import TFooter from './tfooter';
|
|
|
12
13
|
import THeader from './theader';
|
|
13
14
|
|
|
14
15
|
import { GlobalContext } from '../Config-Provider';
|
|
15
|
-
import useLatest from '../hooks/useLatest';
|
|
16
16
|
import mergeContextToProps from '../utils/mergeContextToProps';
|
|
17
17
|
import omit from '../utils/omit';
|
|
18
18
|
import { pickDataAttrs } from '../utils/types';
|
|
@@ -20,7 +20,11 @@ import { pickDataAttrs } from '../utils/types';
|
|
|
20
20
|
import useFilter from './hooks/useFilter';
|
|
21
21
|
import usePagination from './hooks/usePagination';
|
|
22
22
|
import useSorter from './hooks/useSorter';
|
|
23
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
PaginationType,
|
|
25
|
+
TableProps,
|
|
26
|
+
WrapedColumnProps,
|
|
27
|
+
} from './interface';
|
|
24
28
|
import './style/index.less';
|
|
25
29
|
|
|
26
30
|
export const TableCtx = React.createContext({} as any);
|
|
@@ -38,6 +42,9 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
38
42
|
rowKey = 'key',
|
|
39
43
|
bordered,
|
|
40
44
|
tableLayout,
|
|
45
|
+
resizable,
|
|
46
|
+
onColumnResize,
|
|
47
|
+
onColumnResizeEnd,
|
|
41
48
|
loading = false,
|
|
42
49
|
showHeader = true,
|
|
43
50
|
pagination: propPagination = true,
|
|
@@ -64,6 +71,32 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
64
71
|
const dataAttrs = pickDataAttrs(rest as Record<string, unknown>);
|
|
65
72
|
const [measuredColumnWidths, setMeasuredColumnWidths] =
|
|
66
73
|
React.useState<number[]>();
|
|
74
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
75
|
+
const tableRef = useRef<HTMLTableElement>(null);
|
|
76
|
+
|
|
77
|
+
// Filled in right after `useColumns` below. The resize hook needs the latest
|
|
78
|
+
// leaf columns, but `wrapedColumns` is itself derived from the resized widths,
|
|
79
|
+
// so depending on it directly would rebuild the drag handlers mid-drag.
|
|
80
|
+
const wrapedColumnsRef = useRef<WrapedColumnProps[]>([]);
|
|
81
|
+
|
|
82
|
+
const {
|
|
83
|
+
resizedWidths,
|
|
84
|
+
resizingKey,
|
|
85
|
+
onResizeStart,
|
|
86
|
+
onResizeStep,
|
|
87
|
+
resetResizedWidths,
|
|
88
|
+
} = useColumnResize({
|
|
89
|
+
tableRef,
|
|
90
|
+
columnsRef: wrapedColumnsRef,
|
|
91
|
+
onColumnResize,
|
|
92
|
+
onColumnResizeEnd,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Once a snapshot exists, every column carries an explicit px width and the
|
|
96
|
+
// table switches to `table-layout: fixed`, which makes those declared widths
|
|
97
|
+
// authoritative. The measured widths lag one frame behind a drag, so they must
|
|
98
|
+
// stop feeding the fixed-column offsets while a snapshot is in play.
|
|
99
|
+
const hasResizeSnapshot = Object.keys(resizedWidths).length > 0;
|
|
67
100
|
|
|
68
101
|
const getRowKey = React.useCallback(
|
|
69
102
|
(record: Record<string, any>, key = rowKey) => {
|
|
@@ -83,9 +116,42 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
83
116
|
columns: propColumns,
|
|
84
117
|
expandable,
|
|
85
118
|
rowSelection,
|
|
86
|
-
measuredColumnWidths,
|
|
119
|
+
measuredColumnWidths: hasResizeSnapshot ? undefined : measuredColumnWidths,
|
|
120
|
+
resizable,
|
|
121
|
+
resizedWidths,
|
|
87
122
|
});
|
|
88
123
|
|
|
124
|
+
wrapedColumnsRef.current = wrapedColumns;
|
|
125
|
+
|
|
126
|
+
// Total width of the px controlled layout. Deliberately `undefined` unless the
|
|
127
|
+
// snapshot covers every single column: a partial snapshot would leave a hole in
|
|
128
|
+
// the layout, so it is safer to fall back to the browser's own distribution.
|
|
129
|
+
const resizeTotalWidth = useMemo(() => {
|
|
130
|
+
if (!wrapedColumns.length) return undefined;
|
|
131
|
+
let total = 0;
|
|
132
|
+
for (const column of wrapedColumns) {
|
|
133
|
+
const width = column.resizeKey
|
|
134
|
+
? resizedWidths[column.resizeKey]
|
|
135
|
+
: undefined;
|
|
136
|
+
if (!width || !Number.isFinite(width)) return undefined;
|
|
137
|
+
total += width;
|
|
138
|
+
}
|
|
139
|
+
return total;
|
|
140
|
+
}, [wrapedColumns, resizedWidths]);
|
|
141
|
+
|
|
142
|
+
const resizeActive = resizeTotalWidth !== undefined;
|
|
143
|
+
|
|
144
|
+
// Identity of the column set, ignoring widths. When it changes the snapshot is
|
|
145
|
+
// stale (a new column would have no width of its own), so it gets dropped.
|
|
146
|
+
const resizeKeysSignature = useMemo(
|
|
147
|
+
() => wrapedColumns.map((column) => column.resizeKey).join('|'),
|
|
148
|
+
[wrapedColumns],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
resetResizedWidths();
|
|
153
|
+
}, [resizeKeysSignature, resetResizedWidths]);
|
|
154
|
+
|
|
89
155
|
// Filter
|
|
90
156
|
const {
|
|
91
157
|
data: filteredData,
|
|
@@ -157,8 +223,6 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
157
223
|
|
|
158
224
|
// Track filter changes to emit onChange after filtered data is recalculated
|
|
159
225
|
const prevFilterRecordsRef = useRef(filterRecords);
|
|
160
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
161
|
-
const tableRef = useRef<HTMLTableElement>(null);
|
|
162
226
|
const [horizontalScroll, setHorizontalScroll] = React.useState({
|
|
163
227
|
left: false,
|
|
164
228
|
right: false,
|
|
@@ -197,12 +261,10 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
197
261
|
[wrapedColumns],
|
|
198
262
|
);
|
|
199
263
|
|
|
200
|
-
//
|
|
201
|
-
// `wrapedColumns` is derived from `measuredColumnWidths`, so depending
|
|
202
|
-
// directly would recreate the callback (and the ResizeObserver) on every
|
|
264
|
+
// `wrapedColumnsRef` (assigned during render, above) keeps the measure callback
|
|
265
|
+
// stable: `wrapedColumns` is derived from `measuredColumnWidths`, so depending
|
|
266
|
+
// on it directly would recreate the callback (and the ResizeObserver) on every
|
|
203
267
|
// measurement.
|
|
204
|
-
const wrapedColumnsRef = useLatest(wrapedColumns);
|
|
205
|
-
|
|
206
268
|
const updateMeasuredColumnWidths = React.useCallback(() => {
|
|
207
269
|
const columns = wrapedColumnsRef.current;
|
|
208
270
|
if (!columns.some((column) => column.fixed)) return;
|
|
@@ -319,6 +381,9 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
319
381
|
onSort={handleSort}
|
|
320
382
|
onCheckAll={onCheckAll}
|
|
321
383
|
onInternalFilter={onFilterInternal}
|
|
384
|
+
resizingKey={resizingKey}
|
|
385
|
+
onResizeStart={onResizeStart}
|
|
386
|
+
onResizeStep={onResizeStep}
|
|
322
387
|
/>
|
|
323
388
|
);
|
|
324
389
|
};
|
|
@@ -349,8 +414,16 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
349
414
|
const tableProps = {
|
|
350
415
|
style: {
|
|
351
416
|
...styles?.table,
|
|
352
|
-
|
|
353
|
-
|
|
417
|
+
// An explicit total width replaces both `width: 100%` and `scroll.x`:
|
|
418
|
+
// either of those would let the browser stretch the table and redistribute
|
|
419
|
+
// the extra space across the columns, undoing the drag.
|
|
420
|
+
minWidth: resizeActive ? undefined : scroll?.x,
|
|
421
|
+
width: resizeTotalWidth,
|
|
422
|
+
tableLayout: resizeActive
|
|
423
|
+
? 'fixed'
|
|
424
|
+
: pageData?.length
|
|
425
|
+
? tableLayout
|
|
426
|
+
: undefined,
|
|
354
427
|
},
|
|
355
428
|
className: clsx(
|
|
356
429
|
{ [`${prefixCls}-bordered`]: bordered },
|
|
@@ -414,7 +487,11 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
|
|
|
414
487
|
|
|
415
488
|
return (
|
|
416
489
|
<div
|
|
417
|
-
className={clsx(
|
|
490
|
+
className={clsx(
|
|
491
|
+
`${prefixCls}-box`,
|
|
492
|
+
{ [`${prefixCls}-resizing`]: !!resizingKey },
|
|
493
|
+
className,
|
|
494
|
+
)}
|
|
418
495
|
style={style}
|
|
419
496
|
ref={ref}
|
|
420
497
|
{...dataAttrs}
|
|
@@ -29,9 +29,14 @@ const HeadCell: React.FC<HeadCellProps> = (props) => {
|
|
|
29
29
|
width,
|
|
30
30
|
colSpan,
|
|
31
31
|
rowSpan,
|
|
32
|
+
resizable,
|
|
33
|
+
resizeKey,
|
|
34
|
+
resizingKey,
|
|
32
35
|
onHeaderCell,
|
|
33
36
|
onSort,
|
|
34
37
|
onInternalFilter,
|
|
38
|
+
onResizeStart,
|
|
39
|
+
onResizeStep,
|
|
35
40
|
...rest
|
|
36
41
|
} = props;
|
|
37
42
|
|
|
@@ -148,6 +153,41 @@ const HeadCell: React.FC<HeadCellProps> = (props) => {
|
|
|
148
153
|
className,
|
|
149
154
|
);
|
|
150
155
|
|
|
156
|
+
// `resizeKey` is only stamped onto leaf columns, so it doubles as the guard that
|
|
157
|
+
// keeps handles off group header cells (whose width is the sum of their leaves).
|
|
158
|
+
const showResizeHandle = !!(resizable && resizeKey && onResizeStart);
|
|
159
|
+
const isResizing = !!resizeKey && resizingKey === resizeKey;
|
|
160
|
+
|
|
161
|
+
const renderResizeHandle = () => {
|
|
162
|
+
if (!showResizeHandle) return null;
|
|
163
|
+
return (
|
|
164
|
+
<span
|
|
165
|
+
role="separator"
|
|
166
|
+
aria-orientation="vertical"
|
|
167
|
+
aria-label={tableLocale.resizeColumn || 'Drag to resize column'}
|
|
168
|
+
tabIndex={0}
|
|
169
|
+
className={clsx(`${prefixCls}-resize-handle`, {
|
|
170
|
+
[`${prefixCls}-resize-handle-active`]: isResizing,
|
|
171
|
+
})}
|
|
172
|
+
onPointerDown={(event) => onResizeStart?.(event, resizeKey as string)}
|
|
173
|
+
// The whole title area is a sort trigger, and a resize drag ends with a
|
|
174
|
+
// click on top of it; swallow it so dragging never toggles the sorter.
|
|
175
|
+
onClick={(event) => {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
}}
|
|
179
|
+
onKeyDown={(event) => {
|
|
180
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
onResizeStep?.(
|
|
183
|
+
resizeKey as string,
|
|
184
|
+
event.key === 'ArrowLeft' ? -1 : 1,
|
|
185
|
+
);
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
151
191
|
const getThProps = () => {
|
|
152
192
|
const styles = { ...style, width, maxWidth: width, textAlign: align };
|
|
153
193
|
const cellProps = onHeaderCell ? onHeaderCell(props) : {};
|
|
@@ -165,10 +205,20 @@ const HeadCell: React.FC<HeadCellProps> = (props) => {
|
|
|
165
205
|
|
|
166
206
|
const renderCell = () => {
|
|
167
207
|
if (!filter && !sorter) {
|
|
168
|
-
return
|
|
208
|
+
return (
|
|
209
|
+
<th {...thProps}>
|
|
210
|
+
{title}
|
|
211
|
+
{renderResizeHandle()}
|
|
212
|
+
</th>
|
|
213
|
+
);
|
|
169
214
|
}
|
|
170
215
|
|
|
171
|
-
return
|
|
216
|
+
return (
|
|
217
|
+
<th {...thProps}>
|
|
218
|
+
{renderCellWithAction()}
|
|
219
|
+
{renderResizeHandle()}
|
|
220
|
+
</th>
|
|
221
|
+
);
|
|
172
222
|
};
|
|
173
223
|
|
|
174
224
|
const cell = renderCell();
|
package/src/Table/util/index.ts
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
|
-
import { WrapedColumnProps } from '../interface';
|
|
1
|
+
import { ColumnProps, WrapedColumnProps } from '../interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable identity for a leaf column, used to key its resized width.
|
|
5
|
+
*
|
|
6
|
+
* `key`/`dataIndex` survive re-ordering and data changes, so they are preferred.
|
|
7
|
+
* Columns declaring neither fall back to their position, which is the best that
|
|
8
|
+
* can be done without asking the caller for an id.
|
|
9
|
+
*/
|
|
10
|
+
export function getColumnResizeKey(column: ColumnProps, index: number) {
|
|
11
|
+
const id = column.key ?? column.dataIndex;
|
|
12
|
+
return id === undefined || id === null || id === ''
|
|
13
|
+
? `index:${index}`
|
|
14
|
+
: `id:${id}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Stamp every leaf column with its `resizeKey`, resolve whether it is resizable
|
|
19
|
+
* (column level setting wins over the table level one) and override `width` with
|
|
20
|
+
* the dragged value when there is one.
|
|
21
|
+
*
|
|
22
|
+
* This runs before `handleColumns` so that the colgroup, the header cells, the
|
|
23
|
+
* body cells and the sticky offsets of fixed columns all read the same width
|
|
24
|
+
* from a single place.
|
|
25
|
+
*/
|
|
26
|
+
export function applyColumnResize(
|
|
27
|
+
columns: Array<WrapedColumnProps>,
|
|
28
|
+
resizable?: boolean,
|
|
29
|
+
resizedWidths?: Record<string, number>,
|
|
30
|
+
) {
|
|
31
|
+
return columns.map((column, index) => {
|
|
32
|
+
const resizeKey = getColumnResizeKey(column, index);
|
|
33
|
+
const resizedWidth = resizedWidths?.[resizeKey];
|
|
34
|
+
const next: WrapedColumnProps = {
|
|
35
|
+
...column,
|
|
36
|
+
resizeKey,
|
|
37
|
+
resizable: column.resizable ?? resizable ?? false,
|
|
38
|
+
};
|
|
39
|
+
if (Number.isFinite(resizedWidth) && (resizedWidth as number) > 0) {
|
|
40
|
+
next.width = resizedWidth;
|
|
41
|
+
}
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
2
45
|
|
|
3
46
|
const getColumnWidth = (
|
|
4
47
|
column: WrapedColumnProps,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Tabs } from '@rainbow-oh/yee-c';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export default () => {
|
|
5
|
+
const items = Array.from({ length: 24 }, (_, i) => ({
|
|
6
|
+
key: `tab-${i + 1}`,
|
|
7
|
+
label: `Tab ${i + 1}`,
|
|
8
|
+
children: `Content of Tab Pane ${i + 1}`,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const [centerKey, setCenterKey] = useState<string | number>('tab-1');
|
|
12
|
+
const [autoKey, setAutoKey] = useState<string | number>('tab-1');
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
|
16
|
+
<div>
|
|
17
|
+
<p style={{ marginBottom: 8 }}>activeAlign="center"</p>
|
|
18
|
+
<div style={{ maxWidth: 480 }} data-testid="center-wrap">
|
|
19
|
+
<Tabs
|
|
20
|
+
activeAlign="center"
|
|
21
|
+
items={items}
|
|
22
|
+
activeKey={centerKey}
|
|
23
|
+
onChange={setCenterKey}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div>
|
|
28
|
+
<p style={{ marginBottom: 8 }}>default (auto)</p>
|
|
29
|
+
<div style={{ maxWidth: 480 }} data-testid="auto-wrap">
|
|
30
|
+
<Tabs items={items} activeKey={autoKey} onChange={setAutoKey} />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|