@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.
@@ -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 { type InternalNode, type TreeNodeType } from "./types";
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
- items?: Array<TreeNodeType<T>>;
95
- selected?: string;
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
- node && (node.open = !node.open);
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 && (node.open = true);
123
- state.tree = flattenTree(state.items);
124
- return { ...state };
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?.(action.id);
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 sorter = (a: TreeNodeType, b: TreeNodeType) => {
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.sort(sorter).forEach((node) => {
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(node.children, {
70
- level: options.level + 1,
71
- parent: node.id,
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