@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.
- package/dist/cjs/Table/col-group.d.ts +2 -2
- package/dist/cjs/Table/col-group.js +1 -1
- package/dist/cjs/Table/demo/sorter-callback.js +3 -2
- package/dist/cjs/Table/demo/sorter.js +3 -2
- package/dist/cjs/Table/hooks/useExpand.d.ts +2 -2
- package/dist/cjs/Table/hooks/useFilter.d.ts +10 -2
- package/dist/cjs/Table/hooks/useFilter.js +44 -31
- package/dist/cjs/Table/hooks/usePagination.js +8 -8
- package/dist/cjs/Table/hooks/useSelection.d.ts +3 -3
- package/dist/cjs/Table/hooks/useSelection.js +8 -9
- package/dist/cjs/Table/hooks/useSorter.d.ts +6 -3
- package/dist/cjs/Table/hooks/useSorter.js +193 -87
- package/dist/cjs/Table/interface.d.ts +10 -7
- package/dist/cjs/Table/table.js +51 -45
- package/dist/cjs/Table/tbody/index.js +13 -5
- package/dist/cjs/Table/tbody/lazy-row.js +6 -3
- package/dist/cjs/Table/tbody/row.js +5 -4
- package/dist/cjs/Table/theader/cell.js +4 -0
- package/dist/cjs/Table/theader/filter.js +52 -20
- package/dist/cjs/Table/util/index.js +5 -3
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Table/col-group.d.ts +2 -2
- package/dist/esm/Table/col-group.js +1 -1
- package/dist/esm/Table/demo/sorter-callback.js +3 -2
- package/dist/esm/Table/demo/sorter.js +3 -2
- package/dist/esm/Table/hooks/useExpand.d.ts +2 -2
- package/dist/esm/Table/hooks/useFilter.d.ts +10 -2
- package/dist/esm/Table/hooks/useFilter.js +63 -36
- package/dist/esm/Table/hooks/usePagination.js +8 -10
- package/dist/esm/Table/hooks/useSelection.d.ts +3 -3
- package/dist/esm/Table/hooks/useSelection.js +16 -11
- package/dist/esm/Table/hooks/useSorter.d.ts +6 -3
- package/dist/esm/Table/hooks/useSorter.js +218 -105
- package/dist/esm/Table/interface.d.ts +10 -7
- package/dist/esm/Table/table.js +42 -47
- package/dist/esm/Table/tbody/index.js +8 -5
- package/dist/esm/Table/tbody/lazy-row.js +7 -3
- package/dist/esm/Table/tbody/row.js +7 -6
- package/dist/esm/Table/theader/cell.js +7 -1
- package/dist/esm/Table/theader/filter.js +75 -27
- package/dist/esm/Table/util/index.js +6 -3
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Table/col-group.tsx +1 -1
- package/src/Table/demo/sorter-callback.tsx +3 -2
- package/src/Table/demo/sorter.tsx +3 -2
- package/src/Table/hooks/useExpand.ts +3 -3
- package/src/Table/hooks/useFilter.ts +66 -33
- package/src/Table/hooks/usePagination.ts +9 -10
- package/src/Table/hooks/useSelection.ts +18 -20
- package/src/Table/hooks/useSorter.ts +236 -82
- package/src/Table/interface.ts +19 -7
- package/src/Table/table.tsx +54 -49
- package/src/Table/tbody/index.tsx +14 -5
- package/src/Table/tbody/lazy-row.tsx +6 -3
- package/src/Table/tbody/row.tsx +5 -4
- package/src/Table/theader/cell.tsx +10 -0
- package/src/Table/theader/filter.tsx +64 -22
- package/src/Table/util/index.ts +3 -1
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/Table/col-group.tsx
CHANGED
|
@@ -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:
|
|
7
|
-
getRowKey: (record: Record<string, any>) =>
|
|
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:
|
|
8
|
+
value: FilterInput;
|
|
6
9
|
};
|
|
7
10
|
|
|
8
11
|
type FilterRecord = {
|
|
9
|
-
value:
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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>) =>
|
|
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
|
|
51
|
+
return pageData
|
|
52
|
+
.filter((_, index) => disabled[index])
|
|
53
|
+
.map((record) => getRowKey(record));
|
|
49
54
|
}
|
|
50
55
|
if (typeof disabled === 'function') {
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
62
|
-
|
|
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
|
|
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 = (
|
|
122
|
-
|
|
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
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
const getSorterConfig = (column?: ColumnProps): SorterConfig | undefined =>
|
|
17
|
+
column && typeof column.sorter === 'object' ? column.sorter : undefined;
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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,
|