@react-fabric/data 1.0.0-beta.9 → 1.0.2

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