@react-fabric/data 1.0.0-beta.25 → 1.0.0-beta.27
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/tree/TreeNode.js +24 -12
- package/dist/cjs/tree/TreePanel.js +26 -10
- package/dist/cjs/tree/useTree.js +85 -11
- package/dist/cjs/tree/utils.js +13 -4
- package/dist/esm/tree/TreeNode.js +25 -13
- package/dist/esm/tree/TreePanel.js +26 -10
- package/dist/esm/tree/useTree.js +87 -13
- package/dist/esm/tree/utils.js +13 -4
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +1 -1
- package/dist/types/tree/types.d.ts +21 -5
- package/dist/types/tree/useTree.d.ts +2 -9
- package/dist/types/tree/utils.d.ts +4 -1
- package/package.json +8 -8
- package/src/tree/TreeNode.tsx +41 -18
- package/src/tree/TreePanel.tsx +21 -3
- package/src/tree/types.ts +28 -4
- package/src/tree/useTree.ts +93 -20
- package/src/tree/utils.ts +16 -8
package/src/tree/useTree.ts
CHANGED
|
@@ -25,7 +25,11 @@ import { useDebounce } from "@react-fabric/core";
|
|
|
25
25
|
import { EMPTY_ARRAY } from "@react-fabric/utilities";
|
|
26
26
|
import memoizeOne from "memoize-one";
|
|
27
27
|
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type InternalNode,
|
|
30
|
+
type TreeNodeType,
|
|
31
|
+
type TreePanelProps,
|
|
32
|
+
} from "./types";
|
|
29
33
|
import {
|
|
30
34
|
filterTree,
|
|
31
35
|
flattenTree,
|
|
@@ -71,7 +75,11 @@ type TreeActions =
|
|
|
71
75
|
/**
|
|
72
76
|
* collapse all nodes
|
|
73
77
|
*/
|
|
74
|
-
| { type: "collapseAll" }
|
|
78
|
+
| { type: "collapseAll" }
|
|
79
|
+
/**
|
|
80
|
+
* loaded dynamic nodes
|
|
81
|
+
*/
|
|
82
|
+
| { type: "loaded" };
|
|
75
83
|
|
|
76
84
|
interface TreeState {
|
|
77
85
|
items: InternalNode[];
|
|
@@ -87,47 +95,103 @@ export const useTree = <T extends KeyValue>({
|
|
|
87
95
|
items = EMPTY_ARRAY,
|
|
88
96
|
checked = EMPTY_ARRAY,
|
|
89
97
|
selected,
|
|
98
|
+
sorter,
|
|
90
99
|
matcher,
|
|
100
|
+
onLoad,
|
|
91
101
|
onSelect,
|
|
92
102
|
onChecked,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
checked?: string[];
|
|
97
|
-
onSelect?: (id: string) => void;
|
|
98
|
-
onChecked?: (leafs: string[], nodes: string[]) => void;
|
|
99
|
-
matcher?: (data: T, query: string) => boolean;
|
|
100
|
-
}) => {
|
|
103
|
+
defaultExpanded,
|
|
104
|
+
onExpandToggle,
|
|
105
|
+
}: Partial<TreePanelProps<T>>) => {
|
|
101
106
|
const componentEvents = useRef({ onSelect, onChecked });
|
|
102
|
-
|
|
107
|
+
const intialExpand = useRef(defaultExpanded ?? EMPTY_ARRAY);
|
|
103
108
|
const itemList = createItemList(items);
|
|
104
109
|
|
|
110
|
+
const fireExpandToggle = useCallback(
|
|
111
|
+
(itemMap: InternalNode[]) => {
|
|
112
|
+
const expandedList = itemMap
|
|
113
|
+
.filter((node) => node.open)
|
|
114
|
+
.map((node) => node.id);
|
|
115
|
+
onExpandToggle?.(expandedList);
|
|
116
|
+
},
|
|
117
|
+
[onExpandToggle],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const loadChildren = useCallback(
|
|
121
|
+
(node: InternalNode) => {
|
|
122
|
+
if (node.open && !node.leaf && !node.children?.length) {
|
|
123
|
+
node.loading = true;
|
|
124
|
+
void Promise.resolve(onLoad?.(node.id))
|
|
125
|
+
.then((resp = EMPTY_ARRAY) => {
|
|
126
|
+
node.children = refactorTree(
|
|
127
|
+
resp,
|
|
128
|
+
{ sorter, defaultExpanded },
|
|
129
|
+
{ level: node.level + 1, parent: node.id },
|
|
130
|
+
);
|
|
131
|
+
})
|
|
132
|
+
.finally(() => {
|
|
133
|
+
node.loading = false;
|
|
134
|
+
dispatch({ type: "loaded" });
|
|
135
|
+
intialExpand.current?.length > 0 &&
|
|
136
|
+
dispatch({
|
|
137
|
+
type: "expand",
|
|
138
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
dispatch({ type: "loaded" });
|
|
143
|
+
intialExpand.current?.length > 0 &&
|
|
144
|
+
dispatch({
|
|
145
|
+
type: "expand",
|
|
146
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[onLoad],
|
|
151
|
+
);
|
|
152
|
+
|
|
105
153
|
const [state, dispatch] = useReducer(
|
|
106
154
|
(state: TreeState, action: TreeActions) => {
|
|
107
155
|
if (action.type === "load") {
|
|
108
|
-
state.items = refactorTree(action.items);
|
|
156
|
+
state.items = refactorTree(action.items, { sorter, defaultExpanded });
|
|
109
157
|
state.itemMap = makeTreeMap(state.items);
|
|
110
158
|
state.tree = flattenTree(state.items);
|
|
111
159
|
return { ...state };
|
|
112
160
|
}
|
|
113
161
|
if (action.type === "toggleExpand") {
|
|
114
162
|
const node = state.itemMap.get(action.id);
|
|
115
|
-
|
|
163
|
+
if (node && !node.leaf) {
|
|
164
|
+
node && (node.open = !node.open);
|
|
165
|
+
loadChildren(node);
|
|
166
|
+
return { ...state };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (action.type === "loaded") {
|
|
170
|
+
state.itemMap = makeTreeMap(state.items);
|
|
171
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
116
172
|
state.tree = flattenTree(state.items);
|
|
173
|
+
state.selected && updateSelection(state.itemMap, state.selected, true);
|
|
117
174
|
return { ...state };
|
|
118
175
|
}
|
|
119
176
|
if (action.type === "expand") {
|
|
120
|
-
// TODO: check if children available, if not call onLoad to fetch node children
|
|
121
177
|
const node = state.itemMap.get(action.id);
|
|
122
|
-
node &&
|
|
123
|
-
|
|
124
|
-
|
|
178
|
+
if (node && !node.leaf) {
|
|
179
|
+
node.open = true;
|
|
180
|
+
let parentId = node.parent;
|
|
181
|
+
while (parentId) {
|
|
182
|
+
const node = state.itemMap.get(parentId);
|
|
183
|
+
node && (node.open = true);
|
|
184
|
+
parentId = node?.parent;
|
|
185
|
+
}
|
|
186
|
+
loadChildren(node);
|
|
187
|
+
return { ...state };
|
|
188
|
+
}
|
|
125
189
|
}
|
|
126
190
|
if (action.type === "expandAll") {
|
|
127
|
-
// TODO: prevent opening dynamic nodes that dont have children
|
|
128
191
|
state.itemMap.forEach((node) => {
|
|
129
|
-
!node.leaf && (node.open = true);
|
|
192
|
+
!node.leaf && node.children?.length && (node.open = true);
|
|
130
193
|
});
|
|
194
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
131
195
|
state.tree = flattenTree(state.items);
|
|
132
196
|
return { ...state };
|
|
133
197
|
}
|
|
@@ -135,6 +199,7 @@ export const useTree = <T extends KeyValue>({
|
|
|
135
199
|
state.itemMap.forEach((node) => {
|
|
136
200
|
!node.leaf && (node.open = false);
|
|
137
201
|
});
|
|
202
|
+
fireExpandToggle([]);
|
|
138
203
|
state.tree = flattenTree(state.items);
|
|
139
204
|
return { ...state };
|
|
140
205
|
}
|
|
@@ -149,7 +214,10 @@ export const useTree = <T extends KeyValue>({
|
|
|
149
214
|
}
|
|
150
215
|
updateSelection(state.itemMap, action.id, true);
|
|
151
216
|
state.tree = flattenTree(state.items);
|
|
152
|
-
componentEvents.current.onSelect?.(
|
|
217
|
+
componentEvents.current.onSelect?.(
|
|
218
|
+
action.id,
|
|
219
|
+
state.itemMap.get(action.id)?.data,
|
|
220
|
+
);
|
|
153
221
|
return { ...state, selected: action.id };
|
|
154
222
|
}
|
|
155
223
|
if (action.type === "toggleCheck") {
|
|
@@ -181,6 +249,11 @@ export const useTree = <T extends KeyValue>({
|
|
|
181
249
|
|
|
182
250
|
useEffect(() => {
|
|
183
251
|
dispatch({ type: "load", items: itemList });
|
|
252
|
+
intialExpand.current?.length > 0 &&
|
|
253
|
+
dispatch({
|
|
254
|
+
type: "expand",
|
|
255
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
256
|
+
});
|
|
184
257
|
}, [itemList]);
|
|
185
258
|
|
|
186
259
|
useEffect(() => {
|
package/src/tree/utils.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { compareValues, isString, matchString } from "@react-fabric/utilities";
|
|
25
25
|
import { type InternalNode, type TreeNodeType } from "./types";
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const defaultSorter = (a: TreeNodeType, b: TreeNodeType) => {
|
|
28
28
|
if (!!a.leaf !== !!b.leaf) return a.leaf ? 1 : -1;
|
|
29
29
|
return compareValues()(a.label, b.label);
|
|
30
30
|
};
|
|
@@ -48,13 +48,17 @@ const getIntermediate = (list: AnyObject[] = []) => {
|
|
|
48
48
|
// convert node item to internal node used by the tree
|
|
49
49
|
export const refactorTree = (
|
|
50
50
|
nodes: TreeNodeType[],
|
|
51
|
+
{
|
|
52
|
+
sorter = defaultSorter,
|
|
53
|
+
defaultExpanded = [],
|
|
54
|
+
}: { sorter?: any; defaultExpanded?: string[] },
|
|
51
55
|
options: { level: number; parent?: string } = { level: 0, parent: undefined },
|
|
52
56
|
) => {
|
|
53
57
|
const list: InternalNode[] = [];
|
|
54
|
-
|
|
55
|
-
nodes.
|
|
58
|
+
if (sorter !== false) nodes = nodes.sort(sorter);
|
|
59
|
+
nodes.forEach((node) => {
|
|
56
60
|
list.push({
|
|
57
|
-
open: false,
|
|
61
|
+
open: false, // defaultExpanded.includes(node.id),
|
|
58
62
|
level: options.level,
|
|
59
63
|
parent: options.parent,
|
|
60
64
|
checked: 0,
|
|
@@ -66,10 +70,14 @@ export const refactorTree = (
|
|
|
66
70
|
leaf: !!node.leaf,
|
|
67
71
|
children:
|
|
68
72
|
node.children &&
|
|
69
|
-
refactorTree(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
refactorTree(
|
|
74
|
+
node.children,
|
|
75
|
+
{ sorter, defaultExpanded },
|
|
76
|
+
{
|
|
77
|
+
level: options.level + 1,
|
|
78
|
+
parent: node.id,
|
|
79
|
+
},
|
|
80
|
+
),
|
|
73
81
|
});
|
|
74
82
|
});
|
|
75
83
|
|