@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,230 +1,297 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var react = require('react');
5
4
  var core = require('@salt-ds/core');
6
5
  var styles = require('@salt-ds/styles');
7
6
  var window = require('@salt-ds/window');
8
7
  var clsx = require('clsx');
9
- var calcPreferredHeight = require('../common-hooks/calcPreferredHeight.js');
10
- require('../common-hooks/collectionProvider.js');
11
- require('../common-hooks/keyUtils.js');
12
- var listDomUtils = require('../common-hooks/list-dom-utils.js');
13
- var useAutoSizer = require('../common-hooks/useAutoSizer.js');
14
- var useCollectionItems = require('../common-hooks/useCollectionItems.js');
15
- var useSelection = require('../common-hooks/useSelection.js');
16
- require('../responsive/useResizeObserver.js');
17
- var isSelected = require('../common-hooks/utils/isSelected.js');
8
+ var react = require('react');
18
9
  var Tree$1 = require('./Tree.css.js');
19
- var TreeNode = require('./TreeNode.js');
10
+ var TreeContext = require('./TreeContext.js');
20
11
  var useTree = require('./useTree.js');
21
12
 
22
13
  const withBaseName = core.makePrefixer("saltTree");
23
- const Tree = react.forwardRef(function Tree2({
24
- className,
25
- defaultSelected,
26
- disabled,
27
- groupSelection = useSelection.GROUP_SELECTION_NONE,
28
- height,
29
- id: idProp,
30
- onHighlight,
31
- onToggle,
32
- onSelect,
33
- onSelectionChange,
34
- revealSelected,
35
- selected: selectedProp,
36
- selectionStrategy,
37
- source,
38
- style: styleProp,
39
- width,
40
- ...htmlAttributes
41
- }, forwardedRef) {
42
- const targetWindow = window.useWindow();
43
- styles.useComponentCssInjection({
44
- testId: "salt-tree",
45
- css: Tree$1,
46
- window: targetWindow
47
- });
48
- const id = core.useIdMemo(idProp);
49
- const rootRef = react.useRef(null);
50
- const contentRef = react.useRef(null);
51
- const collectionHook = useCollectionItems.useCollectionItems({
52
- id,
53
- source,
54
- options: {
55
- noChildrenLabel: "No children available"}
56
- });
57
- const preferredHeight = height ?? calcPreferredHeight.calcPreferredHeight({
58
- displayedItemCount: 10,
59
- itemCount: collectionHook.data.length,
60
- itemHeight: 36
61
- // getItemHeight,
62
- // itemGapSize,
63
- });
64
- const autoSize = useAutoSizer.useAutoSizer({
65
- containerRef: rootRef,
66
- responsive: width === void 0 || height === void 0,
67
- height: preferredHeight,
68
- width
69
- });
70
- const handleSelect = react.useCallback(
71
- (evt, selectedItem) => {
72
- if (onSelect) {
73
- if (react.isValidElement(selectedItem.value)) {
74
- onSelect(evt, selectedItem.label);
75
- } else if (selectedItem.value !== null) {
76
- onSelect(evt, selectedItem.value);
14
+ const Tree = react.forwardRef(
15
+ function Tree2(props, ref) {
16
+ const {
17
+ children,
18
+ className,
19
+ defaultExpanded,
20
+ expanded,
21
+ onExpandedChange,
22
+ defaultSelected,
23
+ selected,
24
+ onSelectionChange,
25
+ multiselect = false,
26
+ disabled = false,
27
+ onKeyDown,
28
+ onBlur,
29
+ ...rest
30
+ } = props;
31
+ const targetWindow = window.useWindow();
32
+ styles.useComponentCssInjection({
33
+ testId: "salt-tree",
34
+ css: Tree$1,
35
+ window: targetWindow
36
+ });
37
+ const treeState = useTree.useTree({
38
+ defaultExpanded,
39
+ expanded,
40
+ onExpandedChange,
41
+ defaultSelected,
42
+ selected,
43
+ onSelectionChange,
44
+ multiselect,
45
+ disabled,
46
+ children
47
+ });
48
+ const {
49
+ activeNode,
50
+ setActiveNode,
51
+ expandedArray,
52
+ setExpandedArray,
53
+ expandedState,
54
+ toggleExpanded,
55
+ select,
56
+ selectedSet,
57
+ setSelectedState,
58
+ visibleNodes,
59
+ getNodeMeta,
60
+ getElement,
61
+ getParent,
62
+ getChildren,
63
+ treeModel,
64
+ disabledIdsSet
65
+ } = treeState;
66
+ const lastKeypressRef = react.useRef("");
67
+ const keypressTimeoutRef = react.useRef(
68
+ null
69
+ );
70
+ const treeRef = react.useRef(null);
71
+ react.useEffect(() => {
72
+ return () => {
73
+ if (keypressTimeoutRef.current) {
74
+ clearTimeout(keypressTimeoutRef.current);
77
75
  }
76
+ };
77
+ }, []);
78
+ const handleBlur = (event) => {
79
+ var _a;
80
+ onBlur == null ? void 0 : onBlur(event);
81
+ const relatedTarget = event.relatedTarget;
82
+ if (!((_a = treeRef.current) == null ? void 0 : _a.contains(relatedTarget))) {
83
+ setActiveNode(void 0);
78
84
  }
79
- },
80
- [onSelect]
81
- );
82
- const handleSelectionChange = react.useCallback(
83
- (evt, selected2) => {
84
- if (onSelectionChange) {
85
- onSelectionChange(
86
- evt,
87
- Array.isArray(selected2) ? selected2.map((s) => s.value) : selected2 && selected2.value
88
- );
85
+ };
86
+ const focusNode = (value) => {
87
+ const element = getElement(value);
88
+ if (!element) {
89
+ return "missing";
89
90
  }
90
- },
91
- [onSelectionChange]
92
- );
93
- const {
94
- focusVisible,
95
- highlightedIdx,
96
- highlightItemAtIndex,
97
- listHandlers,
98
- listProps,
99
- listItemHandlers,
100
- selected
101
- } = useTree.useTree({
102
- collectionHook,
103
- containerRef: rootRef,
104
- contentRef,
105
- // Note this isn't enough for a Tree, because of nested structure
106
- defaultSelected: collectionHook.itemToCollectionItem(defaultSelected),
107
- disabled,
108
- onHighlight,
109
- onSelect: handleSelect,
110
- onSelectionChange: handleSelectionChange,
111
- onToggle,
112
- selected: collectionHook.itemToCollectionItem(selectedProp),
113
- selectionStrategy
114
- });
115
- const defaultItemHandlers = {
116
- onMouseEnter: (evt) => {
117
- const idx = listDomUtils.closestListItemIndex(evt.target);
118
- if (idx != null) {
119
- highlightItemAtIndex(idx);
91
+ const activeEl = targetWindow == null ? void 0 : targetWindow.document.activeElement;
92
+ if (activeEl === element) {
93
+ return "already-focused";
120
94
  }
121
- }
122
- };
123
- const propsCommonToAllListItems = {
124
- ...defaultItemHandlers,
125
- ...listItemHandlers,
126
- isLeaf: true,
127
- role: "treeitem"
128
- };
129
- function addLeafNode(list, item, idx) {
130
- const itemProps = {
131
- "aria-disabled": disabled || item.disabled,
132
- "aria-level": item.level,
133
- "data-idx": idx.value,
134
- description: item.description,
135
- id: item.id,
136
- key: item.id,
137
- highlighted: idx.value === highlightedIdx || void 0,
138
- selected: isSelected.isSelected(selected, item),
139
- className: clsx.clsx({
140
- focusVisible: focusVisible === idx.value
141
- })
95
+ element.focus();
96
+ element.scrollIntoView({ block: "nearest", inline: "nearest" });
97
+ return "focused";
142
98
  };
143
- list.push(
144
- /* @__PURE__ */ jsxRuntime.jsx(
145
- TreeNode.TreeNode,
146
- {
147
- ...propsCommonToAllListItems,
148
- ...itemProps,
149
- label: item.label
99
+ const handleKeyDown = (event) => {
100
+ var _a, _b;
101
+ onKeyDown == null ? void 0 : onKeyDown(event);
102
+ if (disabled) return;
103
+ if (visibleNodes.length === 0) return;
104
+ const currentIndex = activeNode ? visibleNodes.indexOf(activeNode) : -1;
105
+ let newActiveNode;
106
+ let handled = false;
107
+ switch (event.key) {
108
+ case "ArrowDown": {
109
+ handled = true;
110
+ const nextIndex = currentIndex + 1;
111
+ if (nextIndex < visibleNodes.length) {
112
+ newActiveNode = visibleNodes[nextIndex];
113
+ }
114
+ break;
115
+ }
116
+ case "ArrowUp": {
117
+ handled = true;
118
+ const prevIndex = currentIndex - 1;
119
+ if (prevIndex >= 0) {
120
+ newActiveNode = visibleNodes[prevIndex];
121
+ }
122
+ break;
123
+ }
124
+ case "ArrowRight": {
125
+ handled = true;
126
+ if (activeNode) {
127
+ const nodeMeta = getNodeMeta(activeNode);
128
+ const isDisabled = disabledIdsSet.has(activeNode);
129
+ if (!isDisabled && (nodeMeta == null ? void 0 : nodeMeta.hasChildren)) {
130
+ if (!expandedState.has(activeNode)) {
131
+ toggleExpanded(event, activeNode);
132
+ } else {
133
+ const firstChild = visibleNodes.find(
134
+ (visibleNode) => getParent(visibleNode) === activeNode
135
+ );
136
+ if (firstChild) {
137
+ newActiveNode = firstChild;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ break;
143
+ }
144
+ case "ArrowLeft": {
145
+ handled = true;
146
+ if (activeNode) {
147
+ const isDisabled = disabledIdsSet.has(activeNode);
148
+ if (!isDisabled) {
149
+ if (expandedState.has(activeNode)) {
150
+ toggleExpanded(event, activeNode);
151
+ } else {
152
+ const parent = getParent(activeNode);
153
+ if (parent) {
154
+ newActiveNode = parent;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ break;
160
+ }
161
+ case "Home": {
162
+ handled = true;
163
+ newActiveNode = visibleNodes[0];
164
+ break;
165
+ }
166
+ case "End": {
167
+ handled = true;
168
+ newActiveNode = visibleNodes[visibleNodes.length - 1];
169
+ break;
170
+ }
171
+ case "Enter": {
172
+ handled = true;
173
+ if (activeNode) {
174
+ select(event, activeNode);
175
+ }
176
+ break;
177
+ }
178
+ case " ": {
179
+ handled = true;
180
+ if (activeNode) {
181
+ select(event, activeNode);
182
+ }
183
+ break;
184
+ }
185
+ case "*": {
186
+ handled = true;
187
+ if (activeNode) {
188
+ const parent = getParent(activeNode);
189
+ const siblings = parent ? getChildren(parent) : treeModel.rootValues;
190
+ const toExpand = siblings.filter((sibling) => {
191
+ const siblingMeta = getNodeMeta(sibling);
192
+ return (siblingMeta == null ? void 0 : siblingMeta.hasChildren) && !expandedState.has(sibling) && !disabledIdsSet.has(sibling);
193
+ });
194
+ if (toExpand.length > 0) {
195
+ const newExpanded = [...expandedArray, ...toExpand];
196
+ setExpandedArray(newExpanded);
197
+ onExpandedChange == null ? void 0 : onExpandedChange(event, newExpanded);
198
+ }
199
+ }
200
+ break;
201
+ }
202
+ default: {
203
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
204
+ handled = true;
205
+ if (keypressTimeoutRef.current) {
206
+ clearTimeout(keypressTimeoutRef.current);
207
+ }
208
+ lastKeypressRef.current += event.key.toLowerCase();
209
+ const searchString = lastKeypressRef.current;
210
+ keypressTimeoutRef.current = setTimeout(() => {
211
+ lastKeypressRef.current = "";
212
+ }, 500);
213
+ const currentIndex2 = activeNode ? visibleNodes.indexOf(activeNode) : -1;
214
+ let found = false;
215
+ for (let i = currentIndex2 + 1; i < visibleNodes.length; i++) {
216
+ const element = getElement(visibleNodes[i]);
217
+ if ((_a = element == null ? void 0 : element.textContent) == null ? void 0 : _a.toLowerCase().startsWith(searchString)) {
218
+ newActiveNode = visibleNodes[i];
219
+ found = true;
220
+ break;
221
+ }
222
+ }
223
+ if (!found) {
224
+ for (let i = 0; i <= currentIndex2; i++) {
225
+ const element = getElement(visibleNodes[i]);
226
+ if ((_b = element == null ? void 0 : element.textContent) == null ? void 0 : _b.toLowerCase().startsWith(searchString)) {
227
+ newActiveNode = visibleNodes[i];
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ break;
150
234
  }
151
- )
152
- );
153
- idx.value += 1;
154
- }
155
- function addGroupNode(list, items, idx, id2, title) {
156
- const { value: i } = idx;
157
- const item = items[i];
158
- idx.value += 1;
159
- list.push(
160
- /* @__PURE__ */ react.createElement(
161
- TreeNode.TreeNode,
162
- {
163
- ...defaultItemHandlers,
164
- ...listItemHandlers,
165
- "aria-disabled": disabled || item.disabled,
166
- "aria-expanded": item.expanded,
167
- "aria-level": item.level,
168
- className: clsx.clsx({
169
- focusVisible: focusVisible === i,
170
- [withBaseName("toggle")]: true
171
- }),
172
- "data-idx": i,
173
- "data-selectable": true,
174
- description: item.description,
175
- highlighted: i === highlightedIdx,
176
- id: id2,
177
- key: `header-${i}`,
178
- label: title,
179
- selected: isSelected.isSelected(selected, item)
180
- },
181
- item.expanded ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: withBaseName("child-nodes"), role: "group", children: renderItems(items, idx, (item.level ?? 0) + 1) }) : null
182
- )
183
- );
184
- }
185
- const renderItems = (items, idx = { value: 0 }, level = 1) => {
186
- const listItems = [];
187
- while (idx.value < items.length) {
188
- const item = items[idx.value];
189
- if (item.level != null && item.level < level) {
190
- break;
191
235
  }
192
- if (item.childNodes != null && item.id != null && item.label != null) {
193
- addGroupNode(listItems, items, idx, item.id, item.label);
194
- } else {
195
- addLeafNode(listItems, item, idx);
236
+ if ((event.ctrlKey || event.metaKey) && event.key === "a" && multiselect) {
237
+ handled = true;
238
+ event.preventDefault();
239
+ const allVisibleValues = visibleNodes.filter(
240
+ (visibleNode) => !disabledIdsSet.has(visibleNode)
241
+ );
242
+ const allSelected = allVisibleValues.every(
243
+ (visible) => selectedSet.has(visible)
244
+ );
245
+ const newSelected = allSelected ? [] : allVisibleValues;
246
+ setSelectedState(newSelected);
247
+ onSelectionChange == null ? void 0 : onSelectionChange(event, newSelected);
248
+ return;
196
249
  }
197
- }
198
- return listItems;
199
- };
200
- const renderContent = () => {
201
- if (collectionHook.data.length) {
202
- return renderItems(collectionHook.data);
203
- }
204
- };
205
- return /* @__PURE__ */ jsxRuntime.jsx(
206
- "div",
207
- {
208
- ...htmlAttributes,
209
- ...listHandlers,
210
- ...listProps,
211
- className: clsx.clsx(withBaseName(), className),
212
- id: `Tree-${id}`,
213
- ref: core.useForkRef(rootRef, forwardedRef),
214
- style: { ...styleProp, ...autoSize },
215
- tabIndex: 0,
216
- children: /* @__PURE__ */ jsxRuntime.jsx(
217
- "ul",
218
- {
219
- className: withBaseName("scrollingContentContainer"),
220
- ref: contentRef,
221
- role: "tree",
222
- children: renderContent()
250
+ if (event.shiftKey && (event.key === "ArrowUp" || event.key === "ArrowDown") && multiselect) {
251
+ handled = true;
252
+ const isDown = event.key === "ArrowDown";
253
+ const currentIndex2 = activeNode ? visibleNodes.indexOf(activeNode) : -1;
254
+ const nextIndex = isDown ? currentIndex2 + 1 : currentIndex2 - 1;
255
+ if (nextIndex >= 0 && nextIndex < visibleNodes.length) {
256
+ const nextValue = visibleNodes[nextIndex];
257
+ if (!disabledIdsSet.has(nextValue)) {
258
+ select(event, nextValue);
259
+ newActiveNode = nextValue;
260
+ }
223
261
  }
224
- )
225
- }
226
- );
227
- });
262
+ }
263
+ if (handled) {
264
+ event.preventDefault();
265
+ event.stopPropagation();
266
+ }
267
+ if (newActiveNode !== void 0) {
268
+ const focusResult = focusNode(newActiveNode);
269
+ if (focusResult !== "focused") {
270
+ setActiveNode(newActiveNode);
271
+ }
272
+ }
273
+ };
274
+ const handleRef = core.useForkRef(treeRef, ref);
275
+ return /* @__PURE__ */ jsxRuntime.jsx(TreeContext.TreeProvider, { value: treeState, children: /* @__PURE__ */ jsxRuntime.jsx(
276
+ "ul",
277
+ {
278
+ ref: handleRef,
279
+ role: "tree",
280
+ "aria-multiselectable": multiselect ? true : void 0,
281
+ "aria-disabled": disabled || void 0,
282
+ className: clsx.clsx(
283
+ withBaseName(),
284
+ { [withBaseName("disabled")]: disabled },
285
+ className
286
+ ),
287
+ onKeyDown: handleKeyDown,
288
+ onBlur: handleBlur,
289
+ ...rest,
290
+ children
291
+ }
292
+ ) });
293
+ }
294
+ );
228
295
 
229
296
  exports.Tree = Tree;
230
297
  //# 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":["makePrefixer","forwardRef","Tree","GROUP_SELECTION_NONE","useWindow","useComponentCssInjection","treeCss","useIdMemo","useRef","useCollectionItems","calcPreferredHeight","useAutoSizer","useCallback","isValidElement","selected","useTree","closestListItemIndex","isSelected","clsx","jsx","TreeNode","id","createElement","useForkRef"],"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":["makePrefixer","forwardRef","Tree","useWindow","useComponentCssInjection","treeCss","useTree","useRef","useEffect","currentIndex","useForkRef","jsx","TreeProvider","clsx"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ var core = require('@salt-ds/core');
4
+ var react = require('react');
5
+
6
+ const TreeContext = core.createContext(
7
+ "Tree Context",
8
+ void 0
9
+ );
10
+ const TreeProvider = TreeContext.Provider;
11
+ function useTreeContext() {
12
+ const context = react.useContext(TreeContext);
13
+ if (!context) {
14
+ throw new Error("useTreeContext must be used within a TreeProvider");
15
+ }
16
+ return context;
17
+ }
18
+ const TreeNodeContext = core.createContext(
19
+ "TreeNodeContext",
20
+ void 0
21
+ );
22
+ const TreeNodeProvider = TreeNodeContext.Provider;
23
+ function useTreeNodeContext() {
24
+ return react.useContext(TreeNodeContext);
25
+ }
26
+
27
+ exports.TreeNodeProvider = TreeNodeProvider;
28
+ exports.TreeProvider = TreeProvider;
29
+ exports.useTreeContext = useTreeContext;
30
+ exports.useTreeNodeContext = useTreeNodeContext;
31
+ //# 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":["createContext","useContext"],"mappings":";;;;;AA4DA,MAAM,WAAA,GAAcA,kBAAA;AAAA,EAClB,cAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,eAAe,WAAA,CAAY;AAEjC,SAAS,cAAA,GAAmC;AACjD,EAAA,MAAM,OAAA,GAAUC,iBAAW,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,GAAkBD,kBAAA;AAAA,EACtB,iBAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,mBAAmB,eAAA,CAAgB;AAEzC,SAAS,kBAAA,GAAuD;AACrE,EAAA,OAAOC,iBAAW,eAAe,CAAA;AACnC;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- 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";
3
+ 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";
4
4
 
5
5
  module.exports = css_248z;
6
6
  //# sourceMappingURL=TreeNode.css.js.map