@rainbow-oh/yee-c 0.10.0 → 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.
Files changed (74) hide show
  1. package/dist/cjs/Button/demo/icon.js +1 -1
  2. package/dist/cjs/Table/demo/resizable.d.ts +3 -0
  3. package/dist/cjs/Table/demo/resizable.js +143 -0
  4. package/dist/cjs/Table/hooks/useColumnResize.d.ts +30 -0
  5. package/dist/cjs/Table/hooks/useColumnResize.js +217 -0
  6. package/dist/cjs/Table/hooks/useColumns.d.ts +3 -1
  7. package/dist/cjs/Table/hooks/useColumns.js +14 -3
  8. package/dist/cjs/Table/interface.d.ts +49 -0
  9. package/dist/cjs/Table/style/index.less +42 -0
  10. package/dist/cjs/Table/table.js +62 -9
  11. package/dist/cjs/Table/theader/cell.js +46 -4
  12. package/dist/cjs/Table/util/index.d.ts +19 -1
  13. package/dist/cjs/Table/util/index.js +22 -0
  14. package/dist/cjs/Tabs/interface.d.ts +1 -1
  15. package/dist/cjs/Tabs/tab-header/index.js +2 -1
  16. package/dist/cjs/locale/interface.d.ts +1 -0
  17. package/dist/cjs/locale/lang/en_US.js +2 -1
  18. package/dist/cjs/locale/lang/ja_JP.js +2 -1
  19. package/dist/cjs/locale/lang/zh_CN.js +2 -1
  20. package/dist/cjs/locale/lang/zh_TW.js +2 -1
  21. package/dist/cjs/style/size.css +1 -0
  22. package/dist/cjs/style/themes.css +1 -1
  23. package/dist/cjs/style/variables.css +2 -1
  24. package/dist/cjs/version.d.ts +1 -1
  25. package/dist/cjs/version.js +1 -1
  26. package/dist/esm/Button/demo/icon.js +7 -4
  27. package/dist/esm/Table/demo/resizable.d.ts +3 -0
  28. package/dist/esm/Table/demo/resizable.js +110 -0
  29. package/dist/esm/Table/hooks/useColumnResize.d.ts +30 -0
  30. package/dist/esm/Table/hooks/useColumnResize.js +206 -0
  31. package/dist/esm/Table/hooks/useColumns.d.ts +3 -1
  32. package/dist/esm/Table/hooks/useColumns.js +9 -4
  33. package/dist/esm/Table/interface.d.ts +49 -0
  34. package/dist/esm/Table/style/index.less +42 -0
  35. package/dist/esm/Table/table.js +83 -13
  36. package/dist/esm/Table/theader/cell.js +38 -3
  37. package/dist/esm/Table/util/index.d.ts +19 -1
  38. package/dist/esm/Table/util/index.js +37 -0
  39. package/dist/esm/Tabs/interface.d.ts +1 -1
  40. package/dist/esm/Tabs/tab-header/index.js +2 -1
  41. package/dist/esm/locale/interface.d.ts +1 -0
  42. package/dist/esm/locale/lang/en_US.js +2 -1
  43. package/dist/esm/locale/lang/ja_JP.js +2 -1
  44. package/dist/esm/locale/lang/zh_CN.js +2 -1
  45. package/dist/esm/locale/lang/zh_TW.js +2 -1
  46. package/dist/esm/style/size.css +1 -0
  47. package/dist/esm/style/themes.css +1 -1
  48. package/dist/esm/style/variables.css +2 -1
  49. package/dist/esm/version.d.ts +1 -1
  50. package/dist/esm/version.js +1 -1
  51. package/dist/umd/yee-c.min.css +2 -2
  52. package/dist/umd/yee-c.min.js +1 -1
  53. package/package.json +1 -1
  54. package/src/Button/demo/icon.tsx +5 -3
  55. package/src/Table/demo/resizable.tsx +106 -0
  56. package/src/Table/hooks/useColumnResize.ts +224 -0
  57. package/src/Table/hooks/useColumns.ts +18 -3
  58. package/src/Table/index.en-US.md +7 -0
  59. package/src/Table/index.zh-CN.md +7 -0
  60. package/src/Table/interface.ts +52 -0
  61. package/src/Table/style/index.less +42 -0
  62. package/src/Table/table-resize.test.tsx +234 -0
  63. package/src/Table/table.tsx +90 -13
  64. package/src/Table/theader/cell.tsx +52 -2
  65. package/src/Table/util/index.ts +44 -1
  66. package/src/Tabs/interface.ts +1 -1
  67. package/src/Tabs/tab-header/index.tsx +2 -1
  68. package/src/locale/interface.ts +1 -0
  69. package/src/locale/lang/en_US.ts +1 -0
  70. package/src/locale/lang/ja_JP.ts +1 -0
  71. package/src/locale/lang/zh_CN.ts +1 -0
  72. package/src/locale/lang/zh_TW.ts +1 -0
  73. package/src/style/size.css +1 -0
  74. 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
  /**
@@ -221,7 +221,8 @@ const TabHeader: React.FC<TabHeaderProps> = (props) => {
221
221
  {moreOpen && (
222
222
  <DropDownTabs visible={moreOpen} items={items || []} range={range}>
223
223
  <Button
224
- className={`${prefixCls}-nav-more`}
224
+ className={clsx(`${prefixCls}-nav-more`, classNames?.more)}
225
+ style={styles?.more}
225
226
  size="small"
226
227
  icon={<Ellipsis size={14} strokeWidth={1.5} />}
227
228
  type="text"
@@ -79,6 +79,7 @@ export interface LocaleTable {
79
79
  sortAscending?: string; // "Click to sort ascending"
80
80
  sortDescending?: string; // "Click to sort descending"
81
81
  sortCancel?: string; // "Click to cancel sorting"
82
+ resizeColumn?: string; // "Drag to resize column"
82
83
  }
83
84
 
84
85
  // Pagination component
@@ -105,6 +105,7 @@ const locale: Locale = {
105
105
  sortAscending: 'Click to sort ascending',
106
106
  sortDescending: 'Click to sort descending',
107
107
  sortCancel: 'Click to cancel sorting',
108
+ resizeColumn: 'Drag to resize column',
108
109
  },
109
110
 
110
111
  pagination: {
@@ -105,6 +105,7 @@ const locale: Locale = {
105
105
  sortAscending: 'クリックで昇順に並べ替え',
106
106
  sortDescending: 'クリックで降順に並べ替え',
107
107
  sortCancel: 'クリックで並べ替えを解除',
108
+ resizeColumn: 'ドラッグで列幅を変更',
108
109
  },
109
110
 
110
111
  pagination: {
@@ -96,6 +96,7 @@ const locale: Locale = {
96
96
  sortAscending: '点击升序排列',
97
97
  sortDescending: '点击降序排列',
98
98
  sortCancel: '点击取消排序',
99
+ resizeColumn: '拖动调整列宽',
99
100
  },
100
101
 
101
102
  pagination: {
@@ -96,6 +96,7 @@ const locale: Locale = {
96
96
  sortAscending: '點擊升序排列',
97
97
  sortDescending: '點擊降序排列',
98
98
  sortCancel: '點擊取消排序',
99
+ resizeColumn: '拖動調整欄寬',
99
100
  },
100
101
 
101
102
  pagination: {
@@ -674,6 +674,7 @@
674
674
  --yee-table-bordered-cell-border-width: 1px;
675
675
  --yee-table-bordered-thead-border-width: 1px;
676
676
  --yee-table-thead-action-gap: 8px;
677
+ --yee-table-resize-handle-width: 8px;
677
678
  --yee-table-filter-popup-padding: 8px;
678
679
  --yee-table-content-border-radius: 6px 6px 0 0;
679
680
  --yee-table-cell-padding: 13px 16px;
package/src/version.ts CHANGED
@@ -4,7 +4,7 @@ declare global {
4
4
  }
5
5
  }
6
6
 
7
- export const version = "0.10.0";
7
+ export const version = "0.11.0";
8
8
 
9
9
  if (typeof window !== 'undefined') {
10
10
  window.__yee_c_version__ = version;