@rainbow-oh/yee-c 0.8.11 → 0.8.13

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 (67) hide show
  1. package/dist/cjs/Table/col-group.d.ts +2 -2
  2. package/dist/cjs/Table/col-group.js +1 -1
  3. package/dist/cjs/Table/demo/sorter-callback.js +3 -2
  4. package/dist/cjs/Table/demo/sorter.js +3 -2
  5. package/dist/cjs/Table/hooks/useExpand.d.ts +2 -2
  6. package/dist/cjs/Table/hooks/useFilter.d.ts +10 -2
  7. package/dist/cjs/Table/hooks/useFilter.js +44 -31
  8. package/dist/cjs/Table/hooks/usePagination.js +8 -8
  9. package/dist/cjs/Table/hooks/useSelection.d.ts +3 -3
  10. package/dist/cjs/Table/hooks/useSelection.js +8 -9
  11. package/dist/cjs/Table/hooks/useSorter.d.ts +6 -3
  12. package/dist/cjs/Table/hooks/useSorter.js +193 -87
  13. package/dist/cjs/Table/interface.d.ts +10 -7
  14. package/dist/cjs/Table/table.js +51 -45
  15. package/dist/cjs/Table/tbody/index.js +13 -5
  16. package/dist/cjs/Table/tbody/lazy-row.js +6 -3
  17. package/dist/cjs/Table/tbody/row.js +5 -4
  18. package/dist/cjs/Table/theader/cell.js +4 -0
  19. package/dist/cjs/Table/theader/filter.js +52 -20
  20. package/dist/cjs/Table/util/index.js +5 -3
  21. package/dist/cjs/style/themes.css +1 -1
  22. package/dist/cjs/style/variables.css +1 -1
  23. package/dist/cjs/version.d.ts +1 -1
  24. package/dist/cjs/version.js +1 -1
  25. package/dist/esm/Table/col-group.d.ts +2 -2
  26. package/dist/esm/Table/col-group.js +1 -1
  27. package/dist/esm/Table/demo/sorter-callback.js +3 -2
  28. package/dist/esm/Table/demo/sorter.js +3 -2
  29. package/dist/esm/Table/hooks/useExpand.d.ts +2 -2
  30. package/dist/esm/Table/hooks/useFilter.d.ts +10 -2
  31. package/dist/esm/Table/hooks/useFilter.js +63 -36
  32. package/dist/esm/Table/hooks/usePagination.js +8 -10
  33. package/dist/esm/Table/hooks/useSelection.d.ts +3 -3
  34. package/dist/esm/Table/hooks/useSelection.js +16 -11
  35. package/dist/esm/Table/hooks/useSorter.d.ts +6 -3
  36. package/dist/esm/Table/hooks/useSorter.js +218 -105
  37. package/dist/esm/Table/interface.d.ts +10 -7
  38. package/dist/esm/Table/table.js +42 -47
  39. package/dist/esm/Table/tbody/index.js +8 -5
  40. package/dist/esm/Table/tbody/lazy-row.js +7 -3
  41. package/dist/esm/Table/tbody/row.js +7 -6
  42. package/dist/esm/Table/theader/cell.js +7 -1
  43. package/dist/esm/Table/theader/filter.js +75 -27
  44. package/dist/esm/Table/util/index.js +6 -3
  45. package/dist/esm/style/themes.css +1 -1
  46. package/dist/esm/style/variables.css +1 -1
  47. package/dist/esm/version.d.ts +1 -1
  48. package/dist/esm/version.js +1 -1
  49. package/dist/umd/yee-c.min.js +1 -1
  50. package/package.json +1 -1
  51. package/src/Table/col-group.tsx +1 -1
  52. package/src/Table/demo/sorter-callback.tsx +3 -2
  53. package/src/Table/demo/sorter.tsx +3 -2
  54. package/src/Table/hooks/useExpand.ts +3 -3
  55. package/src/Table/hooks/useFilter.ts +66 -33
  56. package/src/Table/hooks/usePagination.ts +9 -10
  57. package/src/Table/hooks/useSelection.ts +18 -20
  58. package/src/Table/hooks/useSorter.ts +236 -82
  59. package/src/Table/interface.ts +19 -7
  60. package/src/Table/table.tsx +54 -49
  61. package/src/Table/tbody/index.tsx +14 -5
  62. package/src/Table/tbody/lazy-row.tsx +6 -3
  63. package/src/Table/tbody/row.tsx +5 -4
  64. package/src/Table/theader/cell.tsx +10 -0
  65. package/src/Table/theader/filter.tsx +64 -22
  66. package/src/Table/util/index.ts +3 -1
  67. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-oh/yee-c",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
4
4
  "description": "yee basic components",
5
5
  "keywords": [
6
6
  "react",
@@ -39,4 +39,4 @@ const ColGroup = React.memo(({ columns }: TableColGroupProps) => {
39
39
 
40
40
  ColGroup.displayName = 'ColGroup';
41
41
 
42
- export default React.memo(ColGroup);
42
+ export default ColGroup;
@@ -23,8 +23,9 @@ export default () => {
23
23
  title: 'Score',
24
24
  dataIndex: 'score',
25
25
  key: 'score',
26
- sorter: true,
27
- defaultSortOrder: 'descend' as const,
26
+ sorter: {
27
+ defaultSortOrder: 'descend' as const,
28
+ },
28
29
  },
29
30
  {
30
31
  title: 'Address',
@@ -13,8 +13,9 @@ export default () => {
13
13
  title: 'Age',
14
14
  dataIndex: 'age',
15
15
  key: 'age',
16
- sorter: true,
17
- defaultSortOrder: 'descend' as const,
16
+ sorter: {
17
+ defaultSortOrder: 'descend' as const,
18
+ },
18
19
  },
19
20
  {
20
21
  title: 'Address',
@@ -1,10 +1,10 @@
1
1
  import useMergedState from '../../hooks/useMergedState';
2
- import { ExpandableType } from '../interface';
2
+ import { ExpandableType, SelectionKeyType } from '../interface';
3
3
 
4
4
  export default function useExpand(
5
5
  expandable: ExpandableType | undefined,
6
- allKeys: string[],
7
- getRowKey: (record: Record<string, any>) => string,
6
+ allKeys: SelectionKeyType[],
7
+ getRowKey: (record: Record<string, any>) => SelectionKeyType,
8
8
  ) {
9
9
  const {
10
10
  expandedRowKeys, // keys
@@ -1,68 +1,101 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import type { ColumnProps, FilterValue } from '../interface';
3
+
4
+ type FilterInput = FilterValue | FilterValue[];
2
5
 
3
6
  type FilterOptions = {
4
7
  dataIndex: string;
5
- value: string;
8
+ value: FilterInput;
6
9
  };
7
10
 
8
11
  type FilterRecord = {
9
- value: string;
12
+ value: FilterInput;
10
13
  dataIndex: string;
11
14
  };
12
15
 
16
+ const isEmptyFilterValue = (value: FilterInput) =>
17
+ value === '' || (Array.isArray(value) && value.length === 0);
18
+
19
+ const normalizeValue = (value: unknown) =>
20
+ value === null || value === undefined ? '' : String(value).toLowerCase();
21
+
13
22
  export default function useFilter({
14
23
  data,
24
+ columns,
15
25
  }: {
16
26
  data: Array<Record<string, unknown>>;
27
+ columns: ColumnProps[];
17
28
  }) {
18
29
  const [filterRecords, setFilterRecords] = useState<Array<FilterRecord>>([]);
30
+ const columnMap = useMemo(
31
+ () =>
32
+ new Map(
33
+ columns.flatMap((column) =>
34
+ column.dataIndex ? [[column.dataIndex, column] as const] : [],
35
+ ),
36
+ ),
37
+ [columns],
38
+ );
39
+
40
+ useEffect(() => {
41
+ setFilterRecords((current) => {
42
+ const next = current.filter(({ dataIndex }) => columnMap.has(dataIndex));
43
+ return next.length === current.length ? current : next;
44
+ });
45
+ }, [columnMap]);
46
+
19
47
  const onFilter = ({ value, dataIndex }: FilterOptions) => {
20
- let newFilterMap = [...filterRecords];
21
- if (!value || (Array.isArray(value) && !value.length)) {
22
- newFilterMap = newFilterMap.filter(
23
- (item) => item.dataIndex !== dataIndex,
24
- );
25
- } else {
26
- let exited = false;
27
- newFilterMap = newFilterMap.map((item) => {
28
- if (item.dataIndex === dataIndex) {
29
- exited = true;
30
- return { ...item, value };
31
- }
32
- return item;
33
- });
34
- if (!exited) {
35
- newFilterMap.push({ dataIndex, value });
48
+ setFilterRecords((current) => {
49
+ if (isEmptyFilterValue(value)) {
50
+ return current.filter((item) => item.dataIndex !== dataIndex);
36
51
  }
37
- }
38
- setFilterRecords(newFilterMap);
52
+
53
+ const index = current.findIndex((item) => item.dataIndex === dataIndex);
54
+ if (index === -1) {
55
+ return [...current, { dataIndex, value }];
56
+ }
57
+
58
+ return current.map((item, itemIndex) =>
59
+ itemIndex === index ? { ...item, value } : item,
60
+ );
61
+ });
39
62
  };
40
63
 
41
64
  const filtered = useMemo(() => {
42
65
  const handled = filterRecords.filter(
43
- (item) => item.dataIndex && item.value,
66
+ ({ dataIndex, value }) =>
67
+ Boolean(dataIndex) &&
68
+ columnMap.has(dataIndex) &&
69
+ !isEmptyFilterValue(value),
44
70
  );
45
71
  if (!handled.length) {
46
72
  return data;
47
73
  }
48
74
  return data.filter((item) => {
49
- return handled.find((o) => {
50
- if (Array.isArray(o.value)) {
51
- return o.value.some((v) => {
52
- const t = item[o.dataIndex] || '';
53
- return (t as string).toLowerCase() === v.toLowerCase();
54
- });
55
- } else {
56
- const v = o.value.toLowerCase();
57
- const t = item[o.dataIndex] || '';
58
- return (t as string).toLowerCase().includes(v);
75
+ return handled.every(({ dataIndex, value }) => {
76
+ const customFilter = columnMap.get(dataIndex)?.filter?.onFilter;
77
+ if (customFilter) {
78
+ return Array.isArray(value)
79
+ ? value.some((filterValue) => customFilter(filterValue, item))
80
+ : customFilter(value, item);
59
81
  }
82
+
83
+ const targetValue = normalizeValue(item[dataIndex]);
84
+
85
+ if (Array.isArray(value)) {
86
+ return value.some(
87
+ (filterValue) => targetValue === normalizeValue(filterValue),
88
+ );
89
+ }
90
+
91
+ return targetValue.includes(normalizeValue(value));
60
92
  });
61
93
  });
62
- }, [data, filterRecords]);
94
+ }, [columnMap, data, filterRecords]);
63
95
 
64
96
  return {
65
97
  data: filtered,
98
+ filterRecords,
66
99
  onFilter,
67
100
  };
68
101
  }
@@ -81,16 +81,6 @@ export default function usePagination({
81
81
  });
82
82
  };
83
83
 
84
- if (pagination === false) {
85
- return {
86
- pageData: data,
87
- current: 1,
88
- pageSize: data.length,
89
- pagination: false,
90
- };
91
- }
92
-
93
- // eslint-disable-next-line react-hooks/rules-of-hooks
94
84
  const pageData = useMemo(() => {
95
85
  const total = data.length;
96
86
  if (total <= mergedPageSize) return data;
@@ -103,6 +93,15 @@ export default function usePagination({
103
93
  return data.slice(start, start + mergedPageSize);
104
94
  }, [data, meregdCurrent, mergedPageSize]);
105
95
 
96
+ if (pagination === false) {
97
+ return {
98
+ pageData: data,
99
+ current: 1,
100
+ pageSize: data.length,
101
+ pagination: false,
102
+ };
103
+ }
104
+
106
105
  return {
107
106
  pageData,
108
107
  current: meregdCurrent,
@@ -11,7 +11,7 @@ export default function useSelection({
11
11
  }: {
12
12
  pageData: Array<Record<string, unknown>>;
13
13
  dataSource: Array<Record<string, unknown>>;
14
- getRowKey: (record: Record<string, unknown>) => string;
14
+ getRowKey: (record: Record<string, unknown>) => SelectionKeyType;
15
15
  allKeys: Array<SelectionKeyType>;
16
16
  rowSelection?: RowSelectionType;
17
17
  }) {
@@ -43,29 +43,25 @@ export default function useSelection({
43
43
  onChange?.(rowKeys as any, rowRecords);
44
44
  };
45
45
 
46
- const getDisabledKeys = () => {
46
+ const getDisabledKeys = (): SelectionKeyType[] => {
47
+ if (disabled === true) {
48
+ return pageData.map((record) => getRowKey(record));
49
+ }
47
50
  if (Array.isArray(disabled)) {
48
- return disabled;
51
+ return pageData
52
+ .filter((_, index) => disabled[index])
53
+ .map((record) => getRowKey(record));
49
54
  }
50
55
  if (typeof disabled === 'function') {
51
- return (
52
- Array.isArray(pageData) &&
53
- pageData
54
- .filter((record, index) => disabled(record, index))
55
- .map((record) => getRowKey(record))
56
- );
56
+ return pageData
57
+ .filter((record, index) => disabled(record, index))
58
+ .map((record) => getRowKey(record));
57
59
  }
58
60
  return [];
59
61
  };
60
62
 
61
- const getPageDataRowKeys = () => {
62
- return (
63
- Array.isArray(pageData) && pageData.map((record) => getRowKey(record))
64
- );
65
- };
66
-
67
- const disabledKeys = getDisabledKeys() as Array<string>;
68
- const pageDataRowKeys = getPageDataRowKeys() as Array<string>;
63
+ const disabledKeys = getDisabledKeys();
64
+ const pageDataRowKeys = pageData.map((record) => getRowKey(record));
69
65
 
70
66
  const isCheckedAll = () => {
71
67
  const filtered = pageDataRowKeys.filter(
@@ -109,7 +105,7 @@ export default function useSelection({
109
105
  setMergedSelectedRowKeys(rowKeys);
110
106
 
111
107
  onChange?.(rowKeys, rowRecords);
112
- onSelectAll?.(rowKeys as any, rowRecords as any, checked);
108
+ onSelectAll?.(checked, rowKeys, rowRecords);
113
109
  };
114
110
 
115
111
  const onRadioCheck = (checked: boolean, key: number | string) => {
@@ -118,8 +114,10 @@ export default function useSelection({
118
114
  onChange?.([key as any], record as any);
119
115
  };
120
116
 
121
- const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
122
- const key = event.target.value;
117
+ const handleChange = (
118
+ event: ChangeEvent<HTMLInputElement>,
119
+ key: SelectionKeyType,
120
+ ) => {
123
121
  const checked = event.target.checked;
124
122
  if (type === 'checkbox') {
125
123
  onCheck(checked, key);
@@ -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,