@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
package/package.json
CHANGED
package/src/Button/demo/icon.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Button } from '@rainbow-oh/yee-c';
|
|
1
|
+
import { Button, Tooltip } from '@rainbow-oh/yee-c';
|
|
2
2
|
import { Download, Plus, Search } from 'lucide-react';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
5
|
export default () => {
|
|
6
6
|
return (
|
|
7
7
|
<>
|
|
8
|
-
<Button type="primary" icon={<Search size={16} />}>
|
|
8
|
+
<Button type="primary" icon={<Search size={16} />} title="search">
|
|
9
9
|
Search
|
|
10
10
|
</Button>
|
|
11
11
|
<span style={{ margin: '0 10px' }}></span>
|
|
@@ -14,7 +14,9 @@ export default () => {
|
|
|
14
14
|
<br />
|
|
15
15
|
<Button shape="circle" icon={<Plus size={16} />} />
|
|
16
16
|
<span style={{ margin: '0 10px' }}></span>
|
|
17
|
-
<
|
|
17
|
+
<Tooltip title='Search'>
|
|
18
|
+
<Button shape="circle" type="primary" icon={<Search size={16} />} />
|
|
19
|
+
</Tooltip>
|
|
18
20
|
<br />
|
|
19
21
|
<br />
|
|
20
22
|
<Button icon={<Search size={16} />} />
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
border: var(--yee-input-border-width) solid var(--yee-input-border-color);
|
|
14
14
|
border-radius: var(--yee-input-border-radius, 4px);
|
|
15
15
|
font-family: var(--yee-font-family);
|
|
16
|
+
outline: inherit;
|
|
16
17
|
|
|
17
18
|
&:not(@{pls}-disabled):hover {
|
|
18
19
|
border-color: var(--yee-input-hover-border-color);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ColumnProps } from '@rainbow-oh/yee-c';
|
|
2
|
+
import { Table } from '@rainbow-oh/yee-c';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
const columns: ColumnProps[] = [
|
|
6
|
+
{
|
|
7
|
+
title: 'Name',
|
|
8
|
+
dataIndex: 'name',
|
|
9
|
+
key: 'name',
|
|
10
|
+
fixed: 'left',
|
|
11
|
+
width: 140,
|
|
12
|
+
minWidth: 100,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: 'Age',
|
|
16
|
+
dataIndex: 'age',
|
|
17
|
+
key: 'age',
|
|
18
|
+
width: 100,
|
|
19
|
+
sorter: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: 'Address',
|
|
23
|
+
dataIndex: 'address',
|
|
24
|
+
key: 'address',
|
|
25
|
+
width: 240,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'Company',
|
|
29
|
+
dataIndex: 'company',
|
|
30
|
+
key: 'company',
|
|
31
|
+
width: 180,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'Email',
|
|
35
|
+
dataIndex: 'email',
|
|
36
|
+
key: 'email',
|
|
37
|
+
width: 240,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
// Opt this column out of resizing while the rest stay resizable.
|
|
41
|
+
title: 'Action',
|
|
42
|
+
key: 'action',
|
|
43
|
+
fixed: 'right',
|
|
44
|
+
width: 100,
|
|
45
|
+
resizable: false,
|
|
46
|
+
render: (record) => <a onClick={() => console.log(record)}>View</a>,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const dataSource = [
|
|
51
|
+
{
|
|
52
|
+
key: '1',
|
|
53
|
+
name: 'John Brown',
|
|
54
|
+
age: 32,
|
|
55
|
+
address: 'New York No. 1 Lake Park',
|
|
56
|
+
company: 'Acme Inc.',
|
|
57
|
+
email: 'john.brown@example.com',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: '2',
|
|
61
|
+
name: 'Jim Green',
|
|
62
|
+
age: 42,
|
|
63
|
+
address: 'London No. 1 Lake Park',
|
|
64
|
+
company: 'Globex',
|
|
65
|
+
email: 'jim.green@example.com',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: '3',
|
|
69
|
+
name: 'Joe Black',
|
|
70
|
+
age: 28,
|
|
71
|
+
address: 'Sidney No. 1 Lake Park',
|
|
72
|
+
company: 'Initech',
|
|
73
|
+
email: 'joe.black@example.com',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
export default () => {
|
|
78
|
+
const [widths, setWidths] = React.useState<Record<string, number>>({});
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<Table
|
|
83
|
+
resizable
|
|
84
|
+
columns={columns}
|
|
85
|
+
dataSource={dataSource}
|
|
86
|
+
rowKey="key"
|
|
87
|
+
pagination={false}
|
|
88
|
+
scroll={{ x: 1000 }}
|
|
89
|
+
onColumnResizeEnd={(width, column) =>
|
|
90
|
+
setWidths((current) => ({
|
|
91
|
+
...current,
|
|
92
|
+
[String(column.key ?? column.dataIndex)]: width,
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
/>
|
|
96
|
+
<p style={{ marginTop: 12 }}>
|
|
97
|
+
最近调整:
|
|
98
|
+
{Object.keys(widths).length
|
|
99
|
+
? Object.entries(widths)
|
|
100
|
+
.map(([key, width]) => `${key}=${width}px`)
|
|
101
|
+
.join(',')
|
|
102
|
+
: '拖动表头右边缘试试'}
|
|
103
|
+
</p>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import useLatest from '../../hooks/useLatest';
|
|
3
|
+
import type { WrapedColumnProps } from '../interface';
|
|
4
|
+
|
|
5
|
+
/** Floor for a dragged column when it declares no `minWidth` of its own. */
|
|
6
|
+
const DEFAULT_MIN_COLUMN_WIDTH = 40;
|
|
7
|
+
|
|
8
|
+
/** Width delta applied per arrow key press when a handle has focus. */
|
|
9
|
+
const KEYBOARD_STEP = 8;
|
|
10
|
+
|
|
11
|
+
interface UseColumnResizeOptions {
|
|
12
|
+
tableRef: React.RefObject<HTMLTableElement | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Latest leaf columns. Passed as a ref because the columns are themselves
|
|
15
|
+
* derived from `resizedWidths`; depending on the array would make every
|
|
16
|
+
* width change rebuild the drag handlers mid-drag.
|
|
17
|
+
*/
|
|
18
|
+
columnsRef: React.RefObject<WrapedColumnProps[]>;
|
|
19
|
+
onColumnResize?: (width: number, column: WrapedColumnProps) => void;
|
|
20
|
+
onColumnResizeEnd?: (width: number, column: WrapedColumnProps) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const clampWidth = (width: number, column: WrapedColumnProps) => {
|
|
24
|
+
const min = Math.max(column.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, 0);
|
|
25
|
+
const max = column.maxWidth;
|
|
26
|
+
if (width < min) return min;
|
|
27
|
+
if (max !== undefined && width > max) return max;
|
|
28
|
+
return Math.round(width);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pointer (and keyboard) driven column resizing.
|
|
33
|
+
*
|
|
34
|
+
* The table normally lets the browser distribute widths (`width: 100%` with an
|
|
35
|
+
* `auto` layout), which means writing a width onto one column would silently
|
|
36
|
+
* steal space from its neighbours. So the first resize interaction snapshots the
|
|
37
|
+
* rendered width of *every* column; from then on the table is fully px
|
|
38
|
+
* controlled and a drag only moves the column it grabbed.
|
|
39
|
+
*/
|
|
40
|
+
export default function useColumnResize({
|
|
41
|
+
tableRef,
|
|
42
|
+
columnsRef,
|
|
43
|
+
onColumnResize,
|
|
44
|
+
onColumnResizeEnd,
|
|
45
|
+
}: UseColumnResizeOptions) {
|
|
46
|
+
const [resizedWidths, setResizedWidths] = React.useState<
|
|
47
|
+
Record<string, number>
|
|
48
|
+
>({});
|
|
49
|
+
const [resizingKey, setResizingKey] = React.useState<string | null>(null);
|
|
50
|
+
|
|
51
|
+
// Callbacks are consumer supplied and usually re-created every render; reading
|
|
52
|
+
// them from a ref keeps `onResizeStart` stable.
|
|
53
|
+
const onColumnResizeRef = useLatest(onColumnResize);
|
|
54
|
+
const onColumnResizeEndRef = useLatest(onColumnResizeEnd);
|
|
55
|
+
const resizedWidthsRef = useLatest(resizedWidths);
|
|
56
|
+
const frameRef = React.useRef(0);
|
|
57
|
+
const teardownRef = React.useRef<(() => void) | null>(null);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read the rendered width of every column off the `<col>` elements. Returns
|
|
61
|
+
* null when the measurement cannot be trusted, in which case the interaction
|
|
62
|
+
* is abandoned rather than applying a partial snapshot (a partial snapshot
|
|
63
|
+
* would leave holes once the layout switches to `fixed`).
|
|
64
|
+
*/
|
|
65
|
+
const snapshotWidths = React.useCallback(() => {
|
|
66
|
+
const columns = columnsRef.current || [];
|
|
67
|
+
const colElements = tableRef.current?.querySelectorAll(
|
|
68
|
+
':scope > colgroup > col',
|
|
69
|
+
);
|
|
70
|
+
if (!colElements || colElements.length !== columns.length) return null;
|
|
71
|
+
if (!columns.length) return null;
|
|
72
|
+
|
|
73
|
+
const snapshot: Record<string, number> = {};
|
|
74
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
75
|
+
const key = columns[index]?.resizeKey;
|
|
76
|
+
const width = Math.round(
|
|
77
|
+
colElements[index].getBoundingClientRect().width,
|
|
78
|
+
);
|
|
79
|
+
if (!key || !Number.isFinite(width) || width <= 0) return null;
|
|
80
|
+
snapshot[key] = width;
|
|
81
|
+
}
|
|
82
|
+
return snapshot;
|
|
83
|
+
}, [columnsRef, tableRef]);
|
|
84
|
+
|
|
85
|
+
const findColumn = React.useCallback(
|
|
86
|
+
(resizeKey: string) =>
|
|
87
|
+
(columnsRef.current || []).find(
|
|
88
|
+
(column) => column.resizeKey === resizeKey,
|
|
89
|
+
),
|
|
90
|
+
[columnsRef],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const onResizeStart = React.useCallback(
|
|
94
|
+
(event: React.PointerEvent<HTMLElement>, resizeKey: string) => {
|
|
95
|
+
// Ignore secondary buttons, and never let the drag reach the sorter click
|
|
96
|
+
// handler or start a native text selection.
|
|
97
|
+
if (event.button !== 0) return;
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
|
|
101
|
+
const column = findColumn(resizeKey);
|
|
102
|
+
if (!column) return;
|
|
103
|
+
|
|
104
|
+
const snapshot = snapshotWidths();
|
|
105
|
+
if (!snapshot) return;
|
|
106
|
+
|
|
107
|
+
const startX = event.clientX;
|
|
108
|
+
const startWidth = snapshot[resizeKey];
|
|
109
|
+
let latestWidth = startWidth;
|
|
110
|
+
|
|
111
|
+
setResizingKey(resizeKey);
|
|
112
|
+
setResizedWidths(snapshot);
|
|
113
|
+
document.body.style.userSelect = 'none';
|
|
114
|
+
document.body.style.cursor = 'col-resize';
|
|
115
|
+
|
|
116
|
+
const applyWidth = (clientX: number) => {
|
|
117
|
+
const next = clampWidth(startWidth + (clientX - startX), column);
|
|
118
|
+
if (next === latestWidth) return;
|
|
119
|
+
latestWidth = next;
|
|
120
|
+
setResizedWidths((current) => ({ ...current, [resizeKey]: next }));
|
|
121
|
+
onColumnResizeRef.current?.(next, column);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// pointermove fires far more often than the browser can paint, and every
|
|
125
|
+
// width change relayouts the whole table, so coalesce to one per frame.
|
|
126
|
+
const onPointerMove = (moveEvent: PointerEvent) => {
|
|
127
|
+
const { clientX } = moveEvent;
|
|
128
|
+
if (frameRef.current) cancelAnimationFrame(frameRef.current);
|
|
129
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
130
|
+
frameRef.current = 0;
|
|
131
|
+
applyWidth(clientX);
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Detaching listeners and restoring the document is split out from `stop`
|
|
136
|
+
// so that unmounting mid-drag can clean up without emitting a resize-end
|
|
137
|
+
// event or touching state on a gone component.
|
|
138
|
+
// Collected as closures so that `teardown` does not have to name the
|
|
139
|
+
// handlers it removes (`stop` is defined after it, and they are mutual).
|
|
140
|
+
const detachers: Array<() => void> = [];
|
|
141
|
+
const bind = (type: string, handler: (event: PointerEvent) => void) => {
|
|
142
|
+
window.addEventListener(type, handler as EventListener);
|
|
143
|
+
detachers.push(() =>
|
|
144
|
+
window.removeEventListener(type, handler as EventListener),
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const teardown = () => {
|
|
149
|
+
if (frameRef.current) {
|
|
150
|
+
cancelAnimationFrame(frameRef.current);
|
|
151
|
+
frameRef.current = 0;
|
|
152
|
+
}
|
|
153
|
+
detachers.forEach((detach) => detach());
|
|
154
|
+
detachers.length = 0;
|
|
155
|
+
document.body.style.userSelect = '';
|
|
156
|
+
document.body.style.cursor = '';
|
|
157
|
+
teardownRef.current = null;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const stop = () => {
|
|
161
|
+
teardown();
|
|
162
|
+
setResizingKey(null);
|
|
163
|
+
onColumnResizeEndRef.current?.(latestWidth, column);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
teardownRef.current = teardown;
|
|
167
|
+
bind('pointermove', onPointerMove);
|
|
168
|
+
bind('pointerup', stop);
|
|
169
|
+
bind('pointercancel', stop);
|
|
170
|
+
},
|
|
171
|
+
[findColumn, snapshotWidths, onColumnResizeRef, onColumnResizeEndRef],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
/** Keyboard equivalent of a drag, so the handle is operable without a mouse. */
|
|
175
|
+
const onResizeStep = React.useCallback(
|
|
176
|
+
(resizeKey: string, direction: number) => {
|
|
177
|
+
const column = findColumn(resizeKey);
|
|
178
|
+
if (!column) return;
|
|
179
|
+
|
|
180
|
+
const base = resizedWidthsRef.current;
|
|
181
|
+
const snapshot = base[resizeKey] === undefined ? snapshotWidths() : base;
|
|
182
|
+
if (!snapshot) return;
|
|
183
|
+
|
|
184
|
+
const next = clampWidth(
|
|
185
|
+
snapshot[resizeKey] + direction * KEYBOARD_STEP,
|
|
186
|
+
column,
|
|
187
|
+
);
|
|
188
|
+
if (next === snapshot[resizeKey]) return;
|
|
189
|
+
|
|
190
|
+
setResizedWidths({ ...snapshot, [resizeKey]: next });
|
|
191
|
+
onColumnResizeRef.current?.(next, column);
|
|
192
|
+
onColumnResizeEndRef.current?.(next, column);
|
|
193
|
+
},
|
|
194
|
+
[
|
|
195
|
+
findColumn,
|
|
196
|
+
snapshotWidths,
|
|
197
|
+
resizedWidthsRef,
|
|
198
|
+
onColumnResizeRef,
|
|
199
|
+
onColumnResizeEndRef,
|
|
200
|
+
],
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Forget the snapshot. Called when the column set changes: widths keyed to
|
|
205
|
+
* columns that no longer exist would under-report the total table width.
|
|
206
|
+
*/
|
|
207
|
+
const resetResizedWidths = React.useCallback(() => {
|
|
208
|
+
setResizedWidths((current) => (Object.keys(current).length ? {} : current));
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
// Unmounting mid-drag must not leave listeners or body styles behind.
|
|
212
|
+
React.useEffect(() => () => teardownRef.current?.(), []);
|
|
213
|
+
|
|
214
|
+
// Not gated on any `enabled` flag: the state stays empty until something drags,
|
|
215
|
+
// and handles are only rendered for columns that opted in, so an unused Table
|
|
216
|
+
// pays nothing and a column-level `resizable` works with no table-level flag.
|
|
217
|
+
return {
|
|
218
|
+
resizedWidths,
|
|
219
|
+
resizingKey,
|
|
220
|
+
onResizeStart,
|
|
221
|
+
onResizeStep,
|
|
222
|
+
resetResizedWidths,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import deepClone from '../../utils/deepClone';
|
|
3
|
-
import { handleColumns } from '../util';
|
|
3
|
+
import { applyColumnResize, handleColumns } from '../util';
|
|
4
4
|
import {
|
|
5
5
|
flattenLeafColumns,
|
|
6
6
|
injectSelectionExpand,
|
|
@@ -32,6 +32,8 @@ export default function useColumns({
|
|
|
32
32
|
rowSelection,
|
|
33
33
|
expandable,
|
|
34
34
|
measuredColumnWidths,
|
|
35
|
+
resizable,
|
|
36
|
+
resizedWidths,
|
|
35
37
|
}: {
|
|
36
38
|
children?:
|
|
37
39
|
| React.ReactElement<ColumnProps>
|
|
@@ -40,6 +42,8 @@ export default function useColumns({
|
|
|
40
42
|
rowSelection?: RowSelectionType;
|
|
41
43
|
expandable?: ExpandableType;
|
|
42
44
|
measuredColumnWidths?: number[];
|
|
45
|
+
resizable?: boolean;
|
|
46
|
+
resizedWidths?: Record<string, number>;
|
|
43
47
|
}) {
|
|
44
48
|
const childColumns: Array<ColumnProps> | null = useMemo(() => {
|
|
45
49
|
return children
|
|
@@ -55,10 +59,21 @@ export default function useColumns({
|
|
|
55
59
|
[]) as WrapedColumnProps[];
|
|
56
60
|
const tree = injectSelectionExpand(base, rowSelection, expandable);
|
|
57
61
|
const leaves = flattenLeafColumns(tree);
|
|
58
|
-
|
|
62
|
+
// Resized widths are folded in before the fixed-column offsets are computed,
|
|
63
|
+
// so sticky positions follow a drag without any extra bookkeeping.
|
|
64
|
+
const resized = applyColumnResize(leaves, resizable, resizedWidths);
|
|
65
|
+
const wraped = handleColumns(resized, measuredColumnWidths);
|
|
59
66
|
const rows = parseHeaderRows(tree, wraped);
|
|
60
67
|
return { wrapedColumns: wraped, headerRows: rows };
|
|
61
|
-
}, [
|
|
68
|
+
}, [
|
|
69
|
+
childColumns,
|
|
70
|
+
columns,
|
|
71
|
+
rowSelection,
|
|
72
|
+
expandable,
|
|
73
|
+
measuredColumnWidths,
|
|
74
|
+
resizable,
|
|
75
|
+
resizedWidths,
|
|
76
|
+
]);
|
|
62
77
|
|
|
63
78
|
return {
|
|
64
79
|
wrapedColumns,
|
package/src/Table/index.en-US.md
CHANGED
|
@@ -24,6 +24,7 @@ A table displays rows of data.
|
|
|
24
24
|
<code src="./demo/column-filter.tsx" title="column filter" description="Table column filter"></code>
|
|
25
25
|
<code src="./demo/grouping.tsx" title="Grouping" description="Multi-level header"></code>
|
|
26
26
|
<code src="./demo/fixed.tsx" title="Fixed Columns" description="Fix columns on both sides and scroll horizontally to see the effect"></code>
|
|
27
|
+
<code src="./demo/resizable.tsx" title="Resizable Columns" description="Drag the right edge of a header cell to resize it; arrow keys work too"></code>
|
|
27
28
|
<code src="./demo/components-override.tsx" title="components override" description="tbody / row rendering that can be taken over by external libs (e.g. dnd-kit)"></code>
|
|
28
29
|
## API
|
|
29
30
|
|
|
@@ -58,6 +59,9 @@ A table displays rows of data.
|
|
|
58
59
|
| rowKey | `string \| ((record: Record<string, any>) => string)` | Row key | `id` |
|
|
59
60
|
| summary | `(pageData: Array<Record<string, any>>) => React.ReactNode` | Summary row | - |
|
|
60
61
|
| tableLayout | `'auto' \| 'fixed'` | Table layout | - |
|
|
62
|
+
| resizable | `boolean` | Allow resizing columns by dragging the right edge of the header cell; a single column can opt out via `column.resizable` | - |
|
|
63
|
+
| onColumnResize | `(width: number, column: WrapedColumnProps) => void` | Fired on every width change while dragging | - |
|
|
64
|
+
| onColumnResizeEnd | `(width: number, column: WrapedColumnProps) => void` | Fired once a resize interaction finishes | - |
|
|
61
65
|
| virtual | `boolean` | Virtual list | - |
|
|
62
66
|
| onChange | `({ pagination, filters, sorter, currentDataSource, action }) => void` | Change callback | - |
|
|
63
67
|
| onRow | `(record: Record<string, any>, index: number) => Record<string, any>` | Custom row props | - |
|
|
@@ -78,6 +82,9 @@ A table displays rows of data.
|
|
|
78
82
|
| dataIndex | `string` | Data index | - |
|
|
79
83
|
| key | `string \| number` | Unique key | - |
|
|
80
84
|
| width | `number \| string` | Column width | - |
|
|
85
|
+
| resizable | `boolean` | Whether this column is resizable; inherits the table level `resizable` when unset | - |
|
|
86
|
+
| minWidth | `number` | Lower bound (px) applied while resizing | `40` |
|
|
87
|
+
| maxWidth | `number` | Upper bound (px) applied while resizing | - |
|
|
81
88
|
| title | `React.ReactNode` | Column title | - |
|
|
82
89
|
| helper | `string \| React.ReactNode` | Help icon for column header | - |
|
|
83
90
|
| children | `ColumnProps[]` | Sub-columns for header grouping (multi-level header); use `render` for custom cell content | - |
|
package/src/Table/index.zh-CN.md
CHANGED
|
@@ -25,6 +25,7 @@ toc: 'content'
|
|
|
25
25
|
<code src="./demo/merge-cell.tsx" title="合并单元格" description="合并单元格的表格"></code>
|
|
26
26
|
<code src="./demo/grouping.tsx" title="表头分组" description="多级表头"></code>
|
|
27
27
|
<code src="./demo/fixed.tsx" title="固定列" description="固定表格左右列,横向滚动可查看效果"></code>
|
|
28
|
+
<code src="./demo/resizable.tsx" title="列宽调整" description="拖动表头右边缘调整列宽,支持键盘左右方向键"></code>
|
|
28
29
|
<code src="./demo/summary.tsx" title="统计" description="表格的统计行"></code>
|
|
29
30
|
<code src="./demo/filter.tsx" title="筛选" description="表格的筛选功能"></code>
|
|
30
31
|
<code src="./demo/column-filter.tsx" title="列筛选" description="表格的列筛选功能"></code>
|
|
@@ -63,6 +64,9 @@ toc: 'content'
|
|
|
63
64
|
| rowKey | `string \| ((record: Record<string, any>) => string)` | 设置行的唯一键 | `id` |
|
|
64
65
|
| summary | `(pageData: Array<Record<string, any>>) => React.ReactNode` | 总结栏 | - |
|
|
65
66
|
| tableLayout | `'auto' \| 'fixed'` | 设置Table布局 | - |
|
|
67
|
+
| resizable | `boolean` | 是否允许拖动表头右边缘调整列宽,可用 `column.resizable` 单独关闭某列 | - |
|
|
68
|
+
| onColumnResize | `(width: number, column: WrapedColumnProps) => void` | 拖动过程中每次宽度变化的回调 | - |
|
|
69
|
+
| onColumnResizeEnd | `(width: number, column: WrapedColumnProps) => void` | 一次拖动结束时的回调 | - |
|
|
66
70
|
| virtual | `boolean` | 是否开启虚拟列表 | - |
|
|
67
71
|
| onChange | `({ pagination, filters, sorter, currentDataSource, action }) => void` | 分页,排序,筛选发生变化时的回调函数 | - |
|
|
68
72
|
| onRow | `(record: Record<string, any>, index: number) => Record<string, any>` | 自定义行属性 | - |
|
|
@@ -83,6 +87,9 @@ toc: 'content'
|
|
|
83
87
|
| dataIndex | `string` | 行数据索引 | - |
|
|
84
88
|
| key | `string \| number` | 唯一key | - |
|
|
85
89
|
| width | `number \| string` | 设置宽度 | - |
|
|
90
|
+
| resizable | `boolean` | 该列是否可拖动调整列宽,未设置时继承表格的 `resizable` | - |
|
|
91
|
+
| minWidth | `number` | 调整列宽时的最小宽度(px) | `40` |
|
|
92
|
+
| maxWidth | `number` | 调整列宽时的最大宽度(px) | - |
|
|
86
93
|
| title | `React.ReactNode` | 设置列头显示文本 | - |
|
|
87
94
|
| helper | `string \| React.ReactNode` | 设置表头help 帮助图标 | - |
|
|
88
95
|
| children | `ColumnProps[]` | 子列,用于表头分组(多级表头);自定义单元格内容请用 `render` | - |
|
package/src/Table/interface.ts
CHANGED
|
@@ -71,6 +71,21 @@ export interface HeadCellProps extends Omit<WrapedColumnProps, 'title'> {
|
|
|
71
71
|
internalFilters?: { [dataIndex: string]: boolean }; // Whether this column is filtered
|
|
72
72
|
onSort?: (dataIndex: string, sorter: any) => void;
|
|
73
73
|
onInternalFilter?: (dataIndex: string, value: string) => void;
|
|
74
|
+
/**
|
|
75
|
+
* `resizeKey` of the column currently being dragged, if any
|
|
76
|
+
*/
|
|
77
|
+
resizingKey?: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Begin a pointer driven column resize. Injected by Table when resizing is on.
|
|
80
|
+
*/
|
|
81
|
+
onResizeStart?: (
|
|
82
|
+
event: React.PointerEvent<HTMLElement>,
|
|
83
|
+
resizeKey: string,
|
|
84
|
+
) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Nudge a column width by `delta` px, for keyboard driven resizing
|
|
87
|
+
*/
|
|
88
|
+
onResizeStep?: (resizeKey: string, delta: number) => void;
|
|
74
89
|
/**
|
|
75
90
|
* Horizontal span, used for grouped header cells
|
|
76
91
|
*/
|
|
@@ -256,6 +271,20 @@ export interface ColumnProps {
|
|
|
256
271
|
* Set width
|
|
257
272
|
*/
|
|
258
273
|
width?: number | string;
|
|
274
|
+
/**
|
|
275
|
+
* Whether this column can be resized by dragging the right edge of its header
|
|
276
|
+
* cell. Falls back to the table level `resizable` when not set.
|
|
277
|
+
*/
|
|
278
|
+
resizable?: boolean;
|
|
279
|
+
/**
|
|
280
|
+
* Lower bound (in px) applied while resizing this column
|
|
281
|
+
* @default 40
|
|
282
|
+
*/
|
|
283
|
+
minWidth?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Upper bound (in px) applied while resizing this column
|
|
286
|
+
*/
|
|
287
|
+
maxWidth?: number;
|
|
259
288
|
/**
|
|
260
289
|
* Set header display text
|
|
261
290
|
*/
|
|
@@ -295,6 +324,12 @@ export interface WrapedColumnProps
|
|
|
295
324
|
* Whether this is the first column fixed to the right
|
|
296
325
|
*/
|
|
297
326
|
isFixedRightFirst?: boolean;
|
|
327
|
+
/**
|
|
328
|
+
* Stable identity of this leaf column, used as the key of the resized width
|
|
329
|
+
* map. Only assigned to leaf columns, so its presence also marks a column as
|
|
330
|
+
* a valid resize target (group header cells never carry one).
|
|
331
|
+
*/
|
|
332
|
+
resizeKey?: string;
|
|
298
333
|
/**
|
|
299
334
|
* prefixCls
|
|
300
335
|
*/
|
|
@@ -640,6 +675,23 @@ export interface TableProps
|
|
|
640
675
|
* Set table layout
|
|
641
676
|
*/
|
|
642
677
|
tableLayout?: 'auto' | 'fixed'; // Table layout, act on table tag
|
|
678
|
+
/**
|
|
679
|
+
* Whether columns can be resized by dragging the right edge of their header
|
|
680
|
+
* cell. Individual columns can opt out with `column.resizable: false`.
|
|
681
|
+
*
|
|
682
|
+
* The first drag snapshots the rendered width of every column and switches the
|
|
683
|
+
* table to a px controlled `table-layout: fixed`, so column widths stay exactly
|
|
684
|
+
* where they are dragged instead of being redistributed by the browser.
|
|
685
|
+
*/
|
|
686
|
+
resizable?: boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Fired on every width change while a column is being dragged
|
|
689
|
+
*/
|
|
690
|
+
onColumnResize?: (width: number, column: WrapedColumnProps) => void;
|
|
691
|
+
/**
|
|
692
|
+
* Fired once when a column resize interaction finishes
|
|
693
|
+
*/
|
|
694
|
+
onColumnResizeEnd?: (width: number, column: WrapedColumnProps) => void;
|
|
643
695
|
/**
|
|
644
696
|
* Whether to enable virtual list
|
|
645
697
|
*/
|
|
@@ -71,6 +71,48 @@
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Sits inside the header cell's right padding so it never overlaps the sorter
|
|
75
|
+
// or filter controls, which are laid out against the content edge.
|
|
76
|
+
&-resize-handle {
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: 0;
|
|
79
|
+
right: 0;
|
|
80
|
+
z-index: 2;
|
|
81
|
+
width: var(--yee-table-resize-handle-width);
|
|
82
|
+
height: 100%;
|
|
83
|
+
cursor: col-resize;
|
|
84
|
+
background-color: transparent;
|
|
85
|
+
touch-action: none;
|
|
86
|
+
user-select: none;
|
|
87
|
+
|
|
88
|
+
&::after {
|
|
89
|
+
position: absolute;
|
|
90
|
+
content: '';
|
|
91
|
+
top: 25%;
|
|
92
|
+
right: calc(var(--yee-table-resize-handle-width) / 2 - 1px);
|
|
93
|
+
width: 2px;
|
|
94
|
+
height: 50%;
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:hover::after,
|
|
99
|
+
&:focus-visible::after,
|
|
100
|
+
&-active::after {
|
|
101
|
+
background-color: var(--yee-primary-color);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&:focus-visible {
|
|
105
|
+
outline: none;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// While dragging, the pointer leaves the handle constantly; keep the cursor and
|
|
110
|
+
// the selection lock consistent across the whole table.
|
|
111
|
+
&-resizing {
|
|
112
|
+
cursor: col-resize;
|
|
113
|
+
user-select: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
74
116
|
&-filter {
|
|
75
117
|
max-height: var(--yee-table-filter-popup-height);
|
|
76
118
|
background-color: var(--yee-table-filter-list-bg-color);
|