@salt-ds/lab 1.0.0-alpha.86 → 1.0.0-alpha.88

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 (132) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/css/salt-lab.css +145 -212
  3. package/dist-cjs/calendar/internal/CalendarDay.css.js +1 -1
  4. package/dist-cjs/common-hooks/useSelection.js +0 -2
  5. package/dist-cjs/common-hooks/useSelection.js.map +1 -1
  6. package/dist-cjs/contact-details/ContactDetails.css.js +1 -1
  7. package/dist-cjs/date-input/DateInput.css.js +1 -1
  8. package/dist-cjs/date-input/DateInputRange.js +2 -8
  9. package/dist-cjs/date-input/DateInputRange.js.map +1 -1
  10. package/dist-cjs/date-input/DateInputSingle.js +1 -1
  11. package/dist-cjs/date-input/DateInputSingle.js.map +1 -1
  12. package/dist-cjs/index.js +6 -2
  13. package/dist-cjs/index.js.map +1 -1
  14. package/dist-cjs/list-deprecated/ListItem.js.map +1 -1
  15. package/dist-cjs/rating/Rating.css.js +1 -1
  16. package/dist-cjs/rating/Rating.js +8 -7
  17. package/dist-cjs/rating/Rating.js.map +1 -1
  18. package/dist-cjs/tabs-next/TabListNext.js +1 -1
  19. package/dist-cjs/tabs-next/TabListNext.js.map +1 -1
  20. package/dist-cjs/tokenized-input-next/TokenizedInputNext.js +2 -2
  21. package/dist-cjs/tokenized-input-next/TokenizedInputNext.js.map +1 -1
  22. package/dist-cjs/tree/Tree.css.js +1 -1
  23. package/dist-cjs/tree/Tree.js +274 -207
  24. package/dist-cjs/tree/Tree.js.map +1 -1
  25. package/dist-cjs/tree/TreeContext.js +31 -0
  26. package/dist-cjs/tree/TreeContext.js.map +1 -0
  27. package/dist-cjs/tree/TreeNode.css.js +1 -1
  28. package/dist-cjs/tree/TreeNode.js +86 -42
  29. package/dist-cjs/tree/TreeNode.js.map +1 -1
  30. package/dist-cjs/tree/TreeNodeExpansionIcon.css.js +6 -0
  31. package/dist-cjs/tree/TreeNodeExpansionIcon.css.js.map +1 -0
  32. package/dist-cjs/tree/TreeNodeExpansionIcon.js +62 -0
  33. package/dist-cjs/tree/TreeNodeExpansionIcon.js.map +1 -0
  34. package/dist-cjs/tree/TreeNodeLabel.css.js +6 -0
  35. package/dist-cjs/tree/TreeNodeLabel.css.js.map +1 -0
  36. package/dist-cjs/tree/TreeNodeLabel.js +26 -0
  37. package/dist-cjs/tree/TreeNodeLabel.js.map +1 -0
  38. package/dist-cjs/tree/TreeNodeTrigger.css.js +6 -0
  39. package/dist-cjs/tree/TreeNodeTrigger.css.js.map +1 -0
  40. package/dist-cjs/tree/TreeNodeTrigger.js +152 -0
  41. package/dist-cjs/tree/TreeNodeTrigger.js.map +1 -0
  42. package/dist-cjs/tree/useTree.js +305 -133
  43. package/dist-cjs/tree/useTree.js.map +1 -1
  44. package/dist-es/calendar/internal/CalendarDay.css.js +1 -1
  45. package/dist-es/combo-box-deprecated/internal/DefaultComboBox.js +1 -1
  46. package/dist-es/combo-box-deprecated/internal/MultiSelectComboBox.js +1 -1
  47. package/dist-es/combo-box-deprecated/internal/useComboBox.js +1 -1
  48. package/dist-es/combo-box-deprecated/internal/useMultiSelectComboBox.js +1 -1
  49. package/dist-es/common-hooks/useCollectionItems.js +1 -1
  50. package/dist-es/common-hooks/useSelection.js +1 -2
  51. package/dist-es/common-hooks/useSelection.js.map +1 -1
  52. package/dist-es/contact-details/ContactDetails.css.js +1 -1
  53. package/dist-es/date-input/DateInput.css.js +1 -1
  54. package/dist-es/date-input/DateInputRange.js +2 -8
  55. package/dist-es/date-input/DateInputRange.js.map +1 -1
  56. package/dist-es/date-input/DateInputSingle.js +1 -1
  57. package/dist-es/date-input/DateInputSingle.js.map +1 -1
  58. package/dist-es/dropdown/DropdownBase.js +1 -1
  59. package/dist-es/index.js +3 -1
  60. package/dist-es/index.js.map +1 -1
  61. package/dist-es/list-deprecated/ListItem.js.map +1 -1
  62. package/dist-es/rating/Rating.css.js +1 -1
  63. package/dist-es/rating/Rating.js +8 -7
  64. package/dist-es/rating/Rating.js.map +1 -1
  65. package/dist-es/tabs/drag-drop/useDragDropNaturalMovement.js +1 -1
  66. package/dist-es/tabs-next/TabListNext.js +1 -1
  67. package/dist-es/tabs-next/TabListNext.js.map +1 -1
  68. package/dist-es/tokenized-input/TokenizedInputBase.js +1 -1
  69. package/dist-es/tokenized-input-next/TokenizedInputNext.js +2 -2
  70. package/dist-es/tokenized-input-next/TokenizedInputNext.js.map +1 -1
  71. package/dist-es/tree/Tree.css.js +1 -1
  72. package/dist-es/tree/Tree.js +275 -208
  73. package/dist-es/tree/Tree.js.map +1 -1
  74. package/dist-es/tree/TreeContext.js +26 -0
  75. package/dist-es/tree/TreeContext.js.map +1 -0
  76. package/dist-es/tree/TreeNode.css.js +1 -1
  77. package/dist-es/tree/TreeNode.js +87 -43
  78. package/dist-es/tree/TreeNode.js.map +1 -1
  79. package/dist-es/tree/TreeNodeExpansionIcon.css.js +4 -0
  80. package/dist-es/tree/TreeNodeExpansionIcon.css.js.map +1 -0
  81. package/dist-es/tree/TreeNodeExpansionIcon.js +60 -0
  82. package/dist-es/tree/TreeNodeExpansionIcon.js.map +1 -0
  83. package/dist-es/tree/TreeNodeLabel.css.js +4 -0
  84. package/dist-es/tree/TreeNodeLabel.css.js.map +1 -0
  85. package/dist-es/tree/TreeNodeLabel.js +24 -0
  86. package/dist-es/tree/TreeNodeLabel.js.map +1 -0
  87. package/dist-es/tree/TreeNodeTrigger.css.js +4 -0
  88. package/dist-es/tree/TreeNodeTrigger.css.js.map +1 -0
  89. package/dist-es/tree/TreeNodeTrigger.js +150 -0
  90. package/dist-es/tree/TreeNodeTrigger.js.map +1 -0
  91. package/dist-es/tree/useTree.js +306 -134
  92. package/dist-es/tree/useTree.js.map +1 -1
  93. package/dist-types/date-input/DateInputRange.d.ts +1 -1
  94. package/dist-types/date-input/DateInputSingle.d.ts +1 -1
  95. package/dist-types/index.d.ts +0 -1
  96. package/dist-types/list-deprecated/ListItem.d.ts +1 -0
  97. package/dist-types/rating/Rating.d.ts +5 -6
  98. package/dist-types/tokenized-input/internal/InputPill.d.ts +1 -1
  99. package/dist-types/tokenized-input-next/internal/InputPill.d.ts +1 -1
  100. package/dist-types/tree/Tree.d.ts +36 -3
  101. package/dist-types/tree/TreeContext.d.ts +71 -0
  102. package/dist-types/tree/TreeNode.d.ts +23 -10
  103. package/dist-types/tree/TreeNodeExpansionIcon.d.ts +4 -0
  104. package/dist-types/tree/TreeNodeLabel.d.ts +4 -0
  105. package/dist-types/tree/TreeNodeTrigger.d.ts +8 -0
  106. package/dist-types/tree/index.d.ts +3 -0
  107. package/dist-types/tree/useTree.d.ts +79 -3
  108. package/package.json +3 -3
  109. package/dist-cjs/common-hooks/calcPreferredHeight.js +0 -27
  110. package/dist-cjs/common-hooks/calcPreferredHeight.js.map +0 -1
  111. package/dist-cjs/common-hooks/useAutoSizer.js +0 -33
  112. package/dist-cjs/common-hooks/useAutoSizer.js.map +0 -1
  113. package/dist-cjs/kbd/Kbd.css.js +0 -6
  114. package/dist-cjs/kbd/Kbd.css.js.map +0 -1
  115. package/dist-cjs/kbd/Kbd.js +0 -34
  116. package/dist-cjs/kbd/Kbd.js.map +0 -1
  117. package/dist-cjs/tree/use-tree-keyboard-navigation.js +0 -51
  118. package/dist-cjs/tree/use-tree-keyboard-navigation.js.map +0 -1
  119. package/dist-es/common-hooks/calcPreferredHeight.js +0 -25
  120. package/dist-es/common-hooks/calcPreferredHeight.js.map +0 -1
  121. package/dist-es/common-hooks/useAutoSizer.js +0 -31
  122. package/dist-es/common-hooks/useAutoSizer.js.map +0 -1
  123. package/dist-es/kbd/Kbd.css.js +0 -4
  124. package/dist-es/kbd/Kbd.css.js.map +0 -1
  125. package/dist-es/kbd/Kbd.js +0 -32
  126. package/dist-es/kbd/Kbd.js.map +0 -1
  127. package/dist-es/tree/use-tree-keyboard-navigation.js +0 -48
  128. package/dist-es/tree/use-tree-keyboard-navigation.js.map +0 -1
  129. package/dist-types/kbd/Kbd.d.ts +0 -8
  130. package/dist-types/kbd/index.d.ts +0 -1
  131. package/dist-types/tree/treeTypes.d.ts +0 -42
  132. package/dist-types/tree/use-tree-keyboard-navigation.d.ts +0 -15
@@ -1,228 +1,295 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { forwardRef, useRef, useCallback, isValidElement, createElement } from 'react';
3
- import { makePrefixer, useIdMemo, useForkRef } from '@salt-ds/core';
2
+ import { makePrefixer, useForkRef } from '@salt-ds/core';
4
3
  import { useComponentCssInjection } from '@salt-ds/styles';
5
4
  import { useWindow } from '@salt-ds/window';
6
5
  import { clsx } from 'clsx';
7
- import { calcPreferredHeight } from '../common-hooks/calcPreferredHeight.js';
8
- import '../common-hooks/collectionProvider.js';
9
- import '../common-hooks/keyUtils.js';
10
- import { closestListItemIndex } from '../common-hooks/list-dom-utils.js';
11
- import { useAutoSizer } from '../common-hooks/useAutoSizer.js';
12
- import { useCollectionItems } from '../common-hooks/useCollectionItems.js';
13
- import { GROUP_SELECTION_NONE } from '../common-hooks/useSelection.js';
14
- import '../responsive/useResizeObserver.js';
15
- import { isSelected } from '../common-hooks/utils/isSelected.js';
6
+ import { forwardRef, useRef, useEffect } from 'react';
16
7
  import css_248z from './Tree.css.js';
17
- import { TreeNode } from './TreeNode.js';
8
+ import { TreeProvider } from './TreeContext.js';
18
9
  import { useTree } from './useTree.js';
19
10
 
20
11
  const withBaseName = makePrefixer("saltTree");
21
- const Tree = forwardRef(function Tree2({
22
- className,
23
- defaultSelected,
24
- disabled,
25
- groupSelection = GROUP_SELECTION_NONE,
26
- height,
27
- id: idProp,
28
- onHighlight,
29
- onToggle,
30
- onSelect,
31
- onSelectionChange,
32
- revealSelected,
33
- selected: selectedProp,
34
- selectionStrategy,
35
- source,
36
- style: styleProp,
37
- width,
38
- ...htmlAttributes
39
- }, forwardedRef) {
40
- const targetWindow = useWindow();
41
- useComponentCssInjection({
42
- testId: "salt-tree",
43
- css: css_248z,
44
- window: targetWindow
45
- });
46
- const id = useIdMemo(idProp);
47
- const rootRef = useRef(null);
48
- const contentRef = useRef(null);
49
- const collectionHook = useCollectionItems({
50
- id,
51
- source,
52
- options: {
53
- noChildrenLabel: "No children available"}
54
- });
55
- const preferredHeight = height ?? calcPreferredHeight({
56
- displayedItemCount: 10,
57
- itemCount: collectionHook.data.length,
58
- itemHeight: 36
59
- // getItemHeight,
60
- // itemGapSize,
61
- });
62
- const autoSize = useAutoSizer({
63
- containerRef: rootRef,
64
- responsive: width === void 0 || height === void 0,
65
- height: preferredHeight,
66
- width
67
- });
68
- const handleSelect = useCallback(
69
- (evt, selectedItem) => {
70
- if (onSelect) {
71
- if (isValidElement(selectedItem.value)) {
72
- onSelect(evt, selectedItem.label);
73
- } else if (selectedItem.value !== null) {
74
- onSelect(evt, selectedItem.value);
12
+ const Tree = forwardRef(
13
+ function Tree2(props, ref) {
14
+ const {
15
+ children,
16
+ className,
17
+ defaultExpanded,
18
+ expanded,
19
+ onExpandedChange,
20
+ defaultSelected,
21
+ selected,
22
+ onSelectionChange,
23
+ multiselect = false,
24
+ disabled = false,
25
+ onKeyDown,
26
+ onBlur,
27
+ ...rest
28
+ } = props;
29
+ const targetWindow = useWindow();
30
+ useComponentCssInjection({
31
+ testId: "salt-tree",
32
+ css: css_248z,
33
+ window: targetWindow
34
+ });
35
+ const treeState = useTree({
36
+ defaultExpanded,
37
+ expanded,
38
+ onExpandedChange,
39
+ defaultSelected,
40
+ selected,
41
+ onSelectionChange,
42
+ multiselect,
43
+ disabled,
44
+ children
45
+ });
46
+ const {
47
+ activeNode,
48
+ setActiveNode,
49
+ expandedArray,
50
+ setExpandedArray,
51
+ expandedState,
52
+ toggleExpanded,
53
+ select,
54
+ selectedSet,
55
+ setSelectedState,
56
+ visibleNodes,
57
+ getNodeMeta,
58
+ getElement,
59
+ getParent,
60
+ getChildren,
61
+ treeModel,
62
+ disabledIdsSet
63
+ } = treeState;
64
+ const lastKeypressRef = useRef("");
65
+ const keypressTimeoutRef = useRef(
66
+ null
67
+ );
68
+ const treeRef = useRef(null);
69
+ useEffect(() => {
70
+ return () => {
71
+ if (keypressTimeoutRef.current) {
72
+ clearTimeout(keypressTimeoutRef.current);
75
73
  }
74
+ };
75
+ }, []);
76
+ const handleBlur = (event) => {
77
+ var _a;
78
+ onBlur == null ? void 0 : onBlur(event);
79
+ const relatedTarget = event.relatedTarget;
80
+ if (!((_a = treeRef.current) == null ? void 0 : _a.contains(relatedTarget))) {
81
+ setActiveNode(void 0);
76
82
  }
77
- },
78
- [onSelect]
79
- );
80
- const handleSelectionChange = useCallback(
81
- (evt, selected2) => {
82
- if (onSelectionChange) {
83
- onSelectionChange(
84
- evt,
85
- Array.isArray(selected2) ? selected2.map((s) => s.value) : selected2 && selected2.value
86
- );
83
+ };
84
+ const focusNode = (value) => {
85
+ const element = getElement(value);
86
+ if (!element) {
87
+ return "missing";
87
88
  }
88
- },
89
- [onSelectionChange]
90
- );
91
- const {
92
- focusVisible,
93
- highlightedIdx,
94
- highlightItemAtIndex,
95
- listHandlers,
96
- listProps,
97
- listItemHandlers,
98
- selected
99
- } = useTree({
100
- collectionHook,
101
- containerRef: rootRef,
102
- contentRef,
103
- // Note this isn't enough for a Tree, because of nested structure
104
- defaultSelected: collectionHook.itemToCollectionItem(defaultSelected),
105
- disabled,
106
- onHighlight,
107
- onSelect: handleSelect,
108
- onSelectionChange: handleSelectionChange,
109
- onToggle,
110
- selected: collectionHook.itemToCollectionItem(selectedProp),
111
- selectionStrategy
112
- });
113
- const defaultItemHandlers = {
114
- onMouseEnter: (evt) => {
115
- const idx = closestListItemIndex(evt.target);
116
- if (idx != null) {
117
- highlightItemAtIndex(idx);
89
+ const activeEl = targetWindow == null ? void 0 : targetWindow.document.activeElement;
90
+ if (activeEl === element) {
91
+ return "already-focused";
118
92
  }
119
- }
120
- };
121
- const propsCommonToAllListItems = {
122
- ...defaultItemHandlers,
123
- ...listItemHandlers,
124
- isLeaf: true,
125
- role: "treeitem"
126
- };
127
- function addLeafNode(list, item, idx) {
128
- const itemProps = {
129
- "aria-disabled": disabled || item.disabled,
130
- "aria-level": item.level,
131
- "data-idx": idx.value,
132
- description: item.description,
133
- id: item.id,
134
- key: item.id,
135
- highlighted: idx.value === highlightedIdx || void 0,
136
- selected: isSelected(selected, item),
137
- className: clsx({
138
- focusVisible: focusVisible === idx.value
139
- })
93
+ element.focus();
94
+ element.scrollIntoView({ block: "nearest", inline: "nearest" });
95
+ return "focused";
140
96
  };
141
- list.push(
142
- /* @__PURE__ */ jsx(
143
- TreeNode,
144
- {
145
- ...propsCommonToAllListItems,
146
- ...itemProps,
147
- label: item.label
97
+ const handleKeyDown = (event) => {
98
+ var _a, _b;
99
+ onKeyDown == null ? void 0 : onKeyDown(event);
100
+ if (disabled) return;
101
+ if (visibleNodes.length === 0) return;
102
+ const currentIndex = activeNode ? visibleNodes.indexOf(activeNode) : -1;
103
+ let newActiveNode;
104
+ let handled = false;
105
+ switch (event.key) {
106
+ case "ArrowDown": {
107
+ handled = true;
108
+ const nextIndex = currentIndex + 1;
109
+ if (nextIndex < visibleNodes.length) {
110
+ newActiveNode = visibleNodes[nextIndex];
111
+ }
112
+ break;
148
113
  }
149
- )
150
- );
151
- idx.value += 1;
152
- }
153
- function addGroupNode(list, items, idx, id2, title) {
154
- const { value: i } = idx;
155
- const item = items[i];
156
- idx.value += 1;
157
- list.push(
158
- /* @__PURE__ */ createElement(
159
- TreeNode,
160
- {
161
- ...defaultItemHandlers,
162
- ...listItemHandlers,
163
- "aria-disabled": disabled || item.disabled,
164
- "aria-expanded": item.expanded,
165
- "aria-level": item.level,
166
- className: clsx({
167
- focusVisible: focusVisible === i,
168
- [withBaseName("toggle")]: true
169
- }),
170
- "data-idx": i,
171
- "data-selectable": true,
172
- description: item.description,
173
- highlighted: i === highlightedIdx,
174
- id: id2,
175
- key: `header-${i}`,
176
- label: title,
177
- selected: isSelected(selected, item)
178
- },
179
- item.expanded ? /* @__PURE__ */ jsx("ul", { className: withBaseName("child-nodes"), role: "group", children: renderItems(items, idx, (item.level ?? 0) + 1) }) : null
180
- )
181
- );
182
- }
183
- const renderItems = (items, idx = { value: 0 }, level = 1) => {
184
- const listItems = [];
185
- while (idx.value < items.length) {
186
- const item = items[idx.value];
187
- if (item.level != null && item.level < level) {
188
- break;
114
+ case "ArrowUp": {
115
+ handled = true;
116
+ const prevIndex = currentIndex - 1;
117
+ if (prevIndex >= 0) {
118
+ newActiveNode = visibleNodes[prevIndex];
119
+ }
120
+ break;
121
+ }
122
+ case "ArrowRight": {
123
+ handled = true;
124
+ if (activeNode) {
125
+ const nodeMeta = getNodeMeta(activeNode);
126
+ const isDisabled = disabledIdsSet.has(activeNode);
127
+ if (!isDisabled && (nodeMeta == null ? void 0 : nodeMeta.hasChildren)) {
128
+ if (!expandedState.has(activeNode)) {
129
+ toggleExpanded(event, activeNode);
130
+ } else {
131
+ const firstChild = visibleNodes.find(
132
+ (visibleNode) => getParent(visibleNode) === activeNode
133
+ );
134
+ if (firstChild) {
135
+ newActiveNode = firstChild;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ break;
141
+ }
142
+ case "ArrowLeft": {
143
+ handled = true;
144
+ if (activeNode) {
145
+ const isDisabled = disabledIdsSet.has(activeNode);
146
+ if (!isDisabled) {
147
+ if (expandedState.has(activeNode)) {
148
+ toggleExpanded(event, activeNode);
149
+ } else {
150
+ const parent = getParent(activeNode);
151
+ if (parent) {
152
+ newActiveNode = parent;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ break;
158
+ }
159
+ case "Home": {
160
+ handled = true;
161
+ newActiveNode = visibleNodes[0];
162
+ break;
163
+ }
164
+ case "End": {
165
+ handled = true;
166
+ newActiveNode = visibleNodes[visibleNodes.length - 1];
167
+ break;
168
+ }
169
+ case "Enter": {
170
+ handled = true;
171
+ if (activeNode) {
172
+ select(event, activeNode);
173
+ }
174
+ break;
175
+ }
176
+ case " ": {
177
+ handled = true;
178
+ if (activeNode) {
179
+ select(event, activeNode);
180
+ }
181
+ break;
182
+ }
183
+ case "*": {
184
+ handled = true;
185
+ if (activeNode) {
186
+ const parent = getParent(activeNode);
187
+ const siblings = parent ? getChildren(parent) : treeModel.rootValues;
188
+ const toExpand = siblings.filter((sibling) => {
189
+ const siblingMeta = getNodeMeta(sibling);
190
+ return (siblingMeta == null ? void 0 : siblingMeta.hasChildren) && !expandedState.has(sibling) && !disabledIdsSet.has(sibling);
191
+ });
192
+ if (toExpand.length > 0) {
193
+ const newExpanded = [...expandedArray, ...toExpand];
194
+ setExpandedArray(newExpanded);
195
+ onExpandedChange == null ? void 0 : onExpandedChange(event, newExpanded);
196
+ }
197
+ }
198
+ break;
199
+ }
200
+ default: {
201
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
202
+ handled = true;
203
+ if (keypressTimeoutRef.current) {
204
+ clearTimeout(keypressTimeoutRef.current);
205
+ }
206
+ lastKeypressRef.current += event.key.toLowerCase();
207
+ const searchString = lastKeypressRef.current;
208
+ keypressTimeoutRef.current = setTimeout(() => {
209
+ lastKeypressRef.current = "";
210
+ }, 500);
211
+ const currentIndex2 = activeNode ? visibleNodes.indexOf(activeNode) : -1;
212
+ let found = false;
213
+ for (let i = currentIndex2 + 1; i < visibleNodes.length; i++) {
214
+ const element = getElement(visibleNodes[i]);
215
+ if ((_a = element == null ? void 0 : element.textContent) == null ? void 0 : _a.toLowerCase().startsWith(searchString)) {
216
+ newActiveNode = visibleNodes[i];
217
+ found = true;
218
+ break;
219
+ }
220
+ }
221
+ if (!found) {
222
+ for (let i = 0; i <= currentIndex2; i++) {
223
+ const element = getElement(visibleNodes[i]);
224
+ if ((_b = element == null ? void 0 : element.textContent) == null ? void 0 : _b.toLowerCase().startsWith(searchString)) {
225
+ newActiveNode = visibleNodes[i];
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ break;
232
+ }
233
+ }
234
+ if ((event.ctrlKey || event.metaKey) && event.key === "a" && multiselect) {
235
+ handled = true;
236
+ event.preventDefault();
237
+ const allVisibleValues = visibleNodes.filter(
238
+ (visibleNode) => !disabledIdsSet.has(visibleNode)
239
+ );
240
+ const allSelected = allVisibleValues.every(
241
+ (visible) => selectedSet.has(visible)
242
+ );
243
+ const newSelected = allSelected ? [] : allVisibleValues;
244
+ setSelectedState(newSelected);
245
+ onSelectionChange == null ? void 0 : onSelectionChange(event, newSelected);
246
+ return;
189
247
  }
190
- if (item.childNodes != null && item.id != null && item.label != null) {
191
- addGroupNode(listItems, items, idx, item.id, item.label);
192
- } else {
193
- addLeafNode(listItems, item, idx);
248
+ if (event.shiftKey && (event.key === "ArrowUp" || event.key === "ArrowDown") && multiselect) {
249
+ handled = true;
250
+ const isDown = event.key === "ArrowDown";
251
+ const currentIndex2 = activeNode ? visibleNodes.indexOf(activeNode) : -1;
252
+ const nextIndex = isDown ? currentIndex2 + 1 : currentIndex2 - 1;
253
+ if (nextIndex >= 0 && nextIndex < visibleNodes.length) {
254
+ const nextValue = visibleNodes[nextIndex];
255
+ if (!disabledIdsSet.has(nextValue)) {
256
+ select(event, nextValue);
257
+ newActiveNode = nextValue;
258
+ }
259
+ }
260
+ }
261
+ if (handled) {
262
+ event.preventDefault();
263
+ event.stopPropagation();
194
264
  }
195
- }
196
- return listItems;
197
- };
198
- const renderContent = () => {
199
- if (collectionHook.data.length) {
200
- return renderItems(collectionHook.data);
201
- }
202
- };
203
- return /* @__PURE__ */ jsx(
204
- "div",
205
- {
206
- ...htmlAttributes,
207
- ...listHandlers,
208
- ...listProps,
209
- className: clsx(withBaseName(), className),
210
- id: `Tree-${id}`,
211
- ref: useForkRef(rootRef, forwardedRef),
212
- style: { ...styleProp, ...autoSize },
213
- tabIndex: 0,
214
- children: /* @__PURE__ */ jsx(
215
- "ul",
216
- {
217
- className: withBaseName("scrollingContentContainer"),
218
- ref: contentRef,
219
- role: "tree",
220
- children: renderContent()
265
+ if (newActiveNode !== void 0) {
266
+ const focusResult = focusNode(newActiveNode);
267
+ if (focusResult !== "focused") {
268
+ setActiveNode(newActiveNode);
221
269
  }
222
- )
223
- }
224
- );
225
- });
270
+ }
271
+ };
272
+ const handleRef = useForkRef(treeRef, ref);
273
+ return /* @__PURE__ */ jsx(TreeProvider, { value: treeState, children: /* @__PURE__ */ jsx(
274
+ "ul",
275
+ {
276
+ ref: handleRef,
277
+ role: "tree",
278
+ "aria-multiselectable": multiselect ? true : void 0,
279
+ "aria-disabled": disabled || void 0,
280
+ className: clsx(
281
+ withBaseName(),
282
+ { [withBaseName("disabled")]: disabled },
283
+ className
284
+ ),
285
+ onKeyDown: handleKeyDown,
286
+ onBlur: handleBlur,
287
+ ...rest,
288
+ children
289
+ }
290
+ ) });
291
+ }
292
+ );
226
293
 
227
294
  export { Tree };
228
295
  //# sourceMappingURL=Tree.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tree.js","sources":["../src/tree/Tree.tsx"],"sourcesContent":["import { makePrefixer, useForkRef, useIdMemo } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n type ForwardedRef,\n forwardRef,\n isValidElement,\n type MouseEvent,\n type ReactElement,\n useCallback,\n useRef,\n} from \"react\";\nimport {\n type CollectionIndexer,\n type CollectionItem,\n calcPreferredHeight,\n closestListItemIndex,\n GROUP_SELECTION_NONE,\n isSelected,\n type SelectHandler,\n type SelectionChangeHandler,\n type SelectionStrategy,\n type SingleSelectionStrategy,\n useAutoSizer,\n useCollectionItems,\n} from \"../common-hooks\";\nimport treeCss from \"./Tree.css\";\nimport { TreeNode } from \"./TreeNode\";\nimport type { TreeProps } from \"./treeTypes\";\nimport { useTree } from \"./useTree\";\n\nconst withBaseName = makePrefixer(\"saltTree\");\n\nconst getSelectedItemsFromSource = (\n source: any[],\n selectionStrategy: SelectionStrategy,\n result: any[] = [],\n) => {\n const isSingleSelection =\n selectionStrategy === \"default\" || selectionStrategy === \"deselectable\";\n for (const item of source) {\n if (item.selected === true) {\n result.push(item);\n if (isSingleSelection) {\n break;\n }\n }\n if (item.childNodes) {\n getSelectedItemsFromSource(item.childNodes, selectionStrategy, result);\n if (isSingleSelection && result.length === 1) {\n break;\n }\n }\n }\n\n return isSingleSelection ? result[0] : result.length > 0 ? result : undefined;\n};\n\nexport const Tree = forwardRef(function Tree<\n Item,\n Selection extends SelectionStrategy = \"deselectable\",\n>(\n {\n className,\n defaultSelected,\n disabled,\n groupSelection = GROUP_SELECTION_NONE,\n height,\n id: idProp,\n onHighlight,\n onToggle,\n onSelect,\n onSelectionChange,\n revealSelected,\n selected: selectedProp,\n selectionStrategy,\n source,\n style: styleProp,\n width,\n ...htmlAttributes\n }: TreeProps<Item, Selection>,\n forwardedRef?: ForwardedRef<HTMLDivElement>,\n) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-tree\",\n css: treeCss,\n window: targetWindow,\n });\n\n const id = useIdMemo(idProp);\n const rootRef = useRef(null);\n const contentRef = useRef(null);\n\n const collectionHook = useCollectionItems<Item>({\n id,\n source,\n options: {\n noChildrenLabel: \"No children available\",\n revealSelected: revealSelected\n ? Boolean(selectedProp) || Boolean(defaultSelected) || false\n : undefined,\n },\n });\n\n //------------- from original List\n const preferredHeight =\n height ??\n calcPreferredHeight({\n displayedItemCount: 10,\n itemCount: collectionHook.data.length,\n itemHeight: 36,\n // getItemHeight,\n // itemGapSize,\n });\n\n const autoSize = useAutoSizer({\n containerRef: rootRef,\n responsive: width === undefined || height === undefined,\n height: preferredHeight,\n width,\n });\n //---------------\n\n const handleSelect = useCallback<SelectHandler<CollectionItem<Item>>>(\n (evt, selectedItem) => {\n if (onSelect) {\n if (isValidElement(selectedItem.value)) {\n onSelect(evt, selectedItem.label as any);\n } else if (selectedItem.value !== null) {\n onSelect(evt, selectedItem.value);\n }\n }\n },\n [onSelect],\n );\n\n const handleSelectionChange = useCallback<\n SelectionChangeHandler<CollectionItem<Item>, Selection>\n >(\n (evt, selected) => {\n type returnType = Selection extends SingleSelectionStrategy\n ? Item | null\n : Item[];\n if (onSelectionChange) {\n onSelectionChange(\n evt,\n Array.isArray(selected)\n ? (selected.map((s) => s.value) as returnType)\n : selected && (selected.value as returnType),\n );\n }\n },\n [onSelectionChange],\n );\n\n // const getSelected = (\n // sel: Item | null | Item[]\n // ):\n // | undefined\n // | (Selection extends SingleSelectionStrategy\n // ? CollectionItem<Item> | null\n // : CollectionItem<Item>[]) => {\n // if (sel !== undefined) {\n // return collectionHook.itemToCollectionItem<Selection, typeof sel>(sel);\n // } else if (Array.isArray(source)) {\n // const selected = getSelectedItemsFromSource(\n // source,\n // selectionStrategy ?? \"default\"\n // );\n // return Array.isArray(selected)\n // ? collectionHook.itemToCollectionItem(selected)\n // : selected\n // ? collectionHook.toCollectionItem(selected)\n // : undefined;\n // }\n // };\n\n const {\n focusVisible,\n highlightedIdx,\n highlightItemAtIndex,\n listHandlers,\n listProps,\n listItemHandlers,\n selected,\n } = useTree<Item, Selection>({\n collectionHook,\n containerRef: rootRef,\n contentRef,\n // Note this isn't enough for a Tree, because of nested structure\n defaultSelected: collectionHook.itemToCollectionItem<\n Selection,\n typeof defaultSelected\n >(defaultSelected),\n disabled,\n groupSelection,\n onHighlight,\n onSelect: handleSelect,\n onSelectionChange: handleSelectionChange,\n onToggle,\n selected: collectionHook.itemToCollectionItem<\n Selection,\n typeof selectedProp\n >(selectedProp),\n selectionStrategy,\n });\n\n // TODO move into useTree (see useList)\n const defaultItemHandlers = {\n onMouseEnter: (evt: MouseEvent) => {\n // if (!isScrolling.current) {\n const idx = closestListItemIndex(evt.target as HTMLElement);\n if (idx != null) {\n highlightItemAtIndex(idx);\n }\n // onMouseEnterListItem && onMouseEnterListItem(evt, idx);\n // }\n },\n };\n\n const propsCommonToAllListItems = {\n ...defaultItemHandlers,\n ...listItemHandlers,\n isLeaf: true,\n role: \"treeitem\",\n };\n // const allowGroupSelect = groupSelectionEnabled(groupSelection);\n const allowGroupSelect = false;\n\n /**\n * Add a ListItem from source item\n */\n function addLeafNode(\n list: ReactElement[],\n item: CollectionItem<Item>,\n idx: CollectionIndexer,\n ) {\n const itemProps = {\n \"aria-disabled\": disabled || item.disabled,\n \"aria-level\": item.level,\n \"data-idx\": idx.value,\n description: item.description,\n id: item.id,\n key: item.id,\n highlighted: idx.value === highlightedIdx || undefined,\n selected: isSelected<Item>(selected, item),\n className: clsx({\n focusVisible: focusVisible === idx.value,\n }),\n };\n\n list.push(\n <TreeNode\n {...propsCommonToAllListItems}\n {...itemProps}\n label={item.label}\n >\n {/* {item.icon ? <span className={`${classBase}Node-icon`} /> : null} */}\n </TreeNode>,\n );\n idx.value += 1;\n }\n\n function addGroupNode(\n list: ReactElement[],\n items: CollectionItem<Item>[],\n idx: CollectionIndexer,\n id: string,\n title: string,\n ) {\n const { value: i } = idx;\n const item = items[i];\n idx.value += 1;\n list.push(\n <TreeNode\n {...defaultItemHandlers}\n {...listItemHandlers}\n aria-disabled={disabled || item.disabled}\n aria-expanded={item.expanded}\n aria-level={item.level}\n className={clsx({\n focusVisible: focusVisible === i,\n [withBaseName(\"toggle\")]: !allowGroupSelect,\n })}\n // data-icon={child.icon}\n data-idx={i}\n data-selectable\n description={item.description}\n highlighted={i === highlightedIdx}\n id={id}\n key={`header-${i}`}\n label={title}\n selected={isSelected<Item>(selected, item)}\n >\n {item.expanded ? (\n <ul className={withBaseName(\"child-nodes\")} role=\"group\">\n {renderItems(items, idx, (item.level ?? 0) + 1)}\n </ul>\n ) : null}\n </TreeNode>,\n );\n }\n\n const renderItems = (\n items: CollectionItem<Item>[],\n idx: CollectionIndexer = { value: 0 },\n level = 1,\n ): ReactElement[] => {\n const listItems: ReactElement[] = [];\n while (idx.value < items.length) {\n const item = items[idx.value];\n if (item.level != null && item.level < level) {\n break;\n }\n if (item.childNodes != null && item.id != null && item.label != null) {\n addGroupNode(listItems, items, idx, item.id, item.label);\n } else {\n addLeafNode(listItems, item, idx);\n }\n }\n\n return listItems;\n };\n\n function renderEmpty() {\n // if (emptyMessage || showEmptyMessage) {\n // return (\n // <span className={withBaseName(\"empty-message\")}>\n // {emptyMessage ?? defaultEmptyMessage}\n // </span>\n // );\n // } else {\n return null;\n // }\n }\n\n const renderContent = () => {\n if (collectionHook.data.length) {\n return renderItems(collectionHook.data);\n }\n renderEmpty();\n };\n\n return (\n <div\n {...htmlAttributes}\n {...listHandlers}\n {...listProps}\n className={clsx(withBaseName(), className)}\n id={`Tree-${id}`}\n ref={useForkRef(rootRef, forwardedRef)}\n style={{ ...styleProp, ...autoSize }}\n tabIndex={0}\n >\n <ul\n className={withBaseName(\"scrollingContentContainer\")}\n ref={contentRef}\n role=\"tree\"\n // style={{ height: contentHeight }}\n >\n {renderContent()}\n </ul>\n </div>\n );\n});\n"],"names":["Tree","treeCss","selected","id"],"mappings":"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"}
1
+ {"version":3,"file":"Tree.js","sources":["../src/tree/Tree.tsx"],"sourcesContent":["import { makePrefixer, useForkRef } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n type ComponentPropsWithoutRef,\n type FocusEvent,\n forwardRef,\n type KeyboardEvent,\n type SyntheticEvent,\n useEffect,\n useRef,\n} from \"react\";\nimport treeCss from \"./Tree.css\";\nimport { TreeProvider } from \"./TreeContext\";\nimport { useTree } from \"./useTree\";\n\nexport interface TreeProps extends ComponentPropsWithoutRef<\"ul\"> {\n /**\n * Default expanded nodes (uncontrolled)\n */\n defaultExpanded?: string[];\n /**\n * Expanded nodes (controlled)\n */\n expanded?: string[];\n /**\n * Callback on expanded nodes change\n */\n onExpandedChange?: (event: SyntheticEvent, expanded: string[]) => void;\n /**\n * Default selected nodes (uncontrolled)\n */\n defaultSelected?: string[];\n /**\n * Selected nodes\n */\n selected?: string[];\n /**\n * Callback on selected nodes change\n */\n onSelectionChange?: (event: SyntheticEvent, selected: string[]) => void;\n /**\n * Sets multiselect mode with checkboxes and allows for multiple node selection\n */\n multiselect?: boolean;\n /**\n * Sets tree to disabled state, preventing all interaction\n */\n disabled?: boolean;\n}\n\nconst withBaseName = makePrefixer(\"saltTree\");\n\nexport const Tree = forwardRef<HTMLUListElement, TreeProps>(\n function Tree(props, ref) {\n const {\n children,\n className,\n defaultExpanded,\n expanded,\n onExpandedChange,\n defaultSelected,\n selected,\n onSelectionChange,\n multiselect = false,\n disabled = false,\n onKeyDown,\n onBlur,\n ...rest\n } = props;\n\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-tree\",\n css: treeCss,\n window: targetWindow,\n });\n\n const treeState = useTree({\n defaultExpanded,\n expanded,\n onExpandedChange,\n defaultSelected,\n selected,\n onSelectionChange,\n multiselect,\n disabled,\n children,\n });\n\n const {\n activeNode,\n setActiveNode,\n expandedArray,\n setExpandedArray,\n expandedState,\n toggleExpanded,\n select,\n selectedSet,\n setSelectedState,\n visibleNodes,\n getNodeMeta,\n getElement,\n getParent,\n getChildren,\n treeModel,\n disabledIdsSet,\n } = treeState;\n\n const lastKeypressRef = useRef<string>(\"\");\n const keypressTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n const treeRef = useRef<HTMLUListElement>(null);\n\n useEffect(() => {\n return () => {\n if (keypressTimeoutRef.current) {\n clearTimeout(keypressTimeoutRef.current);\n }\n };\n }, []);\n\n const handleBlur = (event: FocusEvent<HTMLUListElement>) => {\n onBlur?.(event);\n const relatedTarget = event.relatedTarget as Node | null;\n if (!treeRef.current?.contains(relatedTarget)) {\n setActiveNode(undefined);\n }\n };\n\n const focusNode = (\n value: string,\n ): \"focused\" | \"already-focused\" | \"missing\" => {\n const element = getElement(value);\n if (!element) {\n return \"missing\";\n }\n\n const activeEl = targetWindow?.document.activeElement;\n if (activeEl === element) {\n return \"already-focused\";\n }\n\n element.focus();\n element.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });\n return \"focused\";\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLUListElement>) => {\n onKeyDown?.(event);\n\n if (disabled) return;\n\n if (visibleNodes.length === 0) return;\n\n const currentIndex = activeNode ? visibleNodes.indexOf(activeNode) : -1;\n\n let newActiveNode: string | undefined;\n let handled = false;\n\n switch (event.key) {\n case \"ArrowDown\": {\n handled = true;\n const nextIndex = currentIndex + 1;\n if (nextIndex < visibleNodes.length) {\n newActiveNode = visibleNodes[nextIndex];\n }\n break;\n }\n case \"ArrowUp\": {\n handled = true;\n const prevIndex = currentIndex - 1;\n if (prevIndex >= 0) {\n newActiveNode = visibleNodes[prevIndex];\n }\n break;\n }\n case \"ArrowRight\": {\n handled = true;\n if (activeNode) {\n const nodeMeta = getNodeMeta(activeNode);\n const isDisabled = disabledIdsSet.has(activeNode);\n if (!isDisabled && nodeMeta?.hasChildren) {\n if (!expandedState.has(activeNode)) {\n toggleExpanded(event, activeNode);\n } else {\n const firstChild = visibleNodes.find(\n (visibleNode) => getParent(visibleNode) === activeNode,\n );\n if (firstChild) {\n newActiveNode = firstChild;\n }\n }\n }\n }\n break;\n }\n case \"ArrowLeft\": {\n handled = true;\n if (activeNode) {\n const isDisabled = disabledIdsSet.has(activeNode);\n if (!isDisabled) {\n if (expandedState.has(activeNode)) {\n toggleExpanded(event, activeNode);\n } else {\n const parent = getParent(activeNode);\n if (parent) {\n newActiveNode = parent;\n }\n }\n }\n }\n break;\n }\n case \"Home\": {\n handled = true;\n newActiveNode = visibleNodes[0];\n break;\n }\n case \"End\": {\n handled = true;\n newActiveNode = visibleNodes[visibleNodes.length - 1];\n break;\n }\n case \"Enter\": {\n handled = true;\n if (activeNode) {\n select(event, activeNode);\n }\n break;\n }\n case \" \": {\n handled = true;\n if (activeNode) {\n select(event, activeNode);\n }\n break;\n }\n case \"*\": {\n handled = true;\n if (activeNode) {\n const parent = getParent(activeNode);\n // Get siblings: either children of parent, or root nodes if no parent\n const siblings = parent\n ? getChildren(parent)\n : treeModel.rootValues;\n\n const toExpand = siblings.filter((sibling) => {\n const siblingMeta = getNodeMeta(sibling);\n return (\n siblingMeta?.hasChildren &&\n !expandedState.has(sibling) &&\n !disabledIdsSet.has(sibling)\n );\n });\n\n if (toExpand.length > 0) {\n const newExpanded = [...expandedArray, ...toExpand];\n setExpandedArray(newExpanded);\n onExpandedChange?.(event, newExpanded);\n }\n }\n break;\n }\n default: {\n // Type-ahead\n if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey\n ) {\n handled = true;\n\n if (keypressTimeoutRef.current) {\n clearTimeout(keypressTimeoutRef.current);\n }\n\n lastKeypressRef.current += event.key.toLowerCase();\n const searchString = lastKeypressRef.current;\n\n keypressTimeoutRef.current = setTimeout(() => {\n lastKeypressRef.current = \"\";\n }, 500);\n\n const currentIndex = activeNode\n ? visibleNodes.indexOf(activeNode)\n : -1;\n let found = false;\n\n for (let i = currentIndex + 1; i < visibleNodes.length; i++) {\n const element = getElement(visibleNodes[i]);\n if (\n element?.textContent?.toLowerCase().startsWith(searchString)\n ) {\n newActiveNode = visibleNodes[i];\n found = true;\n break;\n }\n }\n\n if (!found) {\n for (let i = 0; i <= currentIndex; i++) {\n const element = getElement(visibleNodes[i]);\n if (\n element?.textContent?.toLowerCase().startsWith(searchString)\n ) {\n newActiveNode = visibleNodes[i];\n break;\n }\n }\n }\n }\n break;\n }\n }\n\n if (\n (event.ctrlKey || event.metaKey) &&\n event.key === \"a\" &&\n multiselect\n ) {\n handled = true;\n event.preventDefault();\n\n const allVisibleValues = visibleNodes.filter(\n (visibleNode) => !disabledIdsSet.has(visibleNode),\n );\n const allSelected = allVisibleValues.every((visible) =>\n selectedSet.has(visible),\n );\n\n const newSelected = allSelected ? [] : allVisibleValues;\n\n setSelectedState(newSelected);\n onSelectionChange?.(event, newSelected);\n return;\n }\n\n if (\n event.shiftKey &&\n (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") &&\n multiselect\n ) {\n handled = true;\n const isDown = event.key === \"ArrowDown\";\n const currentIndex = activeNode ? visibleNodes.indexOf(activeNode) : -1;\n const nextIndex = isDown ? currentIndex + 1 : currentIndex - 1;\n\n if (nextIndex >= 0 && nextIndex < visibleNodes.length) {\n const nextValue = visibleNodes[nextIndex];\n\n if (!disabledIdsSet.has(nextValue)) {\n select(event, nextValue);\n newActiveNode = nextValue;\n }\n }\n }\n\n if (handled) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n if (newActiveNode !== undefined) {\n const focusResult = focusNode(newActiveNode);\n if (focusResult !== \"focused\") {\n setActiveNode(newActiveNode);\n }\n }\n };\n\n const handleRef = useForkRef(treeRef, ref);\n\n return (\n <TreeProvider value={treeState}>\n <ul\n ref={handleRef}\n role=\"tree\"\n aria-multiselectable={multiselect ? true : undefined}\n aria-disabled={disabled || undefined}\n className={clsx(\n withBaseName(),\n { [withBaseName(\"disabled\")]: disabled },\n className,\n )}\n onKeyDown={handleKeyDown}\n onBlur={handleBlur}\n {...rest}\n >\n {children}\n </ul>\n </TreeProvider>\n );\n },\n);\n"],"names":["Tree","treeCss","currentIndex"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { createContext } from '@salt-ds/core';
2
+ import { useContext } from 'react';
3
+
4
+ const TreeContext = createContext(
5
+ "Tree Context",
6
+ void 0
7
+ );
8
+ const TreeProvider = TreeContext.Provider;
9
+ function useTreeContext() {
10
+ const context = useContext(TreeContext);
11
+ if (!context) {
12
+ throw new Error("useTreeContext must be used within a TreeProvider");
13
+ }
14
+ return context;
15
+ }
16
+ const TreeNodeContext = createContext(
17
+ "TreeNodeContext",
18
+ void 0
19
+ );
20
+ const TreeNodeProvider = TreeNodeContext.Provider;
21
+ function useTreeNodeContext() {
22
+ return useContext(TreeNodeContext);
23
+ }
24
+
25
+ export { TreeNodeProvider, TreeProvider, useTreeContext, useTreeNodeContext };
26
+ //# sourceMappingURL=TreeContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreeContext.js","sources":["../src/tree/TreeContext.ts"],"sourcesContent":["import { createContext } from \"@salt-ds/core\";\nimport {\n type Dispatch,\n type ReactNode,\n type SetStateAction,\n type SyntheticEvent,\n useContext,\n} from \"react\";\nimport type { TreeModel, TreeNodeMeta } from \"./useTree\";\n\nexport interface TreeContextValue {\n expandedState: Set<string>;\n /** Toggle a node expansion state */\n toggleExpanded: (event: SyntheticEvent, value: string) => void;\n\n /** Selected node values */\n selectedState: string[];\n /** Selected node values as Set for O(1) lookups */\n selectedSet: Set<string>;\n /** Set selected state directly */\n setSelectedState: Dispatch<SetStateAction<string[]>>;\n /** Select node */\n select: (event: SyntheticEvent, value: string) => void;\n\n /** Whether multiselect mode with checkboxes is enabled */\n multiselect: boolean;\n /** Disabled state of the tree */\n disabled: boolean;\n /** Set of disabled node IDs */\n disabledIdsSet: Set<string>;\n\n /** Tree model for traversal */\n treeModel: TreeModel;\n /** Get node metadata from tree model */\n getNodeMeta: (value: string) => TreeNodeMeta | undefined;\n /** Get parent of a node */\n getParent: (value: string) => string | undefined;\n /** Get children of a node */\n getChildren: (value: string) => string[];\n /** Get all descendants of a node */\n getDescendants: (value: string) => string[];\n /** Get all ancestors of a node */\n getAncestors: (value: string) => string[];\n /** Memoized visible (navigable) nodes in tree order */\n visibleNodes: string[];\n /** Memoized tabbable node ID for roving tabindex (computed once at tree level) */\n tabbableNodeId: string | undefined;\n /** Register a DOM element for focus management */\n registerElement: (value: string, element: HTMLElement) => () => void;\n /** Get DOM element for a node (if mounted) */\n getElement: (value: string) => HTMLElement | undefined;\n /** Active node value */\n activeNode: string | undefined;\n /** Set the active node */\n setActiveNode: Dispatch<SetStateAction<string | undefined>>;\n\n /** Set of indeterminate (partially selected) node IDs */\n indeterminateState: Set<string>;\n}\n\nconst TreeContext = createContext<TreeContextValue | undefined>(\n \"Tree Context\",\n undefined,\n);\n\nexport const TreeProvider = TreeContext.Provider;\n\nexport function useTreeContext(): TreeContextValue {\n const context = useContext(TreeContext);\n if (!context) {\n throw new Error(\"useTreeContext must be used within a TreeProvider\");\n }\n return context;\n}\n\nexport interface TreeNodeContextValue {\n /** Current node value */\n value: string;\n /** Current depth level */\n level: number;\n /** Whether node has children */\n hasChildren: boolean;\n /** Whether node is expanded */\n expanded: boolean;\n /** Whether node is disabled */\n disabled: boolean;\n /** Node id for the li element */\n id: string;\n /** Whether node is selected */\n selected: boolean;\n /** Whether node is in indeterminate state (partially selected children) */\n indeterminate: boolean;\n /** Child TreeNode elements to be rendered inside the group */\n nodeChildren: ReactNode;\n}\n\nconst TreeNodeContext = createContext<TreeNodeContextValue | undefined>(\n \"TreeNodeContext\",\n undefined,\n);\n\nexport const TreeNodeProvider = TreeNodeContext.Provider;\n\nexport function useTreeNodeContext(): TreeNodeContextValue | undefined {\n return useContext(TreeNodeContext);\n}\n"],"names":[],"mappings":";;;AA4DA,MAAM,WAAA,GAAc,aAAA;AAAA,EAClB,cAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,eAAe,WAAA,CAAY;AAEjC,SAAS,cAAA,GAAmC;AACjD,EAAA,MAAM,OAAA,GAAU,WAAW,WAAW,CAAA;AACtC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AACA,EAAA,OAAO,OAAA;AACT;AAuBA,MAAM,eAAA,GAAkB,aAAA;AAAA,EACtB,iBAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,mBAAmB,eAAA,CAAgB;AAEzC,SAAS,kBAAA,GAAuD;AACrE,EAAA,OAAO,WAAW,eAAe,CAAA;AACnC;;;;"}
@@ -1,4 +1,4 @@
1
- var css_248z = ".saltTreeNode {\n /* Color */\n --tree-item-text-color: var(--salt-content-primary-foreground);\n --tree-item-background: var(--salt-selectable-background);\n --tree-item-background-hover: var(--salt-selectable-background-hover);\n\n --tree-node-height: var(--saltTree-node-height, var(--salt-size-stackable));\n --tree-node-icon-size: 12px;\n --tree-node-icon-transform: none;\n --tree-node-paddingLeft: 6px;\n\n align-items: flex-start;\n min-height: var(--tree-node-height, auto);\n line-height: var(--salt-text-lineHeight);\n list-style: none;\n position: relative;\n text-align: var(--list-item-textAlign);\n}\n\n.saltTreeNode-highlighted {\n --tree-item-background: var(--salt-selectable-background-hover);\n}\n\n.saltTreeNode-item[aria-selected=\"true\"] {\n background: var(--salt-selectable-background-selected);\n color: var(--salt-content-primary-foreground);\n --saltIcon-color: var(--salt-content-primary-foreground);\n}\n\n.saltTreeNode[aria-expanded=\"true\"] {\n --tree-node-icon-transform: rotate(45deg) translate(1px, 1px);\n}\n\n.saltTreeNode-item {\n align-items: center;\n background: var(--tree-item-background);\n display: flex;\n height: var(--tree-node-height);\n padding-left: calc(var(--tree-node-paddingLeft) + var(--tree-node-indent));\n position: relative;\n}\n\n.saltTreeNode-toggle {\n display: inline-block;\n flex: 0 0 18px;\n height: var(--tree-node-icon-size);\n transform: var(--tree-node-icon-transform);\n transition: transform 0.1s ease;\n}\n\n.saltTreeNode-label {\n align-items: center;\n display: inline-flex;\n height: var(--tree-node-height);\n}\n.saltTreeNode-description {\n align-items: center;\n display: inline-flex;\n height: var(--tree-node-height);\n padding-left: var(--salt-size-unit);\n}\n\n/* Leaf node or the div child of a collapsible node */\n/* .saltTreeNode:not([aria-expanded=\"true\"]), */\n.saltTreeNode[aria-expanded] > .saltTreeNode-label {\n --checkbox-borderColor: black;\n --checkbox-borderWidth: 1px;\n --checkbox-tick: black;\n /* --list-svg-toggle: var(--list-svg-chevron-down); */\n --list-svg-toggle: var(--tree-node-collapse);\n\n color: var(--list-item-text-color);\n flex-wrap: nowrap;\n line-height: var(--tree-node-height);\n padding: var(--list-item-padding);\n position: relative;\n cursor: default;\n margin: 0;\n white-space: nowrap;\n}\n\n.saltTreeNode:not([aria-expanded]) {\n padding-left: calc(var(--tree-node-paddingLeft) + var(--tree-toggle-width) + var(--tree-node-indent));\n}\n\n.saltTreeNode[aria-expanded] > .saltTreeNode-label {\n padding-left: calc(var(--tree-node-paddingLeft) + var(--tree-toggle-width) + var(--tree-node-indent));\n}\n\n.saltTreeNode[aria-expanded] {\n flex-direction: column;\n}\n\n.saltTreeNode[aria-expanded] {\n flex-direction: column;\n height: auto;\n}\n\n.saltTreeNode > *[role=\"group\"] {\n padding-left: 0px;\n}\n\n[aria-level=\"2\"] {\n --tree-node-indent: 24px;\n}\n[aria-level=\"3\"] {\n --tree-node-indent: 36px;\n}\n[aria-level=\"4\"] {\n --tree-node-indent: 48px;\n}\n\n.saltTreeNode:not(.focusVisible):not([aria-expanded])[data-highlighted],\n.saltTreeNode:not(.focusVisible)[aria-expanded][data-highlighted] > div:first-child {\n background: var(--list-background-highlighted);\n}\n\n.saltTree-toggle {\n cursor: pointer;\n}\n\n.saltTreeNode[aria-selected=\"true\"] {\n --list-item-header-twisty-color: var(--list-item-selected-color);\n}\n\n.saltTreeNode:not(.focusVisible):focus {\n background: rgba(0, 0, 0, 0.1);\n}\n\n.saltTreeNode:not([aria-expanded]).focusVisible:before,\n.saltTreeNode[aria-expanded].focusVisible > div:first-child:before {\n content: \"\";\n position: absolute;\n top: 0px;\n left: var(--tree-offset-left-focusVisible, 0px);\n right: 0;\n bottom: 0px;\n border: dotted rgb(141, 154, 179) 2px; /* FIXME: Needs checking */\n background: var(--list-background-highlighted);\n}\n\n/* .saltTreeNode[aria-level='2'] {\n --tree-offset-left-focusVisible: -13px;\n }\n\n .saltTreeNode[aria-level='3'] {\n --tree-offset-left-focusVisible: -24px;\n }\n\n .saltTreeNode[aria-level='4'] {\n --tree-offset-left-focusVisible: -36px;\n } */\n\n.saltTreeNode[aria-expanded=\"false\"] > *:first-child:after {\n --list-svg-toggle: var(--tree-node-expand);\n}\n\n.saltTreeNode[aria-expanded=\"true\"] > *:first-child:after {\n transform: var(--tree-node-expanded-transform);\n}\n\n/* Selection */\n\n.saltTree:not(.checkbox-only) .saltTreeNode:not([aria-expanded])[aria-selected=\"true\"],\n.saltTree:not(.checkbox-only) .saltTreeNode[aria-expanded][aria-selected=\"true\"] > div:first-child {\n --checkbox-borderColor: var(--list-item-selected-color);\n --checkbox-tick: var(--list-item-selected-color);\n --tree-borderColor-focusVisible: var(--list-item-selected-color);\n background: var(--list-item-background-active);\n color: var(--list-item-selected-color);\n}\n\n.with-checkbox .saltTreeNode {\n padding-left: 28px;\n}\n\n.with-checkbox .saltTreeNode:before {\n border-style: solid;\n border-width: var(--checkbox-borderWidth);\n border-color: var(--checkbox-borderColor);\n content: \"\";\n height: 12px;\n left: 3px;\n margin-top: -7px;\n position: absolute;\n top: 50%;\n width: 12px;\n}\n";
1
+ var css_248z = ".saltTreeNode {\n list-style: none;\n position: relative;\n cursor: var(--salt-cursor-hover);\n}\n\n.saltTreeNode:focus {\n outline: none;\n}\n\n/* Focus visible styles - applied when keyboard navigation is used */\n.saltTreeNode:focus-visible > .saltTreeNodeTrigger,\n.saltTreeNode-focusVisible > .saltTreeNodeTrigger {\n outline: var(--salt-focused-outline);\n position: relative;\n z-index: calc(var(--salt-zIndex-default) + 1);\n}\n\n/* Selected + focus visible */\n.saltTreeNode-selected:focus-visible > .saltTreeNodeTrigger,\n.saltTreeNode-selected.saltTreeNode-focusVisible > .saltTreeNodeTrigger {\n outline: var(--salt-focused-outline);\n z-index: calc(var(--salt-zIndex-default) + 1);\n}\n\n.saltTreeNode-group {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.saltTreeNode-checkbox {\n flex-shrink: 0;\n height: var(--salt-size-selectable);\n}\n\n.saltTreeNode-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--salt-size-selectable);\n min-width: var(--salt-size-selectable);\n height: var(--salt-size-selectable);\n flex-shrink: 0;\n}\n\n.saltTreeNode-icon > * {\n color: var(--salt-content-primary-foreground);\n}\n";
2
2
 
3
3
  export { css_248z as default };
4
4
  //# sourceMappingURL=TreeNode.css.js.map