@rainbow-oh/yee-c 0.8.12 → 0.8.14

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 (82) hide show
  1. package/dist/cjs/Pagination/style/index.less +4 -0
  2. package/dist/cjs/Spin/demo/types.js +1 -1
  3. package/dist/cjs/Spin/indicator.js +4 -2
  4. package/dist/cjs/Spin/style/index.less +2 -8
  5. package/dist/cjs/Table/col-group.d.ts +2 -2
  6. package/dist/cjs/Table/col-group.js +1 -1
  7. package/dist/cjs/Table/demo/sorter-callback.js +3 -2
  8. package/dist/cjs/Table/demo/sorter.js +3 -2
  9. package/dist/cjs/Table/hooks/useExpand.d.ts +2 -2
  10. package/dist/cjs/Table/hooks/useFilter.d.ts +8 -2
  11. package/dist/cjs/Table/hooks/useFilter.js +36 -22
  12. package/dist/cjs/Table/hooks/usePagination.js +8 -8
  13. package/dist/cjs/Table/hooks/useSelection.d.ts +3 -3
  14. package/dist/cjs/Table/hooks/useSelection.js +8 -9
  15. package/dist/cjs/Table/hooks/useSorter.d.ts +6 -3
  16. package/dist/cjs/Table/hooks/useSorter.js +193 -87
  17. package/dist/cjs/Table/interface.d.ts +10 -7
  18. package/dist/cjs/Table/table.js +51 -45
  19. package/dist/cjs/Table/tbody/index.js +13 -5
  20. package/dist/cjs/Table/tbody/lazy-row.js +6 -3
  21. package/dist/cjs/Table/tbody/row.js +5 -4
  22. package/dist/cjs/Table/theader/cell.js +4 -0
  23. package/dist/cjs/Table/theader/filter.js +52 -20
  24. package/dist/cjs/Table/util/index.js +5 -3
  25. package/dist/cjs/style/themes.css +1 -1
  26. package/dist/cjs/style/variables.css +1 -1
  27. package/dist/cjs/version.d.ts +1 -1
  28. package/dist/cjs/version.js +1 -1
  29. package/dist/esm/Pagination/style/index.less +4 -0
  30. package/dist/esm/Spin/demo/types.js +6 -3
  31. package/dist/esm/Spin/indicator.js +4 -2
  32. package/dist/esm/Spin/style/index.less +2 -8
  33. package/dist/esm/Table/col-group.d.ts +2 -2
  34. package/dist/esm/Table/col-group.js +1 -1
  35. package/dist/esm/Table/demo/sorter-callback.js +3 -2
  36. package/dist/esm/Table/demo/sorter.js +3 -2
  37. package/dist/esm/Table/hooks/useExpand.d.ts +2 -2
  38. package/dist/esm/Table/hooks/useFilter.d.ts +8 -2
  39. package/dist/esm/Table/hooks/useFilter.js +52 -32
  40. package/dist/esm/Table/hooks/usePagination.js +8 -10
  41. package/dist/esm/Table/hooks/useSelection.d.ts +3 -3
  42. package/dist/esm/Table/hooks/useSelection.js +16 -11
  43. package/dist/esm/Table/hooks/useSorter.d.ts +6 -3
  44. package/dist/esm/Table/hooks/useSorter.js +218 -105
  45. package/dist/esm/Table/interface.d.ts +10 -7
  46. package/dist/esm/Table/table.js +42 -47
  47. package/dist/esm/Table/tbody/index.js +8 -5
  48. package/dist/esm/Table/tbody/lazy-row.js +7 -3
  49. package/dist/esm/Table/tbody/row.js +7 -6
  50. package/dist/esm/Table/theader/cell.js +7 -1
  51. package/dist/esm/Table/theader/filter.js +75 -27
  52. package/dist/esm/Table/util/index.js +6 -3
  53. package/dist/esm/style/themes.css +1 -1
  54. package/dist/esm/style/variables.css +1 -1
  55. package/dist/esm/version.d.ts +1 -1
  56. package/dist/esm/version.js +1 -1
  57. package/dist/umd/yee-c.min.css +1 -1
  58. package/dist/umd/yee-c.min.js +1 -1
  59. package/package.json +1 -1
  60. package/src/Pagination/style/index.less +4 -0
  61. package/src/Spin/demo/types.tsx +3 -3
  62. package/src/Spin/index.en-US.md +2 -8
  63. package/src/Spin/index.zh-CN.md +2 -8
  64. package/src/Spin/indicator.tsx +2 -0
  65. package/src/Spin/style/index.less +2 -8
  66. package/src/Table/col-group.tsx +1 -1
  67. package/src/Table/demo/sorter-callback.tsx +3 -2
  68. package/src/Table/demo/sorter.tsx +3 -2
  69. package/src/Table/hooks/useExpand.ts +3 -3
  70. package/src/Table/hooks/useFilter.ts +47 -23
  71. package/src/Table/hooks/usePagination.ts +9 -10
  72. package/src/Table/hooks/useSelection.ts +18 -20
  73. package/src/Table/hooks/useSorter.ts +236 -82
  74. package/src/Table/interface.ts +19 -7
  75. package/src/Table/table.tsx +54 -49
  76. package/src/Table/tbody/index.tsx +14 -5
  77. package/src/Table/tbody/lazy-row.tsx +6 -3
  78. package/src/Table/tbody/row.tsx +5 -4
  79. package/src/Table/theader/cell.tsx +10 -0
  80. package/src/Table/theader/filter.tsx +64 -22
  81. package/src/Table/util/index.ts +3 -1
  82. package/src/version.ts +1 -1
@@ -1,106 +1,260 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { ColumnProps } from '../interface';
3
3
 
4
- type SortState = {
5
- [dataIndex: string]: 0 | 1 | -1;
4
+ type SortOrder = 1 | -1;
5
+ type SortState = Record<string, SortOrder>;
6
+ type SorterConfig = Exclude<ColumnProps['sorter'], boolean | undefined>;
7
+ type SortQueueItem = {
8
+ dataIndex: string;
9
+ order: SortOrder;
6
10
  };
7
-
8
- export default function useSorter({
9
- data,
10
- columns,
11
- }: {
11
+ type SortChangeResult = {
12
12
  data: Array<Record<string, unknown>>;
13
- columns: Array<ColumnProps>;
14
- }) {
15
- const getDefaultSorters = useCallback(() => {
16
- const sorters: SortState = {};
17
- columns.forEach((column) => {
18
- const { dataIndex, sorter } = column;
19
- if (!sorter) return;
20
- const { defaultSortOrder } =
21
- typeof sorter === 'boolean' ? ({} as any) : sorter;
22
- if (dataIndex && sorter) {
23
- if (defaultSortOrder === 'ascend') {
24
- sorters[dataIndex] = 1;
25
- } else if (defaultSortOrder === 'descend') {
26
- sorters[dataIndex] = -1;
27
- } else {
28
- sorters[dataIndex] = 0;
29
- }
30
- }
31
- });
32
- return sorters;
33
- }, [columns]);
13
+ sorters: SortState;
14
+ };
34
15
 
35
- const [sorters, setSorters] = useState<SortState>(getDefaultSorters());
16
+ const getSorterConfig = (column?: ColumnProps): SorterConfig | undefined =>
17
+ column && typeof column.sorter === 'object' ? column.sorter : undefined;
36
18
 
37
- // when columns changed, reset sorters
38
- useEffect(() => {
39
- setSorters(getDefaultSorters());
40
- }, [getDefaultSorters]);
41
-
42
- // handle sort on column title click
43
- const onSort = (dataIndex: string) => {
44
- setSorters((prev) => {
45
- const newSorters = { ...prev };
46
- const currentOrder = newSorters[dataIndex] ?? 0;
47
-
48
- if (currentOrder === 0) {
49
- newSorters[dataIndex] = 1;
50
- } else if (currentOrder === 1) {
51
- newSorters[dataIndex] = -1;
52
- } else {
53
- newSorters[dataIndex] = 0;
54
- }
19
+ const toSortOrder = (
20
+ order: 'ascend' | 'descend' | null | undefined,
21
+ ): SortOrder | 0 => {
22
+ if (order === 'ascend') return 1;
23
+ if (order === 'descend') return -1;
24
+ return 0;
25
+ };
55
26
 
56
- // is multiple sort
57
- const isMultipleSort = undefined;
27
+ const getNextSortOrder = (order: SortOrder | 0): SortOrder | 0 => {
28
+ if (order === 0) return 1;
29
+ if (order === 1) return -1;
30
+ return 0;
31
+ };
58
32
 
59
- if (isMultipleSort) {
60
- } else {
61
- Object.keys(newSorters).forEach((key) => {
62
- if (key !== dataIndex) {
63
- delete newSorters[key];
64
- }
65
- });
66
- }
33
+ const isMultipleSorter = (column?: ColumnProps) =>
34
+ Boolean(getSorterConfig(column)?.multiple);
67
35
 
68
- return newSorters;
69
- });
70
- };
36
+ const applySortChange = (
37
+ queue: SortQueueItem[],
38
+ column: ColumnProps,
39
+ order: SortOrder | 0,
40
+ columnMap: Map<string, ColumnProps>,
41
+ ): SortQueueItem[] => {
42
+ const { dataIndex } = column;
43
+ if (!dataIndex) return queue;
44
+ if (order === 0) {
45
+ return queue.filter((item) => item.dataIndex !== dataIndex);
46
+ }
47
+
48
+ const multiple = getSorterConfig(column)?.multiple;
49
+ const nextItem = { dataIndex, order };
50
+ if (!multiple) {
51
+ return [nextItem];
52
+ }
53
+
54
+ const next = queue.filter(
55
+ (item) =>
56
+ item.dataIndex === dataIndex ||
57
+ isMultipleSorter(columnMap.get(item.dataIndex)),
58
+ );
59
+ const currentIndex = next.findIndex((item) => item.dataIndex === dataIndex);
60
+ if (currentIndex === -1) {
61
+ next.push(nextItem);
62
+ } else {
63
+ next[currentIndex] = nextItem;
64
+ }
65
+
66
+ if (typeof multiple === 'number') {
67
+ const limit = Math.max(1, Math.floor(multiple));
68
+ while (next.length > limit) {
69
+ const removeIndex = next.findIndex(
70
+ (item) => item.dataIndex !== dataIndex,
71
+ );
72
+ if (removeIndex === -1) break;
73
+ next.splice(removeIndex, 1);
74
+ }
75
+ }
76
+
77
+ return next;
78
+ };
79
+
80
+ const normalizeQueue = (
81
+ queue: SortQueueItem[],
82
+ columnMap: Map<string, ColumnProps>,
83
+ ) =>
84
+ queue.reduce<SortQueueItem[]>((next, item) => {
85
+ const column = columnMap.get(item.dataIndex);
86
+ return column ? applySortChange(next, column, item.order, columnMap) : next;
87
+ }, []);
71
88
 
72
- const sorted = useMemo(() => {
73
- const activeSorters = Object.entries(sorters).filter(
74
- ([, order]) => order !== 0,
89
+ const areQueuesEqual = (a: SortQueueItem[], b: SortQueueItem[]) =>
90
+ a.length === b.length &&
91
+ a.every(
92
+ (item, index) =>
93
+ item.dataIndex === b[index]?.dataIndex && item.order === b[index]?.order,
94
+ );
95
+
96
+ const createColumnMap = (columns: ColumnProps[]) =>
97
+ new Map(
98
+ columns
99
+ .filter((column) => Boolean(column.dataIndex && column.sorter))
100
+ .map((column) => [column.dataIndex as string, column]),
101
+ );
102
+
103
+ const createInitialQueue = (
104
+ columns: ColumnProps[],
105
+ columnMap: Map<string, ColumnProps>,
106
+ ) =>
107
+ columns.reduce<SortQueueItem[]>((queue, column) => {
108
+ const config = getSorterConfig(column);
109
+ const order = toSortOrder(
110
+ config?.sortOrder !== undefined
111
+ ? config.sortOrder
112
+ : config?.defaultSortOrder,
75
113
  );
114
+ return order ? applySortChange(queue, column, order, columnMap) : queue;
115
+ }, []);
76
116
 
77
- if (activeSorters.length === 0) {
78
- return data;
117
+ const mergeControlledSorters = (
118
+ queue: SortQueueItem[],
119
+ columns: ColumnProps[],
120
+ columnMap: Map<string, ColumnProps>,
121
+ ) => {
122
+ const controlledOrders = new Map<string, SortOrder | 0>();
123
+ columns.forEach((column) => {
124
+ const config = getSorterConfig(column);
125
+ if (column.dataIndex && config?.sortOrder !== undefined) {
126
+ controlledOrders.set(column.dataIndex, toSortOrder(config.sortOrder));
79
127
  }
128
+ });
80
129
 
81
- const sortedData = [...data].sort((a, b) => {
82
- for (const [dataIndex, order] of activeSorters) {
83
- const aValue = a[dataIndex];
84
- const bValue = b[dataIndex];
130
+ const uncontrolledQueue = queue.filter(
131
+ (item) => !controlledOrders.has(item.dataIndex),
132
+ );
133
+ const controlledQueue: SortQueueItem[] = [];
134
+ queue.forEach((item) => {
135
+ const controlledOrder = controlledOrders.get(item.dataIndex);
136
+ if (controlledOrder === undefined) return;
137
+ controlledOrders.delete(item.dataIndex);
138
+ if (controlledOrder) {
139
+ controlledQueue.push({
140
+ dataIndex: item.dataIndex,
141
+ order: controlledOrder,
142
+ });
143
+ }
144
+ });
85
145
 
86
- // Handle undefined/null values
87
- if (aValue === null && bValue === null) continue;
88
- if (aValue === null || aValue === undefined) return 1;
89
- if (bValue === null || bValue === undefined) return -1;
146
+ columns.forEach((column) => {
147
+ const { dataIndex } = column;
148
+ if (!dataIndex) return;
149
+ const controlledOrder = controlledOrders.get(dataIndex);
150
+ if (controlledOrder) {
151
+ controlledQueue.push({ dataIndex, order: controlledOrder });
152
+ }
153
+ });
90
154
 
91
- // Compare values
92
- if (aValue < bValue) {
93
- return order === -1 ? 1 : -1;
94
- }
95
- if (aValue > bValue) {
96
- return order === -1 ? -1 : 1;
155
+ return normalizeQueue([...uncontrolledQueue, ...controlledQueue], columnMap);
156
+ };
157
+
158
+ const queueToSortState = (queue: SortQueueItem[]): SortState =>
159
+ Object.fromEntries(
160
+ queue.map(({ dataIndex, order }) => [dataIndex, order]),
161
+ ) as SortState;
162
+
163
+ const compareValues = (a: unknown, b: unknown) => {
164
+ if (Object.is(a, b)) return 0;
165
+ if (typeof a === 'number' && typeof b === 'number') return a - b;
166
+ if (typeof a === 'bigint' && typeof b === 'bigint') return a < b ? -1 : 1;
167
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
168
+ if (typeof a === 'boolean' && typeof b === 'boolean') {
169
+ return Number(a) - Number(b);
170
+ }
171
+ return String(a).localeCompare(String(b));
172
+ };
173
+
174
+ const sortData = (
175
+ data: Array<Record<string, unknown>>,
176
+ queue: SortQueueItem[],
177
+ columnMap: Map<string, ColumnProps>,
178
+ ) => {
179
+ if (!queue.length) return data;
180
+
181
+ return [...data].sort((a, b) => {
182
+ for (const { dataIndex, order } of queue) {
183
+ const aValue = a[dataIndex];
184
+ const bValue = b[dataIndex];
185
+
186
+ const compare = getSorterConfig(columnMap.get(dataIndex))?.sort;
187
+ if (compare) {
188
+ const result = compare(a, b);
189
+ if (result !== 0) {
190
+ return order === -1 ? -result : result;
97
191
  }
192
+ continue;
98
193
  }
99
- return 0;
194
+
195
+ if (aValue === null && bValue === null) continue;
196
+ if (aValue === null || aValue === undefined) return 1;
197
+ if (bValue === null || bValue === undefined) return -1;
198
+
199
+ const result = compareValues(aValue, bValue);
200
+ if (result !== 0) {
201
+ return order === -1 ? -result : result;
202
+ }
203
+ }
204
+ return 0;
205
+ });
206
+ };
207
+
208
+ export default function useSorter({
209
+ data,
210
+ columns,
211
+ }: {
212
+ data: Array<Record<string, unknown>>;
213
+ columns: Array<ColumnProps>;
214
+ }) {
215
+ const columnMap = useMemo(() => createColumnMap(columns), [columns]);
216
+ const [internalQueue, setInternalQueue] = useState<SortQueueItem[]>(() =>
217
+ createInitialQueue(columns, columnMap),
218
+ );
219
+
220
+ useEffect(() => {
221
+ setInternalQueue((current) => {
222
+ const next = normalizeQueue(current, columnMap);
223
+ return areQueuesEqual(current, next) ? current : next;
100
224
  });
225
+ }, [columnMap]);
226
+
227
+ const activeQueue = useMemo(
228
+ () => mergeControlledSorters(internalQueue, columns, columnMap),
229
+ [internalQueue, columns, columnMap],
230
+ );
231
+ const sorters = useMemo(() => queueToSortState(activeQueue), [activeQueue]);
232
+ const sorted = useMemo(
233
+ () => sortData(data, activeQueue, columnMap),
234
+ [data, activeQueue, columnMap],
235
+ );
236
+
237
+ const onSort = useCallback(
238
+ (dataIndex: string): SortChangeResult => {
239
+ const column = columnMap.get(dataIndex);
240
+ if (!column) return { data: sorted, sorters };
241
+
242
+ const currentOrder =
243
+ activeQueue.find((item) => item.dataIndex === dataIndex)?.order ?? 0;
244
+ const nextQueue = applySortChange(
245
+ activeQueue,
246
+ column,
247
+ getNextSortOrder(currentOrder),
248
+ columnMap,
249
+ );
250
+ const nextSorters = queueToSortState(nextQueue);
251
+ const nextData = sortData(data, nextQueue, columnMap);
101
252
 
102
- return sortedData;
103
- }, [data, sorters]);
253
+ setInternalQueue(nextQueue);
254
+ return { data: nextData, sorters: nextSorters };
255
+ },
256
+ [activeQueue, columnMap, data, sorted, sorters],
257
+ );
104
258
 
105
259
  return {
106
260
  data: sorted,
@@ -24,7 +24,7 @@ export interface ExpandableType {
24
24
  /**
25
25
  * Default expanded rows
26
26
  */
27
- defaultExpandedRowKeys?: string[]; // Default expanded rows
27
+ defaultExpandedRowKeys?: SelectionKeyType[]; // Default expanded rows
28
28
  /**
29
29
  * Expanded rows
30
30
  */
@@ -57,6 +57,7 @@ export interface ExpandableType {
57
57
  }
58
58
 
59
59
  export type SelectionKeyType = string | number;
60
+ export type FilterValue = string | number;
60
61
 
61
62
  export type ColumnSemanticType = 'inner' | 'action';
62
63
 
@@ -210,7 +211,7 @@ export interface ColumnProps {
210
211
  /**
211
212
  * Filter callback function
212
213
  */
213
- onFilter?: (value: string, record: Record<string, any>) => boolean;
214
+ onFilter?: (value: FilterValue, record: Record<string, any>) => boolean;
214
215
  };
215
216
  /**
216
217
  * Sorter config
@@ -221,9 +222,13 @@ export interface ColumnProps {
221
222
  /**
222
223
  * Sort function
223
224
  */
224
- sort?: () => number | boolean;
225
+ sort?: (
226
+ a: Record<string, unknown>,
227
+ b: Record<string, unknown>,
228
+ ) => number;
225
229
  /**
226
- * Whether to support multi-column sort, if set to a number, it indicates the number of sortable columns
230
+ * Enable multi-column sorting. A positive number limits the active
231
+ * sorters; when exceeded, the earliest activated sorter is removed.
227
232
  */
228
233
  multiple?: boolean | number;
229
234
  /**
@@ -405,6 +410,7 @@ export interface TableRowProps {
405
410
  pageSize: number;
406
411
  index: number;
407
412
  record: Record<string, any>;
413
+ rowKeyValue: SelectionKeyType;
408
414
  columns: ColumnProps[];
409
415
  expandable?: ExpandableType;
410
416
  expandedRowKeys?: Array<string | number>;
@@ -436,7 +442,10 @@ export interface TableRowProps {
436
442
  index: number;
437
443
  }) => void;
438
444
  // Checkbox or Radio onChange event
439
- onSelectionChange: (event: ChangeEvent<HTMLInputElement>) => void;
445
+ onSelectionChange: (
446
+ event: ChangeEvent<HTMLInputElement>,
447
+ key: SelectionKeyType,
448
+ ) => void;
440
449
  }
441
450
 
442
451
  type LazyLoadType = {
@@ -452,7 +461,10 @@ export interface LazyRowProps extends TableRowProps {
452
461
  lazyLoad?: boolean | LazyLoadType;
453
462
  }
454
463
 
455
- export interface TableBodyProps extends Omit<LazyRowProps, 'index' | 'record'> {
464
+ export interface TableBodyProps extends Omit<
465
+ LazyRowProps,
466
+ 'index' | 'record' | 'rowKeyValue'
467
+ > {
456
468
  pageData: Array<Record<string, any>>;
457
469
  noData?: React.ReactNode;
458
470
  }
@@ -619,7 +631,7 @@ export interface TableProps
619
631
  * Set the unique key for rows
620
632
  * @default id
621
633
  */
622
- rowKey?: string | ((record: Record<string, any>) => string);
634
+ rowKey?: string | ((record: Record<string, any>) => SelectionKeyType);
623
635
  /**
624
636
  * Summary bar
625
637
  */
@@ -83,15 +83,28 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
83
83
  });
84
84
 
85
85
  // Filter
86
- const { data: filteredData, onFilter: onFilterInternal } = useFilter({
86
+ const {
87
+ data: filteredData,
88
+ filterRecords,
89
+ onFilter: onFilterInternal,
90
+ } = useFilter({
87
91
  data: dataSource,
92
+ columns: wrapedColumns,
88
93
  });
89
94
 
95
+ const filters = useMemo(
96
+ () =>
97
+ Object.fromEntries(
98
+ filterRecords.map(({ dataIndex, value }) => [dataIndex, value]),
99
+ ),
100
+ [filterRecords],
101
+ );
102
+
90
103
  // Sort
91
104
  const {
92
105
  data: sortedData,
93
106
  sorters,
94
- onSort,
107
+ onSort: onSortInternal,
95
108
  } = useSorter({ data: filteredData, columns: wrapedColumns });
96
109
  // Pagination
97
110
  const { pageData, current, pageSize, pagination } = usePagination({
@@ -102,7 +115,7 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
102
115
  onChange({
103
116
  pagination: info,
104
117
  sorter: sorters,
105
- filters: undefined, // TODO: Extract filter info from onFilterInternal
118
+ filters,
106
119
  currentDataSource: sortedData,
107
120
  action: 'paginate',
108
121
  });
@@ -110,6 +123,20 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
110
123
  : undefined,
111
124
  });
112
125
 
126
+ const handleSort = React.useCallback(
127
+ (dataIndex: string) => {
128
+ const next = onSortInternal(dataIndex);
129
+ onChange?.({
130
+ pagination: pagination === false ? undefined : { current, pageSize },
131
+ sorter: next.sorters,
132
+ filters,
133
+ currentDataSource: next.data,
134
+ action: 'sort',
135
+ });
136
+ },
137
+ [onSortInternal, onChange, pagination, current, pageSize, filters],
138
+ );
139
+
113
140
  // Expanded row data
114
141
  const { expandedRowKeys, onExpand } = useExpand(
115
142
  expandable,
@@ -124,8 +151,8 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
124
151
  onChange: onSelectionChange,
125
152
  } = useSelection({ pageData, dataSource, getRowKey, rowSelection, allKeys });
126
153
 
127
- // Track previous values to determine what action triggered onChange
128
- const prevSortersRef = useRef<typeof sorters>({});
154
+ // Track filter changes to emit onChange after filtered data is recalculated
155
+ const prevFilterRecordsRef = useRef(filterRecords);
129
156
  const contentWrapperRef = useRef<HTMLDivElement>(null);
130
157
  const contentRef = useRef<HTMLDivElement>(null);
131
158
  const [horizontalScroll, setHorizontalScroll] = React.useState({
@@ -156,51 +183,29 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
156
183
  );
157
184
  }, []);
158
185
 
159
- // Wrap onFilter to trigger onChange
160
- const onFilter = React.useCallback(
161
- (...args: Parameters<typeof onFilterInternal>) => {
162
- onFilterInternal(...args);
163
- // Trigger onChange for filter action
164
- if (onChange) {
165
- onChange({
166
- pagination: pagination === false ? undefined : { current, pageSize },
167
- sorter: sorters,
168
- filters: undefined, // TODO: Extract filter info from args
169
- currentDataSource: filteredData,
170
- action: 'filter',
171
- });
172
- }
173
- },
174
- [
175
- onChange,
176
- pagination,
177
- current,
178
- pageSize,
179
- sorters,
180
- filteredData,
181
- onFilterInternal,
182
- ],
183
- );
184
-
185
- // Trigger onChange when sorter, pagination, or filter changes
186
186
  useEffect(() => {
187
+ if (prevFilterRecordsRef.current === filterRecords) return;
188
+
189
+ prevFilterRecordsRef.current = filterRecords;
187
190
  if (!onChange) return;
188
191
 
189
- // Check if sorters changed
190
- const sortersChanged =
191
- JSON.stringify(prevSortersRef.current) !== JSON.stringify(sorters);
192
- if (sortersChanged && prevSortersRef.current !== sorters) {
193
- prevSortersRef.current = sorters;
194
- onChange({
195
- pagination: pagination === false ? undefined : { current, pageSize },
196
- sorter: sorters,
197
- filters: undefined,
198
- currentDataSource: sortedData,
199
- action: 'sort',
200
- });
201
- return;
202
- }
203
- }, [sorters, current, pageSize, pageData, sortedData, onChange]);
192
+ onChange({
193
+ pagination: pagination === false ? undefined : { current, pageSize },
194
+ sorter: sorters,
195
+ filters,
196
+ currentDataSource: sortedData,
197
+ action: 'filter',
198
+ });
199
+ }, [
200
+ filterRecords,
201
+ filters,
202
+ sortedData,
203
+ pagination,
204
+ current,
205
+ pageSize,
206
+ sorters,
207
+ onChange,
208
+ ]);
204
209
 
205
210
  useEffect(() => {
206
211
  updateHorizontalScroll();
@@ -245,9 +250,9 @@ const Table = React.forwardRef<HTMLDivElement, TableProps>((baseprops, ref) => {
245
250
  headerRows={headerRows}
246
251
  checkedAll={checkedAll}
247
252
  sorters={sorters}
248
- onSort={onSort}
253
+ onSort={handleSort}
249
254
  onCheckAll={onCheckAll}
250
- onInternalFilter={onFilter}
255
+ onInternalFilter={onFilterInternal}
251
256
  />
252
257
  );
253
258
  };
@@ -31,19 +31,27 @@ const TableBody: React.FC<TableBodyProps> = (props) => {
31
31
 
32
32
  const getRenderRows = () => {
33
33
  if (lazyLoad) {
34
- return <LazyRow {...rest} pageData={pageData} columns={columns} />;
34
+ return (
35
+ <LazyRow
36
+ {...rest}
37
+ pageData={pageData}
38
+ columns={columns}
39
+ resolveRowKey={resolveRowKey}
40
+ />
41
+ );
35
42
  }
36
43
  return pageData.map((record: Record<string, any>, index: number) => {
44
+ const resolvedRowKey = resolveRowKey(record);
45
+
37
46
  // 整行接管:由外部组件渲染 <tr> + 单元格(可放拖拽手柄等)
38
47
  if (RowRenderer) {
39
- const rk = resolveRowKey(record);
40
48
  return (
41
49
  <RowRenderer
42
- key={rk ?? index}
50
+ key={resolvedRowKey ?? index}
43
51
  record={record}
44
52
  index={index}
45
53
  columns={columns}
46
- rowKey={rk}
54
+ rowKey={resolvedRowKey}
47
55
  />
48
56
  );
49
57
  }
@@ -52,8 +60,9 @@ const TableBody: React.FC<TableBodyProps> = (props) => {
52
60
  {...rest}
53
61
  columns={columns}
54
62
  record={record}
63
+ rowKeyValue={resolvedRowKey}
55
64
  index={index}
56
- key={`row-${record.key || index}`}
65
+ key={resolvedRowKey ?? `row-${index}`}
57
66
  />
58
67
  );
59
68
  });
@@ -6,7 +6,7 @@ const createBlank = (size: number, key: string) => {
6
6
  : [];
7
7
  };
8
8
  const LazyRow = (props: any) => {
9
- const { range, pageData, columns, ...rest } = props;
9
+ const { range, pageData, columns, resolveRowKey, ...rest } = props;
10
10
  const { start, end } = range;
11
11
  let result: any = [];
12
12
  const pl = pageData.length;
@@ -14,13 +14,16 @@ const LazyRow = (props: any) => {
14
14
  result = result.concat(startBlank);
15
15
 
16
16
  for (let i = start; i < (end > pl ? pl : end); i++) {
17
+ const record = pageData[i];
18
+ const rowKeyValue = resolveRowKey(record);
17
19
  result.push(
18
20
  <TableRow
19
21
  {...rest}
20
22
  columns={columns}
21
- record={pageData[i]}
23
+ record={record}
24
+ rowKeyValue={rowKeyValue}
22
25
  index={i}
23
- key={'row-' + i}
26
+ key={rowKeyValue ?? `row-${i}`}
24
27
  />,
25
28
  );
26
29
  }
@@ -14,6 +14,7 @@ const TableRow: React.FC<TableRowProps> = (props) => {
14
14
  const {
15
15
  index,
16
16
  record,
17
+ rowKeyValue,
17
18
  columns,
18
19
  highlight,
19
20
  stripe,
@@ -30,10 +31,10 @@ const TableRow: React.FC<TableRowProps> = (props) => {
30
31
  ...rest
31
32
  } = props;
32
33
 
33
- const { prefixCls, rowKey } = useContext(TableCtx);
34
+ const { prefixCls } = useContext(TableCtx);
34
35
 
35
36
  const expanded = expandedRowKeys?.includes(
36
- expandedKeyType === 'index' ? index + 1 : record[rowKey],
37
+ expandedKeyType === 'index' ? index + 1 : rowKeyValue,
37
38
  );
38
39
 
39
40
  const high = React.useMemo(() => {
@@ -57,7 +58,6 @@ const TableRow: React.FC<TableRowProps> = (props) => {
57
58
 
58
59
  const getSelection = (column: WrapedColumnProps) => {
59
60
  const { type, disabled: colDisabled, onCell } = column;
60
- const rowKeyValue = record[rowKey];
61
61
  const props = (onCell?.(record, index) || {}) as Record<string, unknown>;
62
62
  const checked = selectedRowKeys?.includes(rowKeyValue);
63
63
 
@@ -75,7 +75,8 @@ const TableRow: React.FC<TableRowProps> = (props) => {
75
75
  const commonProps = {
76
76
  checked,
77
77
  value: rowKeyValue,
78
- onChange: onSelectionChange,
78
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
79
+ onSelectionChange(event, rowKeyValue),
79
80
  disabled,
80
81
  ...props,
81
82
  };