@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,300 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
2
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/tree.module' as tree;
|
|
3
|
+
@use '@cloud-ru/ds-materials' as m;
|
|
4
|
+
|
|
5
|
+
$sizes: ('s', 'm', 'l');
|
|
6
|
+
|
|
7
|
+
.treeNode {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
width: 100%;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.treeNodeIcon {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
|
|
22
|
+
color: base.$sn-theme-color-available-version-textTertiary;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.treeNodeTitle {
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-grow: 1;
|
|
29
|
+
align-items: center;
|
|
30
|
+
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
|
|
34
|
+
color: base.$sn-theme-color-available-version-textMain;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.treeNodeActions {
|
|
38
|
+
// Absolute по правому краю строки: в Figma kebab лежит поверх
|
|
39
|
+
// contentWrapper'а и не сдвигает текст. Если оставить во flex-потоке
|
|
40
|
+
// (даже с `visibility: hidden`), он резервирует ширину и title-ellipsis
|
|
41
|
+
// срабатывает раньше реального правого края контейнера.
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 0 0 0 auto;
|
|
44
|
+
display: flex;
|
|
45
|
+
visibility: hidden;
|
|
46
|
+
|
|
47
|
+
// Растягиваем <button> на всю высоту строки.
|
|
48
|
+
> button {
|
|
49
|
+
align-self: stretch;
|
|
50
|
+
height: auto;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&:focus-visible,
|
|
54
|
+
&[data-focused] {
|
|
55
|
+
visibility: visible;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.treeNodeContent {
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
|
|
62
|
+
position: relative;
|
|
63
|
+
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
width: 100%;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
|
|
71
|
+
@include m.has-state-layer-as-child(#{stateLayer});
|
|
72
|
+
|
|
73
|
+
&:hover .treeNodeActions,
|
|
74
|
+
&[data-droplist-active] .treeNodeActions {
|
|
75
|
+
visibility: visible;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Когда у строки открыт дроплист или фокус на kebab — держим hover-фон,
|
|
79
|
+
// чтобы было видно, какая строка управляется action-меню. Hover-стиль
|
|
80
|
+
// state-layer-а задаёт миксин `has-state-layer-as-child` только под
|
|
81
|
+
// `:hover`; здесь повторяем тот же фон под `[data-droplist-active]`.
|
|
82
|
+
&[data-droplist-active] .stateLayer[data-state='emptyNeutralOnBackground'] {
|
|
83
|
+
background: base.$sn-theme-color-material-stateLayer-neutral-onBackground-hovered-filled;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&[data-droplist-active] .stateLayer[data-state='activatedOnBackground'] {
|
|
87
|
+
background: base.$sn-theme-color-material-stateLayer-activated-hovered-filled;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&:focus-visible {
|
|
91
|
+
outline: base.$sn-primitive-strokeWeight-strokeSemiBold solid base.$sn-theme-color-available-complementary;
|
|
92
|
+
outline-offset: calc(-1 * #{base.$sn-primitive-strokeWeight-strokeSemiBold});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// У выбранной строки макет даёт `focusedFrame/primary/inside` на фоне самой строки
|
|
96
|
+
// (у невыбранной — чёрный `regular/inside`). Режим выбора роли не играет.
|
|
97
|
+
&[aria-selected='true']:focus-visible {
|
|
98
|
+
outline-color: base.$sn-theme-color-primary-accent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&[data-multiselect] {
|
|
102
|
+
cursor: default;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Гасит только `opacity` строки: цвет остаётся тем же, что в покое. `textDisabled`
|
|
106
|
+
// сам несёт альфу, и вместе с этой прозрачностью давал двойное гашение — 0.14
|
|
107
|
+
// вместо 0.40 у подписи (та же ошибка, что чинили в @cloud-ru/ds-input-private).
|
|
108
|
+
&[aria-disabled='true'] {
|
|
109
|
+
cursor: not-allowed;
|
|
110
|
+
opacity: base.$sn-theme-effect-opacity-disabled;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.stateLayer {
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
position: absolute;
|
|
117
|
+
inset: 0;
|
|
118
|
+
border-radius: inherit;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.treeNodeCheckboxWrap {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
|
|
127
|
+
box-sizing: border-box;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.treeNodeExpandButton {
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
|
|
133
|
+
position: relative;
|
|
134
|
+
|
|
135
|
+
display: inline-flex;
|
|
136
|
+
flex-shrink: 0;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
|
|
140
|
+
box-sizing: border-box;
|
|
141
|
+
padding: 0;
|
|
142
|
+
border: 0;
|
|
143
|
+
border-radius: base.$sn-brand-anatomy-radius-button-s;
|
|
144
|
+
|
|
145
|
+
color: base.$sn-theme-color-neutral-text;
|
|
146
|
+
|
|
147
|
+
background: transparent;
|
|
148
|
+
|
|
149
|
+
@include m.has-state-layer-as-child(#{treeNodeExpandButtonStateLayer});
|
|
150
|
+
|
|
151
|
+
svg {
|
|
152
|
+
transition: transform 0.2s ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
&[data-expanded] svg {
|
|
156
|
+
transform: rotate(90deg);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&:focus-visible {
|
|
160
|
+
outline: base.$sn-primitive-strokeWeight-strokeSemiBold solid base.$sn-theme-color-available-complementary;
|
|
161
|
+
outline-offset: calc(-1 * #{base.$sn-primitive-strokeWeight-strokeSemiBold});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.treeNodeExpandButtonStateLayer {
|
|
166
|
+
pointer-events: none;
|
|
167
|
+
position: absolute;
|
|
168
|
+
inset: 0;
|
|
169
|
+
border-radius: inherit;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Пустой слот chevron у листьев — те же размеры, что у кнопки раскрытия.
|
|
173
|
+
.treeNodeExpandButtonSpacer {
|
|
174
|
+
flex-shrink: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Колонка chevron-а: stretch на высоту строки, chevron по центру, TreeLine ниже.
|
|
178
|
+
.treeNodeExpandButtonWrapper {
|
|
179
|
+
position: relative;
|
|
180
|
+
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
flex-shrink: 0;
|
|
185
|
+
align-items: center;
|
|
186
|
+
align-self: stretch;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.treeNodeExpandButtonWrapper > :last-child:not(.treeNodeExpandButton) {
|
|
191
|
+
// TreeLine absolute — чтобы не сдвигать chevron из центра. Занимает зазор низ-шеврона → низ-строки.
|
|
192
|
+
pointer-events: none;
|
|
193
|
+
|
|
194
|
+
position: absolute;
|
|
195
|
+
right: 0;
|
|
196
|
+
bottom: 0;
|
|
197
|
+
left: 0;
|
|
198
|
+
|
|
199
|
+
width: 100%;
|
|
200
|
+
height: calc(50% - var(--tree-chevron-size, #{base.$sn-density-size-icon-s}) / 2);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.treeNodeLink {
|
|
204
|
+
display: block;
|
|
205
|
+
width: 100%;
|
|
206
|
+
color: inherit;
|
|
207
|
+
text-decoration: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
$icon-size-by-size: (
|
|
211
|
+
's': base.$sn-density-size-icon-xs,
|
|
212
|
+
'm': base.$sn-density-size-icon-s,
|
|
213
|
+
'l': base.$sn-density-size-icon-s,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
@each $size in $sizes {
|
|
217
|
+
.treeNode[data-size='#{$size}'] {
|
|
218
|
+
$centered-width: base.simple-var(tree.$tree, 'anatomy', 'size', $size, 'treeItem', 'centeredWrapper', 'width');
|
|
219
|
+
$centered-padding-top: base.simple-var(
|
|
220
|
+
tree.$tree,
|
|
221
|
+
'anatomy',
|
|
222
|
+
'size',
|
|
223
|
+
$size,
|
|
224
|
+
'treeItem',
|
|
225
|
+
'centeredWrapper',
|
|
226
|
+
'padding-top'
|
|
227
|
+
);
|
|
228
|
+
$centered-padding-bottom: base.simple-var(
|
|
229
|
+
tree.$tree,
|
|
230
|
+
'anatomy',
|
|
231
|
+
'size',
|
|
232
|
+
$size,
|
|
233
|
+
'treeItem',
|
|
234
|
+
'centeredWrapper',
|
|
235
|
+
'padding-bottom'
|
|
236
|
+
);
|
|
237
|
+
$icon-size: base.simple-var($icon-size-by-size, $size);
|
|
238
|
+
|
|
239
|
+
--tree-chevron-size: #{$centered-width};
|
|
240
|
+
|
|
241
|
+
.treeNodeContent {
|
|
242
|
+
@include base.composite-var(tree.$tree, 'anatomy', 'size', $size, 'treeItem');
|
|
243
|
+
@include base.composite-var(base.$base-styles, 'sn', 'regular', 'body', $size);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.treeNodeCheckboxWrap,
|
|
247
|
+
.treeNodeExpandButtonWrapper {
|
|
248
|
+
width: $centered-width;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Слоты иконки и переключателя — один и тот же `centeredWrapper` кита: он несёт
|
|
252
|
+
// вертикальный паддинг, из которого и складывается высота строки.
|
|
253
|
+
.treeNodeIcon,
|
|
254
|
+
.treeNodeCheckboxWrap {
|
|
255
|
+
padding-top: $centered-padding-top;
|
|
256
|
+
padding-bottom: $centered-padding-bottom;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.treeNodeExpandButton {
|
|
260
|
+
width: $centered-width;
|
|
261
|
+
height: $centered-width;
|
|
262
|
+
|
|
263
|
+
// Размер иконки шеврона/спиннера задаём через CSS, чтобы не прокидывать
|
|
264
|
+
// size в JSX каждой иконки (см. паттерн в @cloud-ru/ds-button).
|
|
265
|
+
// `max-*` обязателен: иконки из @cloud-ru/ds-icons приходят с инлайновым размером,
|
|
266
|
+
// и он перебивает `width`/`height` из таблицы стилей (приём из @cloud-ru/ds-button).
|
|
267
|
+
> svg {
|
|
268
|
+
width: $icon-size;
|
|
269
|
+
max-width: $icon-size;
|
|
270
|
+
height: $icon-size;
|
|
271
|
+
max-height: $icon-size;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.treeNodeExpandButtonSpacer {
|
|
276
|
+
width: $centered-width;
|
|
277
|
+
height: $centered-width;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.treeNodeIcon svg {
|
|
281
|
+
width: $icon-size;
|
|
282
|
+
max-width: $icon-size;
|
|
283
|
+
height: $icon-size;
|
|
284
|
+
max-height: $icon-size;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Row-actions: гасим padding контейнера отрицательными margin'ами, чтобы
|
|
288
|
+
// elevated-кнопка тянулась на всю высоту строки и упиралась в правый край
|
|
289
|
+
// (иначе остаётся `padding-right` пустой полосы справа от kebab'а).
|
|
290
|
+
.treeNodeActions {
|
|
291
|
+
$padding-right: base.simple-var(tree.$tree, 'anatomy', 'size', $size, 'treeItem', 'padding-right');
|
|
292
|
+
$padding-top: base.simple-var(tree.$tree, 'anatomy', 'size', $size, 'treeItem', 'padding-top');
|
|
293
|
+
$padding-bottom: base.simple-var(tree.$tree, 'anatomy', 'size', $size, 'treeItem', 'padding-bottom');
|
|
294
|
+
|
|
295
|
+
margin-top: calc(-1 * #{$padding-top});
|
|
296
|
+
margin-right: calc(-1 * #{$padding-right});
|
|
297
|
+
margin-bottom: calc(-1 * #{$padding-bottom});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { KeyboardEventHandler, MouseEventHandler } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ParentNode, TreeNodeProps } from '../../types';
|
|
4
|
+
|
|
5
|
+
export type TreeNodeComponentProps = TreeNodeProps & {
|
|
6
|
+
onChevronClick?: MouseEventHandler<HTMLElement>;
|
|
7
|
+
onKeyDown?: KeyboardEventHandler<HTMLElement>;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
parentNode?: ParentNode;
|
|
10
|
+
tabIndexAvailable?: boolean;
|
|
11
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { checkGroupSelection } from '@cloud-ru/ds-utils';
|
|
2
|
+
|
|
3
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
4
|
+
import { findAllChildNodeIds } from './findAllChildNodeIds';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Проверяет состояние выбора дочерних узлов:
|
|
8
|
+
* выбраны ли все, выбраны ли некоторые.
|
|
9
|
+
*
|
|
10
|
+
* @param nodes Дочерние узлы для проверки.
|
|
11
|
+
* @param selectedKeys Текущий список выбранных id.
|
|
12
|
+
*/
|
|
13
|
+
export function checkNestedNodesSelection(nodes: TreeNodeProps[], selectedKeys: TreeNodeId[]) {
|
|
14
|
+
return checkGroupSelection(findAllChildNodeIds(nodes), selectedKeys);
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../types';
|
|
2
|
+
import { traverse } from './traverse';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Возвращает раскрытые узлы, у которых `nested` уже существует, но пока пустой.
|
|
6
|
+
* Используется для определения узлов, требующих дополнительной подгрузки детей.
|
|
7
|
+
*
|
|
8
|
+
* @param nodes Узлы дерева для проверки.
|
|
9
|
+
* @param expandedIds Множество id раскрытых узлов.
|
|
10
|
+
*/
|
|
11
|
+
export function collectEmptyNestedNodesInExpanded<Node extends TreeNodeProps>(
|
|
12
|
+
nodes: Node[],
|
|
13
|
+
expandedIds: Set<string>,
|
|
14
|
+
): Node[] {
|
|
15
|
+
const result: Node[] = [];
|
|
16
|
+
traverse(nodes, node => {
|
|
17
|
+
const hasEmptyNested = Array.isArray(node.nested) && node.nested.length === 0;
|
|
18
|
+
if (hasEmptyNested && expandedIds.has(node.id)) {
|
|
19
|
+
result.push(node);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HierarchicalAncestor } from '@cloud-ru/ds-utils';
|
|
2
|
+
|
|
3
|
+
import { ParentNode } from '../types';
|
|
4
|
+
import { findAllChildNodeIds } from './findAllChildNodeIds';
|
|
5
|
+
|
|
6
|
+
export function collectHierarchicalAncestors(parentNode?: ParentNode): HierarchicalAncestor[] {
|
|
7
|
+
const ancestors: HierarchicalAncestor[] = [];
|
|
8
|
+
let parent: ParentNode | undefined = parentNode;
|
|
9
|
+
|
|
10
|
+
while (parent) {
|
|
11
|
+
if (parent.nested?.length) {
|
|
12
|
+
ancestors.push({
|
|
13
|
+
id: parent.id,
|
|
14
|
+
childIds: findAllChildNodeIds(parent.nested),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
parent = parent.parentNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return ancestors;
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TreeNodeProps } from '../types';
|
|
2
|
+
import { traverse } from './traverse';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Собирает id всех узлов дерева.
|
|
6
|
+
*
|
|
7
|
+
* @param treeNodes Узлы дерева для обхода.
|
|
8
|
+
* @returns Массив id узлов в порядке обхода BFS.
|
|
9
|
+
* @function collectIds
|
|
10
|
+
*/
|
|
11
|
+
export function collectIds(treeNodes: TreeNodeProps[]): string[] {
|
|
12
|
+
const ids: string[] = [];
|
|
13
|
+
|
|
14
|
+
traverse(treeNodes, node => ids.push(node.id));
|
|
15
|
+
|
|
16
|
+
return ids;
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ExtendedTreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Возвращает строковый заголовок узла дерева.
|
|
5
|
+
* Если title задан функцией, использует getTitle (если он доступен).
|
|
6
|
+
*/
|
|
7
|
+
export const extractTreeNodeTitle = ({ title, getTitle }: ExtendedTreeNodeProps) =>
|
|
8
|
+
typeof title === 'string' ? title : (getTitle?.() ?? '');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Собирает id всех недеактивированных узлов в поддереве.
|
|
5
|
+
*
|
|
6
|
+
* @param nodes Стартовые узлы обхода.
|
|
7
|
+
* @returns Массив id всех доступных узлов.
|
|
8
|
+
* @function findAllChildNodeIds
|
|
9
|
+
*/
|
|
10
|
+
export function findAllChildNodeIds(nodes: TreeNodeProps[]): TreeNodeId[] {
|
|
11
|
+
const stack = [...nodes];
|
|
12
|
+
const ids: TreeNodeId[] = [];
|
|
13
|
+
|
|
14
|
+
let node: TreeNodeProps | undefined;
|
|
15
|
+
|
|
16
|
+
while ((node = stack.pop())) {
|
|
17
|
+
if (!node.disabled) {
|
|
18
|
+
ids.push(node.id);
|
|
19
|
+
|
|
20
|
+
if (node.nested?.length) {
|
|
21
|
+
stack.push(...node.nested);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ids;
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TreeNodeId, TreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Возвращает id узлов в поддереве, проходя только по раскрытым веткам.
|
|
5
|
+
*
|
|
6
|
+
* @param nodes Стартовые узлы обхода.
|
|
7
|
+
* @param expandedNodes Список id раскрытых узлов.
|
|
8
|
+
*/
|
|
9
|
+
export function findAllExpandedChildNodeIds(nodes: TreeNodeProps[], expandedNodes: TreeNodeId[]) {
|
|
10
|
+
const stack = [...nodes];
|
|
11
|
+
const ids: TreeNodeId[] = [];
|
|
12
|
+
|
|
13
|
+
let node: TreeNodeProps | undefined;
|
|
14
|
+
|
|
15
|
+
while ((node = stack.shift())) {
|
|
16
|
+
ids.push(node.id);
|
|
17
|
+
|
|
18
|
+
if (node.nested?.length && expandedNodes.includes(node.id)) {
|
|
19
|
+
stack.unshift(...node.nested);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return ids;
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ExtendedTreeNodeProps } from '../types';
|
|
2
|
+
import { extractTreeNodeTitle } from './extractTreeNodeTitle';
|
|
3
|
+
|
|
4
|
+
const isMatchedTreeItem = (search: string) => {
|
|
5
|
+
const searchLower = search.toLocaleLowerCase();
|
|
6
|
+
return (treeItem: ExtendedTreeNodeProps) => {
|
|
7
|
+
const currentValue = extractTreeNodeTitle(treeItem);
|
|
8
|
+
return currentValue.toLocaleLowerCase().includes(searchLower);
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type SearchParams = {
|
|
13
|
+
/** Корневые узлы дерева, по которым выполняется поиск. */
|
|
14
|
+
tree: ExtendedTreeNodeProps[];
|
|
15
|
+
searchOptions?: Partial<{
|
|
16
|
+
/** Поисковая строка (case-insensitive substring match по `title` узла). */
|
|
17
|
+
query: string;
|
|
18
|
+
/**
|
|
19
|
+
* Если `true` и parent матчит query — в результат попадают **все** его дети
|
|
20
|
+
* без дополнительной фильтрации. По умолчанию `false`: и parent, и дети
|
|
21
|
+
* фильтруются независимо.
|
|
22
|
+
*/
|
|
23
|
+
includeChildrenMatchedParent: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Фильтрует дерево по поисковой строке с сохранением иерархии подходящих узлов.
|
|
29
|
+
*
|
|
30
|
+
* @param params {@link SearchParams} — дерево и опции поиска.
|
|
31
|
+
* @returns Новое дерево, содержащее только совпавшие узлы и их путь в иерархии.
|
|
32
|
+
*/
|
|
33
|
+
export const getSearchedTreeItems = ({ tree, searchOptions }: SearchParams) => {
|
|
34
|
+
if (!searchOptions?.query) return tree;
|
|
35
|
+
|
|
36
|
+
const { query = '', includeChildrenMatchedParent } = searchOptions;
|
|
37
|
+
|
|
38
|
+
const matchFunc = isMatchedTreeItem(query);
|
|
39
|
+
|
|
40
|
+
const searchItems = (treeItems: ExtendedTreeNodeProps[]): ExtendedTreeNodeProps[] =>
|
|
41
|
+
treeItems.reduce<ExtendedTreeNodeProps[]>((acc, item) => {
|
|
42
|
+
const hasMatchingTitle = matchFunc(item);
|
|
43
|
+
const needDeepSearch = !(hasMatchingTitle && includeChildrenMatchedParent);
|
|
44
|
+
|
|
45
|
+
const matchedChildren = item.nested && (needDeepSearch ? searchItems(item.nested) : item.nested);
|
|
46
|
+
|
|
47
|
+
if (hasMatchingTitle || matchedChildren?.length) {
|
|
48
|
+
const newItem = { ...item };
|
|
49
|
+
|
|
50
|
+
if (matchedChildren?.length) {
|
|
51
|
+
newItem.nested = matchedChildren;
|
|
52
|
+
} else {
|
|
53
|
+
delete newItem.nested;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
acc.push(newItem);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return acc;
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return searchItems(tree);
|
|
63
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ExtendedTreeNodeProps } from '../types';
|
|
2
|
+
|
|
3
|
+
type ReturnSearch<I, T> = I extends string ? T | null : T[];
|
|
4
|
+
|
|
5
|
+
type SearchByIdParams<I, T> = {
|
|
6
|
+
tree: T[];
|
|
7
|
+
searchOptions: {
|
|
8
|
+
id: I;
|
|
9
|
+
includeNested?: boolean;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ищет узлы дерева по одному id или по массиву id.
|
|
15
|
+
*
|
|
16
|
+
* @param params Параметры поиска.
|
|
17
|
+
* @param params.tree Дерево для поиска.
|
|
18
|
+
* @param params.searchOptions Опции поиска (id и includeNested).
|
|
19
|
+
* @returns Для одиночного id возвращает узел или null, для массива id — список узлов.
|
|
20
|
+
*/
|
|
21
|
+
export const getSearchedTreeNodeById = <I extends string | string[], T extends ExtendedTreeNodeProps>({
|
|
22
|
+
tree,
|
|
23
|
+
searchOptions: { id, includeNested = true },
|
|
24
|
+
}: SearchByIdParams<I, T>): ReturnSearch<I, T> => {
|
|
25
|
+
const isArrayIds = Array.isArray(id);
|
|
26
|
+
|
|
27
|
+
if (!id || (isArrayIds && id.length === 0)) {
|
|
28
|
+
return (isArrayIds ? [] : null) as ReturnSearch<I, T>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const foundItems: T[] = [];
|
|
32
|
+
|
|
33
|
+
const searchNodes = (nodes: T[]): void => {
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
const matchCondition = isArrayIds ? id.includes(node.id) : node.id === id;
|
|
36
|
+
|
|
37
|
+
if (matchCondition) {
|
|
38
|
+
foundItems.push(includeNested ? node : { ...node, nested: undefined });
|
|
39
|
+
|
|
40
|
+
if (!isArrayIds) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (node.nested) {
|
|
46
|
+
searchNodes(node.nested as T[]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
searchNodes(tree);
|
|
52
|
+
|
|
53
|
+
return (isArrayIds ? foundItems : foundItems[0] || null) as ReturnSearch<I, T>;
|
|
54
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './checkNestedNodesSelection';
|
|
2
|
+
export * from './collectEmptyNestedNodesInExpanded';
|
|
3
|
+
export * from './collectIds';
|
|
4
|
+
export * from './extractTreeNodeTitle';
|
|
5
|
+
export * from './findAllChildNodeIds';
|
|
6
|
+
export * from './findAllExpandedChildNodeIds';
|
|
7
|
+
export * from './getSearchedTreeItems';
|
|
8
|
+
export * from './getSearchedTreeNodeById';
|
|
9
|
+
export * from './lookupTreeForSelectedNodes';
|
|
10
|
+
export * from './sortTreeItemsByTitle';
|
|
11
|
+
export * from './traverse';
|
|
12
|
+
export * from './traverseWithTarget';
|
|
13
|
+
export * from './updateTreeNode';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HierarchicalSelectionHandlers } from '@cloud-ru/ds-utils';
|
|
2
|
+
|
|
3
|
+
import { ParentNode, TreeNodeId, TreeNodeProps } from '../types';
|
|
4
|
+
import { collectHierarchicalAncestors } from './collectHierarchicalAncestors';
|
|
5
|
+
import { findAllChildNodeIds } from './findAllChildNodeIds';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Пересчитывает выбранные узлы для режима мультивыбора в дереве.
|
|
9
|
+
* Учитывает потомков текущего узла и корректирует состояние предков.
|
|
10
|
+
*
|
|
11
|
+
* @param params Параметры пересчета выделения.
|
|
12
|
+
* @returns Обновленный список выбранных id.
|
|
13
|
+
*/
|
|
14
|
+
export function lookupTreeForSelectedNodes({
|
|
15
|
+
node,
|
|
16
|
+
selectedNodes,
|
|
17
|
+
parentNode,
|
|
18
|
+
toggleSelection,
|
|
19
|
+
}: {
|
|
20
|
+
node: Pick<TreeNodeProps, 'id' | 'nested' | 'disabled'>;
|
|
21
|
+
selectedNodes: TreeNodeId[];
|
|
22
|
+
parentNode?: ParentNode;
|
|
23
|
+
toggleSelection: HierarchicalSelectionHandlers['toggleSelection'];
|
|
24
|
+
}) {
|
|
25
|
+
const descendantIds = node.nested?.length ? findAllChildNodeIds(node.nested) : [];
|
|
26
|
+
|
|
27
|
+
return toggleSelection({
|
|
28
|
+
nodeId: node.id,
|
|
29
|
+
descendantIds,
|
|
30
|
+
selectedIds: selectedNodes,
|
|
31
|
+
ancestors: collectHierarchicalAncestors(parentNode),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ExtendedTreeNodeProps } from '../types';
|
|
2
|
+
import { extractTreeNodeTitle } from './extractTreeNodeTitle';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Сортирует узлы дерева по заголовку в алфавитном порядке.
|
|
6
|
+
*
|
|
7
|
+
* @param items Список узлов для сортировки.
|
|
8
|
+
* @param options Опции сортировки. По умолчанию сортировка чувствительна к регистру.
|
|
9
|
+
* @returns Новый отсортированный массив.
|
|
10
|
+
*/
|
|
11
|
+
type SortTreeItemsByTitleOptions = {
|
|
12
|
+
caseSensitive?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const sortTreeItemsByTitle = (items: ExtendedTreeNodeProps[], options?: SortTreeItemsByTitleOptions) =>
|
|
16
|
+
items &&
|
|
17
|
+
[...items].sort((itemA, itemB) => {
|
|
18
|
+
const valueA = extractTreeNodeTitle(itemA);
|
|
19
|
+
const valueB = extractTreeNodeTitle(itemB);
|
|
20
|
+
|
|
21
|
+
if (options?.caseSensitive ?? true) {
|
|
22
|
+
return valueA.localeCompare(valueB);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return valueA.toLowerCase().localeCompare(valueB.toLowerCase());
|
|
26
|
+
});
|