@react-fabric/data 1.0.0-beta.3 → 1.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/checklist/CheckList.js +143 -0
- package/dist/cjs/histogram/Histogram.js +16 -5
- package/dist/cjs/hooks/usePagination.js +7 -4
- package/dist/cjs/hooks/useSelectableList.js +54 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/json/JsonViewer.js +30 -23
- package/dist/cjs/table/BodyCell.js +38 -5
- package/dist/cjs/table/BodyRow.js +107 -0
- package/dist/cjs/table/CheckboxCell.js +1 -1
- package/dist/cjs/table/HeaderCell.js +29 -36
- package/dist/cjs/table/Table.js +104 -66
- package/dist/cjs/table/useResizer.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +118 -17
- package/dist/cjs/tree/TreeNode.js +44 -24
- package/dist/cjs/tree/TreePanel.js +28 -10
- package/dist/cjs/tree/useTree.js +96 -14
- package/dist/cjs/tree/utils.js +20 -5
- package/dist/cjs/virtual/Gallery.js +43 -29
- package/dist/cjs/virtual/List.js +43 -31
- package/dist/esm/checklist/CheckList.js +143 -0
- package/dist/esm/histogram/Histogram.js +16 -5
- package/dist/esm/hooks/usePagination.js +7 -4
- package/dist/esm/hooks/useSelectableList.js +54 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/json/JsonViewer.js +31 -24
- package/dist/esm/table/BodyCell.js +38 -5
- package/dist/esm/table/BodyRow.js +107 -0
- package/dist/esm/table/CheckboxCell.js +1 -1
- package/dist/esm/table/HeaderCell.js +30 -37
- package/dist/esm/table/Table.js +106 -68
- package/dist/esm/table/useResizer.js +4 -4
- package/dist/esm/table/useVirtualTable.js +120 -19
- package/dist/esm/tree/TreeNode.js +46 -26
- package/dist/esm/tree/TreePanel.js +28 -10
- package/dist/esm/tree/useTree.js +98 -16
- package/dist/esm/tree/utils.js +20 -5
- package/dist/esm/virtual/Gallery.js +43 -29
- package/dist/esm/virtual/List.js +43 -31
- package/dist/types/checklist/CheckList.d.ts +30 -0
- package/dist/types/histogram/Histogram.d.ts +3 -3
- package/dist/types/hooks/useSelectableList.d.ts +28 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/table/AddColumn.d.ts +2 -2
- package/dist/types/table/BodyCell.d.ts +4 -3
- package/dist/types/table/BodyRow.d.ts +20 -0
- package/dist/types/table/EmptyCell.d.ts +2 -2
- package/dist/types/table/HeaderCell.d.ts +2 -2
- package/dist/types/table/Table.d.ts +1 -1
- package/dist/types/table/types.d.ts +19 -8
- package/dist/types/table/useTableColumns.d.ts +6 -6
- package/dist/types/table/useVirtualTable.d.ts +20 -5
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +1 -1
- package/dist/types/tree/types.d.ts +25 -5
- package/dist/types/tree/useTree.d.ts +2 -9
- package/dist/types/tree/utils.d.ts +4 -1
- package/dist/types/virtual/Gallery.d.ts +11 -3
- package/dist/types/virtual/List.d.ts +7 -3
- package/package.json +8 -7
- package/src/checklist/CheckList.tsx +222 -0
- package/src/histogram/Histogram.tsx +9 -2
- package/src/hooks/usePagination.tsx +13 -7
- package/src/hooks/useSelectableList.ts +94 -0
- package/src/index.ts +2 -1
- package/src/json/JsonViewer.tsx +31 -31
- package/src/table/AddColumn.tsx +2 -2
- package/src/table/BodyCell.tsx +34 -5
- package/src/table/BodyRow.tsx +143 -0
- package/src/table/CheckboxCell.tsx +1 -1
- package/src/table/EmptyCell.tsx +2 -2
- package/src/table/HeaderCell.tsx +27 -13
- package/src/table/Table.tsx +141 -73
- package/src/table/types.ts +22 -7
- package/src/table/useResizer.ts +5 -3
- package/src/table/useTableColumns.ts +7 -7
- package/src/table/useVirtualTable.ts +142 -27
- package/src/tree/TreeNode.tsx +90 -56
- package/src/tree/TreePanel.tsx +23 -3
- package/src/tree/types.ts +32 -4
- package/src/tree/useTree.ts +102 -23
- package/src/tree/utils.ts +23 -8
- package/src/virtual/Gallery.tsx +45 -34
- package/src/virtual/List.tsx +40 -27
package/src/tree/TreeNode.tsx
CHANGED
|
@@ -22,8 +22,10 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { Badge, CoreIcons, getBadgeProps, Icon } from "@react-fabric/core";
|
|
25
|
+
import { isEmpty, isString } from "@react-fabric/utilities";
|
|
25
26
|
import classNames from "classnames";
|
|
26
|
-
import { useMemo } from "react";
|
|
27
|
+
import { isValidElement, useMemo } from "react";
|
|
28
|
+
import { useTranslation } from "react-i18next";
|
|
27
29
|
import { type TreeNodeProps } from "./types";
|
|
28
30
|
|
|
29
31
|
export const TreeNode = ({
|
|
@@ -35,30 +37,43 @@ export const TreeNode = ({
|
|
|
35
37
|
defaultLeafIcon,
|
|
36
38
|
defaultNodeIcon,
|
|
37
39
|
onToggle,
|
|
40
|
+
onClick,
|
|
38
41
|
onSelect,
|
|
39
42
|
onChecked,
|
|
40
43
|
children,
|
|
44
|
+
leafClassName,
|
|
45
|
+
nodeClassName,
|
|
41
46
|
}: TreeNodeProps) => {
|
|
47
|
+
const { t } = useTranslation("data");
|
|
42
48
|
const badgeProps = useMemo(() => {
|
|
43
49
|
return getBadgeProps(node.badge);
|
|
44
50
|
}, [node.badge]);
|
|
45
51
|
|
|
46
52
|
const filler = useMemo(() => {
|
|
47
|
-
return node.lines.slice(
|
|
48
|
-
<
|
|
53
|
+
return node.lines.slice(1).map((line, idx) => (
|
|
54
|
+
<div
|
|
49
55
|
key={idx}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<path d="M14.989 12.389l0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v14.1l0.161 0.389 0.389 0.161h6.6z" />
|
|
57
|
-
)}
|
|
58
|
-
{!noLines && line === 3 && (
|
|
59
|
-
<path d="M8.55 12.55h6.050l0.389-0.161 0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v28.2h1.1z" />
|
|
56
|
+
className={classNames(
|
|
57
|
+
"flex-initial self-stretch flex items-center w-6 relative",
|
|
58
|
+
!noLines &&
|
|
59
|
+
line > 0 &&
|
|
60
|
+
"before:absolute before:top-0 before:start-[7px] before:bg-tint-200 before:min-h-[50%] before:w-[2px] before:scale-x-[.625]",
|
|
61
|
+
!noLines && line !== 2 && "before:h-full",
|
|
60
62
|
)}
|
|
61
|
-
|
|
63
|
+
>
|
|
64
|
+
<svg viewBox="0 0 24 24" className="fill-tint-200 rtl:-scale-x-100">
|
|
65
|
+
{line === 0 && <path d="" />}
|
|
66
|
+
{!noLines && line === 1 && (
|
|
67
|
+
<path d="M7.45 26.1v-28.2h1.1v28.2h-1.1z" />
|
|
68
|
+
)}
|
|
69
|
+
{!noLines && line === 2 && (
|
|
70
|
+
<path d="M14.989 12.389l0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v14.1l0.161 0.389 0.389 0.161h6.6z" />
|
|
71
|
+
)}
|
|
72
|
+
{!noLines && line === 3 && (
|
|
73
|
+
<path d="M8.55 12.55h6.050l0.389-0.161 0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v28.2h1.1z" />
|
|
74
|
+
)}
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
62
77
|
));
|
|
63
78
|
}, [noLines, node.lines]);
|
|
64
79
|
|
|
@@ -82,12 +97,20 @@ export const TreeNode = ({
|
|
|
82
97
|
<div
|
|
83
98
|
role="treeitem"
|
|
84
99
|
aria-selected={node.selected}
|
|
85
|
-
className=
|
|
100
|
+
className={classNames(
|
|
101
|
+
"flex flex-nowrap items-center",
|
|
102
|
+
node.leaf ? leafClassName : nodeClassName,
|
|
103
|
+
)}
|
|
86
104
|
data-testid={node["data-testid"]}
|
|
87
105
|
data-test-value={node["data-test-value"]}
|
|
88
106
|
>
|
|
89
107
|
{filler}
|
|
90
|
-
{
|
|
108
|
+
{node.loading && (
|
|
109
|
+
<div className="flex-initial w-6">
|
|
110
|
+
<Icon icon={CoreIcons.spinner} className="animate-spin text-muted" />
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
{!node.leaf && !node.childFiltered && !node.loading && (
|
|
91
114
|
<div className="flex-initial w-6">
|
|
92
115
|
<Icon
|
|
93
116
|
rtlFlip
|
|
@@ -116,48 +139,59 @@ export const TreeNode = ({
|
|
|
116
139
|
/>
|
|
117
140
|
</div>
|
|
118
141
|
)}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
?
|
|
132
|
-
:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
{!node.empty && (
|
|
143
|
+
<div
|
|
144
|
+
role="none"
|
|
145
|
+
className={classNames(
|
|
146
|
+
"group/tool data-[selected]:bg-primary-100 flex flex-nowrap flex-1 overflow-hidden select-none",
|
|
147
|
+
node.childSelected && "font-medium",
|
|
148
|
+
(canSelect || onClick) && "hover:bg-primary-50 cursor-pointer",
|
|
149
|
+
)}
|
|
150
|
+
data-selected={node.selected}
|
|
151
|
+
onClick={() => {
|
|
152
|
+
onClick?.(node.id, node.data);
|
|
153
|
+
canSelect
|
|
154
|
+
? onSelect(node.id)
|
|
155
|
+
: canCheck
|
|
156
|
+
? onChecked(node.id)
|
|
157
|
+
: onToggle(node.id);
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
{nodeIcon && (
|
|
161
|
+
<div className="flex-content w-6 self-center leading-none">
|
|
162
|
+
{isValidElement(nodeIcon) && nodeIcon}
|
|
163
|
+
{isString(nodeIcon) && (
|
|
164
|
+
<Icon
|
|
165
|
+
icon={nodeIcon}
|
|
166
|
+
bg={node.iconBg}
|
|
167
|
+
color={node.iconColor}
|
|
168
|
+
rtlFlip={node.rtlFlip}
|
|
169
|
+
/>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
{node.childSelected && !node.open && (
|
|
174
|
+
<span className=" flex-content text-primary-500">•</span>
|
|
175
|
+
)}
|
|
176
|
+
<div role="none" className="flex-1 truncate">
|
|
177
|
+
{children}
|
|
143
178
|
</div>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
{!isEmpty(node.badge) && (
|
|
180
|
+
<Badge
|
|
181
|
+
{...badgeProps}
|
|
182
|
+
className={classNames(
|
|
183
|
+
"me-1 self-center flex-content bg-opacity-50",
|
|
184
|
+
badgeProps.className,
|
|
185
|
+
)}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
150
188
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
189
|
+
)}
|
|
190
|
+
{node.empty && (
|
|
191
|
+
<div className="text-muted text-sm select-none">
|
|
192
|
+
{t("tree.noItems")}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
161
195
|
</div>
|
|
162
196
|
);
|
|
163
197
|
};
|
package/src/tree/TreePanel.tsx
CHANGED
|
@@ -55,7 +55,16 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
55
55
|
expander = "box",
|
|
56
56
|
onChecked,
|
|
57
57
|
onLoad,
|
|
58
|
+
onQuery,
|
|
58
59
|
onSelect,
|
|
60
|
+
onClick,
|
|
61
|
+
sorter,
|
|
62
|
+
makeLabel,
|
|
63
|
+
makeIcon,
|
|
64
|
+
defaultExpanded,
|
|
65
|
+
onExpandToggle,
|
|
66
|
+
leafClassName,
|
|
67
|
+
nodeClassName,
|
|
59
68
|
...aria
|
|
60
69
|
}: TreePanelProps<T>) => {
|
|
61
70
|
const {
|
|
@@ -72,8 +81,13 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
72
81
|
checked,
|
|
73
82
|
selected,
|
|
74
83
|
matcher,
|
|
84
|
+
sorter,
|
|
85
|
+
onLoad,
|
|
86
|
+
onQuery,
|
|
75
87
|
onSelect,
|
|
76
88
|
onChecked,
|
|
89
|
+
defaultExpanded,
|
|
90
|
+
onExpandToggle,
|
|
77
91
|
});
|
|
78
92
|
|
|
79
93
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
@@ -99,7 +113,7 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
99
113
|
ref,
|
|
100
114
|
() => ({
|
|
101
115
|
open: (id: string) => expand(id),
|
|
102
|
-
select: (id: string) =>
|
|
116
|
+
select: (id: string) => select(id),
|
|
103
117
|
}),
|
|
104
118
|
[],
|
|
105
119
|
);
|
|
@@ -151,12 +165,18 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
151
165
|
checkable={checkable}
|
|
152
166
|
selectable={selectable}
|
|
153
167
|
defaultLeafIcon={defaultLeafIcon}
|
|
154
|
-
defaultNodeIcon={
|
|
168
|
+
defaultNodeIcon={
|
|
169
|
+
makeIcon?.(tree[index].data) ?? defaultNodeIcon
|
|
170
|
+
}
|
|
155
171
|
onToggle={toggleExpand}
|
|
156
172
|
onChecked={toggleCheck}
|
|
157
173
|
onSelect={select}
|
|
174
|
+
onClick={onClick}
|
|
175
|
+
leafClassName={leafClassName}
|
|
176
|
+
nodeClassName={nodeClassName}
|
|
158
177
|
>
|
|
159
|
-
{(tree[index].data && renderer?.(tree[index].data))
|
|
178
|
+
{(tree[index].data && renderer?.(tree[index].data)) ??
|
|
179
|
+
makeLabel?.(tree[index].data) ??
|
|
160
180
|
tree[index].label}
|
|
161
181
|
</TreeNode>
|
|
162
182
|
</div>
|
package/src/tree/types.ts
CHANGED
|
@@ -30,8 +30,9 @@ import {
|
|
|
30
30
|
type RefProp,
|
|
31
31
|
type TestProps,
|
|
32
32
|
} from "@react-fabric/core/dist/types/types";
|
|
33
|
+
import { type ElementType } from "react";
|
|
33
34
|
|
|
34
|
-
interface TreeBaseNode<T> extends IconProps, CssProp, TestProps {
|
|
35
|
+
interface TreeBaseNode<T> extends Omit<IconProps, "icon">, CssProp, TestProps {
|
|
35
36
|
id: string;
|
|
36
37
|
/**
|
|
37
38
|
* display label
|
|
@@ -54,20 +55,25 @@ interface TreeBaseNode<T> extends IconProps, CssProp, TestProps {
|
|
|
54
55
|
*/
|
|
55
56
|
disabled?: boolean;
|
|
56
57
|
data?: T;
|
|
58
|
+
|
|
59
|
+
icon?: ElementType<IconProps> | string;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export type TreeNodeType<T extends KeyValue = KeyValue> = TreeBaseNode<T> &
|
|
60
63
|
(
|
|
61
64
|
| { leaf: true; children?: never }
|
|
62
65
|
| {
|
|
63
|
-
leaf?:
|
|
66
|
+
leaf?: boolean;
|
|
64
67
|
children?: Array<TreeNodeType<T>>;
|
|
65
68
|
open?: boolean;
|
|
66
69
|
iconOpen?: string;
|
|
67
70
|
}
|
|
68
71
|
);
|
|
69
72
|
|
|
70
|
-
export interface InternalNode
|
|
73
|
+
export interface InternalNode
|
|
74
|
+
extends Omit<IconProps, "icon">,
|
|
75
|
+
CssProp,
|
|
76
|
+
TestProps {
|
|
71
77
|
id: string;
|
|
72
78
|
label: Elements<JSX.Element>;
|
|
73
79
|
queryable?: string;
|
|
@@ -84,6 +90,7 @@ export interface InternalNode extends IconProps, CssProp, TestProps {
|
|
|
84
90
|
loaded: boolean;
|
|
85
91
|
parent?: string;
|
|
86
92
|
empty?: boolean;
|
|
93
|
+
icon?: ElementType<IconProps> | string;
|
|
87
94
|
errored?: string;
|
|
88
95
|
selected?: true;
|
|
89
96
|
childSelected?: true;
|
|
@@ -101,9 +108,12 @@ export interface TreeNodeProps extends ChildProp {
|
|
|
101
108
|
noLines?: boolean;
|
|
102
109
|
selectable?: true | "leafOnly";
|
|
103
110
|
checkable?: true | "leafOnly";
|
|
111
|
+
leafClassName?: string;
|
|
112
|
+
nodeClassName?: string;
|
|
104
113
|
onToggle: (id: string) => void;
|
|
105
114
|
onSelect: (id: string) => void;
|
|
106
115
|
onChecked: (id: string) => void;
|
|
116
|
+
onClick?: (id: string, node: any) => void;
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
export interface TreeRef {
|
|
@@ -164,10 +174,18 @@ export interface TreePanelProps<T extends KeyValue = KeyValue>
|
|
|
164
174
|
| Promise<Array<TreeNodeType<T>> | undefined>
|
|
165
175
|
| Array<TreeNodeType<T>>
|
|
166
176
|
| undefined;
|
|
177
|
+
/**
|
|
178
|
+
* callback to load tree using search query
|
|
179
|
+
*/
|
|
180
|
+
onQuery?: (query: string) => void;
|
|
181
|
+
/**
|
|
182
|
+
* callback on click of tree node
|
|
183
|
+
*/
|
|
184
|
+
onClick?: (id: string, data: T) => void;
|
|
167
185
|
/**
|
|
168
186
|
* callback on selection of tree node
|
|
169
187
|
*/
|
|
170
|
-
onSelect?: (id: string) => void;
|
|
188
|
+
onSelect?: (id: string, data: T) => void;
|
|
171
189
|
/**
|
|
172
190
|
* callback on change of checked list
|
|
173
191
|
*/
|
|
@@ -180,6 +198,16 @@ export interface TreePanelProps<T extends KeyValue = KeyValue>
|
|
|
180
198
|
* item filter matcher
|
|
181
199
|
*/
|
|
182
200
|
matcher?: (data: T, query: string) => boolean;
|
|
201
|
+
|
|
202
|
+
sorter?: false | ((a: T, b: T) => number);
|
|
203
|
+
makeLabel?: (node: T) => string;
|
|
204
|
+
makeIcon?: (node: T) => string;
|
|
205
|
+
|
|
206
|
+
defaultExpanded?: string[];
|
|
207
|
+
onExpandToggle?: (id: string[]) => void;
|
|
208
|
+
|
|
209
|
+
leafClassName?: string;
|
|
210
|
+
nodeClassName?: string;
|
|
183
211
|
}
|
|
184
212
|
|
|
185
213
|
export const iconExpandAll =
|
package/src/tree/useTree.ts
CHANGED
|
@@ -25,7 +25,11 @@ import { useDebounce } from "@react-fabric/core";
|
|
|
25
25
|
import { EMPTY_ARRAY } from "@react-fabric/utilities";
|
|
26
26
|
import memoizeOne from "memoize-one";
|
|
27
27
|
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type InternalNode,
|
|
30
|
+
type TreeNodeType,
|
|
31
|
+
type TreePanelProps,
|
|
32
|
+
} from "./types";
|
|
29
33
|
import {
|
|
30
34
|
filterTree,
|
|
31
35
|
flattenTree,
|
|
@@ -71,7 +75,11 @@ type TreeActions =
|
|
|
71
75
|
/**
|
|
72
76
|
* collapse all nodes
|
|
73
77
|
*/
|
|
74
|
-
| { type: "collapseAll" }
|
|
78
|
+
| { type: "collapseAll" }
|
|
79
|
+
/**
|
|
80
|
+
* loaded dynamic nodes
|
|
81
|
+
*/
|
|
82
|
+
| { type: "loaded" };
|
|
75
83
|
|
|
76
84
|
interface TreeState {
|
|
77
85
|
items: InternalNode[];
|
|
@@ -87,47 +95,104 @@ 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,
|
|
101
|
+
onQuery,
|
|
91
102
|
onSelect,
|
|
92
103
|
onChecked,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
checked?: string[];
|
|
97
|
-
onSelect?: (id: string) => void;
|
|
98
|
-
onChecked?: (leafs: string[], nodes: string[]) => void;
|
|
99
|
-
matcher?: (data: T, query: string) => boolean;
|
|
100
|
-
}) => {
|
|
104
|
+
defaultExpanded,
|
|
105
|
+
onExpandToggle,
|
|
106
|
+
}: Partial<TreePanelProps<T>>) => {
|
|
101
107
|
const componentEvents = useRef({ onSelect, onChecked });
|
|
102
|
-
|
|
108
|
+
const intialExpand = useRef(defaultExpanded ?? EMPTY_ARRAY);
|
|
103
109
|
const itemList = createItemList(items);
|
|
104
110
|
|
|
111
|
+
const fireExpandToggle = useCallback(
|
|
112
|
+
(itemMap: InternalNode[]) => {
|
|
113
|
+
const expandedList = itemMap
|
|
114
|
+
.filter((node) => node.open)
|
|
115
|
+
.map((node) => node.id);
|
|
116
|
+
setTimeout(() => onExpandToggle?.(expandedList), 100);
|
|
117
|
+
},
|
|
118
|
+
[onExpandToggle],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const loadChildren = useCallback(
|
|
122
|
+
(node: InternalNode) => {
|
|
123
|
+
if (node.open && !node.leaf && !node.children?.length) {
|
|
124
|
+
node.loading = true;
|
|
125
|
+
void Promise.resolve(onLoad?.(node.id))
|
|
126
|
+
.then((resp = EMPTY_ARRAY) => {
|
|
127
|
+
node.children = refactorTree(
|
|
128
|
+
resp,
|
|
129
|
+
{ sorter, defaultExpanded },
|
|
130
|
+
{ level: node.level + 1, parent: node.id },
|
|
131
|
+
);
|
|
132
|
+
})
|
|
133
|
+
.finally(() => {
|
|
134
|
+
node.loading = false;
|
|
135
|
+
dispatch({ type: "loaded" });
|
|
136
|
+
intialExpand.current?.length > 0 &&
|
|
137
|
+
dispatch({
|
|
138
|
+
type: "expand",
|
|
139
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
dispatch({ type: "loaded" });
|
|
144
|
+
intialExpand.current?.length > 0 &&
|
|
145
|
+
dispatch({
|
|
146
|
+
type: "expand",
|
|
147
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[onLoad],
|
|
152
|
+
);
|
|
153
|
+
|
|
105
154
|
const [state, dispatch] = useReducer(
|
|
106
155
|
(state: TreeState, action: TreeActions) => {
|
|
107
156
|
if (action.type === "load") {
|
|
108
|
-
state.items = refactorTree(action.items);
|
|
157
|
+
state.items = refactorTree(action.items, { sorter, defaultExpanded });
|
|
109
158
|
state.itemMap = makeTreeMap(state.items);
|
|
110
159
|
state.tree = flattenTree(state.items);
|
|
111
160
|
return { ...state };
|
|
112
161
|
}
|
|
113
162
|
if (action.type === "toggleExpand") {
|
|
114
163
|
const node = state.itemMap.get(action.id);
|
|
115
|
-
|
|
164
|
+
if (node && !node.leaf) {
|
|
165
|
+
node && (node.open = !node.open);
|
|
166
|
+
loadChildren(node);
|
|
167
|
+
return { ...state };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (action.type === "loaded") {
|
|
171
|
+
state.itemMap = makeTreeMap(state.items);
|
|
172
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
116
173
|
state.tree = flattenTree(state.items);
|
|
174
|
+
state.selected && updateSelection(state.itemMap, state.selected, true);
|
|
117
175
|
return { ...state };
|
|
118
176
|
}
|
|
119
177
|
if (action.type === "expand") {
|
|
120
|
-
// TODO: check if children available, if not call onLoad to fetch node children
|
|
121
178
|
const node = state.itemMap.get(action.id);
|
|
122
|
-
node &&
|
|
123
|
-
|
|
124
|
-
|
|
179
|
+
if (node && !node.leaf) {
|
|
180
|
+
node.open = true;
|
|
181
|
+
let parentId = node.parent;
|
|
182
|
+
while (parentId) {
|
|
183
|
+
const node = state.itemMap.get(parentId);
|
|
184
|
+
node && (node.open = true);
|
|
185
|
+
parentId = node?.parent;
|
|
186
|
+
}
|
|
187
|
+
loadChildren(node);
|
|
188
|
+
return { ...state };
|
|
189
|
+
}
|
|
125
190
|
}
|
|
126
191
|
if (action.type === "expandAll") {
|
|
127
|
-
// TODO: prevent opening dynamic nodes that dont have children
|
|
128
192
|
state.itemMap.forEach((node) => {
|
|
129
|
-
!node.leaf && (node.open = true);
|
|
193
|
+
!node.leaf && node.children?.length && (node.open = true);
|
|
130
194
|
});
|
|
195
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
131
196
|
state.tree = flattenTree(state.items);
|
|
132
197
|
return { ...state };
|
|
133
198
|
}
|
|
@@ -135,13 +200,18 @@ export const useTree = <T extends KeyValue>({
|
|
|
135
200
|
state.itemMap.forEach((node) => {
|
|
136
201
|
!node.leaf && (node.open = false);
|
|
137
202
|
});
|
|
203
|
+
fireExpandToggle([]);
|
|
138
204
|
state.tree = flattenTree(state.items);
|
|
139
205
|
return { ...state };
|
|
140
206
|
}
|
|
141
207
|
if (action.type === "filter") {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
208
|
+
if (onQuery) {
|
|
209
|
+
onQuery(action.query ?? "");
|
|
210
|
+
} else {
|
|
211
|
+
filterTree(state.itemMap, action.query, matcher);
|
|
212
|
+
state.tree = flattenTree(state.items);
|
|
213
|
+
return { ...state };
|
|
214
|
+
}
|
|
145
215
|
}
|
|
146
216
|
if (action.type === "select") {
|
|
147
217
|
if (state.selected) {
|
|
@@ -149,7 +219,10 @@ export const useTree = <T extends KeyValue>({
|
|
|
149
219
|
}
|
|
150
220
|
updateSelection(state.itemMap, action.id, true);
|
|
151
221
|
state.tree = flattenTree(state.items);
|
|
152
|
-
componentEvents.current.onSelect?.(
|
|
222
|
+
componentEvents.current.onSelect?.(
|
|
223
|
+
action.id,
|
|
224
|
+
state.itemMap.get(action.id)?.data,
|
|
225
|
+
);
|
|
153
226
|
return { ...state, selected: action.id };
|
|
154
227
|
}
|
|
155
228
|
if (action.type === "toggleCheck") {
|
|
@@ -181,6 +254,12 @@ export const useTree = <T extends KeyValue>({
|
|
|
181
254
|
|
|
182
255
|
useEffect(() => {
|
|
183
256
|
dispatch({ type: "load", items: itemList });
|
|
257
|
+
itemList.length &&
|
|
258
|
+
intialExpand.current?.length > 0 &&
|
|
259
|
+
dispatch({
|
|
260
|
+
type: "expand",
|
|
261
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
262
|
+
});
|
|
184
263
|
}, [itemList]);
|
|
185
264
|
|
|
186
265
|
useEffect(() => {
|
package/src/tree/utils.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { compareValues, isString, matchString } from "@react-fabric/utilities";
|
|
25
25
|
import { type InternalNode, type TreeNodeType } from "./types";
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const defaultSorter = (a: TreeNodeType, b: TreeNodeType) => {
|
|
28
28
|
if (!!a.leaf !== !!b.leaf) return a.leaf ? 1 : -1;
|
|
29
29
|
return compareValues()(a.label, b.label);
|
|
30
30
|
};
|
|
@@ -48,13 +48,17 @@ const getIntermediate = (list: AnyObject[] = []) => {
|
|
|
48
48
|
// convert node item to internal node used by the tree
|
|
49
49
|
export const refactorTree = (
|
|
50
50
|
nodes: TreeNodeType[],
|
|
51
|
+
{
|
|
52
|
+
sorter = defaultSorter,
|
|
53
|
+
defaultExpanded = [],
|
|
54
|
+
}: { sorter?: any; defaultExpanded?: string[] },
|
|
51
55
|
options: { level: number; parent?: string } = { level: 0, parent: undefined },
|
|
52
56
|
) => {
|
|
53
57
|
const list: InternalNode[] = [];
|
|
54
|
-
|
|
55
|
-
nodes.
|
|
58
|
+
if (sorter !== false) nodes = nodes.sort(sorter);
|
|
59
|
+
nodes.forEach((node) => {
|
|
56
60
|
list.push({
|
|
57
|
-
open:
|
|
61
|
+
open: defaultExpanded.includes(node.id) && !!node.children?.length,
|
|
58
62
|
level: options.level,
|
|
59
63
|
parent: options.parent,
|
|
60
64
|
checked: 0,
|
|
@@ -66,10 +70,14 @@ export const refactorTree = (
|
|
|
66
70
|
leaf: !!node.leaf,
|
|
67
71
|
children:
|
|
68
72
|
node.children &&
|
|
69
|
-
refactorTree(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
refactorTree(
|
|
74
|
+
node.children,
|
|
75
|
+
{ sorter, defaultExpanded },
|
|
76
|
+
{
|
|
77
|
+
level: options.level + 1,
|
|
78
|
+
parent: node.id,
|
|
79
|
+
},
|
|
80
|
+
),
|
|
73
81
|
});
|
|
74
82
|
});
|
|
75
83
|
|
|
@@ -106,6 +114,13 @@ export const flattenTree = (
|
|
|
106
114
|
[...lines, idx === filtered.length - 1 ? 0 : 1],
|
|
107
115
|
),
|
|
108
116
|
);
|
|
117
|
+
node.open &&
|
|
118
|
+
node.children?.length === 0 &&
|
|
119
|
+
list.push({
|
|
120
|
+
empty: true,
|
|
121
|
+
leaf: true,
|
|
122
|
+
lines: [...lines, idx === filtered.length - 1 ? 0 : 1, 2],
|
|
123
|
+
} as any);
|
|
109
124
|
});
|
|
110
125
|
return list;
|
|
111
126
|
};
|