@rainbow-oh/yee-c 0.10.0 → 0.12.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.
Files changed (193) hide show
  1. package/dist/cjs/Anchor/style/index.less +3 -3
  2. package/dist/cjs/Button/demo/icon.js +1 -1
  3. package/dist/cjs/Button/style/index.less +72 -52
  4. package/dist/cjs/DatePicker/style/mobile.less +1 -1
  5. package/dist/cjs/Descriptions/style/index.less +6 -6
  6. package/dist/cjs/FloatButton/style/index.less +2 -2
  7. package/dist/cjs/Form/style/index.less +21 -21
  8. package/dist/cjs/ImageViewer/style/index.less +13 -13
  9. package/dist/cjs/Input/style/index.less +1 -1
  10. package/dist/cjs/InputNumber/style/index.less +9 -9
  11. package/dist/cjs/Menu/style/index.less +1 -1
  12. package/dist/cjs/PickerPanel/style/index.less +5 -5
  13. package/dist/cjs/RangePicker/style/index.less +53 -52
  14. package/dist/cjs/Rate/style/index.less +1 -1
  15. package/dist/cjs/Search/style/index.less +10 -10
  16. package/dist/cjs/Segmented/style/index.less +2 -2
  17. package/dist/cjs/Selector/selector.js +1 -1
  18. package/dist/cjs/Selector/style/index.less +1 -1
  19. package/dist/cjs/Table/demo/resizable.d.ts +3 -0
  20. package/dist/cjs/Table/demo/resizable.js +143 -0
  21. package/dist/cjs/Table/hooks/useColumnResize.d.ts +30 -0
  22. package/dist/cjs/Table/hooks/useColumnResize.js +217 -0
  23. package/dist/cjs/Table/hooks/useColumns.d.ts +3 -1
  24. package/dist/cjs/Table/hooks/useColumns.js +14 -3
  25. package/dist/cjs/Table/interface.d.ts +49 -0
  26. package/dist/cjs/Table/style/index.less +43 -1
  27. package/dist/cjs/Table/table.js +62 -9
  28. package/dist/cjs/Table/theader/cell.js +46 -4
  29. package/dist/cjs/Table/util/index.d.ts +19 -1
  30. package/dist/cjs/Table/util/index.js +22 -0
  31. package/dist/cjs/Tabs/interface.d.ts +1 -1
  32. package/dist/cjs/Tabs/style/index.less +9 -9
  33. package/dist/cjs/Tabs/tab-header/index.js +5 -1
  34. package/dist/cjs/Tour/demo/indicators.js +1 -1
  35. package/dist/cjs/Tour/demo/placement.js +1 -1
  36. package/dist/cjs/Transfer/style/index.less +1 -1
  37. package/dist/cjs/Tree/style/index.less +3 -3
  38. package/dist/cjs/TreeSelect/style/index.less +1 -1
  39. package/dist/cjs/Upload/style/index.less +11 -12
  40. package/dist/cjs/WheelPicker/style/index.less +3 -3
  41. package/dist/cjs/locale/interface.d.ts +1 -0
  42. package/dist/cjs/locale/lang/en_US.js +2 -1
  43. package/dist/cjs/locale/lang/ja_JP.js +2 -1
  44. package/dist/cjs/locale/lang/zh_CN.js +2 -1
  45. package/dist/cjs/locale/lang/zh_TW.js +2 -1
  46. package/dist/cjs/message/style/index.less +4 -4
  47. package/dist/cjs/style/color.blue.css +254 -290
  48. package/dist/cjs/style/color.crimson.css +434 -471
  49. package/dist/cjs/style/color.css +736 -508
  50. package/dist/cjs/style/color.dark.css +464 -501
  51. package/dist/cjs/style/color.gemini.css +134 -0
  52. package/dist/cjs/style/color.green.css +459 -495
  53. package/dist/cjs/style/color.jam.css +463 -500
  54. package/dist/cjs/style/color.navy.css +466 -503
  55. package/dist/cjs/style/color.peach.css +464 -501
  56. package/dist/cjs/style/color.pine.css +462 -500
  57. package/dist/cjs/style/color.pitaya.css +465 -502
  58. package/dist/cjs/style/color.pumpkin.css +464 -501
  59. package/dist/cjs/style/color.ruby.css +466 -503
  60. package/dist/cjs/style/size.css +144 -2
  61. package/dist/cjs/style/themes.css +4999 -5268
  62. package/dist/cjs/style/variables.css +882 -512
  63. package/dist/cjs/version.d.ts +1 -1
  64. package/dist/cjs/version.js +1 -1
  65. package/dist/esm/Anchor/style/index.less +3 -3
  66. package/dist/esm/Button/demo/icon.js +7 -4
  67. package/dist/esm/Button/style/index.less +72 -52
  68. package/dist/esm/DatePicker/style/mobile.less +1 -1
  69. package/dist/esm/Descriptions/style/index.less +6 -6
  70. package/dist/esm/FloatButton/style/index.less +2 -2
  71. package/dist/esm/Form/style/index.less +21 -21
  72. package/dist/esm/ImageViewer/style/index.less +13 -13
  73. package/dist/esm/Input/style/index.less +1 -1
  74. package/dist/esm/InputNumber/style/index.less +9 -9
  75. package/dist/esm/Menu/style/index.less +1 -1
  76. package/dist/esm/PickerPanel/style/index.less +5 -5
  77. package/dist/esm/RangePicker/style/index.less +53 -52
  78. package/dist/esm/Rate/style/index.less +1 -1
  79. package/dist/esm/Search/style/index.less +10 -10
  80. package/dist/esm/Segmented/style/index.less +2 -2
  81. package/dist/esm/Selector/selector.js +1 -1
  82. package/dist/esm/Selector/style/index.less +1 -1
  83. package/dist/esm/Table/demo/resizable.d.ts +3 -0
  84. package/dist/esm/Table/demo/resizable.js +110 -0
  85. package/dist/esm/Table/hooks/useColumnResize.d.ts +30 -0
  86. package/dist/esm/Table/hooks/useColumnResize.js +206 -0
  87. package/dist/esm/Table/hooks/useColumns.d.ts +3 -1
  88. package/dist/esm/Table/hooks/useColumns.js +9 -4
  89. package/dist/esm/Table/interface.d.ts +49 -0
  90. package/dist/esm/Table/style/index.less +43 -1
  91. package/dist/esm/Table/table.js +83 -13
  92. package/dist/esm/Table/theader/cell.js +38 -3
  93. package/dist/esm/Table/util/index.d.ts +19 -1
  94. package/dist/esm/Table/util/index.js +37 -0
  95. package/dist/esm/Tabs/interface.d.ts +1 -1
  96. package/dist/esm/Tabs/style/index.less +9 -9
  97. package/dist/esm/Tabs/tab-header/index.js +7 -2
  98. package/dist/esm/Tour/demo/indicators.js +1 -1
  99. package/dist/esm/Tour/demo/placement.js +1 -1
  100. package/dist/esm/Transfer/style/index.less +1 -1
  101. package/dist/esm/Tree/style/index.less +3 -3
  102. package/dist/esm/TreeSelect/style/index.less +1 -1
  103. package/dist/esm/Upload/style/index.less +11 -12
  104. package/dist/esm/WheelPicker/style/index.less +3 -3
  105. package/dist/esm/index.js +1 -0
  106. package/dist/esm/locale/interface.d.ts +1 -0
  107. package/dist/esm/locale/lang/en_US.js +2 -1
  108. package/dist/esm/locale/lang/ja_JP.js +2 -1
  109. package/dist/esm/locale/lang/zh_CN.js +2 -1
  110. package/dist/esm/locale/lang/zh_TW.js +2 -1
  111. package/dist/esm/message/style/index.less +4 -4
  112. package/dist/esm/style/color.blue.css +254 -290
  113. package/dist/esm/style/color.crimson.css +434 -471
  114. package/dist/esm/style/color.css +736 -508
  115. package/dist/esm/style/color.dark.css +464 -501
  116. package/dist/esm/style/color.gemini.css +134 -0
  117. package/dist/esm/style/color.green.css +459 -495
  118. package/dist/esm/style/color.jam.css +463 -500
  119. package/dist/esm/style/color.navy.css +466 -503
  120. package/dist/esm/style/color.peach.css +464 -501
  121. package/dist/esm/style/color.pine.css +462 -500
  122. package/dist/esm/style/color.pitaya.css +465 -502
  123. package/dist/esm/style/color.pumpkin.css +464 -501
  124. package/dist/esm/style/color.ruby.css +466 -503
  125. package/dist/esm/style/size.css +144 -2
  126. package/dist/esm/style/themes.css +4999 -5268
  127. package/dist/esm/style/variables.css +882 -512
  128. package/dist/esm/version.d.ts +1 -1
  129. package/dist/esm/version.js +1 -1
  130. package/dist/umd/yee-c.min.css +80 -22
  131. package/dist/umd/yee-c.min.js +1 -1
  132. package/package.json +4 -2
  133. package/src/Anchor/style/index.less +3 -3
  134. package/src/Button/demo/icon.tsx +5 -3
  135. package/src/Button/style/index.less +72 -52
  136. package/src/DatePicker/style/mobile.less +1 -1
  137. package/src/Descriptions/style/index.less +6 -6
  138. package/src/FloatButton/style/index.less +2 -2
  139. package/src/Form/style/index.less +21 -21
  140. package/src/ImageViewer/style/index.less +13 -13
  141. package/src/Input/style/index.less +1 -1
  142. package/src/InputNumber/style/index.less +9 -9
  143. package/src/Menu/style/index.less +1 -1
  144. package/src/PickerPanel/style/index.less +5 -5
  145. package/src/RangePicker/style/index.less +53 -52
  146. package/src/Rate/style/index.less +1 -1
  147. package/src/Search/style/index.less +10 -10
  148. package/src/Segmented/style/index.less +2 -2
  149. package/src/Selector/selector.tsx +1 -1
  150. package/src/Selector/style/index.less +1 -1
  151. package/src/Table/demo/resizable.tsx +106 -0
  152. package/src/Table/hooks/useColumnResize.ts +224 -0
  153. package/src/Table/hooks/useColumns.ts +18 -3
  154. package/src/Table/index.en-US.md +7 -0
  155. package/src/Table/index.zh-CN.md +7 -0
  156. package/src/Table/interface.ts +52 -0
  157. package/src/Table/style/index.less +43 -1
  158. package/src/Table/table-resize.test.tsx +234 -0
  159. package/src/Table/table.tsx +90 -13
  160. package/src/Table/theader/cell.tsx +52 -2
  161. package/src/Table/util/index.ts +44 -1
  162. package/src/Tabs/interface.ts +1 -1
  163. package/src/Tabs/style/index.less +9 -9
  164. package/src/Tabs/tab-header/index.tsx +5 -1
  165. package/src/Tour/demo/indicators.tsx +2 -2
  166. package/src/Tour/demo/placement.tsx +1 -1
  167. package/src/Transfer/style/index.less +1 -1
  168. package/src/Tree/style/index.less +3 -3
  169. package/src/TreeSelect/style/index.less +1 -1
  170. package/src/Upload/style/index.less +11 -12
  171. package/src/WheelPicker/style/index.less +3 -3
  172. package/src/index.ts +1 -0
  173. package/src/locale/interface.ts +1 -0
  174. package/src/locale/lang/en_US.ts +1 -0
  175. package/src/locale/lang/ja_JP.ts +1 -0
  176. package/src/locale/lang/zh_CN.ts +1 -0
  177. package/src/locale/lang/zh_TW.ts +1 -0
  178. package/src/message/style/index.less +4 -4
  179. package/src/style/color.blue.css +254 -290
  180. package/src/style/color.crimson.css +434 -471
  181. package/src/style/color.css +736 -508
  182. package/src/style/color.dark.css +464 -501
  183. package/src/style/color.gemini.css +134 -0
  184. package/src/style/color.green.css +459 -495
  185. package/src/style/color.jam.css +463 -500
  186. package/src/style/color.navy.css +466 -503
  187. package/src/style/color.peach.css +464 -501
  188. package/src/style/color.pine.css +462 -500
  189. package/src/style/color.pitaya.css +465 -502
  190. package/src/style/color.pumpkin.css +464 -501
  191. package/src/style/color.ruby.css +466 -503
  192. package/src/style/size.css +144 -2
  193. 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
+ });
@@ -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 { PaginationType, TableProps } from './interface';
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
- // Keep the latest columns in a ref so that the measure callback stays stable:
201
- // `wrapedColumns` is derived from `measuredColumnWidths`, so depending on it
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
- minWidth: scroll?.x,
353
- tableLayout: pageData?.length ? tableLayout : undefined,
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(`${prefixCls}-box`, className)}
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 <th {...thProps}>{title}</th>;
208
+ return (
209
+ <th {...thProps}>
210
+ {title}
211
+ {renderResizeHandle()}
212
+ </th>
213
+ );
169
214
  }
170
215
 
171
- return <th {...thProps}>{renderCellWithAction()}</th>;
216
+ return (
217
+ <th {...thProps}>
218
+ {renderCellWithAction()}
219
+ {renderResizeHandle()}
220
+ </th>
221
+ );
172
222
  };
173
223
 
174
224
  const cell = renderCell();
@@ -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,
@@ -1,6 +1,6 @@
1
1
  import type { DataAttributeProps } from '../utils/types';
2
2
 
3
- type SemanticDOM = 'header' | 'content' | 'track' | 'list' | 'activeBar';
3
+ type SemanticDOM = 'header' | 'content' | 'track' | 'list' | 'activeBar' | 'more';
4
4
 
5
5
  export type TabItemType = {
6
6
  /**
@@ -169,7 +169,7 @@
169
169
  user-select: none;
170
170
 
171
171
  &.disabled {
172
- color: var(--yee-tabitem-title-disabled-color);
172
+ color: var(--yee-tabs-tab-title-disabled-color);
173
173
  cursor: not-allowed;
174
174
 
175
175
  & > * {
@@ -178,7 +178,7 @@
178
178
  }
179
179
 
180
180
  &.active {
181
- color: var(--yee-tabitem-title-active-hover-color);
181
+ color: var(--yee-tabs-tab-title-active-hover-color);
182
182
  }
183
183
 
184
184
  &:not(.disabled):not(.active):hover {
@@ -253,8 +253,8 @@
253
253
 
254
254
  @{pls}-tab.active {
255
255
  z-index: 2;
256
- color: var(--yee-tabitem-active-hover-pills-color);
257
- border-color: var(--yee-tabitem-active-pills-border-color);
256
+ color: var(--yee-tabs-tab-active-hover-pills-color);
257
+ border-color: var(--yee-tabs-tab-active-pills-border-color);
258
258
  }
259
259
 
260
260
  @{pls}-active {
@@ -461,16 +461,16 @@
461
461
  cursor: pointer;
462
462
 
463
463
  &:hover {
464
- background-color: var(--yee-tabitem-dropdown-title-hover-bg-color);
464
+ background-color: var(--yee-tabs-tab-dropdown-title-hover-bg-color);
465
465
 
466
466
  @{pls}-tab-title {
467
- color: var(--yee-tabitem-dropdown-title-hover-color);
467
+ color: var(--yee-tabs-tab-dropdown-title-hover-color);
468
468
  }
469
469
  }
470
470
 
471
471
  &:active {
472
- color: var(--yee-tabitem-dropdown-title-active-color);
473
- background-color: var(--yee-tabitem-dropdown-title-active-bg-color);
472
+ color: var(--yee-tabs-tab-dropdown-title-active-color);
473
+ background-color: var(--yee-tabs-tab-dropdown-title-active-bg-color);
474
474
  }
475
475
  }
476
476
  }
@@ -491,7 +491,7 @@
491
491
  user-select: none;
492
492
 
493
493
  &:hover {
494
- color: var(--yee-tabitem-title-active-hover-color);
494
+ color: var(--yee-tabs-tab-title-active-hover-color);
495
495
  }
496
496
  }
497
497
  }
@@ -153,6 +153,9 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
153
153
  const cls = clsx(
154
154
  `${prefixCls}-nav-wrapper`,
155
155
  `${prefixCls}-nav-wrapper-${position}`,
156
+ {
157
+ [`${prefixCls}-nav-wrapper-with-more`]: moreOpen,
158
+ },
156
159
  classNames?.header,
157
160
  );
158
161
 
@@ -221,7 +224,8 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
221
224
  {moreOpen && (
222
225
  <DropDownTabs visible={moreOpen} items={items || []} range={range}>
223
226
  <Button
224
- className={`${prefixCls}-nav-more`}
227
+ className={clsx(`${prefixCls}-nav-more`, classNames?.more)}
228
+ style={styles?.more}
225
229
  size="small"
226
230
  icon={<Ellipsis size={14} strokeWidth={1.5} />}
227
231
  type="text"
@@ -37,8 +37,8 @@ export default () => {
37
37
  borderRadius: '50%',
38
38
  background:
39
39
  i === current
40
- ? 'var(--yee-primary-color)'
41
- : 'var(--yee-border-color)',
40
+ ? 'var(--yee-color-primary)'
41
+ : 'var(--yee-color-border)',
42
42
  transition: 'background 0.2s',
43
43
  }}
44
44
  />
@@ -31,7 +31,7 @@ export default () => {
31
31
  style={{
32
32
  display: 'inline-block',
33
33
  padding: '16px 24px',
34
- border: '1px dashed var(--yee-border-color)',
34
+ border: '1px dashed var(--yee-color-border)',
35
35
  borderRadius: 6,
36
36
  }}
37
37
  >
@@ -122,7 +122,7 @@
122
122
  align-items: center;
123
123
  width: 100%;
124
124
  border-radius: 4px;
125
- border: 1px solid var(--yee-btn-default-border-color);
125
+ border: 1px solid var(--yee-transfer-operation-border-color);
126
126
  padding: 0 8px;
127
127
  cursor: pointer;
128
128
 
@@ -17,15 +17,15 @@
17
17
  }
18
18
 
19
19
  &-drag-over-before {
20
- box-shadow: inset 0 2px 0 0 var(--yee-primary-color);
20
+ box-shadow: inset 0 2px 0 0 var(--yee-color-primary);
21
21
  }
22
22
 
23
23
  &-drag-over-after {
24
- box-shadow: inset 0 -2px 0 0 var(--yee-primary-color);
24
+ box-shadow: inset 0 -2px 0 0 var(--yee-color-primary);
25
25
  }
26
26
 
27
27
  &-drag-over-inside {
28
- background: color-mix(in srgb, var(--yee-primary-color) 12%, transparent);
28
+ background: color-mix(in srgb, var(--yee-color-primary) 12%, transparent);
29
29
  }
30
30
 
31
31
  &-switcher {
@@ -1,7 +1,7 @@
1
1
  @pls: .yee-tree-select;
2
2
 
3
3
  @{pls}-popup {
4
- background-color: var(--yee-treeselect-dropdown-bg-color);
4
+ background-color: var(--yee-tree-select-dropdown-bg-color);
5
5
  height: var(--yee-tree-select-popup-height, 360px);
6
6
  overflow: auto;
7
7
  }