@snack-uikit/tree 0.10.3 → 0.11.1-preview-c3fee040.0

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 (69) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +121 -0
  3. package/dist/cjs/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.d.ts +1 -0
  4. package/dist/cjs/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.js +80 -0
  5. package/dist/cjs/helpers/__tests__/setChildrenOfTreeNode.spec.d.ts +1 -0
  6. package/dist/cjs/helpers/__tests__/setChildrenOfTreeNode.spec.js +102 -0
  7. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.d.ts +2 -0
  8. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.js +17 -0
  9. package/dist/cjs/helpers/index.d.ts +2 -0
  10. package/dist/cjs/helpers/index.js +3 -1
  11. package/dist/cjs/helpers/setChildrenOfTreeNode.d.ts +2 -0
  12. package/dist/cjs/helpers/setChildrenOfTreeNode.js +28 -0
  13. package/dist/cjs/helpers/sortTreeItemsByTitle.js +2 -2
  14. package/dist/cjs/helpers/traverse.d.ts +7 -1
  15. package/dist/cjs/helpers/traverse.js +38 -2
  16. package/dist/cjs/hooks/__tests__/useSearchableTree.spec.d.ts +1 -0
  17. package/dist/cjs/hooks/__tests__/useSearchableTree.spec.js +230 -0
  18. package/dist/cjs/hooks/__tests__/useTreeMultiSelection.spec.d.ts +1 -0
  19. package/dist/cjs/hooks/__tests__/useTreeMultiSelection.spec.js +225 -0
  20. package/dist/cjs/hooks/index.d.ts +2 -0
  21. package/dist/cjs/hooks/index.js +26 -0
  22. package/dist/cjs/hooks/useSearchableTree.d.ts +28 -0
  23. package/dist/cjs/hooks/useSearchableTree.js +147 -0
  24. package/dist/cjs/hooks/useTreeMultiSelection.d.ts +12 -0
  25. package/dist/cjs/hooks/useTreeMultiSelection.js +82 -0
  26. package/dist/cjs/index.d.ts +2 -1
  27. package/dist/cjs/index.js +2 -1
  28. package/dist/cjs/types.d.ts +17 -0
  29. package/dist/esm/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.d.ts +1 -0
  30. package/dist/esm/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.js +64 -0
  31. package/dist/esm/helpers/__tests__/setChildrenOfTreeNode.spec.d.ts +1 -0
  32. package/dist/esm/helpers/__tests__/setChildrenOfTreeNode.spec.js +50 -0
  33. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.d.ts +2 -0
  34. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.js +11 -0
  35. package/dist/esm/helpers/index.d.ts +2 -0
  36. package/dist/esm/helpers/index.js +2 -0
  37. package/dist/esm/helpers/setChildrenOfTreeNode.d.ts +2 -0
  38. package/dist/esm/helpers/setChildrenOfTreeNode.js +20 -0
  39. package/dist/esm/helpers/sortTreeItemsByTitle.js +2 -2
  40. package/dist/esm/helpers/traverse.d.ts +7 -1
  41. package/dist/esm/helpers/traverse.js +24 -0
  42. package/dist/esm/hooks/__tests__/useSearchableTree.spec.d.ts +1 -0
  43. package/dist/esm/hooks/__tests__/useSearchableTree.spec.js +165 -0
  44. package/dist/esm/hooks/__tests__/useTreeMultiSelection.spec.d.ts +1 -0
  45. package/dist/esm/hooks/__tests__/useTreeMultiSelection.spec.js +130 -0
  46. package/dist/esm/hooks/index.d.ts +2 -0
  47. package/dist/esm/hooks/index.js +2 -0
  48. package/dist/esm/hooks/useSearchableTree.d.ts +28 -0
  49. package/dist/esm/hooks/useSearchableTree.js +108 -0
  50. package/dist/esm/hooks/useTreeMultiSelection.d.ts +12 -0
  51. package/dist/esm/hooks/useTreeMultiSelection.js +43 -0
  52. package/dist/esm/index.d.ts +2 -1
  53. package/dist/esm/index.js +1 -0
  54. package/dist/esm/types.d.ts +17 -0
  55. package/package.json +22 -4
  56. package/src/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.ts +88 -0
  57. package/src/helpers/__tests__/setChildrenOfTreeNode.spec.ts +68 -0
  58. package/src/helpers/collectEmptyNestedNodesInExpanded.ts +16 -0
  59. package/src/helpers/index.ts +2 -0
  60. package/src/helpers/setChildrenOfTreeNode.ts +30 -0
  61. package/src/helpers/sortTreeItemsByTitle.ts +2 -2
  62. package/src/helpers/traverse.ts +38 -1
  63. package/src/hooks/__tests__/useSearchableTree.spec.ts +200 -0
  64. package/src/hooks/__tests__/useTreeMultiSelection.spec.ts +165 -0
  65. package/src/hooks/index.ts +2 -0
  66. package/src/hooks/useSearchableTree.ts +163 -0
  67. package/src/hooks/useTreeMultiSelection.ts +61 -0
  68. package/src/index.ts +2 -1
  69. package/src/types.ts +15 -0
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+
3
+ var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) {
5
+ return value instanceof P ? value : new P(function (resolve) {
6
+ resolve(value);
7
+ });
8
+ }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) {
11
+ try {
12
+ step(generator.next(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ }
17
+ function rejected(value) {
18
+ try {
19
+ step(generator["throw"](value));
20
+ } catch (e) {
21
+ reject(e);
22
+ }
23
+ }
24
+ function step(result) {
25
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
26
+ }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", {
31
+ value: true
32
+ });
33
+ const react_1 = require("@testing-library/react");
34
+ const react_2 = require("react");
35
+ const vitest_1 = require("vitest");
36
+ const useSearchableTree_1 = require("../useSearchableTree");
37
+ vitest_1.vi.mock('@siberiacancode/reactuse', () => ({
38
+ useDebounceValue: value => value,
39
+ useDidUpdate: (effect, deps) => {
40
+ const isFirstRenderRef = (0, react_2.useRef)(true);
41
+ (0, react_2.useEffect)(() => {
42
+ if (isFirstRenderRef.current) {
43
+ isFirstRenderRef.current = false;
44
+ return;
45
+ }
46
+ effect();
47
+ }, [deps, effect]);
48
+ },
49
+ useRefState: initialValue => (0, react_2.useRef)(initialValue)
50
+ }));
51
+ const createLeaf = id => ({
52
+ id,
53
+ title: id
54
+ });
55
+ const createParent = (id, nested) => ({
56
+ id,
57
+ title: id,
58
+ nested
59
+ });
60
+ const mapNodeToRecordItem = node => ({
61
+ label: String(node.title)
62
+ });
63
+ (0, vitest_1.describe)('useSearchableTree', () => {
64
+ (0, vitest_1.it)('should initialize tree and treeItemsRecord from initTree', () => {
65
+ const initTree = [createLeaf('node-1'), createParent('node-2', [createLeaf('node-2-1')])];
66
+ const {
67
+ result
68
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
69
+ initTree,
70
+ onPreloadNode: vitest_1.vi.fn(),
71
+ onPreloadNodes: vitest_1.vi.fn(),
72
+ onSearch: vitest_1.vi.fn(),
73
+ mapNodeToRecordItem
74
+ }));
75
+ (0, vitest_1.expect)(result.current.tree.current).toEqual(initTree);
76
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
77
+ 'node-1': {
78
+ label: 'node-1'
79
+ },
80
+ 'node-2': {
81
+ label: 'node-2'
82
+ },
83
+ 'node-2-1': {
84
+ label: 'node-2-1'
85
+ }
86
+ });
87
+ });
88
+ (0, vitest_1.it)('should update expandedNodes on onExpand call', () => {
89
+ const {
90
+ result
91
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
92
+ initTree: [createLeaf('root')],
93
+ onPreloadNode: vitest_1.vi.fn(),
94
+ onPreloadNodes: vitest_1.vi.fn(),
95
+ onSearch: vitest_1.vi.fn(),
96
+ mapNodeToRecordItem
97
+ }));
98
+ (0, react_1.act)(() => {
99
+ result.current.onExpand(['root', 'child']);
100
+ });
101
+ (0, vitest_1.expect)(result.current.expandedNodes.current).toEqual(['root', 'child']);
102
+ });
103
+ (0, vitest_1.it)('should preload node children and update tree and record on onDataLoad', () => __awaiter(void 0, void 0, void 0, function* () {
104
+ const initTree = [createParent('root', [])];
105
+ const preloadedChildren = [createLeaf('child-1'), createLeaf('child-2')];
106
+ const onPreloadNode = vitest_1.vi.fn().mockResolvedValue(preloadedChildren);
107
+ const {
108
+ result
109
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
110
+ initTree,
111
+ onPreloadNode,
112
+ onPreloadNodes: vitest_1.vi.fn(),
113
+ onSearch: vitest_1.vi.fn(),
114
+ mapNodeToRecordItem
115
+ }));
116
+ let loadResult;
117
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
118
+ loadResult = yield result.current.onDataLoad(createParent('root', []));
119
+ }));
120
+ (0, vitest_1.expect)(onPreloadNode).toHaveBeenCalledWith(createParent('root', []));
121
+ (0, vitest_1.expect)(loadResult).toEqual({
122
+ preloadedChildren,
123
+ updatedTree: [createParent('root', preloadedChildren)],
124
+ newTreeItemsRecord: {
125
+ root: {
126
+ label: 'root'
127
+ },
128
+ 'child-1': {
129
+ label: 'child-1'
130
+ },
131
+ 'child-2': {
132
+ label: 'child-2'
133
+ }
134
+ }
135
+ });
136
+ (0, vitest_1.expect)(result.current.tree.current).toEqual([createParent('root', preloadedChildren)]);
137
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
138
+ root: {
139
+ label: 'root'
140
+ },
141
+ 'child-1': {
142
+ label: 'child-1'
143
+ },
144
+ 'child-2': {
145
+ label: 'child-2'
146
+ }
147
+ });
148
+ }));
149
+ (0, vitest_1.it)('should run search and preload required nodes', () => __awaiter(void 0, void 0, void 0, function* () {
150
+ const searchedTree = [createParent('expandable-root', []), createLeaf('leaf')];
151
+ const onSearch = vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
152
+ return {
153
+ tree: searchedTree,
154
+ needPreloadNodes: ['expandable-root', 'external-root']
155
+ };
156
+ }));
157
+ const onPreloadNodes = vitest_1.vi.fn(nodeIds => __awaiter(void 0, void 0, void 0, function* () {
158
+ return Object.assign({
159
+ 'expandable-root': [createLeaf('expandable-child')],
160
+ 'external-root': [createLeaf('external-child')]
161
+ }, Object.fromEntries(nodeIds.filter(id => id !== 'expandable-root' && id !== 'external-root').map(id => [id, []])));
162
+ }));
163
+ const {
164
+ result
165
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
166
+ initTree: [createParent('init-root', [])],
167
+ onPreloadNode: vitest_1.vi.fn(),
168
+ onPreloadNodes,
169
+ onSearch,
170
+ mapNodeToRecordItem
171
+ }));
172
+ (0, react_1.act)(() => {
173
+ result.current.onExpand(['expandable-root']);
174
+ result.current.search.onChange('query');
175
+ });
176
+ yield (0, react_1.waitFor)(() => {
177
+ (0, vitest_1.expect)(onSearch).toHaveBeenCalledWith({
178
+ search: 'query'
179
+ }, vitest_1.expect.any(AbortSignal));
180
+ (0, vitest_1.expect)(onPreloadNodes).toHaveBeenCalledWith(['expandable-root', 'external-root'], vitest_1.expect.any(AbortSignal));
181
+ });
182
+ (0, vitest_1.expect)(result.current.tree.current).toEqual([createParent('expandable-root', [createLeaf('expandable-child')]), createLeaf('leaf')]);
183
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
184
+ 'expandable-root': {
185
+ label: 'expandable-root'
186
+ },
187
+ 'expandable-child': {
188
+ label: 'expandable-child'
189
+ },
190
+ leaf: {
191
+ label: 'leaf'
192
+ }
193
+ });
194
+ }));
195
+ (0, vitest_1.it)('should update tree and record from search result without preloading', () => __awaiter(void 0, void 0, void 0, function* () {
196
+ const onSearch = vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
197
+ return {
198
+ tree: [createParent('root-node', [createLeaf('child-node')])],
199
+ needPreloadNodes: []
200
+ };
201
+ }));
202
+ const {
203
+ result
204
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
205
+ initTree: [createLeaf('initial')],
206
+ onPreloadNode: vitest_1.vi.fn(),
207
+ onPreloadNodes: vitest_1.vi.fn(),
208
+ onSearch,
209
+ mapNodeToRecordItem
210
+ }));
211
+ (0, react_1.act)(() => {
212
+ result.current.search.onChange('query');
213
+ });
214
+ yield (0, react_1.waitFor)(() => {
215
+ (0, vitest_1.expect)(onSearch).toHaveBeenCalledWith({
216
+ search: 'query'
217
+ }, vitest_1.expect.any(AbortSignal));
218
+ (0, vitest_1.expect)(result.current.tree.current).toEqual([createParent('root-node', [createLeaf('child-node')])]);
219
+ });
220
+ (0, vitest_1.expect)(result.current.expandedNodes.current).toEqual([]);
221
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
222
+ 'root-node': {
223
+ label: 'root-node'
224
+ },
225
+ 'child-node': {
226
+ label: 'child-node'
227
+ }
228
+ });
229
+ }));
230
+ });
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+
3
+ var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) {
5
+ return value instanceof P ? value : new P(function (resolve) {
6
+ resolve(value);
7
+ });
8
+ }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) {
11
+ try {
12
+ step(generator.next(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ }
17
+ function rejected(value) {
18
+ try {
19
+ step(generator["throw"](value));
20
+ } catch (e) {
21
+ reject(e);
22
+ }
23
+ }
24
+ function step(result) {
25
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
26
+ }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", {
31
+ value: true
32
+ });
33
+ const react_1 = require("@testing-library/react");
34
+ const vitest_1 = require("vitest");
35
+ const useTreeMultiSelection_1 = require("../useTreeMultiSelection");
36
+ const createLeaf = id => ({
37
+ id,
38
+ title: id
39
+ });
40
+ const createParent = (id, nested) => ({
41
+ id,
42
+ title: id,
43
+ nested
44
+ });
45
+ (0, vitest_1.describe)('useTreeMultiSelection', () => {
46
+ (0, vitest_1.it)('should keep selected state uncontrolled by default', () => __awaiter(void 0, void 0, void 0, function* () {
47
+ const {
48
+ result
49
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
50
+ onDataLoad: vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
51
+ return {
52
+ preloadedChildren: [],
53
+ updatedTree: []
54
+ };
55
+ })),
56
+ onSelect: () => ({
57
+ added: ['a'],
58
+ removed: []
59
+ })
60
+ }));
61
+ (0, vitest_1.expect)(result.current.selected).toEqual([]);
62
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
63
+ yield result.current.onSelect(['a'], createLeaf('a'));
64
+ }));
65
+ (0, vitest_1.expect)(result.current.selected).toEqual(['a']);
66
+ }));
67
+ (0, vitest_1.it)('should call onChangeSelected when selection updates', () => __awaiter(void 0, void 0, void 0, function* () {
68
+ const onChangeSelected = vitest_1.vi.fn();
69
+ const {
70
+ result
71
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
72
+ onDataLoad: vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
73
+ return {
74
+ preloadedChildren: [],
75
+ updatedTree: []
76
+ };
77
+ })),
78
+ onSelect: () => ({
79
+ added: ['a'],
80
+ removed: []
81
+ }),
82
+ onChangeSelected
83
+ }));
84
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
85
+ yield result.current.onSelect(['a'], createLeaf('a'));
86
+ }));
87
+ (0, vitest_1.expect)(onChangeSelected).toHaveBeenCalledWith(['a']);
88
+ }));
89
+ (0, vitest_1.it)('should treat selected prop as controlled', () => __awaiter(void 0, void 0, void 0, function* () {
90
+ const onChangeSelected = vitest_1.vi.fn();
91
+ const {
92
+ result,
93
+ rerender
94
+ } = (0, react_1.renderHook)(_ref => {
95
+ let {
96
+ selected
97
+ } = _ref;
98
+ return (0, useTreeMultiSelection_1.useTreeMultiSelection)({
99
+ onDataLoad: vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
100
+ return {
101
+ preloadedChildren: [],
102
+ updatedTree: []
103
+ };
104
+ })),
105
+ onSelect: () => ({
106
+ added: ['b'],
107
+ removed: []
108
+ }),
109
+ selected,
110
+ onChangeSelected
111
+ });
112
+ }, {
113
+ initialProps: {
114
+ selected: ['a']
115
+ }
116
+ });
117
+ (0, vitest_1.expect)(result.current.selected).toEqual(['a']);
118
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
119
+ yield result.current.onSelect(['a', 'b'], createLeaf('b'));
120
+ }));
121
+ // controlled value does not change until parent updates it
122
+ (0, vitest_1.expect)(result.current.selected).toEqual(['a']);
123
+ (0, vitest_1.expect)(onChangeSelected).toHaveBeenCalledWith(['a', 'b']);
124
+ rerender({
125
+ selected: ['a', 'b']
126
+ });
127
+ (0, vitest_1.expect)(result.current.selected).toEqual(['a', 'b']);
128
+ }));
129
+ (0, vitest_1.it)('should preload children when selecting empty parent node and pass cloned node to onSelect', () => __awaiter(void 0, void 0, void 0, function* () {
130
+ const node = createParent('parent', []);
131
+ const preloadedChildren = [createLeaf('child-1'), createLeaf('child-2')];
132
+ const onDataLoad = vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
133
+ return {
134
+ preloadedChildren,
135
+ updatedTree: [createParent('parent', preloadedChildren)]
136
+ };
137
+ }));
138
+ const onSelect = vitest_1.vi.fn(() => ({
139
+ added: ['parent', 'child-1', 'child-2'],
140
+ removed: []
141
+ }));
142
+ const {
143
+ result
144
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
145
+ onDataLoad,
146
+ onSelect
147
+ }));
148
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
149
+ yield result.current.onSelect(['parent'], node);
150
+ }));
151
+ (0, vitest_1.expect)(onDataLoad).toHaveBeenCalledWith(node);
152
+ (0, vitest_1.expect)(onSelect).toHaveBeenCalledWith({
153
+ selectedKeys: ['parent'],
154
+ node: createParent('parent', preloadedChildren),
155
+ isSelected: true
156
+ });
157
+ (0, vitest_1.expect)(result.current.selected.sort()).toEqual(['child-1', 'child-2', 'parent']);
158
+ }));
159
+ (0, vitest_1.it)('should not preload when parent node already has children', () => __awaiter(void 0, void 0, void 0, function* () {
160
+ const node = createParent('parent', [createLeaf('child')]);
161
+ const onDataLoad = vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
162
+ return {
163
+ preloadedChildren: [],
164
+ updatedTree: []
165
+ };
166
+ }));
167
+ const onSelect = vitest_1.vi.fn(() => ({
168
+ added: ['parent'],
169
+ removed: []
170
+ }));
171
+ const {
172
+ result
173
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
174
+ onDataLoad,
175
+ onSelect
176
+ }));
177
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
178
+ yield result.current.onSelect(['parent'], node);
179
+ }));
180
+ (0, vitest_1.expect)(onDataLoad).not.toHaveBeenCalled();
181
+ (0, vitest_1.expect)(onSelect).toHaveBeenCalledWith({
182
+ selectedKeys: ['parent'],
183
+ node,
184
+ isSelected: true
185
+ });
186
+ }));
187
+ (0, vitest_1.it)('should add and remove ids from selection without duplicates', () => __awaiter(void 0, void 0, void 0, function* () {
188
+ const {
189
+ result
190
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
191
+ onDataLoad: vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
192
+ return {
193
+ preloadedChildren: [],
194
+ updatedTree: []
195
+ };
196
+ })),
197
+ onSelect: () => ({
198
+ added: ['a', 'a', 'b'],
199
+ removed: []
200
+ })
201
+ }));
202
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
203
+ yield result.current.onSelect(['a'], createLeaf('a'));
204
+ }));
205
+ (0, vitest_1.expect)(result.current.selected.sort()).toEqual(['a', 'b']);
206
+ const {
207
+ result: result2
208
+ } = (0, react_1.renderHook)(() => (0, useTreeMultiSelection_1.useTreeMultiSelection)({
209
+ onDataLoad: vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
210
+ return {
211
+ preloadedChildren: [],
212
+ updatedTree: []
213
+ };
214
+ })),
215
+ onSelect: () => ({
216
+ added: [],
217
+ removed: ['a']
218
+ }),
219
+ selected: ['a', 'b']
220
+ }));
221
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
222
+ yield result2.current.onSelect(['b'], createLeaf('a'));
223
+ }));
224
+ }));
225
+ });
@@ -0,0 +1,2 @@
1
+ export * from './useSearchableTree';
2
+ export * from './useTreeMultiSelection';
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ var __createBinding = void 0 && (void 0).__createBinding || (Object.create ? function (o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = {
8
+ enumerable: true,
9
+ get: function () {
10
+ return m[k];
11
+ }
12
+ };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ } : function (o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ });
19
+ var __exportStar = void 0 && (void 0).__exportStar || function (m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ Object.defineProperty(exports, "__esModule", {
23
+ value: true
24
+ });
25
+ __exportStar(require("./useSearchableTree"), exports);
26
+ __exportStar(require("./useTreeMultiSelection"), exports);
@@ -0,0 +1,28 @@
1
+ import type { SearchableTreeDataLoadResult, TreeNodeProps } from '../types';
2
+ export type SearchResult<TTreeNode extends TreeNodeProps> = {
3
+ tree: TTreeNode[];
4
+ needPreloadNodes: string[];
5
+ };
6
+ export type SearchParams = {
7
+ search: string;
8
+ };
9
+ type UseSearchableTreeParams<TRecordValue, TTreeNode extends TreeNodeProps> = {
10
+ initTree: TTreeNode[];
11
+ onPreloadNode: (node: TreeNodeProps) => Promise<TTreeNode[]>;
12
+ onPreloadNodes: (nodes: string[], signal?: AbortSignal) => Promise<Record<string, TTreeNode[]>>;
13
+ onSearch: (params: SearchParams, signal?: AbortSignal) => Promise<SearchResult<TTreeNode>>;
14
+ mapNodeToRecordItem: (node: TTreeNode) => TRecordValue;
15
+ };
16
+ export declare function useSearchableTree<TRecordValue, TTreeNode extends TreeNodeProps>({ initTree, onPreloadNode, onPreloadNodes, onSearch, mapNodeToRecordItem, }: UseSearchableTreeParams<TRecordValue, TTreeNode>): {
17
+ tree: import("@siberiacancode/reactuse").StateRef<TTreeNode[]>;
18
+ expandedNodes: import("@siberiacancode/reactuse").StateRef<string[]>;
19
+ loading: boolean;
20
+ treeItemsRecord: import("@siberiacancode/reactuse").StateRef<Record<string, TRecordValue>>;
21
+ search: {
22
+ value: string;
23
+ onChange: import("react").Dispatch<import("react").SetStateAction<string>>;
24
+ };
25
+ onExpand: (nodes: string[]) => void;
26
+ onDataLoad: (node: TreeNodeProps) => Promise<SearchableTreeDataLoadResult<TTreeNode, TRecordValue>>;
27
+ };
28
+ export {};
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) {
5
+ return value instanceof P ? value : new P(function (resolve) {
6
+ resolve(value);
7
+ });
8
+ }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) {
11
+ try {
12
+ step(generator.next(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ }
17
+ function rejected(value) {
18
+ try {
19
+ step(generator["throw"](value));
20
+ } catch (e) {
21
+ reject(e);
22
+ }
23
+ }
24
+ function step(result) {
25
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
26
+ }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", {
31
+ value: true
32
+ });
33
+ exports.useSearchableTree = useSearchableTree;
34
+ const reactuse_1 = require("@siberiacancode/reactuse");
35
+ const cancelable_promise_1 = require("cancelable-promise");
36
+ const react_1 = require("react");
37
+ const helpers_1 = require("../helpers");
38
+ function useSearchableTree(_ref) {
39
+ let {
40
+ initTree,
41
+ onPreloadNode,
42
+ onPreloadNodes,
43
+ onSearch,
44
+ mapNodeToRecordItem
45
+ } = _ref;
46
+ const tree = (0, reactuse_1.useRefState)(initTree);
47
+ const treeItemsRecord = (0, reactuse_1.useRefState)({});
48
+ const expandedNodes = (0, reactuse_1.useRefState)([]);
49
+ const [search, setSearch] = (0, react_1.useState)('');
50
+ const debouncedSearch = (0, reactuse_1.useDebounceValue)(search, 500);
51
+ const [loading, setLoading] = (0, react_1.useState)(false);
52
+ const searchPromiseRef = (0, react_1.useRef)(null);
53
+ const searchAbortControllerRef = (0, react_1.useRef)(null);
54
+ const buildTreeItemsRecord = (0, react_1.useCallback)(nodes => {
55
+ const record = {};
56
+ (0, helpers_1.traverse)(nodes, node => {
57
+ record[node.id] = mapNodeToRecordItem(node);
58
+ });
59
+ return record;
60
+ }, [mapNodeToRecordItem]);
61
+ const onExpand = (0, react_1.useCallback)(nodes => {
62
+ expandedNodes.current = nodes;
63
+ }, [expandedNodes]);
64
+ const onDataLoad = (0, react_1.useCallback)(node => __awaiter(this, void 0, void 0, function* () {
65
+ const preloadedChildren = yield onPreloadNode(node);
66
+ const updatedTree = (0, helpers_1.setChildrenOfTreeNode)(tree.current, node.id, preloadedChildren);
67
+ tree.current = updatedTree;
68
+ const newTreeItemsRecord = Object.assign({}, treeItemsRecord.current);
69
+ (0, helpers_1.traverse)(preloadedChildren, child => {
70
+ newTreeItemsRecord[child.id] = mapNodeToRecordItem(child);
71
+ });
72
+ treeItemsRecord.current = newTreeItemsRecord;
73
+ return {
74
+ preloadedChildren,
75
+ updatedTree,
76
+ newTreeItemsRecord
77
+ };
78
+ }), [mapNodeToRecordItem, onPreloadNode, tree, treeItemsRecord]);
79
+ const handleSearch = (0, react_1.useCallback)(searchQuery => __awaiter(this, void 0, void 0, function* () {
80
+ var _a, _b;
81
+ (_a = searchPromiseRef.current) === null || _a === void 0 ? void 0 : _a.cancel();
82
+ (_b = searchAbortControllerRef.current) === null || _b === void 0 ? void 0 : _b.abort();
83
+ setLoading(true);
84
+ const abortController = new AbortController();
85
+ searchAbortControllerRef.current = abortController;
86
+ const searchPromise = (0, cancelable_promise_1.cancelable)(onSearch({
87
+ search: searchQuery
88
+ }, abortController.signal));
89
+ searchPromiseRef.current = searchPromise;
90
+ try {
91
+ const {
92
+ tree: searchedTree,
93
+ needPreloadNodes
94
+ } = yield searchPromise;
95
+ if (!searchPromise.isCanceled()) {
96
+ tree.current = searchedTree;
97
+ treeItemsRecord.current = buildTreeItemsRecord(searchedTree);
98
+ const expandedSet = new Set(expandedNodes.current);
99
+ const toPreloadExpandableNodes = (0, helpers_1.collectEmptyNestedNodesInExpanded)(searchedTree, expandedSet);
100
+ const collectedNodesForPreload = Array.from(new Set([...toPreloadExpandableNodes.map(node => node.id), ...needPreloadNodes]));
101
+ if (!collectedNodesForPreload.length) {
102
+ return;
103
+ }
104
+ const preloadedNodes = yield onPreloadNodes(collectedNodesForPreload, abortController.signal);
105
+ if (searchPromiseRef.current !== searchPromise) {
106
+ return;
107
+ }
108
+ let tmpTree = [...searchedTree];
109
+ for (const [nodeId, children] of Object.entries(preloadedNodes)) {
110
+ tmpTree = (0, helpers_1.setChildrenOfTreeNode)(tmpTree, nodeId, children);
111
+ }
112
+ tree.current = tmpTree;
113
+ treeItemsRecord.current = buildTreeItemsRecord(tmpTree);
114
+ }
115
+ } finally {
116
+ if (searchPromiseRef.current === searchPromise) {
117
+ setLoading(false);
118
+ searchPromiseRef.current = null;
119
+ searchAbortControllerRef.current = null;
120
+ }
121
+ }
122
+ }), [buildTreeItemsRecord, expandedNodes, onPreloadNodes, onSearch, tree, treeItemsRecord]);
123
+ (0, reactuse_1.useDidUpdate)(() => {
124
+ handleSearch(debouncedSearch);
125
+ }, [debouncedSearch, handleSearch]);
126
+ (0, react_1.useEffect)(() => {
127
+ tree.current = initTree;
128
+ treeItemsRecord.current = buildTreeItemsRecord(initTree);
129
+ }, [buildTreeItemsRecord, initTree, tree, treeItemsRecord]);
130
+ (0, react_1.useEffect)(() => () => {
131
+ var _a, _b;
132
+ (_a = searchPromiseRef.current) === null || _a === void 0 ? void 0 : _a.cancel();
133
+ (_b = searchAbortControllerRef.current) === null || _b === void 0 ? void 0 : _b.abort();
134
+ }, []);
135
+ return {
136
+ tree,
137
+ expandedNodes,
138
+ loading,
139
+ treeItemsRecord,
140
+ search: {
141
+ value: search,
142
+ onChange: setSearch
143
+ },
144
+ onExpand,
145
+ onDataLoad
146
+ };
147
+ }
@@ -0,0 +1,12 @@
1
+ import { PreloadNodeHandler, SelectHandler, TreeNodeProps } from '../types';
2
+ type UseTreeMultiSelectionParams<TTreeNode extends TreeNodeProps> = {
3
+ onDataLoad: PreloadNodeHandler<TTreeNode>;
4
+ onSelect: SelectHandler;
5
+ selected?: string[];
6
+ onChangeSelected?: (newSelected: string[]) => void;
7
+ };
8
+ export declare function useTreeMultiSelection<TTreeNode extends TreeNodeProps>({ onDataLoad, onSelect: onSelectProp, selected, onChangeSelected, }: UseTreeMultiSelectionParams<TTreeNode>): {
9
+ selected: string[];
10
+ onSelect: (selectedKeys: string[], node: TreeNodeProps) => Promise<void>;
11
+ };
12
+ export {};