@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.
- package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
- package/css/styles.css +24 -0
- package/dist/cjs/checklist/CheckList.js +59 -33
- package/dist/cjs/histogram/Histogram.js +41 -26
- package/dist/cjs/hooks/useFilteredList.js +100 -17
- package/dist/cjs/hooks/usePagination.js +29 -8
- package/dist/cjs/hooks/useSelectableList.js +30 -5
- package/dist/cjs/i18n/{ar.json.js → ar/data.json.js} +8 -1
- package/dist/cjs/i18n/{en.json.js → en/data.json.js} +8 -1
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +30 -0
- package/dist/cjs/json/JsonViewer.js +80 -43
- package/dist/cjs/pagination/Pagination.js +24 -15
- package/dist/cjs/table/AddColumn.js +5 -5
- package/dist/cjs/table/BodyCell.js +20 -13
- package/dist/cjs/table/BodyRow.js +41 -28
- package/dist/cjs/table/CheckboxCell.js +10 -9
- package/dist/cjs/table/ColumnFilters.js +100 -0
- package/dist/cjs/table/EmptyCell.js +4 -4
- package/dist/cjs/table/HeaderCell.js +79 -53
- package/dist/cjs/table/HtmlTable.js +84 -0
- package/dist/cjs/table/{Context.js → Provider.js} +8 -10
- package/dist/cjs/table/Table.js +99 -33
- package/dist/cjs/{tree/types.js → table/context.js} +9 -5
- package/dist/cjs/table/types.js +29 -0
- package/dist/cjs/table/useResizer.js +10 -10
- package/dist/cjs/table/useTableColumns.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +129 -22
- package/dist/cjs/tree/TreeNode.js +66 -46
- package/dist/cjs/tree/TreePanel.js +60 -24
- package/dist/cjs/tree/useTree.js +212 -47
- package/dist/cjs/tree/utils.js +63 -34
- package/dist/cjs/utils/getColor.js +58 -0
- package/dist/cjs/virtual/Gallery.js +119 -61
- package/dist/cjs/virtual/List.js +116 -55
- package/dist/cjs/virtual/Scroller.js +16 -8
- package/dist/esm/checklist/CheckList.js +61 -35
- package/dist/esm/histogram/Histogram.js +43 -28
- package/dist/esm/hooks/useFilteredList.js +101 -18
- package/dist/esm/hooks/usePagination.js +29 -8
- package/dist/esm/hooks/useSelectableList.js +30 -5
- package/dist/esm/i18n/{ar.json.js → ar/data.json.js} +8 -2
- package/dist/esm/i18n/{en.json.js → en/data.json.js} +8 -2
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +31 -0
- package/dist/esm/json/JsonViewer.js +82 -45
- package/dist/esm/pagination/Pagination.js +25 -16
- package/dist/esm/table/AddColumn.js +6 -6
- package/dist/esm/table/BodyCell.js +21 -14
- package/dist/esm/table/BodyRow.js +41 -28
- package/dist/esm/table/CheckboxCell.js +11 -10
- package/dist/esm/table/ColumnFilters.js +100 -0
- package/dist/esm/table/EmptyCell.js +4 -4
- package/dist/esm/table/HeaderCell.js +80 -54
- package/dist/esm/table/HtmlTable.js +84 -0
- package/dist/esm/table/{Context.js → Provider.js} +11 -12
- package/dist/esm/table/Table.js +100 -34
- package/dist/esm/{tree/types.js → table/context.js} +8 -4
- package/dist/esm/table/types.js +29 -0
- package/dist/esm/table/useResizer.js +10 -10
- package/dist/esm/table/useTableColumns.js +4 -4
- package/dist/esm/table/useVirtualTable.js +131 -24
- package/dist/esm/tree/TreeNode.js +69 -49
- package/dist/esm/tree/TreePanel.js +62 -26
- package/dist/esm/tree/useTree.js +215 -50
- package/dist/esm/tree/utils.js +64 -35
- package/dist/esm/utils/getColor.js +58 -0
- package/dist/esm/virtual/Gallery.js +121 -63
- package/dist/esm/virtual/List.js +118 -57
- package/dist/esm/virtual/Scroller.js +17 -9
- package/dist/types/checklist/CheckList.d.ts +25 -6
- package/dist/types/histogram/Histogram.d.ts +28 -6
- package/dist/types/hooks/useFilteredList.d.ts +62 -5
- package/dist/types/hooks/usePagination.d.ts +18 -2
- package/dist/types/hooks/useSelectableList.d.ts +24 -1
- package/dist/types/index.d.ts +27 -0
- package/dist/types/json/JsonViewer.d.ts +23 -3
- package/dist/types/pagination/Pagination.d.ts +8 -2
- package/dist/types/table/AddColumn.d.ts +2 -2
- package/dist/types/table/BodyCell.d.ts +2 -2
- package/dist/types/table/BodyRow.d.ts +9 -9
- package/dist/types/table/CheckboxCell.d.ts +3 -3
- package/dist/types/table/ColumnFilters.d.ts +12 -0
- package/dist/types/table/EmptyCell.d.ts +1 -1
- package/dist/types/table/HeaderCell.d.ts +1 -1
- package/dist/types/table/HtmlTable.d.ts +21 -0
- package/dist/types/table/Provider.d.ts +2 -0
- package/dist/types/table/Table.d.ts +10 -1
- package/dist/types/table/context.d.ts +7 -0
- package/dist/types/table/types.d.ts +52 -10
- package/dist/types/table/useResizer.d.ts +1 -1
- package/dist/types/table/useTableColumns.d.ts +1 -1
- package/dist/types/table/useVirtualTable.d.ts +31 -12
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +36 -1
- package/dist/types/tree/types.d.ts +75 -28
- package/dist/types/tree/useTree.d.ts +8 -12
- package/dist/types/tree/utils.d.ts +15 -8
- package/dist/types/utils/getColor.d.ts +43 -0
- package/dist/types/virtual/Gallery.d.ts +37 -6
- package/dist/types/virtual/List.d.ts +38 -6
- package/dist/types/virtual/Scroller.d.ts +1 -1
- package/package.json +17 -19
- package/src/checklist/CheckList.tsx +68 -71
- package/src/histogram/Histogram.tsx +47 -48
- package/src/hooks/useFilteredList.tsx +111 -28
- package/src/hooks/usePagination.tsx +39 -39
- package/src/hooks/useSelectableList.ts +45 -33
- package/src/i18n/{ar.json → ar/data.json} +5 -0
- package/src/i18n/{en.json → en/data.json} +5 -0
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +23 -17
- package/src/json/JsonViewer.tsx +89 -120
- package/src/pagination/Pagination.tsx +104 -105
- package/src/table/AddColumn.tsx +6 -15
- package/src/table/BodyCell.tsx +27 -36
- package/src/table/BodyRow.tsx +36 -59
- package/src/table/CheckboxCell.tsx +12 -15
- package/src/table/ColumnFilters.tsx +93 -0
- package/src/table/EmptyCell.tsx +5 -18
- package/src/table/HeaderCell.tsx +72 -53
- package/src/table/HtmlTable.tsx +106 -0
- package/src/table/{Context.tsx → Provider.tsx} +11 -28
- package/src/table/Table.tsx +98 -83
- package/src/table/context.ts +33 -0
- package/src/table/types.ts +53 -27
- package/src/table/useResizer.ts +5 -9
- package/src/table/useTableColumns.ts +4 -6
- package/src/table/useVirtualTable.ts +136 -37
- package/src/tree/TreeNode.tsx +91 -86
- package/src/tree/TreePanel.tsx +87 -51
- package/src/tree/types.ts +85 -46
- package/src/tree/useTree.ts +210 -65
- package/src/tree/utils.ts +81 -62
- package/src/utils/getColor.ts +55 -0
- package/src/virtual/Gallery.tsx +129 -120
- package/src/virtual/List.tsx +124 -91
- package/src/virtual/Scroller.tsx +25 -9
- package/dist/cjs/histogram/Histogram.module.css +0 -1
- package/dist/esm/histogram/Histogram.module.css +0 -1
- 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
|
-
|
|
26
|
-
|
|
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
|
|
28
|
+
interface TreeBaseNode<T> extends CssProp, TestProps {
|
|
35
29
|
id: string;
|
|
36
30
|
/**
|
|
37
31
|
* display label
|
|
38
32
|
*/
|
|
39
|
-
label:
|
|
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
|
|
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?:
|
|
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
|
|
68
|
+
export interface InternalNode<T> extends CssProp, TestProps {
|
|
71
69
|
id: string;
|
|
72
|
-
label:
|
|
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
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
164
|
+
* enable multi select
|
|
132
165
|
*/
|
|
133
|
-
|
|
166
|
+
multiple?: boolean;
|
|
134
167
|
/**
|
|
135
|
-
*
|
|
168
|
+
* enable checkable
|
|
136
169
|
*/
|
|
137
|
-
|
|
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
|
|
197
|
+
* callback to load tree using search query
|
|
169
198
|
*/
|
|
170
|
-
|
|
199
|
+
onQuery?: (query: string) => void;
|
|
171
200
|
/**
|
|
172
|
-
* callback on
|
|
201
|
+
* callback on click of tree node
|
|
173
202
|
*/
|
|
174
|
-
|
|
203
|
+
onClick?: (id: string, data: T) => void;
|
|
175
204
|
/**
|
|
176
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
}
|
package/src/tree/useTree.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
123
|
-
|
|
124
|
-
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
160
|
-
.forEach((node) => checkList[node.leaf ? 0 :
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
315
|
+
if (selected) dispatch({ type: "select", id: selected });
|
|
188
316
|
}, [selected]);
|
|
189
317
|
|
|
190
318
|
useEffect(() => {
|
|
191
|
-
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: "
|
|
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(
|
|
219
|
-
|
|
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
|
+
}
|