@cloud-ru/ds-tree 1.0.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.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +341 -0
- package/dist/cjs/components/Tree/Tree.d.ts +4 -0
- package/dist/cjs/components/Tree/Tree.js +31 -0
- package/dist/cjs/components/Tree/index.d.ts +1 -0
- package/dist/cjs/components/Tree/index.js +17 -0
- package/dist/cjs/components/Tree/styles.module.css +17 -0
- package/dist/cjs/components/Tree/utils.d.ts +4 -0
- package/dist/cjs/components/Tree/utils.js +15 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +17 -0
- package/dist/cjs/constants.d.ts +26 -0
- package/dist/cjs/constants.js +29 -0
- package/dist/cjs/contexts/TreeContext.d.ts +30 -0
- package/dist/cjs/contexts/TreeContext.js +108 -0
- package/dist/cjs/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
- package/dist/cjs/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +87 -0
- package/dist/cjs/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ExpandableTreeNode/index.js +17 -0
- package/dist/cjs/helperComponents/ExpandableTreeNode/styles.module.css +37 -0
- package/dist/cjs/helperComponents/TreeItem/TreeItem.d.ts +8 -0
- package/dist/cjs/helperComponents/TreeItem/TreeItem.js +16 -0
- package/dist/cjs/helperComponents/TreeItem/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TreeItem/index.js +17 -0
- package/dist/cjs/helperComponents/TreeLine/TreeLine.d.ts +10 -0
- package/dist/cjs/helperComponents/TreeLine/TreeLine.js +13 -0
- package/dist/cjs/helperComponents/TreeLine/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TreeLine/index.js +17 -0
- package/dist/cjs/helperComponents/TreeLine/styles.module.css +37 -0
- package/dist/cjs/helperComponents/TreeNode/TreeNode.d.ts +4 -0
- package/dist/cjs/helperComponents/TreeNode/TreeNode.js +187 -0
- package/dist/cjs/helperComponents/TreeNode/components/TreeNodeActions.d.ts +15 -0
- package/dist/cjs/helperComponents/TreeNode/components/TreeNodeActions.js +76 -0
- package/dist/cjs/helperComponents/TreeNode/components/TreeNodeHref.d.ts +7 -0
- package/dist/cjs/helperComponents/TreeNode/components/TreeNodeHref.js +12 -0
- package/dist/cjs/helperComponents/TreeNode/components/index.d.ts +2 -0
- package/dist/cjs/helperComponents/TreeNode/components/index.js +18 -0
- package/dist/cjs/helperComponents/TreeNode/constants.d.ts +5 -0
- package/dist/cjs/helperComponents/TreeNode/constants.js +10 -0
- package/dist/cjs/helperComponents/TreeNode/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TreeNode/index.js +17 -0
- package/dist/cjs/helperComponents/TreeNode/styles.module.css +474 -0
- package/dist/cjs/helperComponents/TreeNode/types.d.ts +9 -0
- package/dist/cjs/helperComponents/TreeNode/types.js +2 -0
- package/dist/cjs/helperComponents/TreeNode/utils.d.ts +3 -0
- package/dist/cjs/helperComponents/TreeNode/utils.js +10 -0
- package/dist/cjs/helperComponents/index.d.ts +4 -0
- package/dist/cjs/helperComponents/index.js +20 -0
- package/dist/cjs/helpers/checkNestedNodesSelection.d.ts +9 -0
- package/dist/cjs/helpers/checkNestedNodesSelection.js +15 -0
- package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.d.ts +9 -0
- package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.js +21 -0
- package/dist/cjs/helpers/collectHierarchicalAncestors.d.ts +3 -0
- package/dist/cjs/helpers/collectHierarchicalAncestors.js +18 -0
- package/dist/cjs/helpers/collectIds.d.ts +9 -0
- package/dist/cjs/helpers/collectIds.js +16 -0
- package/dist/cjs/helpers/extractTreeNodeTitle.d.ts +6 -0
- package/dist/cjs/helpers/extractTreeNodeTitle.js +9 -0
- package/dist/cjs/helpers/findAllChildNodeIds.d.ts +9 -0
- package/dist/cjs/helpers/findAllChildNodeIds.js +24 -0
- package/dist/cjs/helpers/findAllExpandedChildNodeIds.d.ts +8 -0
- package/dist/cjs/helpers/findAllExpandedChildNodeIds.js +21 -0
- package/dist/cjs/helpers/getSearchedTreeItems.d.ts +23 -0
- package/dist/cjs/helpers/getSearchedTreeItems.js +41 -0
- package/dist/cjs/helpers/getSearchedTreeNodeById.d.ts +19 -0
- package/dist/cjs/helpers/getSearchedTreeNodeById.js +35 -0
- package/dist/cjs/helpers/index.d.ts +13 -0
- package/dist/cjs/helpers/index.js +29 -0
- package/dist/cjs/helpers/lookupTreeForSelectedNodes.d.ts +15 -0
- package/dist/cjs/helpers/lookupTreeForSelectedNodes.js +21 -0
- package/dist/cjs/helpers/sortTreeItemsByTitle.d.ts +13 -0
- package/dist/cjs/helpers/sortTreeItemsByTitle.js +14 -0
- package/dist/cjs/helpers/traverse.d.ts +11 -0
- package/dist/cjs/helpers/traverse.js +25 -0
- package/dist/cjs/helpers/traverseWithTarget.d.ts +14 -0
- package/dist/cjs/helpers/traverseWithTarget.js +30 -0
- package/dist/cjs/helpers/updateTreeNode.d.ts +13 -0
- package/dist/cjs/helpers/updateTreeNode.js +34 -0
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.js +18 -0
- package/dist/cjs/hooks/internalHooks.d.ts +4 -0
- package/dist/cjs/hooks/internalHooks.js +29 -0
- package/dist/cjs/hooks/useSearchableTree.d.ts +52 -0
- package/dist/cjs/hooks/useSearchableTree.js +113 -0
- package/dist/cjs/hooks/useTreeMultiSelection.d.ts +33 -0
- package/dist/cjs/hooks/useTreeMultiSelection.js +37 -0
- package/dist/cjs/index.d.ts +17 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +565 -0
- package/dist/cjs/types.d.ts +151 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/components/Tree/Tree.d.ts +4 -0
- package/dist/esm/components/Tree/Tree.js +25 -0
- package/dist/esm/components/Tree/index.d.ts +1 -0
- package/dist/esm/components/Tree/index.js +1 -0
- package/dist/esm/components/Tree/styles.module.css +17 -0
- package/dist/esm/components/Tree/utils.d.ts +4 -0
- package/dist/esm/components/Tree/utils.js +12 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/constants.d.ts +26 -0
- package/dist/esm/constants.js +26 -0
- package/dist/esm/contexts/TreeContext.d.ts +30 -0
- package/dist/esm/contexts/TreeContext.js +103 -0
- package/dist/esm/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
- package/dist/esm/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +81 -0
- package/dist/esm/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
- package/dist/esm/helperComponents/ExpandableTreeNode/index.js +1 -0
- package/dist/esm/helperComponents/ExpandableTreeNode/styles.module.css +37 -0
- package/dist/esm/helperComponents/TreeItem/TreeItem.d.ts +8 -0
- package/dist/esm/helperComponents/TreeItem/TreeItem.js +13 -0
- package/dist/esm/helperComponents/TreeItem/index.d.ts +1 -0
- package/dist/esm/helperComponents/TreeItem/index.js +1 -0
- package/dist/esm/helperComponents/TreeLine/TreeLine.d.ts +10 -0
- package/dist/esm/helperComponents/TreeLine/TreeLine.js +7 -0
- package/dist/esm/helperComponents/TreeLine/index.d.ts +1 -0
- package/dist/esm/helperComponents/TreeLine/index.js +1 -0
- package/dist/esm/helperComponents/TreeLine/styles.module.css +37 -0
- package/dist/esm/helperComponents/TreeNode/TreeNode.d.ts +4 -0
- package/dist/esm/helperComponents/TreeNode/TreeNode.js +181 -0
- package/dist/esm/helperComponents/TreeNode/components/TreeNodeActions.d.ts +15 -0
- package/dist/esm/helperComponents/TreeNode/components/TreeNodeActions.js +70 -0
- package/dist/esm/helperComponents/TreeNode/components/TreeNodeHref.d.ts +7 -0
- package/dist/esm/helperComponents/TreeNode/components/TreeNodeHref.js +6 -0
- package/dist/esm/helperComponents/TreeNode/components/index.d.ts +2 -0
- package/dist/esm/helperComponents/TreeNode/components/index.js +2 -0
- package/dist/esm/helperComponents/TreeNode/constants.d.ts +5 -0
- package/dist/esm/helperComponents/TreeNode/constants.js +7 -0
- package/dist/esm/helperComponents/TreeNode/index.d.ts +1 -0
- package/dist/esm/helperComponents/TreeNode/index.js +1 -0
- package/dist/esm/helperComponents/TreeNode/styles.module.css +474 -0
- package/dist/esm/helperComponents/TreeNode/types.d.ts +9 -0
- package/dist/esm/helperComponents/TreeNode/types.js +1 -0
- package/dist/esm/helperComponents/TreeNode/utils.d.ts +3 -0
- package/dist/esm/helperComponents/TreeNode/utils.js +6 -0
- package/dist/esm/helperComponents/index.d.ts +4 -0
- package/dist/esm/helperComponents/index.js +4 -0
- package/dist/esm/helpers/checkNestedNodesSelection.d.ts +9 -0
- package/dist/esm/helpers/checkNestedNodesSelection.js +12 -0
- package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.d.ts +9 -0
- package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.js +18 -0
- package/dist/esm/helpers/collectHierarchicalAncestors.d.ts +3 -0
- package/dist/esm/helpers/collectHierarchicalAncestors.js +15 -0
- package/dist/esm/helpers/collectIds.d.ts +9 -0
- package/dist/esm/helpers/collectIds.js +13 -0
- package/dist/esm/helpers/extractTreeNodeTitle.d.ts +6 -0
- package/dist/esm/helpers/extractTreeNodeTitle.js +5 -0
- package/dist/esm/helpers/findAllChildNodeIds.d.ts +9 -0
- package/dist/esm/helpers/findAllChildNodeIds.js +21 -0
- package/dist/esm/helpers/findAllExpandedChildNodeIds.d.ts +8 -0
- package/dist/esm/helpers/findAllExpandedChildNodeIds.js +18 -0
- package/dist/esm/helpers/getSearchedTreeItems.d.ts +23 -0
- package/dist/esm/helpers/getSearchedTreeItems.js +37 -0
- package/dist/esm/helpers/getSearchedTreeNodeById.d.ts +19 -0
- package/dist/esm/helpers/getSearchedTreeNodeById.js +31 -0
- package/dist/esm/helpers/index.d.ts +13 -0
- package/dist/esm/helpers/index.js +13 -0
- package/dist/esm/helpers/lookupTreeForSelectedNodes.d.ts +15 -0
- package/dist/esm/helpers/lookupTreeForSelectedNodes.js +18 -0
- package/dist/esm/helpers/sortTreeItemsByTitle.d.ts +13 -0
- package/dist/esm/helpers/sortTreeItemsByTitle.js +10 -0
- package/dist/esm/helpers/traverse.d.ts +11 -0
- package/dist/esm/helpers/traverse.js +21 -0
- package/dist/esm/helpers/traverseWithTarget.d.ts +14 -0
- package/dist/esm/helpers/traverseWithTarget.js +26 -0
- package/dist/esm/helpers/updateTreeNode.d.ts +13 -0
- package/dist/esm/helpers/updateTreeNode.js +30 -0
- package/dist/esm/hooks/index.d.ts +2 -0
- package/dist/esm/hooks/index.js +2 -0
- package/dist/esm/hooks/internalHooks.d.ts +4 -0
- package/dist/esm/hooks/internalHooks.js +24 -0
- package/dist/esm/hooks/useSearchableTree.d.ts +52 -0
- package/dist/esm/hooks/useSearchableTree.js +110 -0
- package/dist/esm/hooks/useTreeMultiSelection.d.ts +33 -0
- package/dist/esm/hooks/useTreeMultiSelection.js +34 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/style.css +565 -0
- package/dist/esm/types.d.ts +151 -0
- package/dist/esm/types.js +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +51 -0
- package/src/components/Tree/Tree.tsx +53 -0
- package/src/components/Tree/index.ts +1 -0
- package/src/components/Tree/styles.module.scss +22 -0
- package/src/components/Tree/utils.ts +16 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +29 -0
- package/src/contexts/TreeContext.tsx +189 -0
- package/src/helperComponents/ExpandableTreeNode/ExpandableTreeNode.tsx +156 -0
- package/src/helperComponents/ExpandableTreeNode/index.ts +1 -0
- package/src/helperComponents/ExpandableTreeNode/styles.module.scss +55 -0
- package/src/helperComponents/TreeItem/TreeItem.tsx +26 -0
- package/src/helperComponents/TreeItem/index.ts +1 -0
- package/src/helperComponents/TreeLine/TreeLine.tsx +27 -0
- package/src/helperComponents/TreeLine/index.ts +1 -0
- package/src/helperComponents/TreeLine/styles.module.scss +58 -0
- package/src/helperComponents/TreeNode/TreeNode.tsx +389 -0
- package/src/helperComponents/TreeNode/components/TreeNodeActions.tsx +129 -0
- package/src/helperComponents/TreeNode/components/TreeNodeHref.tsx +29 -0
- package/src/helperComponents/TreeNode/components/index.ts +2 -0
- package/src/helperComponents/TreeNode/constants.ts +7 -0
- package/src/helperComponents/TreeNode/index.ts +1 -0
- package/src/helperComponents/TreeNode/styles.module.scss +300 -0
- package/src/helperComponents/TreeNode/types.ts +11 -0
- package/src/helperComponents/TreeNode/utils.ts +9 -0
- package/src/helperComponents/index.ts +4 -0
- package/src/helpers/checkNestedNodesSelection.ts +15 -0
- package/src/helpers/collectEmptyNestedNodesInExpanded.ts +23 -0
- package/src/helpers/collectHierarchicalAncestors.ts +22 -0
- package/src/helpers/collectIds.ts +17 -0
- package/src/helpers/extractTreeNodeTitle.ts +8 -0
- package/src/helpers/findAllChildNodeIds.ts +27 -0
- package/src/helpers/findAllExpandedChildNodeIds.ts +24 -0
- package/src/helpers/getSearchedTreeItems.ts +63 -0
- package/src/helpers/getSearchedTreeNodeById.ts +54 -0
- package/src/helpers/index.ts +13 -0
- package/src/helpers/lookupTreeForSelectedNodes.ts +33 -0
- package/src/helpers/sortTreeItemsByTitle.ts +26 -0
- package/src/helpers/traverse.ts +28 -0
- package/src/helpers/traverseWithTarget.ts +41 -0
- package/src/helpers/updateTreeNode.ts +46 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/internalHooks.ts +30 -0
- package/src/hooks/useSearchableTree.ts +190 -0
- package/src/hooks/useTreeMultiSelection.ts +67 -0
- package/src/index.ts +17 -0
- package/src/types.ts +162 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
type NodeWithDepth<T extends TreeNodeProps> = { node: T; depth: number };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Обходит дерево в ширину (BFS) и вызывает callback для каждого узла.
|
|
7
|
+
*
|
|
8
|
+
* Очередь — массив с курсором `head`: `shift()` на массиве переиндексирует весь хвост,
|
|
9
|
+
* а сдвиг курсора — O(1).
|
|
10
|
+
*
|
|
11
|
+
* @param nodes Корневые узлы дерева.
|
|
12
|
+
* @param callback Функция, вызываемая для каждого найденного узла.
|
|
13
|
+
*/
|
|
14
|
+
export const traverse = <T extends TreeNodeProps>(nodes: T[], callback: (node: T, depth: number) => void) => {
|
|
15
|
+
const queue: NodeWithDepth<T>[] = nodes.map(node => ({ node, depth: 0 }));
|
|
16
|
+
let head = 0;
|
|
17
|
+
|
|
18
|
+
while (head < queue.length) {
|
|
19
|
+
const { node, depth } = queue[head];
|
|
20
|
+
head += 1;
|
|
21
|
+
|
|
22
|
+
callback(node, depth);
|
|
23
|
+
|
|
24
|
+
for (const child of node.nested ?? []) {
|
|
25
|
+
queue.push({ node: child as T, depth: depth + 1 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
type NodeWithDepthAndTarget<T extends TreeNodeProps> = {
|
|
4
|
+
node: T;
|
|
5
|
+
depth: number;
|
|
6
|
+
targetList: T[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Обходит дерево в ширину (BFS), передавая каждому узлу список,
|
|
11
|
+
* в который должен быть добавлен его трансформированный результат.
|
|
12
|
+
*
|
|
13
|
+
* Очередь — массив с курсором `head`: `shift()` на массиве переиндексирует весь хвост,
|
|
14
|
+
* а сдвиг курсора — O(1).
|
|
15
|
+
*
|
|
16
|
+
* @param nodes Корневые узлы дерева.
|
|
17
|
+
* @param rootTargetList Корневой список-приемник для преобразованных узлов.
|
|
18
|
+
* @param callback Функция обработки узла. Возвращает список для детей
|
|
19
|
+
* или undefined, если вложенные узлы обходить не нужно.
|
|
20
|
+
*/
|
|
21
|
+
export const traverseWithTarget = <T extends TreeNodeProps>(
|
|
22
|
+
nodes: T[],
|
|
23
|
+
rootTargetList: T[],
|
|
24
|
+
callback: (node: T, depth: number, targetList: T[]) => T[] | undefined,
|
|
25
|
+
) => {
|
|
26
|
+
const queue: NodeWithDepthAndTarget<T>[] = nodes.map(node => ({ node, depth: 0, targetList: rootTargetList }));
|
|
27
|
+
let head = 0;
|
|
28
|
+
|
|
29
|
+
while (head < queue.length) {
|
|
30
|
+
const { node, depth, targetList } = queue[head];
|
|
31
|
+
head += 1;
|
|
32
|
+
|
|
33
|
+
const childTargetList = callback(node, depth, targetList);
|
|
34
|
+
|
|
35
|
+
if (childTargetList !== undefined && node.nested?.length) {
|
|
36
|
+
for (const child of node.nested) {
|
|
37
|
+
queue.push({ node: child as T, depth: depth + 1, targetList: childTargetList });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../types';
|
|
2
|
+
import { traverseWithTarget } from './traverseWithTarget';
|
|
3
|
+
|
|
4
|
+
type UpdateTreeNodeData<Node extends TreeNodeProps> = Partial<TreeNodeProps> & {
|
|
5
|
+
nested?: Node[];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Возвращает копию дерева, где у узла с указанным id обновляются переданные поля.
|
|
10
|
+
*
|
|
11
|
+
* @param tree Исходное дерево.
|
|
12
|
+
* @param nodeId Идентификатор узла, который нужно обновить.
|
|
13
|
+
* @param data Частичные данные узла, которые будут применены к найденному элементу.
|
|
14
|
+
*/
|
|
15
|
+
export const updateTreeNode = <Node extends TreeNodeProps>(
|
|
16
|
+
tree: Node[],
|
|
17
|
+
nodeId: string,
|
|
18
|
+
data: UpdateTreeNodeData<Node>,
|
|
19
|
+
): Node[] => {
|
|
20
|
+
const result: Node[] = [];
|
|
21
|
+
|
|
22
|
+
traverseWithTarget(tree, result, (source, _depth, targetList) => {
|
|
23
|
+
const isTarget = source.id === nodeId;
|
|
24
|
+
const hasNested = Array.isArray(source.nested);
|
|
25
|
+
|
|
26
|
+
let newNested: Node[] | undefined;
|
|
27
|
+
|
|
28
|
+
if (isTarget) {
|
|
29
|
+
newNested = data.nested;
|
|
30
|
+
} else if (hasNested) {
|
|
31
|
+
newNested = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const newNode = {
|
|
35
|
+
...source,
|
|
36
|
+
...(isTarget ? data : {}),
|
|
37
|
+
...(newNested !== undefined ? { nested: newNested } : {}),
|
|
38
|
+
} as Node;
|
|
39
|
+
|
|
40
|
+
targetList.push(newNode);
|
|
41
|
+
|
|
42
|
+
return !isTarget && hasNested && source.nested?.length ? newNested : undefined;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DependencyList, EffectCallback, MutableRefObject, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useRefState<T>(initial: T): MutableRefObject<T> {
|
|
4
|
+
const ref = useRef<T>(initial);
|
|
5
|
+
return ref;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useDebounceValue<T>(value: T, delay: number): T {
|
|
9
|
+
const [debounced, setDebounced] = useState(value);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const id = setTimeout(() => setDebounced(value), delay);
|
|
13
|
+
return () => clearTimeout(id);
|
|
14
|
+
}, [value, delay]);
|
|
15
|
+
|
|
16
|
+
return debounced;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useDidUpdate(effect: EffectCallback, deps?: DependencyList) {
|
|
20
|
+
const mounted = useRef(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!mounted.current) {
|
|
24
|
+
mounted.current = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
return effect();
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, deps);
|
|
30
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { collectEmptyNestedNodesInExpanded, traverse, updateTreeNode } from '../helpers';
|
|
4
|
+
import { SearchableTreeDataLoadResult, TreeNodeProps } from '../types';
|
|
5
|
+
import { useDebounceValue, useDidUpdate, useRefState } from './internalHooks';
|
|
6
|
+
|
|
7
|
+
export type SearchResult<TTreeNode extends TreeNodeProps> = {
|
|
8
|
+
tree: TTreeNode[];
|
|
9
|
+
needPreloadNodes: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SearchParams = {
|
|
13
|
+
search: string;
|
|
14
|
+
expandedNodes: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Параметры {@link useSearchableTree}.
|
|
19
|
+
*
|
|
20
|
+
* @typeParam TRecordValue — тип записи в `treeItemsRecord` (по одному элементу на узел).
|
|
21
|
+
* @typeParam TTreeNode — тип узла дерева.
|
|
22
|
+
*/
|
|
23
|
+
type UseSearchableTreeParams<TRecordValue, TTreeNode extends TreeNodeProps> = {
|
|
24
|
+
/** Начальное дерево узлов. */
|
|
25
|
+
initTree: TTreeNode[];
|
|
26
|
+
/** Подгрузка дочерних узлов для одного узла. */
|
|
27
|
+
onPreloadNode: (node: TreeNodeProps) => Promise<TTreeNode[]>;
|
|
28
|
+
/** Пакетная подгрузка детей по id родителей. */
|
|
29
|
+
onPreloadNodes: (nodes: string[], signal?: AbortSignal) => Promise<Record<string, TTreeNode[]>>;
|
|
30
|
+
/** Поиск по дереву. */
|
|
31
|
+
onSearch: (params: SearchParams, signal?: AbortSignal) => Promise<SearchResult<TTreeNode>>;
|
|
32
|
+
/** Сопоставление узла с элементом записи `treeItemsRecord` (по `node.id`). */
|
|
33
|
+
mapNodeToRecordItem: (node: TTreeNode) => TRecordValue;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Хук для работы с деревом, поддерживающим поиск и асинхронную подгрузку узлов.
|
|
38
|
+
*
|
|
39
|
+
* Возможности:
|
|
40
|
+
* - хранение актуального дерева и записи элементов (`treeItemsRecord`);
|
|
41
|
+
* - управление раскрытыми узлами;
|
|
42
|
+
* - дебаунс-поиск с отменой предыдущего запроса;
|
|
43
|
+
* - дозагрузка дочерних узлов после поиска или раскрытия.
|
|
44
|
+
*
|
|
45
|
+
* @param params Параметры и обработчики для работы с деревом.
|
|
46
|
+
* @returns Ссылки на состояние дерева, контролы поиска и колбэки для `expand`/`dataLoad`.
|
|
47
|
+
*/
|
|
48
|
+
export function useSearchableTree<TRecordValue, TTreeNode extends TreeNodeProps>({
|
|
49
|
+
initTree,
|
|
50
|
+
onPreloadNode,
|
|
51
|
+
onPreloadNodes,
|
|
52
|
+
onSearch,
|
|
53
|
+
mapNodeToRecordItem,
|
|
54
|
+
}: UseSearchableTreeParams<TRecordValue, TTreeNode>) {
|
|
55
|
+
const tree = useRefState<TTreeNode[]>(initTree);
|
|
56
|
+
const treeItemsRecord = useRefState<Record<string, TRecordValue>>({});
|
|
57
|
+
|
|
58
|
+
const expandedNodes = useRefState<string[]>([]);
|
|
59
|
+
|
|
60
|
+
const [search, setSearch] = useState<string>('');
|
|
61
|
+
const debouncedSearch = useDebounceValue(search, 500);
|
|
62
|
+
|
|
63
|
+
const [loading, setLoading] = useState(false);
|
|
64
|
+
// Актуальный запрос поиска. Он же маркер «этот вызов ещё не вытеснен следующим»:
|
|
65
|
+
// после каждого await сверяемся по идентичности, а не по отдельному флагу отмены.
|
|
66
|
+
const searchAbortControllerRef = useRef<AbortController | null>(null);
|
|
67
|
+
|
|
68
|
+
const buildTreeItemsRecord = useCallback(
|
|
69
|
+
(nodes: TTreeNode[]): Record<string, TRecordValue> => {
|
|
70
|
+
const record: Record<string, TRecordValue> = {};
|
|
71
|
+
|
|
72
|
+
traverse<TTreeNode>(nodes, node => {
|
|
73
|
+
record[node.id] = mapNodeToRecordItem(node);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return record;
|
|
77
|
+
},
|
|
78
|
+
[mapNodeToRecordItem],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const onExpand = useCallback(
|
|
82
|
+
(nodes: string[]) => {
|
|
83
|
+
expandedNodes.current = nodes;
|
|
84
|
+
},
|
|
85
|
+
[expandedNodes],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const onDataLoad = useCallback(
|
|
89
|
+
async (node: TreeNodeProps): Promise<SearchableTreeDataLoadResult<TTreeNode, TRecordValue>> => {
|
|
90
|
+
const preloadedChildren = await onPreloadNode(node);
|
|
91
|
+
const updatedTree = updateTreeNode(tree.current, node.id, { nested: preloadedChildren });
|
|
92
|
+
tree.current = updatedTree;
|
|
93
|
+
|
|
94
|
+
const newTreeItemsRecord: Record<string, TRecordValue> = { ...treeItemsRecord.current };
|
|
95
|
+
traverse<TTreeNode>(preloadedChildren, child => {
|
|
96
|
+
newTreeItemsRecord[child.id] = mapNodeToRecordItem(child);
|
|
97
|
+
});
|
|
98
|
+
treeItemsRecord.current = newTreeItemsRecord;
|
|
99
|
+
|
|
100
|
+
return { preloadedChildren, updatedTree, newTreeItemsRecord };
|
|
101
|
+
},
|
|
102
|
+
[mapNodeToRecordItem, onPreloadNode, tree, treeItemsRecord],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const handleSearch = useCallback(
|
|
106
|
+
async (searchQuery: string) => {
|
|
107
|
+
searchAbortControllerRef.current?.abort();
|
|
108
|
+
|
|
109
|
+
setLoading(true);
|
|
110
|
+
const abortController = new AbortController();
|
|
111
|
+
searchAbortControllerRef.current = abortController;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const { tree: searchedTree, needPreloadNodes } = await onSearch(
|
|
115
|
+
{ search: searchQuery, expandedNodes: expandedNodes.current },
|
|
116
|
+
abortController.signal,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!abortController.signal.aborted) {
|
|
120
|
+
tree.current = searchedTree;
|
|
121
|
+
treeItemsRecord.current = buildTreeItemsRecord(searchedTree);
|
|
122
|
+
|
|
123
|
+
const expandedSet = new Set(expandedNodes.current);
|
|
124
|
+
const toPreloadExpandableNodes = collectEmptyNestedNodesInExpanded(searchedTree, expandedSet);
|
|
125
|
+
const collectedNodesForPreload = Array.from(
|
|
126
|
+
new Set([...toPreloadExpandableNodes.map(node => node.id), ...needPreloadNodes]),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!collectedNodesForPreload.length) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const preloadedNodes = await onPreloadNodes(collectedNodesForPreload, abortController.signal);
|
|
134
|
+
|
|
135
|
+
if (searchAbortControllerRef.current !== abortController) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let tmpTree = [...searchedTree];
|
|
140
|
+
for (const [nodeId, children] of Object.entries(preloadedNodes)) {
|
|
141
|
+
tmpTree = updateTreeNode(tmpTree, nodeId, { nested: children });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
tree.current = tmpTree;
|
|
145
|
+
treeItemsRecord.current = buildTreeItemsRecord(tmpTree);
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// Прерванный запрос — штатный путь: следующий ввод уже вытеснил этот вызов.
|
|
149
|
+
if (!abortController.signal.aborted) {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
if (searchAbortControllerRef.current === abortController) {
|
|
154
|
+
setLoading(false);
|
|
155
|
+
searchAbortControllerRef.current = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
[buildTreeItemsRecord, expandedNodes, onPreloadNodes, onSearch, tree, treeItemsRecord],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
useDidUpdate(() => {
|
|
163
|
+
handleSearch(debouncedSearch);
|
|
164
|
+
}, [debouncedSearch, handleSearch]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
tree.current = initTree;
|
|
168
|
+
treeItemsRecord.current = buildTreeItemsRecord(initTree);
|
|
169
|
+
}, [buildTreeItemsRecord, initTree, tree, treeItemsRecord]);
|
|
170
|
+
|
|
171
|
+
useEffect(
|
|
172
|
+
() => () => {
|
|
173
|
+
searchAbortControllerRef.current?.abort();
|
|
174
|
+
},
|
|
175
|
+
[],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
tree,
|
|
180
|
+
expandedNodes,
|
|
181
|
+
loading,
|
|
182
|
+
treeItemsRecord,
|
|
183
|
+
search: {
|
|
184
|
+
value: search,
|
|
185
|
+
onChange: setSearch,
|
|
186
|
+
},
|
|
187
|
+
onExpand,
|
|
188
|
+
onDataLoad,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { applySelectionDiff, useValueControl } from '@cloud-ru/ds-utils';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { PreloadNodeHandler, SelectHandler, TreeNodeProps } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Параметры {@link useTreeMultiSelection}.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam TTreeNode — тип узла дерева.
|
|
10
|
+
*/
|
|
11
|
+
type UseTreeMultiSelectionParams<TTreeNode extends TreeNodeProps> = {
|
|
12
|
+
/**
|
|
13
|
+
* Подгрузка дочерних узлов, если у узла есть `nested`, но массив пуст (ленивая загрузка перед выбором).
|
|
14
|
+
*/
|
|
15
|
+
onDataLoad: PreloadNodeHandler<TTreeNode>;
|
|
16
|
+
/** Логика мультивыбора: по клику возвращает, какие id добавить и снять. */
|
|
17
|
+
onSelect: SelectHandler;
|
|
18
|
+
/** Controlled: текущие выбранные id; без пропа — внутреннее состояние хука. */
|
|
19
|
+
selected?: string[];
|
|
20
|
+
/** Вызывается при изменении набора выбранных id (controlled-сценарий). */
|
|
21
|
+
onChangeSelected?: (newSelected: string[]) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Хук мультивыбора для дерева.
|
|
26
|
+
*
|
|
27
|
+
* Выполняет пересчет выбранных id после клика по узлу, поддерживает
|
|
28
|
+
* controlled/uncontrolled режим (`selected` + `onChangeSelected`) и при необходимости
|
|
29
|
+
* подгружает детей узла перед вычислением итогового выбора.
|
|
30
|
+
*
|
|
31
|
+
* @param params Параметры управления выбором и подгрузкой данных.
|
|
32
|
+
* @returns Текущий список выбранных id и обработчик выбора узла.
|
|
33
|
+
*/
|
|
34
|
+
export function useTreeMultiSelection<TTreeNode extends TreeNodeProps>({
|
|
35
|
+
onDataLoad,
|
|
36
|
+
onSelect: onSelectProp,
|
|
37
|
+
selected,
|
|
38
|
+
onChangeSelected,
|
|
39
|
+
}: UseTreeMultiSelectionParams<TTreeNode>) {
|
|
40
|
+
const [selectedIds = [], setSelectedIds] = useValueControl<string[]>({
|
|
41
|
+
value: selected,
|
|
42
|
+
defaultValue: [],
|
|
43
|
+
onChange: onChangeSelected,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const onSelect = useCallback(
|
|
47
|
+
async (selectedKeys: string[], node: TreeNodeProps) => {
|
|
48
|
+
const isSelected = selectedKeys.includes(node.id);
|
|
49
|
+
const clonedNode = { ...node };
|
|
50
|
+
|
|
51
|
+
if (node.nested && !node.nested.length) {
|
|
52
|
+
const { preloadedChildren } = await onDataLoad(node as TTreeNode);
|
|
53
|
+
clonedNode.nested = preloadedChildren;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { added, removed } = onSelectProp({ selectedKeys, node: clonedNode, isSelected });
|
|
57
|
+
const updatedSelectedIds = applySelectionDiff(selectedIds, added, removed);
|
|
58
|
+
setSelectedIds(updatedSelectedIds);
|
|
59
|
+
},
|
|
60
|
+
[onDataLoad, onSelectProp, selectedIds, setSelectedIds],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
selected: selectedIds,
|
|
65
|
+
onSelect,
|
|
66
|
+
};
|
|
67
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './components';
|
|
2
|
+
export * from './constants';
|
|
3
|
+
export { checkNestedNodesSelection } from './helpers/checkNestedNodesSelection';
|
|
4
|
+
export { collectEmptyNestedNodesInExpanded } from './helpers/collectEmptyNestedNodesInExpanded';
|
|
5
|
+
export { collectIds } from './helpers/collectIds';
|
|
6
|
+
export { extractTreeNodeTitle } from './helpers/extractTreeNodeTitle';
|
|
7
|
+
export { findAllChildNodeIds } from './helpers/findAllChildNodeIds';
|
|
8
|
+
export { findAllExpandedChildNodeIds } from './helpers/findAllExpandedChildNodeIds';
|
|
9
|
+
export { getSearchedTreeItems } from './helpers/getSearchedTreeItems';
|
|
10
|
+
export { getSearchedTreeNodeById } from './helpers/getSearchedTreeNodeById';
|
|
11
|
+
export { lookupTreeForSelectedNodes } from './helpers/lookupTreeForSelectedNodes';
|
|
12
|
+
export { sortTreeItemsByTitle } from './helpers/sortTreeItemsByTitle';
|
|
13
|
+
export { traverse } from './helpers/traverse';
|
|
14
|
+
export { traverseWithTarget } from './helpers/traverseWithTarget';
|
|
15
|
+
export { updateTreeNode } from './helpers/updateTreeNode';
|
|
16
|
+
export * from './hooks';
|
|
17
|
+
export * from './types';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ItemProps } from '@cloud-ru/ds-list';
|
|
2
|
+
import { ValueOf, WithSupportProps } from '@cloud-ru/ds-utils';
|
|
3
|
+
import { MouseEvent, MouseEventHandler, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { SELECTION_MODE, SIZE } from './constants';
|
|
6
|
+
|
|
7
|
+
export type SelectionMode = ValueOf<typeof SELECTION_MODE>;
|
|
8
|
+
export type Size = ValueOf<typeof SIZE>;
|
|
9
|
+
|
|
10
|
+
export type TreeNodeId = string;
|
|
11
|
+
|
|
12
|
+
export type BaseTreeNode = WithSupportProps<{
|
|
13
|
+
/** Идентификатор элемента */
|
|
14
|
+
id: TreeNodeId;
|
|
15
|
+
/** Имя элемента */
|
|
16
|
+
title: string | ((value: Pick<BaseTreeNode, 'id' | 'disabled'>) => ReactNode);
|
|
17
|
+
/** Является ли элемент деактивированным */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Обработчик клика по элементу */
|
|
20
|
+
onClick?: MouseEventHandler;
|
|
21
|
+
/** CSS-класс */
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Ссылка на элемент */
|
|
24
|
+
href?: string;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
export type ChildTreeNode = BaseTreeNode & {
|
|
28
|
+
/** Иконка дочернего элемента */
|
|
29
|
+
icon?: ReactNode;
|
|
30
|
+
nested?: never;
|
|
31
|
+
expandedIcon?: never;
|
|
32
|
+
collapsedIcon?: never;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ParentTreeNode = BaseTreeNode & {
|
|
36
|
+
icon?: never;
|
|
37
|
+
/** Иконка элемента-родителя в открытом состоянии */
|
|
38
|
+
expandedIcon?: ReactNode;
|
|
39
|
+
/** Иконка элемента-родителя в закрытом состоянии */
|
|
40
|
+
collapsedIcon?: ReactNode;
|
|
41
|
+
/** Вложенные элементы дерева */
|
|
42
|
+
nested: (ChildTreeNode | ParentTreeNode)[];
|
|
43
|
+
/** Имя элемента-родителя (расширяет вариант из `BaseTreeNode` — в callback приходят также `nested`-узлы) */
|
|
44
|
+
title: string | ((value: Pick<ParentTreeNode, 'id' | 'disabled' | 'nested'>) => ReactNode);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type TreeNodeProps = ChildTreeNode | ParentTreeNode;
|
|
48
|
+
|
|
49
|
+
export type ParentNode = Pick<TreeNodeProps, 'id' | 'nested'> & { parentNode?: ParentNode };
|
|
50
|
+
|
|
51
|
+
export type OnNodeClick = (node: TreeNodeProps, e: MouseEvent) => void;
|
|
52
|
+
|
|
53
|
+
export type TreeCommonProps = {
|
|
54
|
+
/** Данные для отрисовки */
|
|
55
|
+
data: TreeNodeProps[];
|
|
56
|
+
/**
|
|
57
|
+
* Режим выбора элементов:
|
|
58
|
+
* <br> - `Single` - одиночный выбор
|
|
59
|
+
* <br> - `Multi` - множественный выбор
|
|
60
|
+
* <br> - `undefined` - без выбора
|
|
61
|
+
*/
|
|
62
|
+
selectionMode?: SelectionMode;
|
|
63
|
+
/** Обработчик клика по элементу дерева */
|
|
64
|
+
onNodeClick?: OnNodeClick;
|
|
65
|
+
/** Состояние для раскрытых элементов */
|
|
66
|
+
expandedNodes?: TreeNodeId[];
|
|
67
|
+
/** Колбэк при раскрытии/закрытии элементов */
|
|
68
|
+
onExpand?(expandedKeys: TreeNodeId[], node: TreeNodeProps): void;
|
|
69
|
+
/** Состояние для выбранных элементов */
|
|
70
|
+
selected?: TreeNodeId[] | TreeNodeId;
|
|
71
|
+
/** Колбэк для асинхронной загрузки данных при раскрытии дерева */
|
|
72
|
+
onDataLoad?(node: TreeNodeProps): Promise<boolean | unknown>;
|
|
73
|
+
/** Дополнительные действия для элемента-родителя */
|
|
74
|
+
parentActions?(node: TreeNodeProps): ItemProps[];
|
|
75
|
+
/** Дополнительные действия для элемента-потомка */
|
|
76
|
+
nodeActions?(node: TreeNodeProps): ItemProps[];
|
|
77
|
+
/**
|
|
78
|
+
* Флаг отвечающий за отображение иконок у элементов дерева
|
|
79
|
+
* @default true
|
|
80
|
+
*/
|
|
81
|
+
showIcons?: boolean;
|
|
82
|
+
/** Флаг отвечающий за отображение линий вложенности */
|
|
83
|
+
showLines?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Размер строк дерева (Figma variant `size`).
|
|
86
|
+
* @default 'm'
|
|
87
|
+
*/
|
|
88
|
+
size?: Size;
|
|
89
|
+
/**
|
|
90
|
+
* Максимальное число строк заголовка узла до обрезки многоточием.
|
|
91
|
+
* Значение больше 1 разрешает перенос; строка растёт по высоте.
|
|
92
|
+
* @default 1
|
|
93
|
+
*/
|
|
94
|
+
titleMaxLines?: number;
|
|
95
|
+
/** CSS-класс */
|
|
96
|
+
className?: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type TreeView = Omit<TreeCommonProps, 'selected' | 'selectionMode'> & {
|
|
100
|
+
selectionMode?: never;
|
|
101
|
+
selected?: never;
|
|
102
|
+
onSelect?: never;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type TreeSingleSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
106
|
+
selectionMode: 'single';
|
|
107
|
+
/** Состояние для выбраного элемента */
|
|
108
|
+
/** <br> - При <strong>selectionMode</strong>=`Single` - принимает строку */
|
|
109
|
+
selected?: TreeNodeId;
|
|
110
|
+
/** <br> - При <strong>selectionMode</strong>=`Single` - возвращает строку */
|
|
111
|
+
onSelect?(selectedKey: TreeNodeId, node: TreeNodeProps): void;
|
|
112
|
+
showToggle?: boolean;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type TreeMultiSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
116
|
+
selectionMode: 'multiple';
|
|
117
|
+
/**
|
|
118
|
+
* Состояние для выбраных элементов:
|
|
119
|
+
* <br> - При <strong>selectionMode</strong>=`Multiple` - принимает массив строк
|
|
120
|
+
*/
|
|
121
|
+
selected?: TreeNodeId[];
|
|
122
|
+
/**
|
|
123
|
+
* Колбэк при изменении выбраных элементов:
|
|
124
|
+
* <br> - При <strong>selectionMode</strong>=`Multiple` - возвращает массив строк
|
|
125
|
+
*/
|
|
126
|
+
onSelect?(selectedKeys: TreeNodeId[], node: TreeNodeProps): void;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type TreeBaseProps = TreeView | TreeMultiSelect | TreeSingleSelect;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Расширение `TreeNodeProps` для внутренних helper'ов поиска/сортировки.
|
|
133
|
+
*
|
|
134
|
+
* Когда `title` — функция (а не строка), helper'ы не могут отсортировать или
|
|
135
|
+
* отфильтровать узел по содержимому JSX. `getTitle` даёт строковый ключ, по
|
|
136
|
+
* которому можно сравнивать узлы.
|
|
137
|
+
*/
|
|
138
|
+
export type ExtendedTreeNodeProps = TreeNodeProps & {
|
|
139
|
+
/** Возвращает строковый ключ узла для сравнения/поиска, когда `title` — ReactNode. */
|
|
140
|
+
getTitle?(): string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type PreloadNodeHandler<TTreeNode extends TreeNodeProps> = (
|
|
144
|
+
node: TreeNodeProps,
|
|
145
|
+
) => Promise<{ preloadedChildren: TTreeNode[]; updatedTree: TTreeNode[] }>;
|
|
146
|
+
|
|
147
|
+
export type SearchableTreeDataLoadResult<TTreeNode extends TreeNodeProps, TRecordValue> = {
|
|
148
|
+
preloadedChildren: TTreeNode[];
|
|
149
|
+
updatedTree: TTreeNode[];
|
|
150
|
+
newTreeItemsRecord: Record<string, TRecordValue>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Резолвер выделения для multi-select-режима: получает текущий список выбранных
|
|
155
|
+
* id, целевой узел и его новое состояние, возвращает diff (`added`/`removed`)
|
|
156
|
+
* относительно входного `selectedKeys`. Используется для кастомных стратегий
|
|
157
|
+
* каскадирования.
|
|
158
|
+
*/
|
|
159
|
+
export type SelectHandler = (props: { selectedKeys: string[]; node: TreeNodeProps; isSelected: boolean }) => {
|
|
160
|
+
added: string[];
|
|
161
|
+
removed: string[];
|
|
162
|
+
};
|