@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.
Files changed (231) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +341 -0
  4. package/dist/cjs/components/Tree/Tree.d.ts +4 -0
  5. package/dist/cjs/components/Tree/Tree.js +31 -0
  6. package/dist/cjs/components/Tree/index.d.ts +1 -0
  7. package/dist/cjs/components/Tree/index.js +17 -0
  8. package/dist/cjs/components/Tree/styles.module.css +17 -0
  9. package/dist/cjs/components/Tree/utils.d.ts +4 -0
  10. package/dist/cjs/components/Tree/utils.js +15 -0
  11. package/dist/cjs/components/index.d.ts +1 -0
  12. package/dist/cjs/components/index.js +17 -0
  13. package/dist/cjs/constants.d.ts +26 -0
  14. package/dist/cjs/constants.js +29 -0
  15. package/dist/cjs/contexts/TreeContext.d.ts +30 -0
  16. package/dist/cjs/contexts/TreeContext.js +108 -0
  17. package/dist/cjs/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
  18. package/dist/cjs/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +87 -0
  19. package/dist/cjs/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
  20. package/dist/cjs/helperComponents/ExpandableTreeNode/index.js +17 -0
  21. package/dist/cjs/helperComponents/ExpandableTreeNode/styles.module.css +37 -0
  22. package/dist/cjs/helperComponents/TreeItem/TreeItem.d.ts +8 -0
  23. package/dist/cjs/helperComponents/TreeItem/TreeItem.js +16 -0
  24. package/dist/cjs/helperComponents/TreeItem/index.d.ts +1 -0
  25. package/dist/cjs/helperComponents/TreeItem/index.js +17 -0
  26. package/dist/cjs/helperComponents/TreeLine/TreeLine.d.ts +10 -0
  27. package/dist/cjs/helperComponents/TreeLine/TreeLine.js +13 -0
  28. package/dist/cjs/helperComponents/TreeLine/index.d.ts +1 -0
  29. package/dist/cjs/helperComponents/TreeLine/index.js +17 -0
  30. package/dist/cjs/helperComponents/TreeLine/styles.module.css +37 -0
  31. package/dist/cjs/helperComponents/TreeNode/TreeNode.d.ts +4 -0
  32. package/dist/cjs/helperComponents/TreeNode/TreeNode.js +187 -0
  33. package/dist/cjs/helperComponents/TreeNode/components/TreeNodeActions.d.ts +15 -0
  34. package/dist/cjs/helperComponents/TreeNode/components/TreeNodeActions.js +76 -0
  35. package/dist/cjs/helperComponents/TreeNode/components/TreeNodeHref.d.ts +7 -0
  36. package/dist/cjs/helperComponents/TreeNode/components/TreeNodeHref.js +12 -0
  37. package/dist/cjs/helperComponents/TreeNode/components/index.d.ts +2 -0
  38. package/dist/cjs/helperComponents/TreeNode/components/index.js +18 -0
  39. package/dist/cjs/helperComponents/TreeNode/constants.d.ts +5 -0
  40. package/dist/cjs/helperComponents/TreeNode/constants.js +10 -0
  41. package/dist/cjs/helperComponents/TreeNode/index.d.ts +1 -0
  42. package/dist/cjs/helperComponents/TreeNode/index.js +17 -0
  43. package/dist/cjs/helperComponents/TreeNode/styles.module.css +474 -0
  44. package/dist/cjs/helperComponents/TreeNode/types.d.ts +9 -0
  45. package/dist/cjs/helperComponents/TreeNode/types.js +2 -0
  46. package/dist/cjs/helperComponents/TreeNode/utils.d.ts +3 -0
  47. package/dist/cjs/helperComponents/TreeNode/utils.js +10 -0
  48. package/dist/cjs/helperComponents/index.d.ts +4 -0
  49. package/dist/cjs/helperComponents/index.js +20 -0
  50. package/dist/cjs/helpers/checkNestedNodesSelection.d.ts +9 -0
  51. package/dist/cjs/helpers/checkNestedNodesSelection.js +15 -0
  52. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.d.ts +9 -0
  53. package/dist/cjs/helpers/collectEmptyNestedNodesInExpanded.js +21 -0
  54. package/dist/cjs/helpers/collectHierarchicalAncestors.d.ts +3 -0
  55. package/dist/cjs/helpers/collectHierarchicalAncestors.js +18 -0
  56. package/dist/cjs/helpers/collectIds.d.ts +9 -0
  57. package/dist/cjs/helpers/collectIds.js +16 -0
  58. package/dist/cjs/helpers/extractTreeNodeTitle.d.ts +6 -0
  59. package/dist/cjs/helpers/extractTreeNodeTitle.js +9 -0
  60. package/dist/cjs/helpers/findAllChildNodeIds.d.ts +9 -0
  61. package/dist/cjs/helpers/findAllChildNodeIds.js +24 -0
  62. package/dist/cjs/helpers/findAllExpandedChildNodeIds.d.ts +8 -0
  63. package/dist/cjs/helpers/findAllExpandedChildNodeIds.js +21 -0
  64. package/dist/cjs/helpers/getSearchedTreeItems.d.ts +23 -0
  65. package/dist/cjs/helpers/getSearchedTreeItems.js +41 -0
  66. package/dist/cjs/helpers/getSearchedTreeNodeById.d.ts +19 -0
  67. package/dist/cjs/helpers/getSearchedTreeNodeById.js +35 -0
  68. package/dist/cjs/helpers/index.d.ts +13 -0
  69. package/dist/cjs/helpers/index.js +29 -0
  70. package/dist/cjs/helpers/lookupTreeForSelectedNodes.d.ts +15 -0
  71. package/dist/cjs/helpers/lookupTreeForSelectedNodes.js +21 -0
  72. package/dist/cjs/helpers/sortTreeItemsByTitle.d.ts +13 -0
  73. package/dist/cjs/helpers/sortTreeItemsByTitle.js +14 -0
  74. package/dist/cjs/helpers/traverse.d.ts +11 -0
  75. package/dist/cjs/helpers/traverse.js +25 -0
  76. package/dist/cjs/helpers/traverseWithTarget.d.ts +14 -0
  77. package/dist/cjs/helpers/traverseWithTarget.js +30 -0
  78. package/dist/cjs/helpers/updateTreeNode.d.ts +13 -0
  79. package/dist/cjs/helpers/updateTreeNode.js +34 -0
  80. package/dist/cjs/hooks/index.d.ts +2 -0
  81. package/dist/cjs/hooks/index.js +18 -0
  82. package/dist/cjs/hooks/internalHooks.d.ts +4 -0
  83. package/dist/cjs/hooks/internalHooks.js +29 -0
  84. package/dist/cjs/hooks/useSearchableTree.d.ts +52 -0
  85. package/dist/cjs/hooks/useSearchableTree.js +113 -0
  86. package/dist/cjs/hooks/useTreeMultiSelection.d.ts +33 -0
  87. package/dist/cjs/hooks/useTreeMultiSelection.js +37 -0
  88. package/dist/cjs/index.d.ts +17 -0
  89. package/dist/cjs/index.js +47 -0
  90. package/dist/cjs/package.json +3 -0
  91. package/dist/cjs/style.css +565 -0
  92. package/dist/cjs/types.d.ts +151 -0
  93. package/dist/cjs/types.js +2 -0
  94. package/dist/esm/components/Tree/Tree.d.ts +4 -0
  95. package/dist/esm/components/Tree/Tree.js +25 -0
  96. package/dist/esm/components/Tree/index.d.ts +1 -0
  97. package/dist/esm/components/Tree/index.js +1 -0
  98. package/dist/esm/components/Tree/styles.module.css +17 -0
  99. package/dist/esm/components/Tree/utils.d.ts +4 -0
  100. package/dist/esm/components/Tree/utils.js +12 -0
  101. package/dist/esm/components/index.d.ts +1 -0
  102. package/dist/esm/components/index.js +1 -0
  103. package/dist/esm/constants.d.ts +26 -0
  104. package/dist/esm/constants.js +26 -0
  105. package/dist/esm/contexts/TreeContext.d.ts +30 -0
  106. package/dist/esm/contexts/TreeContext.js +103 -0
  107. package/dist/esm/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
  108. package/dist/esm/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +81 -0
  109. package/dist/esm/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
  110. package/dist/esm/helperComponents/ExpandableTreeNode/index.js +1 -0
  111. package/dist/esm/helperComponents/ExpandableTreeNode/styles.module.css +37 -0
  112. package/dist/esm/helperComponents/TreeItem/TreeItem.d.ts +8 -0
  113. package/dist/esm/helperComponents/TreeItem/TreeItem.js +13 -0
  114. package/dist/esm/helperComponents/TreeItem/index.d.ts +1 -0
  115. package/dist/esm/helperComponents/TreeItem/index.js +1 -0
  116. package/dist/esm/helperComponents/TreeLine/TreeLine.d.ts +10 -0
  117. package/dist/esm/helperComponents/TreeLine/TreeLine.js +7 -0
  118. package/dist/esm/helperComponents/TreeLine/index.d.ts +1 -0
  119. package/dist/esm/helperComponents/TreeLine/index.js +1 -0
  120. package/dist/esm/helperComponents/TreeLine/styles.module.css +37 -0
  121. package/dist/esm/helperComponents/TreeNode/TreeNode.d.ts +4 -0
  122. package/dist/esm/helperComponents/TreeNode/TreeNode.js +181 -0
  123. package/dist/esm/helperComponents/TreeNode/components/TreeNodeActions.d.ts +15 -0
  124. package/dist/esm/helperComponents/TreeNode/components/TreeNodeActions.js +70 -0
  125. package/dist/esm/helperComponents/TreeNode/components/TreeNodeHref.d.ts +7 -0
  126. package/dist/esm/helperComponents/TreeNode/components/TreeNodeHref.js +6 -0
  127. package/dist/esm/helperComponents/TreeNode/components/index.d.ts +2 -0
  128. package/dist/esm/helperComponents/TreeNode/components/index.js +2 -0
  129. package/dist/esm/helperComponents/TreeNode/constants.d.ts +5 -0
  130. package/dist/esm/helperComponents/TreeNode/constants.js +7 -0
  131. package/dist/esm/helperComponents/TreeNode/index.d.ts +1 -0
  132. package/dist/esm/helperComponents/TreeNode/index.js +1 -0
  133. package/dist/esm/helperComponents/TreeNode/styles.module.css +474 -0
  134. package/dist/esm/helperComponents/TreeNode/types.d.ts +9 -0
  135. package/dist/esm/helperComponents/TreeNode/types.js +1 -0
  136. package/dist/esm/helperComponents/TreeNode/utils.d.ts +3 -0
  137. package/dist/esm/helperComponents/TreeNode/utils.js +6 -0
  138. package/dist/esm/helperComponents/index.d.ts +4 -0
  139. package/dist/esm/helperComponents/index.js +4 -0
  140. package/dist/esm/helpers/checkNestedNodesSelection.d.ts +9 -0
  141. package/dist/esm/helpers/checkNestedNodesSelection.js +12 -0
  142. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.d.ts +9 -0
  143. package/dist/esm/helpers/collectEmptyNestedNodesInExpanded.js +18 -0
  144. package/dist/esm/helpers/collectHierarchicalAncestors.d.ts +3 -0
  145. package/dist/esm/helpers/collectHierarchicalAncestors.js +15 -0
  146. package/dist/esm/helpers/collectIds.d.ts +9 -0
  147. package/dist/esm/helpers/collectIds.js +13 -0
  148. package/dist/esm/helpers/extractTreeNodeTitle.d.ts +6 -0
  149. package/dist/esm/helpers/extractTreeNodeTitle.js +5 -0
  150. package/dist/esm/helpers/findAllChildNodeIds.d.ts +9 -0
  151. package/dist/esm/helpers/findAllChildNodeIds.js +21 -0
  152. package/dist/esm/helpers/findAllExpandedChildNodeIds.d.ts +8 -0
  153. package/dist/esm/helpers/findAllExpandedChildNodeIds.js +18 -0
  154. package/dist/esm/helpers/getSearchedTreeItems.d.ts +23 -0
  155. package/dist/esm/helpers/getSearchedTreeItems.js +37 -0
  156. package/dist/esm/helpers/getSearchedTreeNodeById.d.ts +19 -0
  157. package/dist/esm/helpers/getSearchedTreeNodeById.js +31 -0
  158. package/dist/esm/helpers/index.d.ts +13 -0
  159. package/dist/esm/helpers/index.js +13 -0
  160. package/dist/esm/helpers/lookupTreeForSelectedNodes.d.ts +15 -0
  161. package/dist/esm/helpers/lookupTreeForSelectedNodes.js +18 -0
  162. package/dist/esm/helpers/sortTreeItemsByTitle.d.ts +13 -0
  163. package/dist/esm/helpers/sortTreeItemsByTitle.js +10 -0
  164. package/dist/esm/helpers/traverse.d.ts +11 -0
  165. package/dist/esm/helpers/traverse.js +21 -0
  166. package/dist/esm/helpers/traverseWithTarget.d.ts +14 -0
  167. package/dist/esm/helpers/traverseWithTarget.js +26 -0
  168. package/dist/esm/helpers/updateTreeNode.d.ts +13 -0
  169. package/dist/esm/helpers/updateTreeNode.js +30 -0
  170. package/dist/esm/hooks/index.d.ts +2 -0
  171. package/dist/esm/hooks/index.js +2 -0
  172. package/dist/esm/hooks/internalHooks.d.ts +4 -0
  173. package/dist/esm/hooks/internalHooks.js +24 -0
  174. package/dist/esm/hooks/useSearchableTree.d.ts +52 -0
  175. package/dist/esm/hooks/useSearchableTree.js +110 -0
  176. package/dist/esm/hooks/useTreeMultiSelection.d.ts +33 -0
  177. package/dist/esm/hooks/useTreeMultiSelection.js +34 -0
  178. package/dist/esm/index.d.ts +17 -0
  179. package/dist/esm/index.js +17 -0
  180. package/dist/esm/style.css +565 -0
  181. package/dist/esm/types.d.ts +151 -0
  182. package/dist/esm/types.js +1 -0
  183. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  184. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  185. package/package.json +51 -0
  186. package/src/components/Tree/Tree.tsx +53 -0
  187. package/src/components/Tree/index.ts +1 -0
  188. package/src/components/Tree/styles.module.scss +22 -0
  189. package/src/components/Tree/utils.ts +16 -0
  190. package/src/components/index.ts +1 -0
  191. package/src/constants.ts +29 -0
  192. package/src/contexts/TreeContext.tsx +189 -0
  193. package/src/helperComponents/ExpandableTreeNode/ExpandableTreeNode.tsx +156 -0
  194. package/src/helperComponents/ExpandableTreeNode/index.ts +1 -0
  195. package/src/helperComponents/ExpandableTreeNode/styles.module.scss +55 -0
  196. package/src/helperComponents/TreeItem/TreeItem.tsx +26 -0
  197. package/src/helperComponents/TreeItem/index.ts +1 -0
  198. package/src/helperComponents/TreeLine/TreeLine.tsx +27 -0
  199. package/src/helperComponents/TreeLine/index.ts +1 -0
  200. package/src/helperComponents/TreeLine/styles.module.scss +58 -0
  201. package/src/helperComponents/TreeNode/TreeNode.tsx +389 -0
  202. package/src/helperComponents/TreeNode/components/TreeNodeActions.tsx +129 -0
  203. package/src/helperComponents/TreeNode/components/TreeNodeHref.tsx +29 -0
  204. package/src/helperComponents/TreeNode/components/index.ts +2 -0
  205. package/src/helperComponents/TreeNode/constants.ts +7 -0
  206. package/src/helperComponents/TreeNode/index.ts +1 -0
  207. package/src/helperComponents/TreeNode/styles.module.scss +300 -0
  208. package/src/helperComponents/TreeNode/types.ts +11 -0
  209. package/src/helperComponents/TreeNode/utils.ts +9 -0
  210. package/src/helperComponents/index.ts +4 -0
  211. package/src/helpers/checkNestedNodesSelection.ts +15 -0
  212. package/src/helpers/collectEmptyNestedNodesInExpanded.ts +23 -0
  213. package/src/helpers/collectHierarchicalAncestors.ts +22 -0
  214. package/src/helpers/collectIds.ts +17 -0
  215. package/src/helpers/extractTreeNodeTitle.ts +8 -0
  216. package/src/helpers/findAllChildNodeIds.ts +27 -0
  217. package/src/helpers/findAllExpandedChildNodeIds.ts +24 -0
  218. package/src/helpers/getSearchedTreeItems.ts +63 -0
  219. package/src/helpers/getSearchedTreeNodeById.ts +54 -0
  220. package/src/helpers/index.ts +13 -0
  221. package/src/helpers/lookupTreeForSelectedNodes.ts +33 -0
  222. package/src/helpers/sortTreeItemsByTitle.ts +26 -0
  223. package/src/helpers/traverse.ts +28 -0
  224. package/src/helpers/traverseWithTarget.ts +41 -0
  225. package/src/helpers/updateTreeNode.ts +46 -0
  226. package/src/hooks/index.ts +2 -0
  227. package/src/hooks/internalHooks.ts +30 -0
  228. package/src/hooks/useSearchableTree.ts +190 -0
  229. package/src/hooks/useTreeMultiSelection.ts +67 -0
  230. package/src/index.ts +17 -0
  231. package/src/types.ts +162 -0
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TreeNode = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const system_1 = require("@cloud-ru/ds-icons/interface/system");
9
+ const loader_1 = require("@cloud-ru/ds-loader");
10
+ const toggles_1 = require("@cloud-ru/ds-toggles");
11
+ const truncate_string_1 = require("@cloud-ru/ds-truncate-string");
12
+ const typography_1 = require("@cloud-ru/ds-typography");
13
+ const utils_1 = require("@cloud-ru/ds-utils");
14
+ const classnames_1 = __importDefault(require("classnames"));
15
+ const react_1 = require("react");
16
+ const constants_1 = require("../../constants");
17
+ const TreeContext_1 = require("../../contexts/TreeContext");
18
+ const helpers_1 = require("../../helpers");
19
+ const TreeLine_1 = require("../TreeLine");
20
+ const components_1 = require("./components");
21
+ const constants_2 = require("./constants");
22
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
23
+ const utils_2 = require("./utils");
24
+ exports.TreeNode = (0, react_1.forwardRef)(({ id, title, icon: iconProp, expandedIcon: expandedIconProp, collapsedIcon: collapsedIconProp, disabled, onClick, nested, className, onChevronClick, onKeyDown, loading, parentNode, tabIndexAvailable, href, ...rest }, ref) => {
25
+ const { isMultiSelect, isSelectable, onNodeClick, selected, expandedNodes, onSelect, nodeActions, parentActions, setFocusPosition, resetFocusPosition, focusedNodeId, setFocusIndex, focusableNodeIds, showToggle, showLines, showIcons, size = 'm', titleMaxLines = 1, getSelectionState, } = (0, TreeContext_1.useTreeContext)();
26
+ const [isDroplistOpen, setDroplistOpen] = (0, react_1.useState)(false);
27
+ const [isDroplistTriggerFocused, setFocusDroplistTrigger] = (0, react_1.useState)(false);
28
+ const contentRef = (0, react_1.useRef)(null);
29
+ const anchorRef = (0, react_1.useRef)(null);
30
+ const isExpandable = Array.isArray(nested);
31
+ const isExpanded = isExpandable ? expandedNodes?.includes(id) : undefined;
32
+ const selectedIds = (0, react_1.useMemo)(() => {
33
+ if (Array.isArray(selected)) {
34
+ return selected;
35
+ }
36
+ if (selected) {
37
+ return [selected];
38
+ }
39
+ return [];
40
+ }, [selected]);
41
+ const descendantIds = (0, react_1.useMemo)(() => (nested?.length ? (0, helpers_1.findAllChildNodeIds)(nested) : []), [nested]);
42
+ const nestedNodesSelection = (0, react_1.useMemo)(() => {
43
+ if (!nested || !selected)
44
+ return undefined;
45
+ return (0, helpers_1.checkNestedNodesSelection)(nested, selectedIds);
46
+ }, [nested, selected, selectedIds]);
47
+ const multiSelectState = (0, react_1.useMemo)(() => {
48
+ if (!isMultiSelect) {
49
+ return { checked: false, indeterminate: false };
50
+ }
51
+ return getSelectionState({
52
+ nodeId: id,
53
+ childIds: descendantIds,
54
+ selectedIds,
55
+ });
56
+ }, [descendantIds, getSelectionState, id, isMultiSelect, selectedIds]);
57
+ const isSelected = isMultiSelect ? multiSelectState.checked : selected === id || false;
58
+ const isIndeterminate = isMultiSelect && multiSelectState.indeterminate;
59
+ const isFocused = focusedNodeId === id;
60
+ (0, react_1.useEffect)(() => {
61
+ if (contentRef.current && isFocused) {
62
+ contentRef.current.focus();
63
+ }
64
+ }, [isFocused]);
65
+ const treeNodeIcon = (0, react_1.useMemo)(() => {
66
+ if (!showIcons)
67
+ return undefined;
68
+ // Размер svg задаётся в styles.module.scss через `.treeNodeIcon svg`,
69
+ // зависит от data-size корня; size в JSX не прокидываем.
70
+ if (isExpandable) {
71
+ return isExpanded ? (expandedIconProp ?? (0, jsx_runtime_1.jsx)(system_1.FolderOpenSVG, {})) : (collapsedIconProp ?? (0, jsx_runtime_1.jsx)(system_1.FolderSVG, {}));
72
+ }
73
+ return iconProp ?? (0, jsx_runtime_1.jsx)(system_1.FileSVG, {});
74
+ }, [showIcons, isExpandable, isExpanded, iconProp, expandedIconProp, collapsedIconProp]);
75
+ const handleClick = e => {
76
+ onNodeClick({
77
+ id,
78
+ title,
79
+ disabled,
80
+ nested,
81
+ onClick,
82
+ href,
83
+ }, e);
84
+ };
85
+ const handleAnchorClick = (e) => {
86
+ e.stopPropagation();
87
+ if (e?.metaKey || e?.ctrlKey || e?.button === 1) {
88
+ return;
89
+ }
90
+ // preventDefault только если есть кастомный onClick на ноде — иначе
91
+ // позволяем браузеру навигировать по href. Но handleClick зовём всегда,
92
+ // чтобы Tree успел прокинуть onNodeClick/onSelect.
93
+ if (onClick) {
94
+ e.preventDefault();
95
+ }
96
+ handleClick(e);
97
+ };
98
+ const handleSelect = () => {
99
+ onSelect({
100
+ id,
101
+ disabled,
102
+ nested,
103
+ }, parentNode);
104
+ };
105
+ const handleFocus = (e) => {
106
+ setFocusPosition(id);
107
+ if (!e) {
108
+ setFocusDroplistTrigger(false);
109
+ contentRef.current?.focus();
110
+ }
111
+ };
112
+ const handleBlurActions = () => {
113
+ if (isDroplistTriggerFocused && !isDroplistOpen) {
114
+ setFocusDroplistTrigger(false);
115
+ contentRef.current?.blur();
116
+ }
117
+ };
118
+ const handleKeyDown = e => {
119
+ onKeyDown?.(e);
120
+ const focusableCount = focusableNodeIds.length;
121
+ switch (e.key) {
122
+ case 'ArrowDown': {
123
+ e.preventDefault();
124
+ setFocusIndex((prev = 0) => {
125
+ if (prev >= focusableCount - 1) {
126
+ return focusableCount - 1;
127
+ }
128
+ return prev + 1;
129
+ });
130
+ return;
131
+ }
132
+ case 'ArrowUp': {
133
+ e.preventDefault();
134
+ setFocusIndex((prev = 0) => {
135
+ if (!prev) {
136
+ return 0;
137
+ }
138
+ return prev - 1;
139
+ });
140
+ return;
141
+ }
142
+ case 'ArrowRight': {
143
+ e.preventDefault();
144
+ // ArrowRight уводит фокус на droplist trigger row-actions — это нужно
145
+ // только если на строке вообще есть actions. Без них клавиша не
146
+ // должна давать визуального hover-эффекта (`data-droplist-active`).
147
+ const hasRowActions = Boolean(nested ? parentActions : nodeActions);
148
+ if (hasRowActions && (isExpanded || !isExpandable || disabled)) {
149
+ setFocusDroplistTrigger(true);
150
+ }
151
+ return;
152
+ }
153
+ case 'ArrowLeft': {
154
+ e.preventDefault();
155
+ if (isDroplistTriggerFocused) {
156
+ setFocusDroplistTrigger(false);
157
+ contentRef.current?.focus();
158
+ }
159
+ return;
160
+ }
161
+ case 'Escape': {
162
+ contentRef.current?.blur();
163
+ return;
164
+ }
165
+ case ' ':
166
+ case 'Enter': {
167
+ e.preventDefault();
168
+ handleSelect();
169
+ if (href && anchorRef.current) {
170
+ anchorRef.current.click();
171
+ }
172
+ return;
173
+ }
174
+ default:
175
+ return;
176
+ }
177
+ };
178
+ const getNodeActions = nested ? parentActions : nodeActions;
179
+ return ((0, jsx_runtime_1.jsxs)("div", { role: 'presentation', className: (0, classnames_1.default)(styles_module_scss_1.default.treeNode, className), ...(0, utils_1.extractSupportProps)(rest), "data-node-id": id, "data-size": size, ref: ref, children: [parentNode && (0, jsx_runtime_1.jsx)(TreeLine_1.TreeLine, { halfWidth: true, horizontal: true, visible: showLines, "data-test-id": constants_1.TEST_IDS.line }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.treeNodeExpandButtonWrapper, children: isExpandable ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: 'button', onClick: onChevronClick, "data-expanded": isExpanded || undefined, className: styles_module_scss_1.default.treeNodeExpandButton, tabIndex: -1, "data-test-id": constants_1.TEST_IDS.chevron, "aria-label": isExpanded ? 'Collapse' : 'Expand', children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.treeNodeExpandButtonStateLayer, "aria-hidden": true, "data-state": 'emptyNeutralOnBackground' }), loading ? (0, jsx_runtime_1.jsx)(loader_1.Spinner, { size: constants_2.CONTROL_SIZE_BY_TREE_SIZE[size] }) : (0, jsx_runtime_1.jsx)(system_1.ChevronRightSVG, {})] }), (0, jsx_runtime_1.jsx)(TreeLine_1.TreeLine, { visible: isExpanded && showLines })] })) : ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.treeNodeExpandButtonSpacer, "aria-hidden": true })) }), (0, jsx_runtime_1.jsxs)("div", { role: 'treeitem', "aria-expanded": isExpanded, "aria-selected": isSelectable
180
+ ? isSelected || (nestedNodesSelection?.someSelected && !isExpanded && !isMultiSelect)
181
+ : undefined, "aria-disabled": disabled, "data-multiselect": isMultiSelect || undefined, "data-droplist-active": isDroplistOpen || isDroplistTriggerFocused || undefined, onClick: handleClick, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: resetFocusPosition, tabIndex: tabIndexAvailable ? 0 : -1, className: styles_module_scss_1.default.treeNodeContent, "data-test-id": constants_1.TEST_IDS.item, ref: contentRef, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_scss_1.default.stateLayer, "aria-hidden": true, "data-state": isSelected && !isMultiSelect ? 'activatedOnBackground' : 'emptyNeutralOnBackground' }), (isMultiSelect || showToggle) && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.treeNodeCheckboxWrap, children: [isMultiSelect && ((0, jsx_runtime_1.jsx)(toggles_1.Checkbox, { size: constants_2.CONTROL_SIZE_BY_TREE_SIZE[size], disabled: disabled, checked: isSelected, indeterminate: isIndeterminate, onChange: handleSelect, onClick: utils_2.stopPropagationClick, "data-test-id": constants_1.TEST_IDS.checkbox, tabIndex: -1 })), showToggle && ((0, jsx_runtime_1.jsx)(toggles_1.Radio, { size: constants_2.CONTROL_SIZE_BY_TREE_SIZE[size], checked: isSelected, disabled: disabled, "data-test-id": constants_1.TEST_IDS.radio, tabIndex: -1 }))] })), treeNodeIcon && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.treeNodeIcon, "data-test-id": constants_1.TEST_IDS.icon, children: treeNodeIcon })), (0, jsx_runtime_1.jsx)(typography_1.Typography, { variant: 'body', size: size, as: 'div', className: styles_module_scss_1.default.treeNodeTitle, children: (0, jsx_runtime_1.jsxs)(components_1.TreeNodeHref, { href: href, onClick: handleAnchorClick, ref: anchorRef, children: [typeof title === 'string' && ((0, jsx_runtime_1.jsx)(truncate_string_1.TruncateString, { text: title, maxLines: titleMaxLines, "data-test-id": constants_1.TEST_IDS.title })), typeof title !== 'string' && title({ id, disabled, nested })] }) }), getNodeActions && ((0, jsx_runtime_1.jsx)(components_1.TreeNodeActions, { getNodeActions: getNodeActions, node: {
182
+ id,
183
+ title,
184
+ disabled,
185
+ nested,
186
+ }, focusNode: handleFocus, onBlurActions: handleBlurActions, isDroplistTriggerFocused: isDroplistTriggerFocused, isDroplistOpen: isDroplistOpen, setDroplistOpen: setDroplistOpen, size: size }))] })] }));
187
+ });
@@ -0,0 +1,15 @@
1
+ import { ItemProps } from '@cloud-ru/ds-list';
2
+ import { Dispatch, SetStateAction } from 'react';
3
+ import { Size, TreeNodeProps } from '../../../types';
4
+ type TreeNodeActionsProps = {
5
+ isDroplistOpen: boolean;
6
+ setDroplistOpen: Dispatch<SetStateAction<boolean>>;
7
+ getNodeActions(node: Omit<TreeNodeProps, 'href'>): ItemProps[];
8
+ node: Omit<TreeNodeProps, 'href'>;
9
+ isDroplistTriggerFocused: boolean;
10
+ focusNode(): void;
11
+ onBlurActions(): void;
12
+ size: Size;
13
+ };
14
+ export declare function TreeNodeActions({ getNodeActions, isDroplistTriggerFocused, focusNode, isDroplistOpen, setDroplistOpen, onBlurActions, node, size, }: TreeNodeActionsProps): import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TreeNodeActions = TreeNodeActions;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const button_1 = require("@cloud-ru/ds-button");
9
+ const system_1 = require("@cloud-ru/ds-icons/interface/system");
10
+ const list_1 = require("@cloud-ru/ds-list");
11
+ const react_1 = require("react");
12
+ const constants_1 = require("../../../constants");
13
+ const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
14
+ const utils_1 = require("../utils");
15
+ function TreeNodeActions({ getNodeActions, isDroplistTriggerFocused, focusNode, isDroplistOpen, setDroplistOpen, onBlurActions, node, size, }) {
16
+ const droplistActions = getNodeActions(node);
17
+ const localRef = (0, react_1.useRef)(null);
18
+ (0, react_1.useEffect)(() => {
19
+ if (localRef.current && isDroplistTriggerFocused) {
20
+ localRef.current.focus();
21
+ }
22
+ }, [isDroplistTriggerFocused, localRef]);
23
+ const handleKeyDown = e => {
24
+ switch (e.key) {
25
+ case 'Tab': {
26
+ focusNode();
27
+ setDroplistOpen(false);
28
+ e.preventDefault();
29
+ e.stopPropagation();
30
+ return;
31
+ }
32
+ case 'ArrowLeft': {
33
+ if (isDroplistTriggerFocused) {
34
+ focusNode();
35
+ setDroplistOpen(false);
36
+ e.stopPropagation();
37
+ }
38
+ return;
39
+ }
40
+ case ' ':
41
+ case 'Enter': {
42
+ e.stopPropagation();
43
+ return;
44
+ }
45
+ case 'ArrowDown': {
46
+ if (isDroplistTriggerFocused) {
47
+ setDroplistOpen(true);
48
+ }
49
+ e.stopPropagation();
50
+ return;
51
+ }
52
+ case 'ArrowUp': {
53
+ setDroplistOpen(false);
54
+ localRef.current?.focus();
55
+ e.stopPropagation();
56
+ return;
57
+ }
58
+ default:
59
+ return;
60
+ }
61
+ };
62
+ if (!droplistActions.length) {
63
+ return null;
64
+ }
65
+ return ((0, jsx_runtime_1.jsx)("div", { role: 'presentation', className: styles_module_scss_1.default.treeNodeActions, "data-focused": isDroplistTriggerFocused || undefined, onClick: utils_1.stopPropagationClick, onKeyDown: handleKeyDown, onFocus: utils_1.stopPropagationFocus, children: (0, jsx_runtime_1.jsx)(list_1.Droplist, { open: isDroplistOpen, onOpenChange: open => {
66
+ setDroplistOpen(open);
67
+ // Закрытие дроплиста (по клику в пункт, ESC, outside-click) должно
68
+ // вернуть фокус на строку и снять `isDroplistTriggerFocused` —
69
+ // иначе kebab остаётся в visible-hover-состоянии. Tab и ArrowLeft
70
+ // уже делают это сами; для пути через `closeDroplistOnItemClick`
71
+ // ресет навешиваем здесь.
72
+ if (!open) {
73
+ focusNode();
74
+ }
75
+ }, items: droplistActions, closeDroplistOnItemClick: true, placement: 'bottom-end', size: size, children: (0, jsx_runtime_1.jsx)(button_1.Button, { view: 'elevated', appearance: 'neutral', size: size, icon: (0, jsx_runtime_1.jsx)(system_1.KebabSVG, {}), onBlur: onBlurActions, tabIndex: -1, "data-test-id": constants_1.TEST_IDS.droplistTrigger, innerRef: localRef }) }) }));
76
+ }
@@ -0,0 +1,7 @@
1
+ import { MouseEventHandler, ReactNode } from 'react';
2
+ export type TreeNodeHrefProps = {
3
+ href?: string;
4
+ children: ReactNode;
5
+ onClick?: MouseEventHandler<HTMLAnchorElement>;
6
+ };
7
+ export declare const TreeNodeHref: import("react").ForwardRefExoticComponent<TreeNodeHrefProps & import("react").RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TreeNodeHref = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const constants_1 = require("../../../constants");
10
+ const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
11
+ exports.TreeNodeHref = (0, react_1.forwardRef)(({ href, children, onClick }, ref) => href ? ((0, jsx_runtime_1.jsx)("a", { href: href, className: styles_module_scss_1.default.treeNodeLink, onClick: onClick, "data-test-id": constants_1.TEST_IDS.link, tabIndex: -2, ref: ref, children: children })) : (children));
12
+ exports.TreeNodeHref.displayName = 'TreeNodeHref';
@@ -0,0 +1,2 @@
1
+ export * from './TreeNodeActions';
2
+ export * from './TreeNodeHref';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./TreeNodeActions"), exports);
18
+ __exportStar(require("./TreeNodeHref"), exports);
@@ -0,0 +1,5 @@
1
+ export declare const CONTROL_SIZE_BY_TREE_SIZE: {
2
+ readonly s: "xs";
3
+ readonly m: "s";
4
+ readonly l: "s";
5
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTROL_SIZE_BY_TREE_SIZE = void 0;
4
+ // Size-ось контролов (Checkbox / Radio / Spinner) короче, чем у Tree:
5
+ // 's'-строка → 'xs'-контрол, 'm'/'l' → 's'.
6
+ exports.CONTROL_SIZE_BY_TREE_SIZE = {
7
+ s: 'xs',
8
+ m: 's',
9
+ l: 's',
10
+ };
@@ -0,0 +1 @@
1
+ export * from './TreeNode';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./TreeNode"), exports);