@snack-uikit/tree 0.11.0 → 0.11.1-preview-02fd9f01.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 (63) hide show
  1. package/README.md +11 -4
  2. package/dist/cjs/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.d.ts +1 -0
  3. package/dist/cjs/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.js +80 -0
  4. package/dist/cjs/helpers/__tests__/setChildrenOfTreeNode.spec.d.ts +1 -0
  5. package/dist/cjs/helpers/__tests__/setChildrenOfTreeNode.spec.js +102 -0
  6. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.d.ts +2 -0
  7. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.js +17 -0
  8. package/dist/cjs/helpers/index.d.ts +2 -0
  9. package/dist/cjs/helpers/index.js +3 -1
  10. package/dist/cjs/helpers/setChildrenOfTreeNode.d.ts +2 -0
  11. package/dist/cjs/helpers/setChildrenOfTreeNode.js +28 -0
  12. package/dist/cjs/helpers/sortTreeItemsByTitle.js +2 -2
  13. package/dist/cjs/helpers/traverse.d.ts +7 -1
  14. package/dist/cjs/helpers/traverse.js +38 -2
  15. package/dist/cjs/hooks/__tests__/useSearchableTree.spec.d.ts +1 -0
  16. package/dist/cjs/hooks/__tests__/useSearchableTree.spec.js +232 -0
  17. package/dist/cjs/hooks/__tests__/useTreeMultiSelection.spec.d.ts +1 -0
  18. package/dist/cjs/hooks/__tests__/useTreeMultiSelection.spec.js +225 -0
  19. package/dist/cjs/hooks/index.d.ts +2 -0
  20. package/dist/cjs/hooks/index.js +26 -0
  21. package/dist/cjs/hooks/useSearchableTree.d.ts +29 -0
  22. package/dist/cjs/hooks/useSearchableTree.js +148 -0
  23. package/dist/cjs/hooks/useTreeMultiSelection.d.ts +12 -0
  24. package/dist/cjs/hooks/useTreeMultiSelection.js +82 -0
  25. package/dist/cjs/types.d.ts +17 -0
  26. package/dist/esm/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.d.ts +1 -0
  27. package/dist/esm/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.js +64 -0
  28. package/dist/esm/helpers/__tests__/setChildrenOfTreeNode.spec.d.ts +1 -0
  29. package/dist/esm/helpers/__tests__/setChildrenOfTreeNode.spec.js +50 -0
  30. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.d.ts +2 -0
  31. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.js +11 -0
  32. package/dist/esm/helpers/index.d.ts +2 -0
  33. package/dist/esm/helpers/index.js +2 -0
  34. package/dist/esm/helpers/setChildrenOfTreeNode.d.ts +2 -0
  35. package/dist/esm/helpers/setChildrenOfTreeNode.js +20 -0
  36. package/dist/esm/helpers/sortTreeItemsByTitle.js +2 -2
  37. package/dist/esm/helpers/traverse.d.ts +7 -1
  38. package/dist/esm/helpers/traverse.js +24 -0
  39. package/dist/esm/hooks/__tests__/useSearchableTree.spec.d.ts +1 -0
  40. package/dist/esm/hooks/__tests__/useSearchableTree.spec.js +165 -0
  41. package/dist/esm/hooks/__tests__/useTreeMultiSelection.spec.d.ts +1 -0
  42. package/dist/esm/hooks/__tests__/useTreeMultiSelection.spec.js +130 -0
  43. package/dist/esm/hooks/index.d.ts +2 -0
  44. package/dist/esm/hooks/index.js +2 -0
  45. package/dist/esm/hooks/useSearchableTree.d.ts +29 -0
  46. package/dist/esm/hooks/useSearchableTree.js +108 -0
  47. package/dist/esm/hooks/useTreeMultiSelection.d.ts +12 -0
  48. package/dist/esm/hooks/useTreeMultiSelection.js +43 -0
  49. package/dist/esm/types.d.ts +17 -0
  50. package/package.json +22 -4
  51. package/src/helpers/__tests__/collectEmptyNestedNodesInExpanded.spec.ts +88 -0
  52. package/src/helpers/__tests__/setChildrenOfTreeNode.spec.ts +68 -0
  53. package/src/helpers/collectEmptyNestedNodesInExpanded.ts +16 -0
  54. package/src/helpers/index.ts +2 -0
  55. package/src/helpers/setChildrenOfTreeNode.ts +30 -0
  56. package/src/helpers/sortTreeItemsByTitle.ts +2 -2
  57. package/src/helpers/traverse.ts +38 -1
  58. package/src/hooks/__tests__/useSearchableTree.spec.ts +203 -0
  59. package/src/hooks/__tests__/useTreeMultiSelection.spec.ts +165 -0
  60. package/src/hooks/index.ts +2 -0
  61. package/src/hooks/useSearchableTree.ts +166 -0
  62. package/src/hooks/useTreeMultiSelection.ts +61 -0
  63. package/src/types.ts +15 -0
package/README.md CHANGED
@@ -112,8 +112,8 @@ function TreeAsyncLoadExample() {
112
112
  ### Props
113
113
  | name | type | default value | description |
114
114
  |------|------|---------------|-------------|
115
- | __@unscopables@986* | `{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; ... 29 more ...; readonly [Symbol.unscopables]?: boolean; }` | - | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
116
- | __@iterator@984* | `() => ArrayIterator<TreeNodeProps>` | - | Iterator |
115
+ | __@unscopables@987* | `{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; ... 29 more ...; readonly [Symbol.unscopables]?: boolean; }` | - | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
116
+ | __@iterator@985* | `() => ArrayIterator<TreeNodeProps>` | - | Iterator |
117
117
  | with* | `(index: number, value: TreeNodeProps) => TreeNodeProps[]` | - | Copies an array, then overwrites the value at the provided index with the given value. If the index is negative, then it replaces from the end of the array. @param index The index of the value to overwrite. If the index is negative, then it replaces from the end of the array. @param value The value to write into the copied array. @returns The copied array with the updated value. |
118
118
  | toSpliced* | `{ (start: number, deleteCount: number, ...items: TreeNodeProps[]): TreeNodeProps[]; (start: number, deleteCount?: number): TreeNodeProps[]; }` | - | Copies an array and removes elements and, if necessary, inserts new elements in their place. Returns the copied array. Copies an array and removes elements while returning the remaining elements. @param start The zero-based location in the array from which to start removing elements. @param deleteCount The number of elements to remove. @param items Elements to insert into the copied array in place of the deleted elements. @returns The copied array. @param start The zero-based location in the array from which to start removing elements. @param deleteCount The number of elements to remove. @returns A copy of the original array with the remaining elements. |
119
119
  | toSorted* | `(compareFn?: (a: TreeNodeProps, b: TreeNodeProps) => number) => TreeNodeProps[]` | - | Returns a copy of an array with its elements sorted. @param compareFn Function used to determine the order of the elements. It is expected to return a negative value if the first argument is less than the second argument, zero if they're equal, and a positive value otherwise. If omitted, the elements are sorted in ascending, UTF-16 code unit order. ```ts [11, 2, 22, 1].toSorted((a, b) => a - b) // [1, 2, 11, 22] ``` |
@@ -176,8 +176,8 @@ function TreeAsyncLoadExample() {
176
176
  ### Props
177
177
  | name | type | default value | description |
178
178
  |------|------|---------------|-------------|
179
- | __@unscopables@986* | `{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; ... 29 more ...; readonly [Symbol.unscopables]?: boolean; }` | - | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
180
- | __@iterator@984* | `() => ArrayIterator<ExtendedTreeNodeProps>` | - | Iterator |
179
+ | __@unscopables@987* | `{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; ... 29 more ...; readonly [Symbol.unscopables]?: boolean; }` | - | Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement. |
180
+ | __@iterator@985* | `() => ArrayIterator<ExtendedTreeNodeProps>` | - | Iterator |
181
181
  | with* | `(index: number, value: ExtendedTreeNodeProps) => ExtendedTreeNodeProps[]` | - | Copies an array, then overwrites the value at the provided index with the given value. If the index is negative, then it replaces from the end of the array. @param index The index of the value to overwrite. If the index is negative, then it replaces from the end of the array. @param value The value to write into the copied array. @returns The copied array with the updated value. |
182
182
  | toSpliced* | `{ (start: number, deleteCount: number, ...items: ExtendedTreeNodeProps[]): ExtendedTreeNodeProps[]; (start: number, deleteCount?: number): ExtendedTreeNodeProps[]; }` | - | Copies an array and removes elements and, if necessary, inserts new elements in their place. Returns the copied array. Copies an array and removes elements while returning the remaining elements. @param start The zero-based location in the array from which to start removing elements. @param deleteCount The number of elements to remove. @param items Elements to insert into the copied array in place of the deleted elements. @returns The copied array. @param start The zero-based location in the array from which to start removing elements. @param deleteCount The number of elements to remove. @returns A copy of the original array with the remaining elements. |
183
183
  | toSorted* | `(compareFn?: (a: ExtendedTreeNodeProps, b: ExtendedTreeNodeProps) => number) => ExtendedTreeNodeProps[]` | - | Returns a copy of an array with its elements sorted. @param compareFn Function used to determine the order of the elements. It is expected to return a negative value if the first argument is less than the second argument, zero if they're equal, and a positive value otherwise. If omitted, the elements are sorted in ascending, UTF-16 code unit order. ```ts [11, 2, 22, 1].toSorted((a, b) => a - b) // [1, 2, 11, 22] ``` |
@@ -217,6 +217,13 @@ function TreeAsyncLoadExample() {
217
217
  | length* | `number` | - | Gets or sets the length of the array. This is a number one higher than the highest index in the array. |
218
218
  | toString | `() => string` | function toString() { [native code] } | Returns a string representation of an array. |
219
219
  | toLocaleString | `{ (): string; (locales: string \| string[], options?: NumberFormatOptions & DateTimeFormatOptions): string; }` | function toLocaleString() { [native code] } | Returns a string representation of an array. The elements are converted to string using their toLocaleString methods. |
220
+ ## traverseWithTarget
221
+ BFS с указанием целевого списка для каждого узла.
222
+ Очередь хранит (node, depth, targetList). Callback добавляет узел в targetList
223
+ и возвращает массив для детей (или undefined, чтобы не обходить детей).
224
+ ### Props
225
+ | name | type | default value | description |
226
+ |------|------|---------------|-------------|
220
227
 
221
228
 
222
229
  [//]: DOCUMENTATION_SECTION_END
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ const vitest_1 = require("vitest");
7
+ const collectEmptyNestedNodesInExpanded_1 = require("../collectEmptyNestedNodesInExpanded");
8
+ const node = (id, nested) => Object.assign({
9
+ id,
10
+ title: id
11
+ }, nested !== undefined && {
12
+ nested
13
+ });
14
+ (0, vitest_1.describe)('collectEmptyNestedNodesInExpanded', () => {
15
+ (0, vitest_1.it)('returns nodes with empty nested array that are in expandedIds', () => {
16
+ const tree = [node('a', []), node('b', [node('b1')])];
17
+ const expandedIds = new Set(['a']);
18
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
19
+ (0, vitest_1.expect)(result).toHaveLength(1);
20
+ (0, vitest_1.expect)(result[0]).toMatchObject({
21
+ id: 'a',
22
+ nested: []
23
+ });
24
+ });
25
+ (0, vitest_1.it)('does not return nodes with empty nested if not in expandedIds', () => {
26
+ const tree = [node('a', []), node('b', [])];
27
+ const expandedIds = new Set(['b']);
28
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
29
+ (0, vitest_1.expect)(result).toHaveLength(1);
30
+ (0, vitest_1.expect)(result[0].id).toBe('b');
31
+ });
32
+ (0, vitest_1.it)('does not return nodes that have non-empty nested', () => {
33
+ const tree = [node('a', [node('a1')]), node('b', [])];
34
+ const expandedIds = new Set(['a', 'b']);
35
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
36
+ (0, vitest_1.expect)(result).toHaveLength(1);
37
+ (0, vitest_1.expect)(result[0].id).toBe('b');
38
+ });
39
+ (0, vitest_1.it)('does not return nodes without nested property (leaf)', () => {
40
+ const tree = [node('leaf'), node('empty', [])];
41
+ const expandedIds = new Set(['leaf', 'empty']);
42
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
43
+ (0, vitest_1.expect)(result).toHaveLength(1);
44
+ (0, vitest_1.expect)(result[0].id).toBe('empty');
45
+ });
46
+ (0, vitest_1.it)('returns empty array for empty tree', () => {
47
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)([], new Set(['any']));
48
+ (0, vitest_1.expect)(result).toEqual([]);
49
+ });
50
+ (0, vitest_1.it)('returns empty array when expandedIds is empty', () => {
51
+ const tree = [node('a', []), node('b', [])];
52
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, new Set());
53
+ (0, vitest_1.expect)(result).toEqual([]);
54
+ });
55
+ (0, vitest_1.it)('collects from deeply nested nodes', () => {
56
+ const child = node('child', []);
57
+ const tree = [node('root', [node('mid', [child])])];
58
+ const expandedIds = new Set(['root', 'mid', 'child']);
59
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
60
+ (0, vitest_1.expect)(result).toHaveLength(1);
61
+ (0, vitest_1.expect)(result[0]).toMatchObject({
62
+ id: 'child',
63
+ nested: []
64
+ });
65
+ });
66
+ (0, vitest_1.it)('returns all matching nodes from different levels', () => {
67
+ const tree = [node('a', []), node('b', [node('b1', []), node('b2', [])])];
68
+ const expandedIds = new Set(['a', 'b', 'b1', 'b2']);
69
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
70
+ (0, vitest_1.expect)(result).toHaveLength(3);
71
+ (0, vitest_1.expect)(result.map(n => n.id).sort()).toEqual(['a', 'b1', 'b2']);
72
+ });
73
+ (0, vitest_1.it)('only includes node if both empty nested and expanded', () => {
74
+ const tree = [node('expanded', []), node('collapsed', [])];
75
+ const expandedIds = new Set(['expanded']);
76
+ const result = (0, collectEmptyNestedNodesInExpanded_1.collectEmptyNestedNodesInExpanded)(tree, expandedIds);
77
+ (0, vitest_1.expect)(result).toHaveLength(1);
78
+ (0, vitest_1.expect)(result[0].id).toBe('expanded');
79
+ });
80
+ });
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ const vitest_1 = require("vitest");
7
+ const setChildrenOfTreeNode_1 = require("../setChildrenOfTreeNode");
8
+ const node = (id, nested) => Object.assign({
9
+ id,
10
+ title: id
11
+ }, nested !== undefined && {
12
+ nested: nested.map(n => Object.assign(Object.assign({}, n), {
13
+ title: n.id
14
+ }))
15
+ });
16
+ (0, vitest_1.describe)('setChildrenOfTreeNode', () => {
17
+ (0, vitest_1.it)('replaces nested for root-level node', () => {
18
+ const tree = [node('a', [{
19
+ id: 'a1',
20
+ title: 'a1'
21
+ }]), node('b', [{
22
+ id: 'b1',
23
+ title: 'b1'
24
+ }])];
25
+ const children = [node('new1'), node('new2')];
26
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)(tree, 'b', children);
27
+ (0, vitest_1.expect)(result).toHaveLength(2);
28
+ (0, vitest_1.expect)(result[0]).toMatchObject({
29
+ id: 'a',
30
+ nested: [{
31
+ id: 'a1'
32
+ }]
33
+ });
34
+ (0, vitest_1.expect)(result[1]).toMatchObject({
35
+ id: 'b',
36
+ nested: [{
37
+ id: 'new1'
38
+ }, {
39
+ id: 'new2'
40
+ }]
41
+ });
42
+ });
43
+ (0, vitest_1.it)('replaces nested for deeply nested node', () => {
44
+ const childWithNested = node('child');
45
+ childWithNested.nested = [{
46
+ id: 'grand',
47
+ title: 'grand'
48
+ }];
49
+ const tree = [node('root', [childWithNested])];
50
+ const children = [node('replacement')];
51
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)(tree, 'child', children);
52
+ const root = result[0];
53
+ (0, vitest_1.expect)(root.nested).toHaveLength(1);
54
+ (0, vitest_1.expect)(root.nested[0]).toMatchObject({
55
+ id: 'child',
56
+ nested: [{
57
+ id: 'replacement'
58
+ }]
59
+ });
60
+ });
61
+ (0, vitest_1.it)('returns cloned tree when node is not found', () => {
62
+ const tree = [node('a'), node('b')];
63
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)(tree, 'missing', [node('x')]);
64
+ (0, vitest_1.expect)(result).toHaveLength(2);
65
+ (0, vitest_1.expect)(result[0]).toMatchObject({
66
+ id: 'a'
67
+ });
68
+ (0, vitest_1.expect)(result[1]).toMatchObject({
69
+ id: 'b'
70
+ });
71
+ (0, vitest_1.expect)(result).not.toBe(tree);
72
+ });
73
+ (0, vitest_1.it)('returns empty array for empty tree', () => {
74
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)([], 'any', [node('x')]);
75
+ (0, vitest_1.expect)(result).toEqual([]);
76
+ });
77
+ (0, vitest_1.it)('sets children for target node that had no nested (leaf)', () => {
78
+ const tree = [node('leaf'), node('other')];
79
+ const children = [node('new1')];
80
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)(tree, 'leaf', children);
81
+ (0, vitest_1.expect)(result[0]).toMatchObject({
82
+ id: 'leaf',
83
+ nested: [{
84
+ id: 'new1'
85
+ }]
86
+ });
87
+ (0, vitest_1.expect)(result[1]).toMatchObject({
88
+ id: 'other'
89
+ });
90
+ });
91
+ (0, vitest_1.it)('preserves order of root nodes when target is second', () => {
92
+ const tree = [node('first'), node('second', [{
93
+ id: 's1',
94
+ title: 's1'
95
+ }]), node('third')];
96
+ const children = [node('new1'), node('new2')];
97
+ const result = (0, setChildrenOfTreeNode_1.setChildrenOfTreeNode)(tree, 'second', children);
98
+ (0, vitest_1.expect)(result.map(n => n.id)).toEqual(['first', 'second', 'third']);
99
+ (0, vitest_1.expect)(result[1].nested).toHaveLength(2);
100
+ (0, vitest_1.expect)(result[1].nested.map(n => n.id)).toEqual(['new1', 'new2']);
101
+ });
102
+ });
@@ -0,0 +1,2 @@
1
+ import { TreeNodeProps } from '../types';
2
+ export declare function collectEmptyNestedNodesInExpanded<TTreeNode extends TreeNodeProps>(nodes: TTreeNode[], expandedIds: Set<string>): TTreeNode[];
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.collectEmptyNestedNodesInExpanded = collectEmptyNestedNodesInExpanded;
7
+ const traverse_1 = require("./traverse");
8
+ function collectEmptyNestedNodesInExpanded(nodes, expandedIds) {
9
+ const result = [];
10
+ (0, traverse_1.traverse)(nodes, node => {
11
+ const hasEmptyNested = Array.isArray(node.nested) && node.nested.length === 0;
12
+ if (hasEmptyNested && expandedIds.has(node.id)) {
13
+ result.push(node);
14
+ }
15
+ });
16
+ return result;
17
+ }
@@ -6,3 +6,5 @@ export * from './getSearchedTreeNodeById';
6
6
  export * from './lookupTreeForSelectedNodes';
7
7
  export * from './sortTreeItemsByTitle';
8
8
  export * from './traverse';
9
+ export * from './setChildrenOfTreeNode';
10
+ export * from './collectEmptyNestedNodesInExpanded';
@@ -29,4 +29,6 @@ __exportStar(require("./getSearchedTreeItems"), exports);
29
29
  __exportStar(require("./getSearchedTreeNodeById"), exports);
30
30
  __exportStar(require("./lookupTreeForSelectedNodes"), exports);
31
31
  __exportStar(require("./sortTreeItemsByTitle"), exports);
32
- __exportStar(require("./traverse"), exports);
32
+ __exportStar(require("./traverse"), exports);
33
+ __exportStar(require("./setChildrenOfTreeNode"), exports);
34
+ __exportStar(require("./collectEmptyNestedNodesInExpanded"), exports);
@@ -0,0 +1,2 @@
1
+ import { TreeNodeProps } from '../types';
2
+ export declare const setChildrenOfTreeNode: <TTreeNode extends TreeNodeProps>(tree: TTreeNode[], nodeId: string, children: TTreeNode[]) => TTreeNode[];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.setChildrenOfTreeNode = void 0;
7
+ const traverse_1 = require("./traverse");
8
+ const setChildrenOfTreeNode = (tree, nodeId, children) => {
9
+ const result = [];
10
+ (0, traverse_1.traverseWithTarget)(tree, result, (source, _depth, targetList) => {
11
+ var _a;
12
+ const isTarget = source.id === nodeId;
13
+ const hasNested = Array.isArray(source.nested);
14
+ let newNested;
15
+ if (isTarget) {
16
+ newNested = children;
17
+ } else if (hasNested) {
18
+ newNested = [];
19
+ }
20
+ const newNode = newNested !== undefined ? Object.assign(Object.assign({}, source), {
21
+ nested: newNested
22
+ }) : Object.assign({}, source);
23
+ targetList.push(newNode);
24
+ return !isTarget && hasNested && ((_a = source.nested) === null || _a === void 0 ? void 0 : _a.length) ? newNested : undefined;
25
+ });
26
+ return result;
27
+ };
28
+ exports.setChildrenOfTreeNode = setChildrenOfTreeNode;
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.sortTreeItemsByTitle = void 0;
7
7
  const extractTreeNodeTitle_1 = require("./extractTreeNodeTitle");
8
8
  const sortTreeItemsByTitle = items => items === null || items === void 0 ? void 0 : items.toSorted((itemA, itemB) => {
9
- const valueA = (0, extractTreeNodeTitle_1.extractTreeNodeTitle)(itemA);
10
- const valueB = (0, extractTreeNodeTitle_1.extractTreeNodeTitle)(itemB);
9
+ const valueA = (0, extractTreeNodeTitle_1.extractTreeNodeTitle)(itemA).toLowerCase();
10
+ const valueB = (0, extractTreeNodeTitle_1.extractTreeNodeTitle)(itemB).toLowerCase();
11
11
  return valueA.localeCompare(valueB);
12
12
  });
13
13
  exports.sortTreeItemsByTitle = sortTreeItemsByTitle;
@@ -1,2 +1,8 @@
1
- import { TreeNodeProps } from '../';
1
+ import { TreeNodeProps } from '../types';
2
2
  export declare const traverse: <T extends TreeNodeProps>(nodes: T[], callback: (node: T, depth: number) => void) => void;
3
+ /**
4
+ * BFS с указанием целевого списка для каждого узла.
5
+ * Очередь хранит (node, depth, targetList). Callback добавляет узел в targetList
6
+ * и возвращает массив для детей (или undefined, чтобы не обходить детей).
7
+ */
8
+ export declare const traverseWithTarget: <T extends TreeNodeProps>(nodes: T[], rootTargetList: T[], callback: (node: T, depth: number, targetList: T[]) => T[] | undefined) => void;
@@ -8,7 +8,7 @@ var __importDefault = void 0 && (void 0).__importDefault || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", {
9
9
  value: true
10
10
  });
11
- exports.traverse = void 0;
11
+ exports.traverseWithTarget = exports.traverse = void 0;
12
12
  const queue_fifo_1 = __importDefault(require("queue-fifo"));
13
13
  const traverse = (nodes, callback) => {
14
14
  var _a;
@@ -37,4 +37,40 @@ const traverse = (nodes, callback) => {
37
37
  }
38
38
  }
39
39
  };
40
- exports.traverse = traverse;
40
+ exports.traverse = traverse;
41
+ /**
42
+ * BFS с указанием целевого списка для каждого узла.
43
+ * Очередь хранит (node, depth, targetList). Callback добавляет узел в targetList
44
+ * и возвращает массив для детей (или undefined, чтобы не обходить детей).
45
+ */
46
+ const traverseWithTarget = (nodes, rootTargetList, callback) => {
47
+ var _a;
48
+ const queue = new queue_fifo_1.default();
49
+ for (const node of nodes) {
50
+ queue.enqueue({
51
+ node,
52
+ depth: 0,
53
+ targetList: rootTargetList
54
+ });
55
+ }
56
+ while (!queue.isEmpty()) {
57
+ const item = queue.dequeue();
58
+ if (!item) continue;
59
+ const {
60
+ node,
61
+ depth,
62
+ targetList
63
+ } = item;
64
+ const childTargetList = callback(node, depth, targetList);
65
+ if (childTargetList !== undefined && ((_a = node.nested) === null || _a === void 0 ? void 0 : _a.length)) {
66
+ for (const child of node.nested) {
67
+ queue.enqueue({
68
+ node: child,
69
+ depth: depth + 1,
70
+ targetList: childTargetList
71
+ });
72
+ }
73
+ }
74
+ }
75
+ };
76
+ exports.traverseWithTarget = traverseWithTarget;
@@ -0,0 +1,232 @@
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
+ expandedNodes: ['expandable-root']
180
+ }, vitest_1.expect.any(AbortSignal));
181
+ (0, vitest_1.expect)(onPreloadNodes).toHaveBeenCalledWith(['expandable-root', 'external-root'], vitest_1.expect.any(AbortSignal));
182
+ });
183
+ (0, vitest_1.expect)(result.current.tree.current).toEqual([createParent('expandable-root', [createLeaf('expandable-child')]), createLeaf('leaf')]);
184
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
185
+ 'expandable-root': {
186
+ label: 'expandable-root'
187
+ },
188
+ 'expandable-child': {
189
+ label: 'expandable-child'
190
+ },
191
+ leaf: {
192
+ label: 'leaf'
193
+ }
194
+ });
195
+ }));
196
+ (0, vitest_1.it)('should update tree and record from search result without preloading', () => __awaiter(void 0, void 0, void 0, function* () {
197
+ const onSearch = vitest_1.vi.fn(() => __awaiter(void 0, void 0, void 0, function* () {
198
+ return {
199
+ tree: [createParent('root-node', [createLeaf('child-node')])],
200
+ needPreloadNodes: []
201
+ };
202
+ }));
203
+ const {
204
+ result
205
+ } = (0, react_1.renderHook)(() => (0, useSearchableTree_1.useSearchableTree)({
206
+ initTree: [createLeaf('initial')],
207
+ onPreloadNode: vitest_1.vi.fn(),
208
+ onPreloadNodes: vitest_1.vi.fn(),
209
+ onSearch,
210
+ mapNodeToRecordItem
211
+ }));
212
+ (0, react_1.act)(() => {
213
+ result.current.search.onChange('query');
214
+ });
215
+ yield (0, react_1.waitFor)(() => {
216
+ (0, vitest_1.expect)(onSearch).toHaveBeenCalledWith({
217
+ search: 'query',
218
+ expandedNodes: []
219
+ }, vitest_1.expect.any(AbortSignal));
220
+ (0, vitest_1.expect)(result.current.tree.current).toEqual([createParent('root-node', [createLeaf('child-node')])]);
221
+ });
222
+ (0, vitest_1.expect)(result.current.expandedNodes.current).toEqual([]);
223
+ (0, vitest_1.expect)(result.current.treeItemsRecord.current).toEqual({
224
+ 'root-node': {
225
+ label: 'root-node'
226
+ },
227
+ 'child-node': {
228
+ label: 'child-node'
229
+ }
230
+ });
231
+ }));
232
+ });