@react-fabric/data 1.0.0-beta.6 → 1.0.0-beta.61

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 (104) hide show
  1. package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
  2. package/{dist/esm/tree/types.js → css/styles.css} +2 -7
  3. package/dist/cjs/checklist/CheckList.js +27 -4
  4. package/dist/cjs/histogram/Histogram.js +39 -9
  5. package/dist/cjs/hooks/useFilteredList.js +88 -10
  6. package/dist/cjs/hooks/usePagination.js +27 -4
  7. package/dist/cjs/hooks/useSelectableList.js +29 -0
  8. package/dist/cjs/i18n/ar.json.js +8 -1
  9. package/dist/cjs/i18n/en.json.js +8 -1
  10. package/dist/cjs/json/JsonViewer.js +68 -28
  11. package/dist/cjs/pagination/Pagination.js +14 -4
  12. package/dist/cjs/table/AddColumn.js +3 -3
  13. package/dist/cjs/table/BodyCell.js +14 -6
  14. package/dist/cjs/table/BodyRow.js +4 -4
  15. package/dist/cjs/table/CheckboxCell.js +2 -2
  16. package/dist/cjs/table/ColumnFilters.js +97 -0
  17. package/dist/cjs/table/HeaderCell.js +71 -43
  18. package/dist/cjs/table/Table.js +62 -13
  19. package/dist/cjs/table/types.js +29 -0
  20. package/dist/cjs/table/useResizer.js +4 -4
  21. package/dist/cjs/table/useVirtualTable.js +127 -19
  22. package/dist/cjs/tree/TreeNode.js +50 -32
  23. package/dist/cjs/tree/TreePanel.js +52 -19
  24. package/dist/cjs/tree/useTree.js +199 -38
  25. package/dist/cjs/tree/utils.js +39 -12
  26. package/dist/cjs/virtual/Gallery.js +97 -45
  27. package/dist/cjs/virtual/List.js +93 -39
  28. package/dist/cjs/virtual/Scroller.js +16 -8
  29. package/dist/esm/checklist/CheckList.js +28 -5
  30. package/dist/esm/histogram/Histogram.js +41 -11
  31. package/dist/esm/hooks/useFilteredList.js +89 -11
  32. package/dist/esm/hooks/usePagination.js +27 -4
  33. package/dist/esm/hooks/useSelectableList.js +29 -0
  34. package/dist/esm/i18n/ar.json.js +8 -2
  35. package/dist/esm/i18n/en.json.js +8 -2
  36. package/dist/esm/json/JsonViewer.js +69 -29
  37. package/dist/esm/pagination/Pagination.js +15 -5
  38. package/dist/esm/table/AddColumn.js +4 -4
  39. package/dist/esm/table/BodyCell.js +15 -7
  40. package/dist/esm/table/BodyRow.js +4 -4
  41. package/dist/esm/table/CheckboxCell.js +3 -3
  42. package/dist/esm/table/ColumnFilters.js +97 -0
  43. package/dist/esm/table/HeaderCell.js +72 -44
  44. package/dist/esm/table/Table.js +63 -14
  45. package/dist/esm/table/types.js +29 -0
  46. package/dist/esm/table/useResizer.js +4 -4
  47. package/dist/esm/table/useVirtualTable.js +129 -21
  48. package/dist/esm/tree/TreeNode.js +53 -35
  49. package/dist/esm/tree/TreePanel.js +54 -21
  50. package/dist/esm/tree/useTree.js +202 -41
  51. package/dist/esm/tree/utils.js +40 -13
  52. package/dist/esm/virtual/Gallery.js +97 -45
  53. package/dist/esm/virtual/List.js +93 -39
  54. package/dist/esm/virtual/Scroller.js +17 -9
  55. package/dist/types/checklist/CheckList.d.ts +22 -0
  56. package/dist/types/histogram/Histogram.d.ts +28 -1
  57. package/dist/types/hooks/useFilteredList.d.ts +66 -5
  58. package/dist/types/hooks/usePagination.d.ts +19 -0
  59. package/dist/types/hooks/useSelectableList.d.ts +29 -0
  60. package/dist/types/json/JsonViewer.d.ts +27 -2
  61. package/dist/types/pagination/Pagination.d.ts +10 -1
  62. package/dist/types/table/ColumnFilters.d.ts +9 -0
  63. package/dist/types/table/HeaderCell.d.ts +1 -1
  64. package/dist/types/table/Table.d.ts +14 -1
  65. package/dist/types/table/types.d.ts +46 -7
  66. package/dist/types/table/useVirtualTable.d.ts +23 -9
  67. package/dist/types/tree/TreeNode.d.ts +1 -1
  68. package/dist/types/tree/TreePanel.d.ts +42 -1
  69. package/dist/types/tree/types.d.ts +65 -21
  70. package/dist/types/tree/useTree.d.ts +9 -10
  71. package/dist/types/tree/utils.d.ts +10 -3
  72. package/dist/types/virtual/Gallery.d.ts +42 -3
  73. package/dist/types/virtual/List.d.ts +42 -3
  74. package/package.json +16 -14
  75. package/src/checklist/CheckList.tsx +39 -7
  76. package/src/histogram/Histogram.tsx +43 -19
  77. package/src/hooks/useFilteredList.tsx +109 -9
  78. package/src/hooks/usePagination.tsx +32 -7
  79. package/src/hooks/useSelectableList.ts +29 -0
  80. package/src/i18n/ar.json +5 -0
  81. package/src/i18n/en.json +5 -0
  82. package/src/json/JsonViewer.tsx +76 -37
  83. package/src/pagination/Pagination.tsx +15 -5
  84. package/src/table/AddColumn.tsx +4 -4
  85. package/src/table/BodyCell.tsx +24 -7
  86. package/src/table/BodyRow.tsx +7 -4
  87. package/src/table/CheckboxCell.tsx +5 -5
  88. package/src/table/ColumnFilters.tsx +105 -0
  89. package/src/table/HeaderCell.tsx +74 -21
  90. package/src/table/Table.tsx +107 -12
  91. package/src/table/types.ts +49 -10
  92. package/src/table/useResizer.ts +5 -3
  93. package/src/table/useVirtualTable.ts +158 -27
  94. package/src/tree/TreeNode.tsx +96 -62
  95. package/src/tree/TreePanel.tsx +88 -22
  96. package/src/tree/types.ts +73 -23
  97. package/src/tree/useTree.ts +220 -44
  98. package/src/tree/utils.ts +58 -20
  99. package/src/virtual/Gallery.tsx +118 -54
  100. package/src/virtual/List.tsx +99 -39
  101. package/src/virtual/Scroller.tsx +25 -9
  102. package/dist/cjs/histogram/Histogram.module.css +0 -1
  103. package/dist/cjs/tree/types.js +0 -30
  104. package/dist/esm/histogram/Histogram.module.css +0 -1
package/src/tree/types.ts CHANGED
@@ -21,22 +21,26 @@
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 { type IconProps } from "@react-fabric/core/dist/types/components/icon/Icon";
24
25
  import {
25
26
  type BadgeType,
26
27
  type ChildProp,
27
28
  type CssProp,
28
29
  type Elements,
29
- type IconProps,
30
30
  type RefProp,
31
31
  type TestProps,
32
32
  } from "@react-fabric/core/dist/types/types";
33
33
 
34
- interface TreeBaseNode<T> extends IconProps, CssProp, TestProps {
34
+ interface TreeBaseNode<T> extends CssProp, TestProps {
35
35
  id: string;
36
36
  /**
37
37
  * display label
38
38
  */
39
39
  label: Elements<JSX.Element>;
40
+ /**
41
+ * icon path or props
42
+ */
43
+ icon?: IconProps;
40
44
  /**
41
45
  * full text to be used for querying purposes
42
46
  */
@@ -60,14 +64,14 @@ export type TreeNodeType<T extends KeyValue = KeyValue> = TreeBaseNode<T> &
60
64
  (
61
65
  | { leaf: true; children?: never }
62
66
  | {
63
- leaf?: false;
67
+ leaf?: boolean;
64
68
  children?: Array<TreeNodeType<T>>;
65
69
  open?: boolean;
66
70
  iconOpen?: string;
67
71
  }
68
72
  );
69
73
 
70
- export interface InternalNode extends IconProps, CssProp, TestProps {
74
+ export interface InternalNode extends CssProp, TestProps {
71
75
  id: string;
72
76
  label: Elements<JSX.Element>;
73
77
  queryable?: string;
@@ -84,6 +88,7 @@ export interface InternalNode extends IconProps, CssProp, TestProps {
84
88
  loaded: boolean;
85
89
  parent?: string;
86
90
  empty?: boolean;
91
+ icon?: IconProps;
87
92
  errored?: string;
88
93
  selected?: true;
89
94
  childSelected?: true;
@@ -96,22 +101,59 @@ export interface InternalNode extends IconProps, CssProp, TestProps {
96
101
  export interface TreeNodeProps extends ChildProp {
97
102
  node: InternalNode;
98
103
  expanders: [string, string];
99
- defaultNodeIcon?: string;
100
- defaultLeafIcon?: string;
104
+ defaultNodeIcon?: Elements<JSX.Element>;
105
+ defaultLeafIcon?: Elements<JSX.Element>;
101
106
  noLines?: boolean;
102
107
  selectable?: true | "leafOnly";
103
108
  checkable?: true | "leafOnly";
109
+ leafClassName?: string;
110
+ nodeClassName?: string;
104
111
  onToggle: (id: string) => void;
105
- onSelect: (id: string) => void;
112
+ onSelect: (id: string, shiftKey?: boolean) => void;
106
113
  onChecked: (id: string) => void;
114
+ onClick?: (id: string, node: any) => void;
107
115
  }
108
116
 
109
117
  export interface TreeRef {
110
118
  select: (id: string) => void;
111
119
  open: (id: string) => void;
120
+ openAndLoad: (list: string[]) => void;
112
121
  }
113
122
 
114
- export interface TreePanelProps<T extends KeyValue = KeyValue>
123
+ export type TreePanelProps<T extends KeyValue = KeyValue> =
124
+ BaseTreePanelProps<T> &
125
+ (
126
+ | {
127
+ /**
128
+ * enable multi select
129
+ */
130
+ multiple?: never | false;
131
+ /**
132
+ * selected item id
133
+ */
134
+ selected?: string;
135
+ /**
136
+ * callback on selection of tree node
137
+ */
138
+ onSelect?: (id: string, data: T) => void;
139
+ }
140
+ | {
141
+ /**
142
+ * enable multi select
143
+ */
144
+ multiple: true;
145
+ /**
146
+ * selected item id
147
+ */
148
+ selected?: string[];
149
+ /**
150
+ * callback on selection of tree node
151
+ */
152
+ onSelect?: (id: string[], data: T) => void;
153
+ }
154
+ );
155
+
156
+ interface BaseTreePanelProps<T extends KeyValue = KeyValue>
115
157
  extends TestProps,
116
158
  RefProp<TreeRef> {
117
159
  items?: Array<TreeNodeType<T>>;
@@ -128,13 +170,13 @@ export interface TreePanelProps<T extends KeyValue = KeyValue>
128
170
  */
129
171
  selectable?: true | "leafOnly";
130
172
  /**
131
- * enable checkable
173
+ * enable multi select
132
174
  */
133
- checkable?: true | "leafOnly";
175
+ multiple?: boolean;
134
176
  /**
135
- * selected item id
177
+ * enable checkable
136
178
  */
137
- selected?: string;
179
+ checkable?: true | "leafOnly";
138
180
  /**
139
181
  * checked items
140
182
  */
@@ -165,24 +207,32 @@ export interface TreePanelProps<T extends KeyValue = KeyValue>
165
207
  | Array<TreeNodeType<T>>
166
208
  | undefined;
167
209
  /**
168
- * callback on selection of tree node
210
+ * callback to load tree using search query
169
211
  */
170
- onSelect?: (id: string) => void;
212
+ onQuery?: (query: string) => void;
171
213
  /**
172
- * callback on change of checked list
214
+ * callback on click of tree node
173
215
  */
174
- onChecked?: (leafs: string[], nodes: string[]) => void;
216
+ onClick?: (id: string, data: T) => void;
175
217
  /**
176
- * item label renderer
218
+ * callback on change of checked list
219
+ * @param leafs - list of checked leaf nodes
220
+ * @param nodes - list of checked nodes
221
+ * @param partials - list of partially checked nodes
177
222
  */
178
- renderer?: (data: T) => JSX.Element;
223
+ onChecked?: (leafs: string[], nodes: string[], partials: string[]) => void;
179
224
  /**
180
225
  * item filter matcher
181
226
  */
182
227
  matcher?: (data: T, query: string) => boolean;
183
- }
184
228
 
185
- export const iconExpandAll =
186
- "M12.984 9v6h3l-3.984 3.984-3.984-3.984h3v-6h-3l3.984-3.984 3.984 3.984h-3zM3.984 2.016h16.031v1.969h-16.031v-1.969zM3.984 20.016h16.031v1.969h-16.031v-1.969z";
187
- export const iconCollapseAll =
188
- "M3.984 11.016h16.031v1.969h-16.031v-1.969zM15.984 5.016l-3.984 3.984-3.984-3.984h3v-4.031h1.969v4.031h3zM8.016 18.984l3.984-3.984 3.984 3.984h-3v4.031h-1.969v-4.031h-3z";
229
+ sorter?: false | ((a: T, b: T) => number);
230
+ makeLabel?: (node: T) => Elements<JSX.Element>;
231
+ makeIcon?: (node: T) => Elements<JSX.Element>;
232
+
233
+ defaultExpanded?: string[];
234
+ onExpandToggle?: (id: string[]) => void;
235
+
236
+ leafClassName?: string;
237
+ nodeClassName?: string;
238
+ }
@@ -22,10 +22,20 @@
22
22
  */
23
23
 
24
24
  import { useDebounce } from "@react-fabric/core";
25
- import { EMPTY_ARRAY } from "@react-fabric/utilities";
25
+ import { EMPTY_ARRAY, isEmpty } from "@react-fabric/utilities";
26
26
  import memoizeOne from "memoize-one";
27
- import { useCallback, useEffect, useReducer, useRef } from "react";
28
- import { type InternalNode, type TreeNodeType } from "./types";
27
+ import {
28
+ useCallback,
29
+ useEffect,
30
+ useReducer,
31
+ useRef,
32
+ useEffectEvent,
33
+ } from "react";
34
+ import {
35
+ type InternalNode,
36
+ type TreeNodeType,
37
+ type TreePanelProps,
38
+ } from "./types";
29
39
  import {
30
40
  filterTree,
31
41
  flattenTree,
@@ -52,6 +62,10 @@ type TreeActions =
52
62
  * update item check state
53
63
  */
54
64
  | { type: "checked"; id: string[] }
65
+ /**
66
+ * toggle item select, used as action handler
67
+ */
68
+ | { type: "toggleSelect"; id: string; shiftKey?: boolean }
55
69
  /**
56
70
  * toggle item check, used as action handler
57
71
  */
@@ -71,13 +85,18 @@ type TreeActions =
71
85
  /**
72
86
  * collapse all nodes
73
87
  */
74
- | { type: "collapseAll" };
88
+ | { type: "collapseAll" }
89
+ /**
90
+ * loaded dynamic nodes
91
+ */
92
+ | { type: "loaded" };
75
93
 
76
94
  interface TreeState {
77
95
  items: InternalNode[];
78
96
  itemMap: Map<string, InternalNode>;
79
97
  tree: InternalNode[];
80
- selected?: string;
98
+ query?: string;
99
+ selected?: AnyObject;
81
100
  checked?: string[];
82
101
  }
83
102
 
@@ -87,47 +106,139 @@ export const useTree = <T extends KeyValue>({
87
106
  items = EMPTY_ARRAY,
88
107
  checked = EMPTY_ARRAY,
89
108
  selected,
109
+ multiple,
110
+ sorter,
90
111
  matcher,
112
+ onLoad,
113
+ onQuery,
91
114
  onSelect,
92
115
  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;
116
+ defaultExpanded,
117
+ onExpandToggle,
118
+ }: Partial<TreePanelProps<T>> & {
119
+ selected?: AnyObject;
120
+ multiple?: boolean;
100
121
  }) => {
101
- const componentEvents = useRef({ onSelect, onChecked });
102
-
122
+ const eventSelect = useEffectEvent(onSelect ?? (() => undefined));
123
+ const eventChecked = useEffectEvent(onChecked ?? (() => undefined));
124
+ const intialExpand = useRef([...(defaultExpanded ?? EMPTY_ARRAY)]);
103
125
  const itemList = createItemList(items);
104
126
 
127
+ const fireExpandToggle = useCallback(
128
+ (itemMap: InternalNode[]) => {
129
+ const expandedList = itemMap
130
+ .filter((node) => node.open)
131
+ .map((node) => node.id);
132
+ setTimeout(() => onExpandToggle?.(expandedList), 100);
133
+ },
134
+ [onExpandToggle],
135
+ );
136
+
137
+ const loadChildren = useCallback(
138
+ (node: InternalNode) => {
139
+ if (node.open && !node.leaf && !node.children?.length) {
140
+ node.loading = true;
141
+ // delay onload callback to avoid state issues
142
+ setTimeout(() => {
143
+ void Promise.resolve(onLoad?.(node.id))
144
+ .then((resp = EMPTY_ARRAY) => {
145
+ node.children = refactorTree(
146
+ resp,
147
+ { sorter, defaultExpanded },
148
+ { level: node.level + 1, parent: node.id },
149
+ );
150
+ })
151
+ .finally(() => {
152
+ node.loading = false;
153
+ dispatch({ type: "loaded" });
154
+ intialExpand.current?.length > 0 &&
155
+ dispatch({
156
+ type: "expand",
157
+ id: intialExpand.current.shift() as unknown as string,
158
+ });
159
+ });
160
+ }, 100);
161
+ } else {
162
+ dispatch({ type: "loaded" });
163
+ intialExpand.current?.length > 0 &&
164
+ dispatch({
165
+ type: "expand",
166
+ id: intialExpand.current.shift() as unknown as string,
167
+ });
168
+ }
169
+ },
170
+ [onLoad],
171
+ );
172
+
173
+ const fireSelected = useDebounce((nodeId: any, data: T) => {
174
+ eventSelect(nodeId, data);
175
+ }, []);
176
+
177
+ const fireChecked = useDebounce(
178
+ (leafs: string[], nodes: string[], partials: string[]) => {
179
+ eventChecked(leafs, nodes, partials);
180
+ },
181
+ [],
182
+ );
183
+
184
+ const fireQuery = useDebounce(
185
+ (query: string) => {
186
+ onQuery?.(query);
187
+ },
188
+ [onQuery],
189
+ );
190
+
105
191
  const [state, dispatch] = useReducer(
106
192
  (state: TreeState, action: TreeActions) => {
107
193
  if (action.type === "load") {
108
- state.items = refactorTree(action.items);
194
+ state.items = refactorTree(action.items, { sorter, defaultExpanded });
109
195
  state.itemMap = makeTreeMap(state.items);
110
196
  state.tree = flattenTree(state.items);
197
+ state.selected = undefined;
198
+ state.checked = undefined;
199
+ state.query = "";
111
200
  return { ...state };
112
201
  }
113
202
  if (action.type === "toggleExpand") {
114
203
  const node = state.itemMap.get(action.id);
115
- node && (node.open = !node.open);
204
+ if (node && !node.leaf) {
205
+ node && (node.open = !node.open);
206
+ loadChildren(node);
207
+ return { ...state };
208
+ }
209
+ }
210
+ if (action.type === "loaded") {
211
+ state.itemMap = makeTreeMap(state.items);
212
+ fireExpandToggle(Array.from(state.itemMap.values()));
116
213
  state.tree = flattenTree(state.items);
214
+ state.selected = selected;
215
+ updateSelection({
216
+ nodes: state.itemMap,
217
+ multiple,
218
+ selected,
219
+ });
220
+ state.checked?.forEach((id) => updateChecked(state.itemMap, id, 1));
117
221
  return { ...state };
118
222
  }
119
223
  if (action.type === "expand") {
120
- // TODO: check if children available, if not call onLoad to fetch node children
121
224
  const node = state.itemMap.get(action.id);
122
- node && (node.open = true);
123
- state.tree = flattenTree(state.items);
124
- return { ...state };
225
+ if (node && !node.leaf) {
226
+ node.open = true;
227
+ let parentId = node.parent;
228
+ while (parentId) {
229
+ const node = state.itemMap.get(parentId);
230
+ node && (node.open = true);
231
+ parentId = node?.parent;
232
+ }
233
+ loadChildren(node);
234
+ return { ...state };
235
+ }
125
236
  }
126
237
  if (action.type === "expandAll") {
127
- // TODO: prevent opening dynamic nodes that dont have children
128
238
  state.itemMap.forEach((node) => {
129
- !node.leaf && (node.open = true);
239
+ !node.leaf && node.children?.length && (node.open = true);
130
240
  });
241
+ fireExpandToggle(Array.from(state.itemMap.values()));
131
242
  state.tree = flattenTree(state.items);
132
243
  return { ...state };
133
244
  }
@@ -135,34 +246,77 @@ export const useTree = <T extends KeyValue>({
135
246
  state.itemMap.forEach((node) => {
136
247
  !node.leaf && (node.open = false);
137
248
  });
249
+ fireExpandToggle([]);
138
250
  state.tree = flattenTree(state.items);
139
251
  return { ...state };
140
252
  }
141
253
  if (action.type === "filter") {
142
- filterTree(state.itemMap, action.query, matcher);
254
+ if (onQuery) {
255
+ // TODO: onQuery should provide new tree listing
256
+ // save current tree state as preQueryState
257
+ // on clearing filter restore preQueryState
258
+ if (!isEmpty(action.query)) {
259
+ // state.prevItems = state.items;
260
+ fireQuery(action.query);
261
+ } else {
262
+ // state.items = state.prevItems;
263
+ // build tree map
264
+ }
265
+ } else {
266
+ filterTree(state.itemMap, action.query, matcher);
267
+ state.tree = flattenTree(state.items);
268
+ }
269
+ return { ...state, query: action.query };
270
+ }
271
+ if (action.type === "toggleSelect") {
272
+ if (multiple) {
273
+ const isSelected = state.selected?.includes(action.id);
274
+ if (action.shiftKey && isSelected)
275
+ state.selected =
276
+ state.selected?.filter((id: string) => id !== action.id) ?? [];
277
+ else if (action.shiftKey && !isSelected)
278
+ state.selected = [...(state.selected ?? []), action.id];
279
+ else state.selected = [action.id];
280
+ } else {
281
+ state.selected = action.id;
282
+ }
283
+ updateSelection({
284
+ nodes: state.itemMap,
285
+ selected: state.selected,
286
+ multiple,
287
+ });
143
288
  state.tree = flattenTree(state.items);
289
+ fireSelected(state.selected, state.itemMap.get(action.id)?.data);
144
290
  return { ...state };
145
291
  }
146
292
  if (action.type === "select") {
147
- if (state.selected) {
148
- updateSelection(state.itemMap, state.selected);
149
- }
150
- updateSelection(state.itemMap, action.id, true);
293
+ state.selected = action.id;
294
+ updateSelection({
295
+ nodes: state.itemMap,
296
+ multiple,
297
+ selected: action.id,
298
+ });
151
299
  state.tree = flattenTree(state.items);
152
- componentEvents.current.onSelect?.(action.id);
153
- return { ...state, selected: action.id };
300
+ return { ...state };
154
301
  }
155
302
  if (action.type === "toggleCheck") {
156
303
  updateChecked(state.itemMap, action.id);
157
- const checkList: string[][] = [[], []];
304
+ const checkList: string[][] = [[], [], []];
158
305
  Array.from(state.itemMap.values())
159
- .filter((node) => node.checked === 1)
160
- .forEach((node) => checkList[node.leaf ? 0 : 1].push(node.id));
161
- componentEvents.current.onChecked?.(checkList[0], checkList[1]);
162
- return { ...state };
306
+ .filter((node) => node.checked !== 0)
307
+ .forEach((node) =>
308
+ checkList[node.leaf ? 0 : node.checked].push(node.id),
309
+ );
310
+ fireChecked(
311
+ checkList[0], // leafs
312
+ checkList[1], // nodes
313
+ checkList[2], // partials
314
+ );
315
+ return { ...state, checked: checkList[0] };
163
316
  }
164
317
  if (action.type === "checked") {
165
- action.id.forEach((id) => updateChecked(state.itemMap, id));
318
+ state.itemMap.forEach((item) => (item.checked = 0));
319
+ action.id.forEach((id) => updateChecked(state.itemMap, id, 1));
166
320
  return { ...state, checked: action.id };
167
321
  }
168
322
  return state;
@@ -172,15 +326,20 @@ export const useTree = <T extends KeyValue>({
172
326
  items: [],
173
327
  tree: [],
174
328
  checked: [],
329
+ query: "",
175
330
  },
176
331
  );
177
332
 
178
- useEffect(() => {
179
- componentEvents.current = { onSelect, onChecked };
180
- }, [onSelect, onChecked]);
181
-
182
333
  useEffect(() => {
183
334
  dispatch({ type: "load", items: itemList });
335
+ itemList.length &&
336
+ intialExpand.current?.length > 0 &&
337
+ dispatch({
338
+ type: "expand",
339
+ id: intialExpand.current.shift() as unknown as string,
340
+ });
341
+ selected && dispatch({ type: "select", id: selected });
342
+ checked && dispatch({ type: "checked", id: checked });
184
343
  }, [itemList]);
185
344
 
186
345
  useEffect(() => {
@@ -199,14 +358,24 @@ export const useTree = <T extends KeyValue>({
199
358
  dispatch({ type: "toggleCheck", id });
200
359
  }, []);
201
360
 
202
- const select = useCallback((id: string) => {
203
- dispatch({ type: "select", id });
361
+ const select = useCallback((id: string, shiftKey = false) => {
362
+ dispatch({ type: "toggleSelect", id, shiftKey });
204
363
  }, []);
205
364
 
206
365
  const expand = useCallback((id: string) => {
207
366
  dispatch({ type: "expand", id });
208
367
  }, []);
209
368
 
369
+ const expandAndLoad = useCallback((expandList: string[]) => {
370
+ if (expandList.length > 0) {
371
+ intialExpand.current = [...expandList];
372
+ dispatch({
373
+ type: "expand",
374
+ id: intialExpand.current.shift() as unknown as string,
375
+ });
376
+ }
377
+ }, []);
378
+
210
379
  const expandAll = useCallback(() => {
211
380
  dispatch({ type: "expandAll" });
212
381
  }, []);
@@ -215,16 +384,23 @@ export const useTree = <T extends KeyValue>({
215
384
  dispatch({ type: "collapseAll" });
216
385
  }, []);
217
386
 
218
- const onFilter = useDebounce((query: string) => {
219
- dispatch({ type: "filter", query });
220
- }, []);
387
+ const onFilter = useDebounce(
388
+ (query: string) => {
389
+ dispatch({ type: "filter", query });
390
+ },
391
+ [],
392
+ 300,
393
+ );
221
394
 
222
395
  return {
223
396
  tree: state.tree,
397
+ query: state.query,
398
+ selected: state.selected,
224
399
  toggleExpand,
225
400
  expandAll,
226
401
  collapseAll,
227
402
  toggleCheck,
403
+ expandAndLoad,
228
404
  select,
229
405
  expand,
230
406
  onFilter,
package/src/tree/utils.ts CHANGED
@@ -21,10 +21,15 @@
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, isString, matchString } from "@react-fabric/utilities";
24
+ import {
25
+ compareValues,
26
+ isArray,
27
+ isString,
28
+ matchString,
29
+ } from "@react-fabric/utilities";
25
30
  import { type InternalNode, type TreeNodeType } from "./types";
26
31
 
27
- const sorter = (a: TreeNodeType, b: TreeNodeType) => {
32
+ const defaultSorter = (a: TreeNodeType, b: TreeNodeType) => {
28
33
  if (!!a.leaf !== !!b.leaf) return a.leaf ? 1 : -1;
29
34
  return compareValues()(a.label, b.label);
30
35
  };
@@ -48,13 +53,17 @@ const getIntermediate = (list: AnyObject[] = []) => {
48
53
  // convert node item to internal node used by the tree
49
54
  export const refactorTree = (
50
55
  nodes: TreeNodeType[],
56
+ {
57
+ sorter = defaultSorter,
58
+ defaultExpanded = [],
59
+ }: { sorter?: any; defaultExpanded?: string[] },
51
60
  options: { level: number; parent?: string } = { level: 0, parent: undefined },
52
61
  ) => {
53
62
  const list: InternalNode[] = [];
54
-
55
- nodes.sort(sorter).forEach((node) => {
63
+ if (sorter !== false) nodes = nodes.sort(sorter);
64
+ nodes.forEach((node) => {
56
65
  list.push({
57
- open: false,
66
+ open: defaultExpanded.includes(node.id) && !!node.children?.length,
58
67
  level: options.level,
59
68
  parent: options.parent,
60
69
  checked: 0,
@@ -66,10 +75,14 @@ export const refactorTree = (
66
75
  leaf: !!node.leaf,
67
76
  children:
68
77
  node.children &&
69
- refactorTree(node.children, {
70
- level: options.level + 1,
71
- parent: node.id,
72
- }),
78
+ refactorTree(
79
+ node.children,
80
+ { sorter, defaultExpanded },
81
+ {
82
+ level: options.level + 1,
83
+ parent: node.id,
84
+ },
85
+ ),
73
86
  });
74
87
  });
75
88
 
@@ -106,34 +119,59 @@ export const flattenTree = (
106
119
  [...lines, idx === filtered.length - 1 ? 0 : 1],
107
120
  ),
108
121
  );
122
+ node.open &&
123
+ node.children?.length === 0 &&
124
+ list.push({
125
+ empty: true,
126
+ leaf: true,
127
+ lines: [...lines, idx === filtered.length - 1 ? 0 : 1, 2],
128
+ } as any);
109
129
  });
110
130
  return list;
111
131
  };
112
132
 
113
- export const updateSelection = (
114
- nodes: Map<string, InternalNode>,
115
- id: string,
116
- selected?: true,
117
- ) => {
118
- const node = nodes.get(id);
133
+ const selectNode = (nodes: Map<string, InternalNode>, id?: string) => {
134
+ const node = nodes.get(id ?? "");
119
135
  if (node) {
120
136
  // change selection
121
- node.selected = selected;
137
+ node.selected = true;
122
138
  // update all parent childSelected flag
123
139
  let parent = node.parent;
124
140
  while (parent) {
125
141
  const parentNode = nodes.get(parent);
126
- parentNode && (parentNode.childSelected = selected);
127
- selected && parentNode && (parentNode.open = true);
142
+ parentNode && (parentNode.childSelected = true);
143
+ // selected && parentNode && (parentNode.open = true);
128
144
  parent = parentNode?.parent;
129
145
  }
130
146
  }
131
147
  };
132
148
 
133
- export const updateChecked = (list: Map<string, AnyObject>, id: string) => {
149
+ export const updateSelection = ({
150
+ nodes,
151
+ multiple,
152
+ selected,
153
+ }: {
154
+ nodes: Map<string, InternalNode>;
155
+ multiple?: boolean;
156
+ selected?: string | string[];
157
+ }) => {
158
+ nodes.forEach((node) => {
159
+ node.selected = undefined;
160
+ node.childSelected = undefined;
161
+ });
162
+ multiple && isArray(selected)
163
+ ? selected?.forEach((id) => selectNode(nodes, id))
164
+ : selectNode(nodes, selected as string);
165
+ };
166
+
167
+ export const updateChecked = (
168
+ list: Map<string, AnyObject>,
169
+ id: string,
170
+ checked?: 0 | 1 | 2,
171
+ ) => {
134
172
  const node = list.get(id);
135
173
  if (node) {
136
- node.checked = node.checked === 1 ? 0 : 1;
174
+ node.checked = checked ?? node.checked === 0 ? 1 : 0;
137
175
  // check all children
138
176
  node.children &&
139
177
  getChildren(node.children).forEach(