@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.
Files changed (83) hide show
  1. package/dist/cjs/checklist/CheckList.js +143 -0
  2. package/dist/cjs/histogram/Histogram.js +16 -5
  3. package/dist/cjs/hooks/usePagination.js +7 -4
  4. package/dist/cjs/hooks/useSelectableList.js +54 -0
  5. package/dist/cjs/index.js +2 -0
  6. package/dist/cjs/json/JsonViewer.js +30 -23
  7. package/dist/cjs/table/BodyCell.js +38 -5
  8. package/dist/cjs/table/BodyRow.js +107 -0
  9. package/dist/cjs/table/CheckboxCell.js +1 -1
  10. package/dist/cjs/table/HeaderCell.js +29 -36
  11. package/dist/cjs/table/Table.js +104 -66
  12. package/dist/cjs/table/useResizer.js +4 -4
  13. package/dist/cjs/table/useVirtualTable.js +118 -17
  14. package/dist/cjs/tree/TreeNode.js +44 -24
  15. package/dist/cjs/tree/TreePanel.js +28 -10
  16. package/dist/cjs/tree/useTree.js +96 -14
  17. package/dist/cjs/tree/utils.js +20 -5
  18. package/dist/cjs/virtual/Gallery.js +43 -29
  19. package/dist/cjs/virtual/List.js +43 -31
  20. package/dist/esm/checklist/CheckList.js +143 -0
  21. package/dist/esm/histogram/Histogram.js +16 -5
  22. package/dist/esm/hooks/usePagination.js +7 -4
  23. package/dist/esm/hooks/useSelectableList.js +54 -0
  24. package/dist/esm/index.js +1 -0
  25. package/dist/esm/json/JsonViewer.js +31 -24
  26. package/dist/esm/table/BodyCell.js +38 -5
  27. package/dist/esm/table/BodyRow.js +107 -0
  28. package/dist/esm/table/CheckboxCell.js +1 -1
  29. package/dist/esm/table/HeaderCell.js +30 -37
  30. package/dist/esm/table/Table.js +106 -68
  31. package/dist/esm/table/useResizer.js +4 -4
  32. package/dist/esm/table/useVirtualTable.js +120 -19
  33. package/dist/esm/tree/TreeNode.js +46 -26
  34. package/dist/esm/tree/TreePanel.js +28 -10
  35. package/dist/esm/tree/useTree.js +98 -16
  36. package/dist/esm/tree/utils.js +20 -5
  37. package/dist/esm/virtual/Gallery.js +43 -29
  38. package/dist/esm/virtual/List.js +43 -31
  39. package/dist/types/checklist/CheckList.d.ts +30 -0
  40. package/dist/types/histogram/Histogram.d.ts +3 -3
  41. package/dist/types/hooks/useSelectableList.d.ts +28 -0
  42. package/dist/types/index.d.ts +2 -1
  43. package/dist/types/table/AddColumn.d.ts +2 -2
  44. package/dist/types/table/BodyCell.d.ts +4 -3
  45. package/dist/types/table/BodyRow.d.ts +20 -0
  46. package/dist/types/table/EmptyCell.d.ts +2 -2
  47. package/dist/types/table/HeaderCell.d.ts +2 -2
  48. package/dist/types/table/Table.d.ts +1 -1
  49. package/dist/types/table/types.d.ts +19 -8
  50. package/dist/types/table/useTableColumns.d.ts +6 -6
  51. package/dist/types/table/useVirtualTable.d.ts +20 -5
  52. package/dist/types/tree/TreeNode.d.ts +1 -1
  53. package/dist/types/tree/TreePanel.d.ts +1 -1
  54. package/dist/types/tree/types.d.ts +25 -5
  55. package/dist/types/tree/useTree.d.ts +2 -9
  56. package/dist/types/tree/utils.d.ts +4 -1
  57. package/dist/types/virtual/Gallery.d.ts +11 -3
  58. package/dist/types/virtual/List.d.ts +7 -3
  59. package/package.json +8 -7
  60. package/src/checklist/CheckList.tsx +222 -0
  61. package/src/histogram/Histogram.tsx +9 -2
  62. package/src/hooks/usePagination.tsx +13 -7
  63. package/src/hooks/useSelectableList.ts +94 -0
  64. package/src/index.ts +2 -1
  65. package/src/json/JsonViewer.tsx +31 -31
  66. package/src/table/AddColumn.tsx +2 -2
  67. package/src/table/BodyCell.tsx +34 -5
  68. package/src/table/BodyRow.tsx +143 -0
  69. package/src/table/CheckboxCell.tsx +1 -1
  70. package/src/table/EmptyCell.tsx +2 -2
  71. package/src/table/HeaderCell.tsx +27 -13
  72. package/src/table/Table.tsx +141 -73
  73. package/src/table/types.ts +22 -7
  74. package/src/table/useResizer.ts +5 -3
  75. package/src/table/useTableColumns.ts +7 -7
  76. package/src/table/useVirtualTable.ts +142 -27
  77. package/src/tree/TreeNode.tsx +90 -56
  78. package/src/tree/TreePanel.tsx +23 -3
  79. package/src/tree/types.ts +32 -4
  80. package/src/tree/useTree.ts +102 -23
  81. package/src/tree/utils.ts +23 -8
  82. package/src/virtual/Gallery.tsx +45 -34
  83. package/src/virtual/List.tsx +40 -27
@@ -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(noLines ? 1 : 0).map((line, idx) => (
48
- <svg
53
+ return node.lines.slice(1).map((line, idx) => (
54
+ <div
49
55
  key={idx}
50
- viewBox="0 0 24 24"
51
- className="flex-initial w-6 fill-tint-200"
52
- >
53
- {line === 0 && <path d="" />}
54
- {!noLines && line === 1 && <path d="M7.45 26.1v-28.2h1.1v28.2h-1.1z" />}
55
- {!noLines && line === 2 && (
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
- </svg>
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="flex flex-nowrap"
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
- {!node.leaf && !node.childFiltered && (
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
- <div
120
- role="none"
121
- className={classNames(
122
- "group data-[selected]:bg-primary-100 flex flex-nowrap flex-1 items-center overflow-hidden select-none",
123
- node.childSelected && "font-medium",
124
- canSelect && "hover:bg-primary-50 cursor-pointer",
125
- )}
126
- data-selected={node.selected}
127
- onClick={() =>
128
- canSelect
129
- ? onSelect(node.id)
130
- : canCheck
131
- ? onChecked(node.id)
132
- : onToggle(node.id)
133
- }
134
- >
135
- {nodeIcon && (
136
- <div className="flex-content w-6">
137
- <Icon
138
- icon={nodeIcon}
139
- bg={node.iconBg}
140
- color={node.iconColor}
141
- rtlFlip={node.rtlFlip}
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
- {node.childSelected && !node.open && (
146
- <span className=" flex-content text-primary-500">•</span>
147
- )}
148
- <div role="none" className="flex-1 truncate">
149
- {children}
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
- {node.badge && (
152
- <Badge
153
- {...badgeProps}
154
- className={classNames(
155
- "me-1 self-center flex-content bg-opacity-50",
156
- badgeProps.className,
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
  };
@@ -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) => expand(id),
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={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?: false;
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 extends IconProps, CssProp, TestProps {
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 =
@@ -25,7 +25,11 @@ import { useDebounce } from "@react-fabric/core";
25
25
  import { EMPTY_ARRAY } from "@react-fabric/utilities";
26
26
  import memoizeOne from "memoize-one";
27
27
  import { useCallback, useEffect, useReducer, useRef } from "react";
28
- import { type InternalNode, type TreeNodeType } from "./types";
28
+ import {
29
+ type InternalNode,
30
+ type TreeNodeType,
31
+ type TreePanelProps,
32
+ } from "./types";
29
33
  import {
30
34
  filterTree,
31
35
  flattenTree,
@@ -71,7 +75,11 @@ type TreeActions =
71
75
  /**
72
76
  * collapse all nodes
73
77
  */
74
- | { type: "collapseAll" };
78
+ | { type: "collapseAll" }
79
+ /**
80
+ * loaded dynamic nodes
81
+ */
82
+ | { type: "loaded" };
75
83
 
76
84
  interface TreeState {
77
85
  items: InternalNode[];
@@ -87,47 +95,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
- 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
- }) => {
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
- node && (node.open = !node.open);
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 && (node.open = true);
123
- state.tree = flattenTree(state.items);
124
- return { ...state };
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
- filterTree(state.itemMap, action.query, matcher);
143
- state.tree = flattenTree(state.items);
144
- return { ...state };
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?.(action.id);
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 sorter = (a: TreeNodeType, b: TreeNodeType) => {
27
+ const defaultSorter = (a: TreeNodeType, b: TreeNodeType) => {
28
28
  if (!!a.leaf !== !!b.leaf) return a.leaf ? 1 : -1;
29
29
  return compareValues()(a.label, b.label);
30
30
  };
@@ -48,13 +48,17 @@ const getIntermediate = (list: AnyObject[] = []) => {
48
48
  // convert node item to internal node used by the tree
49
49
  export const refactorTree = (
50
50
  nodes: TreeNodeType[],
51
+ {
52
+ sorter = defaultSorter,
53
+ defaultExpanded = [],
54
+ }: { sorter?: any; defaultExpanded?: string[] },
51
55
  options: { level: number; parent?: string } = { level: 0, parent: undefined },
52
56
  ) => {
53
57
  const list: InternalNode[] = [];
54
-
55
- nodes.sort(sorter).forEach((node) => {
58
+ if (sorter !== false) nodes = nodes.sort(sorter);
59
+ nodes.forEach((node) => {
56
60
  list.push({
57
- open: false,
61
+ open: 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(node.children, {
70
- level: options.level + 1,
71
- parent: node.id,
72
- }),
73
+ refactorTree(
74
+ node.children,
75
+ { sorter, defaultExpanded },
76
+ {
77
+ level: options.level + 1,
78
+ parent: node.id,
79
+ },
80
+ ),
73
81
  });
74
82
  });
75
83
 
@@ -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
  };