@react-fabric/data 1.0.0-beta.54 → 1.0.0-beta.55
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/hooks/useFilteredList.js +68 -10
- package/dist/cjs/table/HeaderCell.js +3 -2
- package/dist/cjs/table/Table.js +7 -1
- package/dist/cjs/table/useVirtualTable.js +18 -0
- package/dist/cjs/tree/TreeNode.js +2 -2
- package/dist/cjs/tree/TreePanel.js +5 -4
- package/dist/cjs/tree/useTree.js +62 -15
- package/dist/cjs/tree/utils.js +3 -3
- package/dist/cjs/virtual/Gallery.js +2 -2
- package/dist/cjs/virtual/List.js +2 -2
- package/dist/esm/hooks/useFilteredList.js +69 -11
- package/dist/esm/table/HeaderCell.js +3 -2
- package/dist/esm/table/Table.js +7 -1
- package/dist/esm/table/useVirtualTable.js +19 -1
- package/dist/esm/tree/TreeNode.js +2 -2
- package/dist/esm/tree/TreePanel.js +5 -4
- package/dist/esm/tree/useTree.js +63 -16
- package/dist/esm/tree/utils.js +3 -3
- package/dist/esm/virtual/Gallery.js +2 -2
- package/dist/esm/virtual/List.js +2 -2
- package/dist/types/hooks/useFilteredList.d.ts +49 -6
- package/dist/types/table/HeaderCell.d.ts +1 -1
- package/dist/types/table/Table.d.ts +1 -1
- package/dist/types/table/types.d.ts +3 -0
- package/dist/types/table/useVirtualTable.d.ts +3 -0
- package/dist/types/tree/TreePanel.d.ts +1 -1
- package/dist/types/tree/types.d.ts +11 -13
- package/dist/types/tree/useTree.d.ts +1 -0
- package/dist/types/tree/utils.d.ts +1 -1
- package/package.json +5 -5
- package/src/hooks/useFilteredList.tsx +90 -9
- package/src/table/HeaderCell.tsx +3 -2
- package/src/table/Table.tsx +5 -0
- package/src/table/types.ts +4 -0
- package/src/table/useVirtualTable.ts +24 -1
- package/src/tree/TreeNode.tsx +3 -5
- package/src/tree/TreePanel.tsx +3 -3
- package/src/tree/types.ts +12 -12
- package/src/tree/useTree.ts +57 -13
- package/src/tree/utils.ts +8 -3
- package/src/virtual/Gallery.tsx +1 -1
- package/src/virtual/List.tsx +1 -1
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { isString, matchString } from "@react-fabric/utilities";
|
|
24
|
+
import { isArray, isString, matchString } from "@react-fabric/utilities";
|
|
25
25
|
import {
|
|
26
26
|
useCallback,
|
|
27
27
|
useDeferredValue,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
const filterList = (
|
|
34
34
|
items: AnyObject[],
|
|
35
35
|
query: AnyObject = "",
|
|
36
|
+
matchAll: boolean = true,
|
|
36
37
|
matcher?: AnyObject,
|
|
37
38
|
) => {
|
|
38
39
|
const newList: AnyObject[] = [];
|
|
@@ -40,7 +41,20 @@ const filterList = (
|
|
|
40
41
|
if (matcher) {
|
|
41
42
|
return matcher?.(item, query) && newList.push(item);
|
|
42
43
|
}
|
|
43
|
-
if (query &&
|
|
44
|
+
if (query && isArray(query) && query.length > 0) {
|
|
45
|
+
if (
|
|
46
|
+
matchAll &&
|
|
47
|
+
query.every((q) => matchString(item.label ?? item.toString(), q))
|
|
48
|
+
) {
|
|
49
|
+
return newList.push(item);
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
!matchAll &&
|
|
53
|
+
query.some((q) => matchString(item.label ?? item.toString(), q))
|
|
54
|
+
) {
|
|
55
|
+
return newList.push(item);
|
|
56
|
+
}
|
|
57
|
+
} else if (query && isString(query)) {
|
|
44
58
|
if (matchString(item.label ?? item.toString(), query)) {
|
|
45
59
|
return newList.push(item);
|
|
46
60
|
}
|
|
@@ -51,6 +65,61 @@ const filterList = (
|
|
|
51
65
|
return newList;
|
|
52
66
|
};
|
|
53
67
|
|
|
68
|
+
interface RetType<T, Q> {
|
|
69
|
+
onSearch: (query?: Q) => void;
|
|
70
|
+
query: Q;
|
|
71
|
+
filteredList: T[];
|
|
72
|
+
isSearching: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Custom hook to filter a list of items based on a query.
|
|
77
|
+
* It provides a way to search through the items and returns the filtered list.
|
|
78
|
+
* The hook uses a deferred value to optimize performance during transitions.
|
|
79
|
+
* It also allows for a custom matcher function to define how items should be matched against the query.
|
|
80
|
+
*
|
|
81
|
+
* @param items - The list of items to filter.
|
|
82
|
+
* @param matchAll - Match all phrases in the query list against the items.
|
|
83
|
+
* @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```jsx
|
|
87
|
+
* useFilteredList(items, (item, query) => {
|
|
88
|
+
* return item.name.includes(query);
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-usefilteredlist--docs}
|
|
93
|
+
*/
|
|
94
|
+
export function useFilteredList<
|
|
95
|
+
T extends AnyObject = KeyValue,
|
|
96
|
+
Q extends AnyObject = string,
|
|
97
|
+
>(items: T[], matchAll?: boolean): RetType<T, Q>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Custom hook to filter a list of items based on a query.
|
|
101
|
+
* It provides a way to search through the items and returns the filtered list.
|
|
102
|
+
* The hook uses a deferred value to optimize performance during transitions.
|
|
103
|
+
* It also allows for a custom matcher function to define how items should be matched against the query.
|
|
104
|
+
*
|
|
105
|
+
* @param items - The list of items to filter.
|
|
106
|
+
* @param matcher - An optional function to match items against the query.
|
|
107
|
+
* @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```jsx
|
|
111
|
+
* useFilteredList(items, (item, query) => {
|
|
112
|
+
* return item.name.includes(query);
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-usefilteredlist--docs}
|
|
117
|
+
*/
|
|
118
|
+
export function useFilteredList<
|
|
119
|
+
T extends AnyObject = KeyValue,
|
|
120
|
+
Q extends AnyObject = string,
|
|
121
|
+
>(items: T[], matcher?: (item: T, query: Q) => boolean): RetType<T, Q>;
|
|
122
|
+
|
|
54
123
|
/**
|
|
55
124
|
* Custom hook to filter a list of items based on a query.
|
|
56
125
|
* It provides a way to search through the items and returns the filtered list.
|
|
@@ -58,6 +127,7 @@ const filterList = (
|
|
|
58
127
|
* It also allows for a custom matcher function to define how items should be matched against the query.
|
|
59
128
|
*
|
|
60
129
|
* @param items - The list of items to filter.
|
|
130
|
+
* @param matchAll - Match all phrases in the query list against the items.
|
|
61
131
|
* @param matcher - An optional function to match items against the query.
|
|
62
132
|
* @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
|
|
63
133
|
*
|
|
@@ -70,28 +140,39 @@ const filterList = (
|
|
|
70
140
|
*
|
|
71
141
|
* @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-usefilteredlist--docs}
|
|
72
142
|
*/
|
|
73
|
-
export
|
|
143
|
+
export function useFilteredList<
|
|
74
144
|
T extends AnyObject = KeyValue,
|
|
75
145
|
Q extends AnyObject = string,
|
|
76
146
|
>(
|
|
77
147
|
items: T[],
|
|
148
|
+
matchAll?: boolean,
|
|
78
149
|
matcher?: (item: T, query: Q) => boolean,
|
|
79
|
-
)
|
|
150
|
+
): RetType<T, Q>;
|
|
151
|
+
|
|
152
|
+
export function useFilteredList<
|
|
153
|
+
T extends AnyObject = KeyValue,
|
|
154
|
+
Q extends AnyObject = string,
|
|
155
|
+
>(items: T[], matchAll?: AnyObject, matcher?: (item: T, query: Q) => boolean) {
|
|
80
156
|
const [query, setQuery] = useState<Q>();
|
|
81
157
|
const _items = useDeferredValue(items ?? []);
|
|
82
158
|
const [filteredList, setFilteredList] = useState<T[]>([]);
|
|
83
159
|
const [isSearching, startTransition] = useTransition();
|
|
84
160
|
|
|
161
|
+
const matchingAll = typeof matchAll === "boolean" ? matchAll : true;
|
|
162
|
+
const matcherFn = typeof matchAll === "function" ? matchAll : matcher;
|
|
163
|
+
|
|
85
164
|
const filterItems = useCallback(
|
|
86
|
-
(items: T[], query?: Q) =>
|
|
87
|
-
startTransition(() =>
|
|
165
|
+
(items: T[], query?: Q, matchingAll?: boolean) =>
|
|
166
|
+
startTransition(() =>
|
|
167
|
+
setFilteredList(filterList(items, query, matchingAll, matcherFn)),
|
|
168
|
+
),
|
|
88
169
|
[],
|
|
89
170
|
);
|
|
90
171
|
|
|
91
172
|
useEffect(() => {
|
|
92
|
-
if (query) filterItems(_items, query);
|
|
173
|
+
if (query) filterItems(_items, query, matchingAll);
|
|
93
174
|
else setFilteredList(items);
|
|
94
|
-
}, [query, _items]);
|
|
175
|
+
}, [query, matchingAll, _items]);
|
|
95
176
|
|
|
96
177
|
return { onSearch: setQuery, query, filteredList, isSearching };
|
|
97
|
-
}
|
|
178
|
+
}
|
package/src/table/HeaderCell.tsx
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
|
|
46
46
|
export const HeaderCell = ({
|
|
47
47
|
id,
|
|
48
|
+
field,
|
|
48
49
|
dataType,
|
|
49
50
|
label,
|
|
50
51
|
icon,
|
|
@@ -99,7 +100,7 @@ export const HeaderCell = ({
|
|
|
99
100
|
type={dataType}
|
|
100
101
|
list={filterOptions}
|
|
101
102
|
filter={filter}
|
|
102
|
-
onFilter={(value) => onFilter?.(id, value)}
|
|
103
|
+
onFilter={(value) => onFilter?.(field ?? id, value)}
|
|
103
104
|
/>
|
|
104
105
|
</Menu>,
|
|
105
106
|
);
|
|
@@ -131,7 +132,7 @@ export const HeaderCell = ({
|
|
|
131
132
|
type={dataType}
|
|
132
133
|
list={filterOptions}
|
|
133
134
|
filter={filter}
|
|
134
|
-
onFilter={(value) => onFilter?.(id, value)}
|
|
135
|
+
onFilter={(value) => onFilter?.(field ?? id, value)}
|
|
135
136
|
/>,
|
|
136
137
|
];
|
|
137
138
|
}
|
package/src/table/Table.tsx
CHANGED
|
@@ -82,6 +82,8 @@ export const Table = <T extends KeyValue = KeyValue>({
|
|
|
82
82
|
onScroll,
|
|
83
83
|
onRowClick,
|
|
84
84
|
onCheckedChanged,
|
|
85
|
+
total = 0,
|
|
86
|
+
onLoadMore,
|
|
85
87
|
}: TableProps<T>) => {
|
|
86
88
|
const { t } = useTranslation("data");
|
|
87
89
|
const fireCheckChanged = useDebounce(
|
|
@@ -109,6 +111,9 @@ export const Table = <T extends KeyValue = KeyValue>({
|
|
|
109
111
|
checked,
|
|
110
112
|
keyProperty,
|
|
111
113
|
groupProperty,
|
|
114
|
+
total,
|
|
115
|
+
loading,
|
|
116
|
+
onLoadMore,
|
|
112
117
|
onCheckChanged: fireCheckChanged,
|
|
113
118
|
});
|
|
114
119
|
const refBody = useRef<HTMLDivElement>(null);
|
package/src/table/types.ts
CHANGED
|
@@ -67,6 +67,7 @@ export interface TableColumn<T = KeyValue>
|
|
|
67
67
|
IconProps,
|
|
68
68
|
TestProps {
|
|
69
69
|
id: keyof T | string;
|
|
70
|
+
field?: string;
|
|
70
71
|
label?: string;
|
|
71
72
|
tooltip?: string;
|
|
72
73
|
actions?: ReactElement[];
|
|
@@ -134,4 +135,7 @@ export interface TableProps<T = KeyValue> extends RefProp<TableRef> {
|
|
|
134
135
|
children?: (record: T) => Elements<AnyObject>;
|
|
135
136
|
|
|
136
137
|
onFilter?: (id: keyof T | string, value?: AnyObject) => void;
|
|
138
|
+
|
|
139
|
+
total?: number;
|
|
140
|
+
onLoadMore?: (lastIndex?: number) => void;
|
|
137
141
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { compareValues, groupBy } from "@react-fabric/utilities";
|
|
24
|
+
import { compareValues, debounce, groupBy } from "@react-fabric/utilities";
|
|
25
25
|
import {
|
|
26
26
|
defaultRangeExtractor,
|
|
27
27
|
type Range,
|
|
@@ -60,6 +60,9 @@ export const useVirtualTable = <T extends KeyValue = KeyValue>(
|
|
|
60
60
|
checked?: AnyObject[];
|
|
61
61
|
keyProperty: keyof T;
|
|
62
62
|
groupProperty?: keyof T;
|
|
63
|
+
loading?: boolean;
|
|
64
|
+
total: number;
|
|
65
|
+
onLoadMore?: (startIndex: number) => void;
|
|
63
66
|
onCheckChanged?: (checked: AnyObject[]) => void;
|
|
64
67
|
},
|
|
65
68
|
) => {
|
|
@@ -202,6 +205,26 @@ export const useVirtualTable = <T extends KeyValue = KeyValue>(
|
|
|
202
205
|
dispatch({ type: "updateState", checked: options.checked ?? [] });
|
|
203
206
|
}, [options.checked]);
|
|
204
207
|
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (options.onLoadMore) {
|
|
210
|
+
const firstItem = virtualItems[0]?.index;
|
|
211
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
212
|
+
const canLoadPrevPage =
|
|
213
|
+
firstItem !== undefined && state.items[firstItem] === null;
|
|
214
|
+
const canLoadNextPage =
|
|
215
|
+
lastItem !== undefined &&
|
|
216
|
+
lastItem === state.items.length - 1 &&
|
|
217
|
+
state.items.length < options.total;
|
|
218
|
+
const db = debounce(options.onLoadMore);
|
|
219
|
+
if (canLoadPrevPage) {
|
|
220
|
+
db(firstItem);
|
|
221
|
+
} else if (!options.loading && canLoadNextPage) {
|
|
222
|
+
db(lastItem + 1);
|
|
223
|
+
}
|
|
224
|
+
return () => db.cancel();
|
|
225
|
+
}
|
|
226
|
+
}, [options.onLoadMore, virtualItems]);
|
|
227
|
+
|
|
205
228
|
return {
|
|
206
229
|
scrollerRef,
|
|
207
230
|
items: virtualItems,
|
package/src/tree/TreeNode.tsx
CHANGED
|
@@ -97,11 +97,7 @@ export const TreeNode = ({
|
|
|
97
97
|
<div
|
|
98
98
|
role="treeitem"
|
|
99
99
|
aria-selected={node.selected}
|
|
100
|
-
className={classNames(
|
|
101
|
-
"flex flex-nowrap items-center",
|
|
102
|
-
node.className,
|
|
103
|
-
node.leaf ? leafClassName : nodeClassName,
|
|
104
|
-
)}
|
|
100
|
+
className={classNames("flex flex-nowrap items-center")}
|
|
105
101
|
data-testid={node["data-testid"]}
|
|
106
102
|
data-test-value={node["data-test-value"]}
|
|
107
103
|
>
|
|
@@ -147,6 +143,8 @@ export const TreeNode = ({
|
|
|
147
143
|
"group/tool data-[selected]:bg-primary-100 flex flex-nowrap flex-1 overflow-hidden select-none",
|
|
148
144
|
node.childSelected && "font-medium",
|
|
149
145
|
node.disabled && "text-muted opacity-65",
|
|
146
|
+
node.className,
|
|
147
|
+
node.leaf ? leafClassName : nodeClassName,
|
|
150
148
|
!node.disabled &&
|
|
151
149
|
(canSelect || onClick) &&
|
|
152
150
|
"hover:bg-primary-50 cursor-pointer",
|
package/src/tree/TreePanel.tsx
CHANGED
|
@@ -86,7 +86,6 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
86
86
|
checked,
|
|
87
87
|
checkable,
|
|
88
88
|
noLines,
|
|
89
|
-
renderer,
|
|
90
89
|
searchable,
|
|
91
90
|
selectable,
|
|
92
91
|
matcher,
|
|
@@ -110,6 +109,7 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
110
109
|
}: TreePanelProps<T>) => {
|
|
111
110
|
const {
|
|
112
111
|
tree,
|
|
112
|
+
query,
|
|
113
113
|
toggleExpand,
|
|
114
114
|
toggleCheck,
|
|
115
115
|
expandAll,
|
|
@@ -167,6 +167,7 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
167
167
|
>
|
|
168
168
|
{searchable && (
|
|
169
169
|
<Search
|
|
170
|
+
value={query}
|
|
170
171
|
onSearch={onFilter}
|
|
171
172
|
allowClear
|
|
172
173
|
placeholder={filterPlaceholder}
|
|
@@ -216,8 +217,7 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
216
217
|
leafClassName={leafClassName}
|
|
217
218
|
nodeClassName={nodeClassName}
|
|
218
219
|
>
|
|
219
|
-
{(tree[index].data &&
|
|
220
|
-
makeLabel?.(tree[index].data) ??
|
|
220
|
+
{(tree[index].data && makeLabel?.(tree[index].data)) ??
|
|
221
221
|
tree[index].label}
|
|
222
222
|
</TreeNode>
|
|
223
223
|
</div>
|
package/src/tree/types.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { AvatarProps } from "@react-fabric/core/dist/types/components/avatar/Avatar";
|
|
24
25
|
import {
|
|
25
26
|
type BadgeType,
|
|
26
27
|
type ChildProp,
|
|
@@ -56,7 +57,7 @@ interface TreeBaseNode<T> extends Omit<IconProps, "icon">, CssProp, TestProps {
|
|
|
56
57
|
disabled?: boolean;
|
|
57
58
|
data?: T;
|
|
58
59
|
|
|
59
|
-
icon?:
|
|
60
|
+
icon?: Elements<JSX.Element>;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export type TreeNodeType<T extends KeyValue = KeyValue> = TreeBaseNode<T> &
|
|
@@ -90,7 +91,7 @@ export interface InternalNode
|
|
|
90
91
|
loaded: boolean;
|
|
91
92
|
parent?: string;
|
|
92
93
|
empty?: boolean;
|
|
93
|
-
icon?:
|
|
94
|
+
icon?: Elements<JSX.Element>;
|
|
94
95
|
errored?: string;
|
|
95
96
|
selected?: true;
|
|
96
97
|
childSelected?: true;
|
|
@@ -103,8 +104,8 @@ export interface InternalNode
|
|
|
103
104
|
export interface TreeNodeProps extends ChildProp {
|
|
104
105
|
node: InternalNode;
|
|
105
106
|
expanders: [string, string];
|
|
106
|
-
defaultNodeIcon?:
|
|
107
|
-
defaultLeafIcon?:
|
|
107
|
+
defaultNodeIcon?: Elements<JSX.Element>;
|
|
108
|
+
defaultLeafIcon?: Elements<JSX.Element>;
|
|
108
109
|
noLines?: boolean;
|
|
109
110
|
selectable?: true | "leafOnly";
|
|
110
111
|
checkable?: true | "leafOnly";
|
|
@@ -185,23 +186,22 @@ export interface TreePanelProps<T extends KeyValue = KeyValue>
|
|
|
185
186
|
/**
|
|
186
187
|
* callback on selection of tree node
|
|
187
188
|
*/
|
|
188
|
-
onSelect?: (id: string, data: T) =>
|
|
189
|
+
onSelect?: (id: string, data: T) => boolean | undefined;
|
|
189
190
|
/**
|
|
190
191
|
* callback on change of checked list
|
|
192
|
+
* @param leafs - list of checked leaf nodes
|
|
193
|
+
* @param nodes - list of checked nodes
|
|
194
|
+
* @param partials - list of partially checked nodes
|
|
191
195
|
*/
|
|
192
|
-
onChecked?: (leafs: string[], nodes: string[]) => void;
|
|
193
|
-
/**
|
|
194
|
-
* item label renderer
|
|
195
|
-
*/
|
|
196
|
-
renderer?: (data: T) => JSX.Element;
|
|
196
|
+
onChecked?: (leafs: string[], nodes: string[], partials: string[]) => void;
|
|
197
197
|
/**
|
|
198
198
|
* item filter matcher
|
|
199
199
|
*/
|
|
200
200
|
matcher?: (data: T, query: string) => boolean;
|
|
201
201
|
|
|
202
202
|
sorter?: false | ((a: T, b: T) => number);
|
|
203
|
-
makeLabel?: (node: T) =>
|
|
204
|
-
makeIcon?: (node: T) =>
|
|
203
|
+
makeLabel?: (node: T) => Elements<JSX.Element>;
|
|
204
|
+
makeIcon?: (node: T) => Elements<JSX.Element>;
|
|
205
205
|
|
|
206
206
|
defaultExpanded?: string[];
|
|
207
207
|
onExpandToggle?: (id: string[]) => void;
|
package/src/tree/useTree.ts
CHANGED
|
@@ -56,6 +56,10 @@ type TreeActions =
|
|
|
56
56
|
* update item check state
|
|
57
57
|
*/
|
|
58
58
|
| { type: "checked"; id: string[] }
|
|
59
|
+
/**
|
|
60
|
+
* toggle item select, used as action handler
|
|
61
|
+
*/
|
|
62
|
+
| { type: "toggleSelect"; id: string }
|
|
59
63
|
/**
|
|
60
64
|
* toggle item check, used as action handler
|
|
61
65
|
*/
|
|
@@ -85,6 +89,7 @@ interface TreeState {
|
|
|
85
89
|
items: InternalNode[];
|
|
86
90
|
itemMap: Map<string, InternalNode>;
|
|
87
91
|
tree: InternalNode[];
|
|
92
|
+
query?: string;
|
|
88
93
|
selected?: string;
|
|
89
94
|
checked?: string[];
|
|
90
95
|
}
|
|
@@ -151,12 +156,29 @@ export const useTree = <T extends KeyValue>({
|
|
|
151
156
|
[onLoad],
|
|
152
157
|
);
|
|
153
158
|
|
|
159
|
+
const fireSelected = useDebounce(
|
|
160
|
+
(nodeId: string, data: T) => {
|
|
161
|
+
componentEvents.current.onSelect?.(nodeId, data);
|
|
162
|
+
},
|
|
163
|
+
[onSelect],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const fireChecked = useDebounce(
|
|
167
|
+
(leafs: string[], nodes: string[], partials: string[]) => {
|
|
168
|
+
componentEvents.current.onChecked?.(leafs, nodes, partials);
|
|
169
|
+
},
|
|
170
|
+
[onChecked],
|
|
171
|
+
);
|
|
172
|
+
|
|
154
173
|
const [state, dispatch] = useReducer(
|
|
155
174
|
(state: TreeState, action: TreeActions) => {
|
|
156
175
|
if (action.type === "load") {
|
|
157
176
|
state.items = refactorTree(action.items, { sorter, defaultExpanded });
|
|
158
177
|
state.itemMap = makeTreeMap(state.items);
|
|
159
178
|
state.tree = flattenTree(state.items);
|
|
179
|
+
state.selected = undefined;
|
|
180
|
+
state.checked = undefined;
|
|
181
|
+
state.query = "";
|
|
160
182
|
return { ...state };
|
|
161
183
|
}
|
|
162
184
|
if (action.type === "toggleExpand") {
|
|
@@ -172,6 +194,7 @@ export const useTree = <T extends KeyValue>({
|
|
|
172
194
|
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
173
195
|
state.tree = flattenTree(state.items);
|
|
174
196
|
state.selected && updateSelection(state.itemMap, state.selected, true);
|
|
197
|
+
state.checked?.forEach((id) => updateChecked(state.itemMap, id, 1));
|
|
175
198
|
return { ...state };
|
|
176
199
|
}
|
|
177
200
|
if (action.type === "expand") {
|
|
@@ -210,32 +233,49 @@ export const useTree = <T extends KeyValue>({
|
|
|
210
233
|
} else {
|
|
211
234
|
filterTree(state.itemMap, action.query, matcher);
|
|
212
235
|
state.tree = flattenTree(state.items);
|
|
213
|
-
return { ...state };
|
|
214
236
|
}
|
|
237
|
+
return { ...state, query: action.query };
|
|
238
|
+
}
|
|
239
|
+
if (action.type === "toggleSelect") {
|
|
240
|
+
if (state.selected) {
|
|
241
|
+
updateSelection(state.itemMap, state.selected);
|
|
242
|
+
}
|
|
243
|
+
if (!onSelect) {
|
|
244
|
+
updateSelection(state.itemMap, action.id, true);
|
|
245
|
+
state.selected = action.id;
|
|
246
|
+
state.tree = flattenTree(state.items);
|
|
247
|
+
} else {
|
|
248
|
+
fireSelected(action.id, state.itemMap.get(action.id)?.data);
|
|
249
|
+
}
|
|
250
|
+
return { ...state };
|
|
215
251
|
}
|
|
216
252
|
if (action.type === "select") {
|
|
217
253
|
if (state.selected) {
|
|
218
254
|
updateSelection(state.itemMap, state.selected);
|
|
219
255
|
}
|
|
220
256
|
updateSelection(state.itemMap, action.id, true);
|
|
257
|
+
state.selected = action.id;
|
|
221
258
|
state.tree = flattenTree(state.items);
|
|
222
|
-
|
|
223
|
-
action.id,
|
|
224
|
-
state.itemMap.get(action.id)?.data,
|
|
225
|
-
);
|
|
226
|
-
return { ...state, selected: action.id };
|
|
259
|
+
return { ...state };
|
|
227
260
|
}
|
|
228
261
|
if (action.type === "toggleCheck") {
|
|
229
262
|
updateChecked(state.itemMap, action.id);
|
|
230
|
-
const checkList: string[][] = [[], []];
|
|
263
|
+
const checkList: string[][] = [[], [], []];
|
|
231
264
|
Array.from(state.itemMap.values())
|
|
232
|
-
.filter((node) => node.checked
|
|
233
|
-
.forEach((node) =>
|
|
234
|
-
|
|
235
|
-
|
|
265
|
+
.filter((node) => node.checked !== 0)
|
|
266
|
+
.forEach((node) =>
|
|
267
|
+
checkList[node.leaf ? 0 : node.checked].push(node.id),
|
|
268
|
+
);
|
|
269
|
+
fireChecked(
|
|
270
|
+
checkList[0], // leafs
|
|
271
|
+
checkList[1], // nodes
|
|
272
|
+
checkList[2], // partials
|
|
273
|
+
);
|
|
274
|
+
return { ...state, checked: checkList[0] };
|
|
236
275
|
}
|
|
237
276
|
if (action.type === "checked") {
|
|
238
|
-
|
|
277
|
+
state.itemMap.forEach((item) => (item.checked = 0));
|
|
278
|
+
action.id.forEach((id) => updateChecked(state.itemMap, id, 1));
|
|
239
279
|
return { ...state, checked: action.id };
|
|
240
280
|
}
|
|
241
281
|
return state;
|
|
@@ -245,6 +285,7 @@ export const useTree = <T extends KeyValue>({
|
|
|
245
285
|
items: [],
|
|
246
286
|
tree: [],
|
|
247
287
|
checked: [],
|
|
288
|
+
query: "",
|
|
248
289
|
},
|
|
249
290
|
);
|
|
250
291
|
|
|
@@ -260,6 +301,8 @@ export const useTree = <T extends KeyValue>({
|
|
|
260
301
|
type: "expand",
|
|
261
302
|
id: intialExpand.current.shift() as unknown as string,
|
|
262
303
|
});
|
|
304
|
+
selected && dispatch({ type: "select", id: selected });
|
|
305
|
+
checked && dispatch({ type: "checked", id: checked });
|
|
263
306
|
}, [itemList]);
|
|
264
307
|
|
|
265
308
|
useEffect(() => {
|
|
@@ -279,7 +322,7 @@ export const useTree = <T extends KeyValue>({
|
|
|
279
322
|
}, []);
|
|
280
323
|
|
|
281
324
|
const select = useCallback((id: string) => {
|
|
282
|
-
dispatch({ type: "
|
|
325
|
+
dispatch({ type: "toggleSelect", id });
|
|
283
326
|
}, []);
|
|
284
327
|
|
|
285
328
|
const expand = useCallback((id: string) => {
|
|
@@ -304,6 +347,7 @@ export const useTree = <T extends KeyValue>({
|
|
|
304
347
|
|
|
305
348
|
return {
|
|
306
349
|
tree: state.tree,
|
|
350
|
+
query: state.query,
|
|
307
351
|
toggleExpand,
|
|
308
352
|
expandAll,
|
|
309
353
|
collapseAll,
|
package/src/tree/utils.ts
CHANGED
|
@@ -139,16 +139,21 @@ export const updateSelection = (
|
|
|
139
139
|
while (parent) {
|
|
140
140
|
const parentNode = nodes.get(parent);
|
|
141
141
|
parentNode && (parentNode.childSelected = selected);
|
|
142
|
-
selected && parentNode && (parentNode.open = true);
|
|
142
|
+
// selected && parentNode && (parentNode.open = true);
|
|
143
143
|
parent = parentNode?.parent;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
-
export const updateChecked = (
|
|
148
|
+
export const updateChecked = (
|
|
149
|
+
list: Map<string, AnyObject>,
|
|
150
|
+
id: string,
|
|
151
|
+
checked?: 0 | 1 | 2,
|
|
152
|
+
) => {
|
|
149
153
|
const node = list.get(id);
|
|
150
154
|
if (node) {
|
|
151
|
-
node.checked =
|
|
155
|
+
node.checked =
|
|
156
|
+
checked === undefined ? (node.checked === 0 ? 1 : 0) : checked;
|
|
152
157
|
// check all children
|
|
153
158
|
node.children &&
|
|
154
159
|
getChildren(node.children).forEach(
|
package/src/virtual/Gallery.tsx
CHANGED
|
@@ -189,7 +189,7 @@ const _VirtualGallery = <T extends AnyObject>({
|
|
|
189
189
|
|
|
190
190
|
const currentIndex = useRef(0);
|
|
191
191
|
useEffect(() => {
|
|
192
|
-
currentIndex.current = virtualItems[
|
|
192
|
+
currentIndex.current = virtualItems[2]?.index ?? 0;
|
|
193
193
|
if (
|
|
194
194
|
scrollerRef.current &&
|
|
195
195
|
virtualItems[0] &&
|
package/src/virtual/List.tsx
CHANGED
|
@@ -159,7 +159,7 @@ const _VirtualList = <T extends AnyObject>({
|
|
|
159
159
|
|
|
160
160
|
const currentIndex = useRef(0);
|
|
161
161
|
useEffect(() => {
|
|
162
|
-
currentIndex.current = virtualItems[
|
|
162
|
+
currentIndex.current = virtualItems[2]?.index ?? 0;
|
|
163
163
|
if (
|
|
164
164
|
scrollerRef.current &&
|
|
165
165
|
virtualItems[0] &&
|