@neo4j-ndl/react 4.21.2 → 4.22.1

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 (185) hide show
  1. package/lib/cjs/_common/form-control/FormFieldLabel.js +52 -0
  2. package/lib/cjs/_common/form-control/FormFieldLabel.js.map +1 -0
  3. package/lib/cjs/_common/form-control/FormFieldMessage.js +72 -0
  4. package/lib/cjs/_common/form-control/FormFieldMessage.js.map +1 -0
  5. package/lib/cjs/_common/form-control/form-control-types.js +23 -0
  6. package/lib/cjs/_common/form-control/form-control-types.js.map +1 -0
  7. package/lib/cjs/_common/form-control/index.js +39 -0
  8. package/lib/cjs/_common/form-control/index.js.map +1 -0
  9. package/lib/cjs/_common/form-control/use-form-field.js +76 -0
  10. package/lib/cjs/_common/form-control/use-form-field.js.map +1 -0
  11. package/lib/cjs/_common/form-control/use-inline-field-label.js +76 -0
  12. package/lib/cjs/_common/form-control/use-inline-field-label.js.map +1 -0
  13. package/lib/cjs/checkbox/Checkbox.js +9 -21
  14. package/lib/cjs/checkbox/Checkbox.js.map +1 -1
  15. package/lib/cjs/combobox/Combobox.js +61 -45
  16. package/lib/cjs/combobox/Combobox.js.map +1 -1
  17. package/lib/cjs/combobox/combobox-types.js.map +1 -1
  18. package/lib/cjs/date-picker/DatePicker.js.map +1 -1
  19. package/lib/cjs/dropzone/stories/dropzone-full.story.js +5 -5
  20. package/lib/cjs/dropzone/stories/dropzone-full.story.js.map +1 -1
  21. package/lib/cjs/helpers/index.js +1 -0
  22. package/lib/cjs/helpers/index.js.map +1 -1
  23. package/lib/cjs/helpers/platform.js +59 -0
  24. package/lib/cjs/helpers/platform.js.map +1 -0
  25. package/lib/cjs/kbd/kbd-utils.js +6 -22
  26. package/lib/cjs/kbd/kbd-utils.js.map +1 -1
  27. package/lib/cjs/next/tree-view/TreeView.js +384 -66
  28. package/lib/cjs/next/tree-view/TreeView.js.map +1 -1
  29. package/lib/cjs/next/tree-view/selection.js +181 -0
  30. package/lib/cjs/next/tree-view/selection.js.map +1 -0
  31. package/lib/cjs/next/tree-view/stories/index.js +13 -1
  32. package/lib/cjs/next/tree-view/stories/index.js.map +1 -1
  33. package/lib/cjs/next/tree-view/stories/tree-view-large.story.js +57 -0
  34. package/lib/cjs/next/tree-view/stories/tree-view-large.story.js.map +1 -0
  35. package/lib/cjs/next/tree-view/stories/tree-view-lazy.story.js +1 -0
  36. package/lib/cjs/next/tree-view/stories/tree-view-lazy.story.js.map +1 -1
  37. package/lib/cjs/next/tree-view/stories/tree-view-multi.story.js +16 -40
  38. package/lib/cjs/next/tree-view/stories/tree-view-multi.story.js.map +1 -1
  39. package/lib/cjs/next/tree-view/stories/tree-view-multiple-replace.story.js +38 -0
  40. package/lib/cjs/next/tree-view/stories/tree-view-multiple-replace.story.js.map +1 -0
  41. package/lib/cjs/next/tree-view/stories/tree-view-non-selectable-folders.story.js +7 -4
  42. package/lib/cjs/next/tree-view/stories/tree-view-non-selectable-folders.story.js.map +1 -1
  43. package/lib/cjs/next/tree-view/stories/tree-view-root-selection.story.js +125 -0
  44. package/lib/cjs/next/tree-view/stories/tree-view-root-selection.story.js.map +1 -0
  45. package/lib/cjs/radio/Radio.js +12 -22
  46. package/lib/cjs/radio/Radio.js.map +1 -1
  47. package/lib/cjs/select/Select.js +14 -5
  48. package/lib/cjs/select/Select.js.map +1 -1
  49. package/lib/cjs/select/types.js.map +1 -1
  50. package/lib/cjs/switch/Switch.js +9 -21
  51. package/lib/cjs/switch/Switch.js.map +1 -1
  52. package/lib/cjs/text-area/TextArea.js +8 -21
  53. package/lib/cjs/text-area/TextArea.js.map +1 -1
  54. package/lib/cjs/text-input/TextInput.js +15 -38
  55. package/lib/cjs/text-input/TextInput.js.map +1 -1
  56. package/lib/cjs/text-input/stories/text-input-sizes.story.js +1 -1
  57. package/lib/cjs/text-input/stories/text-input-sizes.story.js.map +1 -1
  58. package/lib/cjs/time-picker/TimePicker.js +13 -7
  59. package/lib/cjs/time-picker/TimePicker.js.map +1 -1
  60. package/lib/cjs/timezone-picker/TimeZonePicker.js +13 -7
  61. package/lib/cjs/timezone-picker/TimeZonePicker.js.map +1 -1
  62. package/lib/cjs/toast/ToastControlled.js +1 -1
  63. package/lib/cjs/toast/ToastControlled.js.map +1 -1
  64. package/lib/esm/_common/form-control/FormFieldLabel.js +45 -0
  65. package/lib/esm/_common/form-control/FormFieldLabel.js.map +1 -0
  66. package/lib/esm/_common/form-control/FormFieldMessage.js +65 -0
  67. package/lib/esm/_common/form-control/FormFieldMessage.js.map +1 -0
  68. package/lib/esm/_common/form-control/form-control-types.js +22 -0
  69. package/lib/esm/_common/form-control/form-control-types.js.map +1 -0
  70. package/lib/esm/_common/form-control/index.js +32 -0
  71. package/lib/esm/_common/form-control/index.js.map +1 -0
  72. package/lib/esm/_common/form-control/use-form-field.js +72 -0
  73. package/lib/esm/_common/form-control/use-form-field.js.map +1 -0
  74. package/lib/esm/_common/form-control/use-inline-field-label.js +69 -0
  75. package/lib/esm/_common/form-control/use-inline-field-label.js.map +1 -0
  76. package/lib/esm/checkbox/Checkbox.js +10 -22
  77. package/lib/esm/checkbox/Checkbox.js.map +1 -1
  78. package/lib/esm/combobox/Combobox.js +62 -46
  79. package/lib/esm/combobox/Combobox.js.map +1 -1
  80. package/lib/esm/combobox/combobox-types.js.map +1 -1
  81. package/lib/esm/date-picker/DatePicker.js.map +1 -1
  82. package/lib/esm/dropzone/stories/dropzone-full.story.js +5 -5
  83. package/lib/esm/dropzone/stories/dropzone-full.story.js.map +1 -1
  84. package/lib/esm/helpers/index.js +1 -0
  85. package/lib/esm/helpers/index.js.map +1 -1
  86. package/lib/esm/helpers/platform.js +55 -0
  87. package/lib/esm/helpers/platform.js.map +1 -0
  88. package/lib/esm/kbd/kbd-utils.js +2 -18
  89. package/lib/esm/kbd/kbd-utils.js.map +1 -1
  90. package/lib/esm/next/tree-view/TreeView.js +385 -67
  91. package/lib/esm/next/tree-view/TreeView.js.map +1 -1
  92. package/lib/esm/next/tree-view/selection.js +175 -0
  93. package/lib/esm/next/tree-view/selection.js.map +1 -0
  94. package/lib/esm/next/tree-view/stories/index.js +9 -0
  95. package/lib/esm/next/tree-view/stories/index.js.map +1 -1
  96. package/lib/esm/next/tree-view/stories/tree-view-large.story.js +55 -0
  97. package/lib/esm/next/tree-view/stories/tree-view-large.story.js.map +1 -0
  98. package/lib/esm/next/tree-view/stories/tree-view-lazy.story.js +1 -0
  99. package/lib/esm/next/tree-view/stories/tree-view-lazy.story.js.map +1 -1
  100. package/lib/esm/next/tree-view/stories/tree-view-multi.story.js +16 -40
  101. package/lib/esm/next/tree-view/stories/tree-view-multi.story.js.map +1 -1
  102. package/lib/esm/next/tree-view/stories/tree-view-multiple-replace.story.js +36 -0
  103. package/lib/esm/next/tree-view/stories/tree-view-multiple-replace.story.js.map +1 -0
  104. package/lib/esm/next/tree-view/stories/tree-view-non-selectable-folders.story.js +7 -4
  105. package/lib/esm/next/tree-view/stories/tree-view-non-selectable-folders.story.js.map +1 -1
  106. package/lib/esm/next/tree-view/stories/tree-view-root-selection.story.js +123 -0
  107. package/lib/esm/next/tree-view/stories/tree-view-root-selection.story.js.map +1 -0
  108. package/lib/esm/radio/Radio.js +12 -22
  109. package/lib/esm/radio/Radio.js.map +1 -1
  110. package/lib/esm/select/Select.js +14 -5
  111. package/lib/esm/select/Select.js.map +1 -1
  112. package/lib/esm/select/types.js.map +1 -1
  113. package/lib/esm/switch/Switch.js +10 -22
  114. package/lib/esm/switch/Switch.js.map +1 -1
  115. package/lib/esm/text-area/TextArea.js +9 -22
  116. package/lib/esm/text-area/TextArea.js.map +1 -1
  117. package/lib/esm/text-input/TextInput.js +15 -38
  118. package/lib/esm/text-input/TextInput.js.map +1 -1
  119. package/lib/esm/text-input/stories/text-input-sizes.story.js +1 -1
  120. package/lib/esm/text-input/stories/text-input-sizes.story.js.map +1 -1
  121. package/lib/esm/time-picker/TimePicker.js +13 -7
  122. package/lib/esm/time-picker/TimePicker.js.map +1 -1
  123. package/lib/esm/timezone-picker/TimeZonePicker.js +13 -7
  124. package/lib/esm/timezone-picker/TimeZonePicker.js.map +1 -1
  125. package/lib/esm/toast/ToastControlled.js +1 -1
  126. package/lib/esm/toast/ToastControlled.js.map +1 -1
  127. package/lib/types/_common/form-control/FormFieldLabel.d.ts +58 -0
  128. package/lib/types/_common/form-control/FormFieldLabel.d.ts.map +1 -0
  129. package/lib/types/_common/form-control/FormFieldMessage.d.ts +46 -0
  130. package/lib/types/_common/form-control/FormFieldMessage.d.ts.map +1 -0
  131. package/lib/types/_common/form-control/form-control-types.d.ts +92 -0
  132. package/lib/types/_common/form-control/form-control-types.d.ts.map +1 -0
  133. package/lib/types/_common/form-control/index.d.ts +33 -0
  134. package/lib/types/_common/form-control/index.d.ts.map +1 -0
  135. package/lib/types/_common/form-control/use-form-field.d.ts +53 -0
  136. package/lib/types/_common/form-control/use-form-field.d.ts.map +1 -0
  137. package/lib/types/_common/form-control/use-inline-field-label.d.ts +69 -0
  138. package/lib/types/_common/form-control/use-inline-field-label.d.ts.map +1 -0
  139. package/lib/types/checkbox/Checkbox.d.ts.map +1 -1
  140. package/lib/types/combobox/Combobox.d.ts.map +1 -1
  141. package/lib/types/combobox/combobox-types.d.ts +1 -1
  142. package/lib/types/combobox/combobox-types.d.ts.map +1 -1
  143. package/lib/types/helpers/index.d.ts +1 -0
  144. package/lib/types/helpers/index.d.ts.map +1 -1
  145. package/lib/types/helpers/platform.d.ts +29 -0
  146. package/lib/types/helpers/platform.d.ts.map +1 -0
  147. package/lib/types/kbd/kbd-utils.d.ts +2 -5
  148. package/lib/types/kbd/kbd-utils.d.ts.map +1 -1
  149. package/lib/types/next/tree-view/TreeView.d.ts +29 -5
  150. package/lib/types/next/tree-view/TreeView.d.ts.map +1 -1
  151. package/lib/types/next/tree-view/selection.d.ts +78 -0
  152. package/lib/types/next/tree-view/selection.d.ts.map +1 -0
  153. package/lib/types/next/tree-view/stories/index.d.ts +6 -0
  154. package/lib/types/next/tree-view/stories/index.d.ts.map +1 -1
  155. package/lib/types/next/tree-view/stories/tree-view-large.story.d.ts +24 -0
  156. package/lib/types/next/tree-view/stories/tree-view-large.story.d.ts.map +1 -0
  157. package/lib/types/next/tree-view/stories/tree-view-lazy.story.d.ts.map +1 -1
  158. package/lib/types/next/tree-view/stories/tree-view-multi.story.d.ts +10 -0
  159. package/lib/types/next/tree-view/stories/tree-view-multi.story.d.ts.map +1 -1
  160. package/lib/types/next/tree-view/stories/tree-view-multiple-replace.story.d.ts +29 -0
  161. package/lib/types/next/tree-view/stories/tree-view-multiple-replace.story.d.ts.map +1 -0
  162. package/lib/types/next/tree-view/stories/tree-view-non-selectable-folders.story.d.ts +5 -0
  163. package/lib/types/next/tree-view/stories/tree-view-non-selectable-folders.story.d.ts.map +1 -1
  164. package/lib/types/next/tree-view/stories/tree-view-root-selection.story.d.ts +24 -0
  165. package/lib/types/next/tree-view/stories/tree-view-root-selection.story.d.ts.map +1 -0
  166. package/lib/types/radio/Radio.d.ts.map +1 -1
  167. package/lib/types/select/Select.d.ts.map +1 -1
  168. package/lib/types/select/types.d.ts.map +1 -1
  169. package/lib/types/switch/Switch.d.ts.map +1 -1
  170. package/lib/types/text-area/TextArea.d.ts.map +1 -1
  171. package/lib/types/text-input/TextInput.d.ts.map +1 -1
  172. package/lib/types/text-input/stories/text-input-sizes.story.d.ts.map +1 -1
  173. package/lib/types/time-picker/TimePicker.d.ts +3 -1
  174. package/lib/types/time-picker/TimePicker.d.ts.map +1 -1
  175. package/lib/types/timezone-picker/TimeZonePicker.d.ts +4 -1
  176. package/lib/types/timezone-picker/TimeZonePicker.d.ts.map +1 -1
  177. package/package.json +2 -2
  178. package/skills/ndl-react/SKILL.md +1 -1
  179. package/skills/ndl-react/components/color-picker.md +32 -32
  180. package/skills/ndl-react/components/combobox.md +2 -2
  181. package/skills/ndl-react/components/dropzone.md +5 -5
  182. package/skills/ndl-react/components/next/tree-view.md +444 -104
  183. package/skills/ndl-react/components/text-input.md +6 -0
  184. package/skills/ndl-react/components/time-picker.md +1 -0
  185. package/skills/ndl-react/components/timezone-picker.md +1 -0
@@ -40,29 +40,71 @@ const react_1 = require("@floating-ui/react");
40
40
  const classnames_1 = __importDefault(require("classnames"));
41
41
  const react_2 = require("react");
42
42
  const use_semi_controlled_state_1 = require("../../_common/use-semi-controlled-state");
43
+ const utils_1 = require("../../_common/utils");
43
44
  const checkbox_1 = require("../../checkbox");
44
45
  const clean_icon_button_1 = require("../../clean-icon-button");
45
46
  const conditional_wrap_1 = require("../../conditional-wrap");
47
+ const platform_1 = require("../../helpers/platform");
46
48
  const ChevronRightIconOutline_1 = require("../../icons/generated/heroIcons/ChevronRightIconOutline");
47
49
  const EllipsisHorizontalIconOutline_1 = require("../../icons/generated/heroIcons/EllipsisHorizontalIconOutline");
48
50
  const menu_1 = require("../../menu");
49
51
  const skeleton_1 = require("../../skeleton");
50
52
  const tooltip_1 = require("../../tooltip");
53
+ const selection_1 = require("./selection");
51
54
  const noop = () => undefined;
55
+ const EMPTY_EDGES = new Map();
56
+ const EMPTY_IDS = new Set();
57
+ const EMPTY_ID_LIST = [];
58
+ /**
59
+ * Volatile per-interaction state (focus, root-managed selection, edge
60
+ * styling) lives in this store instead of React context, so each item can
61
+ * subscribe to its own primitive slice via `useSyncExternalStore` and an
62
+ * interaction only re-renders the rows whose slice actually changed.
63
+ */
64
+ function createTreeViewStore(initialState) {
65
+ let state = Object.assign({ edgeById: EMPTY_EDGES, focusedId: null, selectedIds: EMPTY_IDS }, initialState);
66
+ const listeners = new Set();
67
+ return {
68
+ getState: () => state,
69
+ setState: (partial) => {
70
+ state = Object.assign(Object.assign({}, state), partial);
71
+ listeners.forEach((listener) => listener());
72
+ },
73
+ subscribe: (listener) => {
74
+ listeners.add(listener);
75
+ return () => {
76
+ listeners.delete(listener);
77
+ };
78
+ },
79
+ };
80
+ }
52
81
  const TREE_EVENTS = {
53
82
  action: 'treeitem-action',
54
83
  collapse: 'treeitem-collapse',
55
84
  expand: 'treeitem-expand',
56
- select: 'treeitem-select',
57
85
  };
58
86
  const TreeViewContext = (0, react_2.createContext)({
59
- focusedId: null,
87
+ isRootManaged: false,
60
88
  level: 1,
89
+ onSelectionIntent: noop,
90
+ parentId: null,
91
+ registerItem: noop,
61
92
  selectionMode: 'single',
62
93
  setFocusedId: noop,
94
+ store: createTreeViewStore(),
63
95
  });
96
+ /**
97
+ * On Apple platforms Ctrl+click is the context menu gesture, so only Cmd may
98
+ * stand for an additive selection there.
99
+ */
100
+ function getSelectionIntent(event) {
101
+ return {
102
+ isRange: event.shiftKey,
103
+ isToggle: (0, platform_1.isApplePlatform)() ? event.metaKey : event.ctrlKey,
104
+ };
105
+ }
64
106
  function getVisibleTreeItems(root) {
65
- return Array.from(root.querySelectorAll('[role="treeitem"]:not([role="group"][hidden] [role="treeitem"])')).filter((el) => {
107
+ return Array.from(root.querySelectorAll('[role="treeitem"]')).filter((el) => {
66
108
  let parent = el.parentElement;
67
109
  while (parent && parent !== root) {
68
110
  if (parent.getAttribute('role') === 'group' && parent.hidden) {
@@ -73,6 +115,31 @@ function getVisibleTreeItems(root) {
73
115
  return true;
74
116
  });
75
117
  }
118
+ function getVisibleItemIds(root) {
119
+ return getVisibleTreeItems(root).map((el) => el.id);
120
+ }
121
+ function areIdSetsEqual(first, second) {
122
+ if (first.size !== second.size) {
123
+ return false;
124
+ }
125
+ for (const id of first) {
126
+ if (!second.has(id)) {
127
+ return false;
128
+ }
129
+ }
130
+ return true;
131
+ }
132
+ function areEdgeMapsEqual(first, second) {
133
+ if (first.size !== second.size) {
134
+ return false;
135
+ }
136
+ for (const [id, edge] of first) {
137
+ if (second.get(id) !== edge) {
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
76
143
  function annotateTreeItems(children) {
77
144
  const items = react_2.Children.toArray(children);
78
145
  const treeItemCount = items.filter((child) => (0, react_2.isValidElement)(child) && child.type === Item).length;
@@ -105,10 +172,186 @@ function annotateTreeItems(children) {
105
172
  * @alpha - this component is still in development and may change in the future. It might not have feature parity with the old tree view yet.
106
173
  */
107
174
  const Root = (_a) => {
108
- var { children, selectionMode = 'single', ariaLabel, ariaLabelledby, className, style, htmlAttributes, ref } = _a, restProps = __rest(_a, ["children", "selectionMode", "ariaLabel", "ariaLabelledby", "className", "style", "htmlAttributes", "ref"]);
175
+ var _b, _c;
176
+ var { children, defaultSelectedIds, selectedIds: selectedIdsProp, selectionMode = 'single', ariaLabel, ariaLabelledby, className, style, htmlAttributes, onSelectedIdsChange, ref } = _a, restProps = __rest(_a, ["children", "defaultSelectedIds", "selectedIds", "selectionMode", "ariaLabel", "ariaLabelledby", "className", "style", "htmlAttributes", "onSelectedIdsChange", "ref"]);
109
177
  const treeRef = (0, react_2.useRef)(null);
110
178
  const mergedRef = (0, react_1.useMergeRefs)([treeRef, ref]);
111
- const [focusedId, setFocusedId] = (0, react_2.useState)(null);
179
+ const [announcement, setAnnouncement] = (0, react_2.useState)('');
180
+ const anchorRef = (0, react_2.useRef)(null);
181
+ const entriesRef = (0, react_2.useRef)(new Map());
182
+ const isRootManaged = selectedIdsProp !== undefined || defaultSelectedIds !== undefined;
183
+ const storeRef = (0, react_2.useRef)(null);
184
+ if (storeRef.current === null) {
185
+ storeRef.current = createTreeViewStore({
186
+ selectedIds: new Set((_b = selectedIdsProp !== null && selectedIdsProp !== void 0 ? selectedIdsProp : defaultSelectedIds) !== null && _b !== void 0 ? _b : EMPTY_ID_LIST),
187
+ });
188
+ }
189
+ const store = storeRef.current;
190
+ const setFocusedId = (0, react_2.useCallback)((id) => {
191
+ if (store.getState().focusedId !== id) {
192
+ store.setState({ focusedId: id });
193
+ }
194
+ }, [store]);
195
+ const [selectedIdList, setSelectedIdList] = (0, use_semi_controlled_state_1.useSemicontrolledState)({
196
+ isControlled: selectedIdsProp !== undefined,
197
+ onChange: onSelectedIdsChange,
198
+ state: (_c = selectedIdsProp !== null && selectedIdsProp !== void 0 ? selectedIdsProp : defaultSelectedIds) !== null && _c !== void 0 ? _c : EMPTY_ID_LIST,
199
+ });
200
+ // useSemicontrolledState recreates its setter whenever the value changes;
201
+ // reading it through a ref keeps onSelectionIntent (and with it the
202
+ // memoized context value) stable across selection changes.
203
+ const setSelectedIdListRef = (0, react_2.useRef)(setSelectedIdList);
204
+ (0, react_2.useLayoutEffect)(() => {
205
+ setSelectedIdListRef.current = setSelectedIdList;
206
+ });
207
+ const readSelectedIds = (0, react_2.useCallback)(() => {
208
+ if (isRootManaged) {
209
+ return new Set(store.getState().selectedIds);
210
+ }
211
+ const selected = new Set();
212
+ entriesRef.current.forEach((entry, itemId) => {
213
+ if (entry.isSelected) {
214
+ selected.add(itemId);
215
+ }
216
+ });
217
+ return selected;
218
+ }, [isRootManaged, store]);
219
+ const readItemMaps = (0, react_2.useCallback)(() => {
220
+ const parentIdById = new Map();
221
+ const selectableIds = new Set();
222
+ entriesRef.current.forEach((entry, itemId) => {
223
+ if (entry.parentId !== null) {
224
+ parentIdById.set(itemId, entry.parentId);
225
+ }
226
+ if (entry.isSelectable && !entry.isDisabled) {
227
+ selectableIds.add(itemId);
228
+ }
229
+ });
230
+ return { parentIdById, selectableIds };
231
+ }, []);
232
+ const recomputeEdges = (0, react_2.useCallback)(() => {
233
+ const root = treeRef.current;
234
+ const previousEdges = store.getState().edgeById;
235
+ if (!root || selectionMode !== 'multiple-replace') {
236
+ if (previousEdges.size > 0) {
237
+ store.setState({ edgeById: EMPTY_EDGES });
238
+ }
239
+ return;
240
+ }
241
+ const selectedIds = readSelectedIds();
242
+ if (selectedIds.size === 0 && previousEdges.size === 0) {
243
+ return;
244
+ }
245
+ const nextEdges = (0, selection_1.computeSelectionEdges)(getVisibleItemIds(root), selectedIds);
246
+ if (!areEdgeMapsEqual(previousEdges, nextEdges)) {
247
+ store.setState({ edgeById: nextEdges });
248
+ }
249
+ }, [readSelectedIds, selectionMode, store]);
250
+ const recomputeEdgesRef = (0, react_2.useRef)(recomputeEdges);
251
+ (0, react_2.useLayoutEffect)(() => {
252
+ recomputeEdgesRef.current = recomputeEdges;
253
+ });
254
+ // Debounced through a microtask so a commit that touches many items (mount,
255
+ // expansion, range selection) triggers a single recompute, after all item
256
+ // layout effects have registered and before the browser paints.
257
+ const isEdgeRecomputeScheduledRef = (0, react_2.useRef)(false);
258
+ const scheduleEdgeRecompute = (0, react_2.useCallback)(() => {
259
+ if (isEdgeRecomputeScheduledRef.current) {
260
+ return;
261
+ }
262
+ isEdgeRecomputeScheduledRef.current = true;
263
+ queueMicrotask(() => {
264
+ isEdgeRecomputeScheduledRef.current = false;
265
+ recomputeEdgesRef.current();
266
+ });
267
+ }, []);
268
+ const registerItem = (0, react_2.useCallback)((itemId, entry) => {
269
+ if (entry === null) {
270
+ entriesRef.current.delete(itemId);
271
+ }
272
+ else {
273
+ entriesRef.current.set(itemId, entry);
274
+ }
275
+ // Covers item mount/unmount and expansion toggles (a collapsing parent
276
+ // re-registers because its toggleExpanded callback is recreated).
277
+ scheduleEdgeRecompute();
278
+ }, [scheduleEdgeRecompute]);
279
+ const onSelectionIntent = (0, react_2.useCallback)((itemId, intent) => {
280
+ var _a;
281
+ const root = treeRef.current;
282
+ const entry = entriesRef.current.get(itemId);
283
+ if (!root || !entry || entry.isDisabled) {
284
+ return;
285
+ }
286
+ if (!entry.isSelectable) {
287
+ entry.toggleExpanded();
288
+ return;
289
+ }
290
+ const { parentIdById, selectableIds } = readItemMaps();
291
+ const selectedIds = readSelectedIds();
292
+ const result = (0, selection_1.computeNextSelection)({
293
+ anchor: anchorRef.current,
294
+ intent,
295
+ parentIdById,
296
+ selectableIds,
297
+ selectedIds,
298
+ selectionMode,
299
+ targetId: itemId,
300
+ visibleIds: getVisibleItemIds(root),
301
+ });
302
+ anchorRef.current = result.anchor;
303
+ const nextSelectedIds = result.selectedIds;
304
+ const changedCount = (0, selection_1.countChangedIds)(selectedIds, nextSelectedIds);
305
+ if (isRootManaged) {
306
+ if (changedCount > 0) {
307
+ setSelectedIdListRef.current([...nextSelectedIds]);
308
+ }
309
+ }
310
+ else if (selectionMode === 'single') {
311
+ // Single mode leaves clearing the previously selected item to the
312
+ // consumer, so only the clicked item is told about the change.
313
+ if (!selectedIds.has(itemId)) {
314
+ (_a = entry.onSelectedChange) === null || _a === void 0 ? void 0 : _a.call(entry, true);
315
+ }
316
+ }
317
+ else {
318
+ entriesRef.current.forEach((itemEntry, id) => {
319
+ var _a;
320
+ const isSelected = nextSelectedIds.has(id);
321
+ if (itemEntry.isSelected !== isSelected) {
322
+ (_a = itemEntry.onSelectedChange) === null || _a === void 0 ? void 0 : _a.call(itemEntry, isSelected);
323
+ }
324
+ });
325
+ }
326
+ scheduleEdgeRecompute();
327
+ if (selectionMode !== 'single') {
328
+ setAnnouncement(intent.isRange
329
+ ? (0, selection_1.formatSelectionAnnouncement)(nextSelectedIds.size)
330
+ : '');
331
+ }
332
+ }, [
333
+ isRootManaged,
334
+ readItemMaps,
335
+ readSelectedIds,
336
+ scheduleEdgeRecompute,
337
+ selectionMode,
338
+ ]);
339
+ // Mirrors the root-managed selection into the store, so items subscribe to
340
+ // their own slice and event handlers read it without a state dependency.
341
+ (0, react_2.useLayoutEffect)(() => {
342
+ const next = isRootManaged ? new Set(selectedIdList) : EMPTY_IDS;
343
+ if (areIdSetsEqual(store.getState().selectedIds, next)) {
344
+ return;
345
+ }
346
+ store.setState({ selectedIds: next });
347
+ scheduleEdgeRecompute();
348
+ }, [isRootManaged, scheduleEdgeRecompute, selectedIdList, store]);
349
+ // Computes edges on mount and whenever the edge inputs change identity
350
+ // (selection mode, root-managed flag). Interaction-driven recomputes go
351
+ // through scheduleEdgeRecompute instead.
352
+ (0, react_2.useLayoutEffect)(() => {
353
+ recomputeEdges();
354
+ }, [recomputeEdges]);
112
355
  const handleKeyDown = (0, react_2.useCallback)((event) => {
113
356
  const root = treeRef.current;
114
357
  if (!root) {
@@ -118,13 +361,35 @@ const Root = (_a) => {
118
361
  if (items.length === 0) {
119
362
  return;
120
363
  }
121
- const currentIndex = items.findIndex((el) => el.id === focusedId);
364
+ const currentIndex = items.findIndex((el) => el.id === store.getState().focusedId);
122
365
  let isHandled = true;
366
+ const extendRangeTo = (item) => {
367
+ if (selectionMode === 'single' || currentIndex < 0) {
368
+ return;
369
+ }
370
+ const entry = entriesRef.current.get(item.id);
371
+ if (!(entry === null || entry === void 0 ? void 0 : entry.isSelectable) || entry.isDisabled) {
372
+ return;
373
+ }
374
+ if (anchorRef.current === null) {
375
+ // A keyboard range has no preceding click to repeat, so it grows the
376
+ // selection outwards from wherever focus already is.
377
+ anchorRef.current = {
378
+ baseSelectedIds: readSelectedIds(),
379
+ id: items[currentIndex].id,
380
+ isSelected: true,
381
+ };
382
+ }
383
+ onSelectionIntent(item.id, { isRange: true, isToggle: false });
384
+ };
123
385
  switch (event.key) {
124
386
  case 'ArrowDown': {
125
387
  const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : currentIndex;
126
388
  const nextItem = items[nextIndex];
127
389
  if (nextItem) {
390
+ if (event.shiftKey) {
391
+ extendRangeTo(nextItem);
392
+ }
128
393
  setFocusedId(nextItem.id);
129
394
  nextItem.focus();
130
395
  }
@@ -134,6 +399,9 @@ const Root = (_a) => {
134
399
  const prevIndex = currentIndex > 0 ? currentIndex - 1 : 0;
135
400
  const prevItem = items[prevIndex];
136
401
  if (prevItem) {
402
+ if (event.shiftKey) {
403
+ extendRangeTo(prevItem);
404
+ }
137
405
  setFocusedId(prevItem.id);
138
406
  prevItem.focus();
139
407
  }
@@ -199,7 +467,7 @@ const Root = (_a) => {
199
467
  case ' ': {
200
468
  const current = currentIndex >= 0 ? items[currentIndex] : null;
201
469
  if (current) {
202
- current.dispatchEvent(new CustomEvent(TREE_EVENTS.select));
470
+ onSelectionIntent(current.id, getSelectionIntent(event));
203
471
  }
204
472
  break;
205
473
  }
@@ -222,7 +490,7 @@ const Root = (_a) => {
222
490
  event.preventDefault();
223
491
  event.stopPropagation();
224
492
  }
225
- }, [focusedId]);
493
+ }, [onSelectionIntent, readSelectedIds, selectionMode, setFocusedId, store]);
226
494
  const handleFocus = (0, react_2.useCallback)((event) => {
227
495
  const root = treeRef.current;
228
496
  if (!root) {
@@ -230,6 +498,7 @@ const Root = (_a) => {
230
498
  }
231
499
  if (event.target === root) {
232
500
  const items = getVisibleTreeItems(root);
501
+ const { focusedId } = store.getState();
233
502
  const alreadyFocused = focusedId
234
503
  ? items.find((el) => el.id === focusedId)
235
504
  : null;
@@ -239,9 +508,36 @@ const Root = (_a) => {
239
508
  target.focus();
240
509
  }
241
510
  }
242
- }, [focusedId]);
243
- const classes = (0, classnames_1.default)(className, 'ndl-tree-view-new');
244
- return ((0, jsx_runtime_1.jsx)(TreeViewContext.Provider, { value: { focusedId, level: 1, selectionMode, setFocusedId }, children: (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: mergedRef, role: "tree", "aria-multiselectable": selectionMode === 'multiple' || undefined, tabIndex: 0, className: classes, onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : undefined, "aria-labelledby": ariaLabelledby, style: style }, htmlAttributes, restProps, { children: annotateTreeItems(children) })) }));
511
+ }, [setFocusedId, store]);
512
+ const classes = (0, classnames_1.default)(className, 'ndl-tree-view-new', {
513
+ 'ndl-tree-view-new--checkbox': selectionMode === 'multiple',
514
+ });
515
+ // Cached on children identity so Root-internal state commits (announcement,
516
+ // uncontrolled selection) reuse the same annotated elements and React bails
517
+ // out of the whole item subtree instead of re-rendering every row.
518
+ const annotatedChildren = (0, react_2.useMemo)(() => annotateTreeItems(children), [children]);
519
+ // Everything in here is stable or primitive, so items only re-render via
520
+ // context when the tree's configuration changes, not per interaction.
521
+ const contextValue = (0, react_2.useMemo)(() => ({
522
+ isRootManaged,
523
+ level: 1,
524
+ onSelectionIntent,
525
+ parentId: null,
526
+ registerItem,
527
+ selectionMode,
528
+ setFocusedId,
529
+ store,
530
+ }), [
531
+ isRootManaged,
532
+ onSelectionIntent,
533
+ registerItem,
534
+ selectionMode,
535
+ setFocusedId,
536
+ store,
537
+ ]);
538
+ return ((0, jsx_runtime_1.jsxs)(TreeViewContext.Provider, { value: contextValue, children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ ref: mergedRef, role: "tree", "aria-multiselectable": selectionMode === 'multiple' || selectionMode === 'multiple-replace'
539
+ ? true
540
+ : undefined, tabIndex: 0, className: classes, onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : undefined, "aria-labelledby": ariaLabelledby, style: style }, htmlAttributes, restProps, { children: annotatedChildren })), (0, jsx_runtime_1.jsx)("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "ndl-tree-view-new-live-region", children: announcement })] }));
245
541
  };
246
542
  /**
247
543
  * A single item within a `TreeView`. Renders a `role="treeitem"` element that
@@ -266,16 +562,23 @@ const Root = (_a) => {
266
562
  * ```
267
563
  */
268
564
  const Item = (_a) => {
269
- var { actionMenuItems, children, defaultExpanded = false, hasChildren: hasChildrenProp, isDisabled, isExpanded: isExpandedProp, isIndeterminate, isLoading, isSelected, leadingVisual, title, tooltipContent, tooltipProps, trailingContent, onExpandedChange, onSelectedChange, actionMenuProps, className, style, htmlAttributes, ref } = _a, restProps = __rest(_a, ["actionMenuItems", "children", "defaultExpanded", "hasChildren", "isDisabled", "isExpanded", "isIndeterminate", "isLoading", "isSelected", "leadingVisual", "title", "tooltipContent", "tooltipProps", "trailingContent", "onExpandedChange", "onSelectedChange", "actionMenuProps", "className", "style", "htmlAttributes", "ref"]);
565
+ var _b;
566
+ var { actionMenuItems, children, defaultExpanded = false, hasChildren: hasChildrenProp, id: idProp, isDisabled, isExpanded: isExpandedProp, isIndeterminate, isLoading, isSelectable: isSelectableProp, isSelected: isSelectedProp, leadingVisual, title, tooltipContent, tooltipProps, trailingContent, onExpandedChange, onSelectedChange, actionMenuProps, className, style, htmlAttributes, ref } = _a, restProps = __rest(_a, ["actionMenuItems", "children", "defaultExpanded", "hasChildren", "id", "isDisabled", "isExpanded", "isIndeterminate", "isLoading", "isSelectable", "isSelected", "leadingVisual", "title", "tooltipContent", "tooltipProps", "trailingContent", "onExpandedChange", "onSelectedChange", "actionMenuProps", "className", "style", "htmlAttributes", "ref"]);
270
567
  const generatedId = (0, react_2.useId)();
271
- const itemId = `ndl-treeitem-${generatedId}`;
568
+ const itemId = (_b = idProp !== null && idProp !== void 0 ? idProp : htmlAttributes === null || htmlAttributes === void 0 ? void 0 : htmlAttributes.id) !== null && _b !== void 0 ? _b : `ndl-treeitem-${generatedId}`;
272
569
  const titleId = `ndl-treeitem-title-${generatedId}`;
273
570
  const groupId = `ndl-treegroup-${generatedId}`;
274
571
  const itemRef = (0, react_2.useRef)(null);
275
572
  const actionButtonRef = (0, react_2.useRef)(null);
276
573
  const didOpenViaKeyboard = (0, react_2.useRef)(false);
277
574
  const [isMenuOpen, setIsMenuOpen] = (0, react_2.useState)(false);
278
- const { focusedId, level, selectionMode, setFocusedId } = (0, react_2.useContext)(TreeViewContext);
575
+ const treeViewContext = (0, react_2.useContext)(TreeViewContext);
576
+ const { isRootManaged, level, onSelectionIntent, parentId, registerItem, selectionMode, setFocusedId, store, } = treeViewContext;
577
+ // Primitive slices of the store, so Object.is bails out of re-renders for
578
+ // rows whose focus/selection/edge state did not change.
579
+ const isFocused = (0, react_2.useSyncExternalStore)(store.subscribe, () => store.getState().focusedId === itemId, () => store.getState().focusedId === itemId);
580
+ const isRootSelected = (0, react_2.useSyncExternalStore)(store.subscribe, () => store.getState().selectedIds.has(itemId), () => store.getState().selectedIds.has(itemId));
581
+ const edge = (0, react_2.useSyncExternalStore)(store.subscribe, () => store.getState().edgeById.get(itemId), () => store.getState().edgeById.get(itemId));
279
582
  const isControlledExpansion = isExpandedProp !== undefined;
280
583
  const [isExpanded, setIsExpanded] = (0, use_semi_controlled_state_1.useSemicontrolledState)({
281
584
  isControlled: isControlledExpansion,
@@ -283,7 +586,10 @@ const Item = (_a) => {
283
586
  state: isExpandedProp !== null && isExpandedProp !== void 0 ? isExpandedProp : defaultExpanded,
284
587
  });
285
588
  const hasChildren = hasChildrenProp !== null && hasChildrenProp !== void 0 ? hasChildrenProp : react_2.Children.count(children) > 0;
286
- const isSelectable = onSelectedChange !== undefined;
589
+ const isSelectable = isRootManaged
590
+ ? (isSelectableProp !== null && isSelectableProp !== void 0 ? isSelectableProp : true)
591
+ : (isSelectableProp !== null && isSelectableProp !== void 0 ? isSelectableProp : onSelectedChange !== undefined);
592
+ const isSelected = isRootManaged ? isRootSelected : (isSelectedProp !== null && isSelectedProp !== void 0 ? isSelectedProp : false);
287
593
  const isMultiple = selectionMode === 'multiple';
288
594
  const toggleExpanded = (0, react_2.useCallback)(() => {
289
595
  if (isDisabled) {
@@ -293,31 +599,46 @@ const Item = (_a) => {
293
599
  setIsExpanded(!isExpanded);
294
600
  }
295
601
  }, [isDisabled, hasChildren, setIsExpanded, isExpanded]);
296
- const handleSelect = (0, react_2.useCallback)(() => {
297
- if (isDisabled) {
602
+ (0, react_2.useLayoutEffect)(() => {
603
+ registerItem(itemId, {
604
+ isDisabled: isDisabled !== null && isDisabled !== void 0 ? isDisabled : false,
605
+ isSelectable,
606
+ isSelected,
607
+ onSelectedChange,
608
+ parentId,
609
+ toggleExpanded,
610
+ });
611
+ }, [
612
+ isDisabled,
613
+ isSelectable,
614
+ isSelected,
615
+ itemId,
616
+ onSelectedChange,
617
+ parentId,
618
+ registerItem,
619
+ toggleExpanded,
620
+ ]);
621
+ (0, react_2.useLayoutEffect)(() => {
622
+ return () => registerItem(itemId, null);
623
+ }, [itemId, registerItem]);
624
+ (0, react_2.useEffect)(() => {
625
+ if (!isRootManaged) {
298
626
  return;
299
627
  }
300
- onSelectedChange === null || onSelectedChange === void 0 ? void 0 : onSelectedChange(!isSelected);
301
- }, [isDisabled, isSelected, onSelectedChange]);
302
- const handleClick = (0, react_2.useCallback)(() => {
628
+ if (idProp === undefined) {
629
+ (0, utils_1.needleWarningMessage)('TreeView.Item needs an `id` when the tree manages selection with `selectedIds`.');
630
+ }
631
+ if (isSelectedProp !== undefined || onSelectedChange !== undefined) {
632
+ (0, utils_1.needleWarningMessage)('TreeView.Item `isSelected` and `onSelectedChange` are ignored when the tree manages selection with `selectedIds`.');
633
+ }
634
+ }, [idProp, isRootManaged, isSelectedProp, onSelectedChange]);
635
+ const handleClick = (0, react_2.useCallback)((event) => {
303
636
  if (isDisabled) {
304
637
  return;
305
638
  }
306
639
  setFocusedId(itemId);
307
- if (isSelectable) {
308
- handleSelect();
309
- }
310
- else {
311
- toggleExpanded();
312
- }
313
- }, [
314
- isDisabled,
315
- setFocusedId,
316
- itemId,
317
- isSelectable,
318
- handleSelect,
319
- toggleExpanded,
320
- ]);
640
+ onSelectionIntent(itemId, getSelectionIntent(event));
641
+ }, [isDisabled, setFocusedId, itemId, onSelectionIntent]);
321
642
  const handleChevronClick = (0, react_2.useCallback)((event) => {
322
643
  event.stopPropagation();
323
644
  if (isDisabled) {
@@ -347,15 +668,6 @@ const Item = (_a) => {
347
668
  setIsExpanded(false);
348
669
  }
349
670
  }
350
- else if (event.type === TREE_EVENTS.select) {
351
- event.stopPropagation();
352
- if (isSelectable) {
353
- handleSelect();
354
- }
355
- else {
356
- toggleExpanded();
357
- }
358
- }
359
671
  else if (event.type === TREE_EVENTS.action) {
360
672
  event.stopPropagation();
361
673
  if (actionMenuItems && !isDisabled) {
@@ -363,59 +675,51 @@ const Item = (_a) => {
363
675
  setIsMenuOpen(true);
364
676
  }
365
677
  }
366
- }, [
367
- actionMenuItems,
368
- hasChildren,
369
- isExpanded,
370
- isDisabled,
371
- isSelectable,
372
- setIsExpanded,
373
- handleSelect,
374
- toggleExpanded,
375
- ]);
678
+ }, [actionMenuItems, hasChildren, isExpanded, isDisabled, setIsExpanded]);
376
679
  const callbackRef = (0, react_2.useCallback)((node) => {
377
680
  const prev = itemRef.current;
378
681
  if (prev) {
379
682
  prev.removeEventListener(TREE_EVENTS.expand, handleCustomEvents);
380
683
  prev.removeEventListener(TREE_EVENTS.collapse, handleCustomEvents);
381
- prev.removeEventListener(TREE_EVENTS.select, handleCustomEvents);
382
684
  prev.removeEventListener(TREE_EVENTS.action, handleCustomEvents);
383
685
  }
384
686
  itemRef.current = node;
385
687
  if (node) {
386
688
  node.addEventListener(TREE_EVENTS.expand, handleCustomEvents);
387
689
  node.addEventListener(TREE_EVENTS.collapse, handleCustomEvents);
388
- node.addEventListener(TREE_EVENTS.select, handleCustomEvents);
389
690
  node.addEventListener(TREE_EVENTS.action, handleCustomEvents);
390
691
  }
391
692
  }, [handleCustomEvents]);
392
693
  const mergedRef = (0, react_1.useMergeRefs)([callbackRef, ref]);
393
- const isFocused = focusedId === itemId;
394
694
  const selectionAriaProps = (() => {
395
695
  if (!isSelectable) {
396
696
  return {};
397
697
  }
398
698
  if (isMultiple) {
399
699
  return {
400
- 'aria-checked': isIndeterminate
401
- ? 'mixed'
402
- : (isSelected !== null && isSelected !== void 0 ? isSelected : false),
700
+ 'aria-checked': isIndeterminate ? 'mixed' : isSelected,
403
701
  };
404
702
  }
405
- return { 'aria-selected': isSelected !== null && isSelected !== void 0 ? isSelected : false };
703
+ return { 'aria-selected': isSelected };
406
704
  })();
705
+ const edgeClass = edge === undefined ? undefined : `ndl-tree-view-new-item--selected-${edge}`;
706
+ const childContextValue = (0, react_2.useMemo)(() => (Object.assign(Object.assign({}, treeViewContext), { level: treeViewContext.level + 1, parentId: itemId })), [itemId, treeViewContext]);
707
+ // Same caching as in Root: keeps child element identity stable across
708
+ // renders of this item (selection, focus, expansion) so unchanged
709
+ // descendants bail out of re-rendering.
710
+ const annotatedChildren = (0, react_2.useMemo)(() => annotateTreeItems(children), [children]);
407
711
  const itemElement = (
408
712
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- keyboard handled on tree root; aria-selected/aria-checked applied via spread
409
- (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: mergedRef, id: itemId,
713
+ (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: mergedRef,
410
714
  // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-selected/aria-checked applied conditionally via spread
411
- role: "treeitem" }, selectionAriaProps, { "aria-expanded": hasChildren ? isExpanded : undefined, "aria-owns": hasChildren ? groupId : undefined, "aria-disabled": isDisabled || undefined, "aria-busy": isLoading || undefined, "aria-keyshortcuts": actionMenuItems ? 'Shift+F10' : undefined, "aria-labelledby": titleId, "aria-level": level, tabIndex: isFocused ? 0 : -1, className: (0, classnames_1.default)('ndl-tree-view-new-item', className, {
715
+ role: "treeitem" }, selectionAriaProps, { "aria-expanded": hasChildren ? isExpanded : undefined, "aria-owns": hasChildren ? groupId : undefined, "aria-disabled": isDisabled || undefined, "aria-busy": isLoading || undefined, "aria-keyshortcuts": actionMenuItems ? 'Shift+F10' : undefined, "aria-labelledby": titleId, "aria-level": level, tabIndex: isFocused ? 0 : -1, className: (0, classnames_1.default)('ndl-tree-view-new-item', className, edgeClass, {
412
716
  'ndl-tree-view-new-item--disabled': isDisabled,
413
717
  'ndl-tree-view-new-item--parent': hasChildren,
414
718
  'ndl-tree-view-new-item--selected': isSelected && isSelectable,
415
719
  }), style: Object.assign({ '--tree-level': level - 1 }, style), onClick: (event) => {
416
720
  event.stopPropagation();
417
- handleClick();
418
- } }, htmlAttributes, restProps, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)('ndl-tree-view-new-item-content', {
721
+ handleClick(event);
722
+ } }, htmlAttributes, restProps, { id: itemId, children: (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)('ndl-tree-view-new-item-content', {
419
723
  'ndl-tree-view-new-item-content--parent': hasChildren,
420
724
  }), children: [hasChildren && (
421
725
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- keyboard handled on tree root
@@ -434,7 +738,7 @@ const Item = (_a) => {
434
738
  },
435
739
  tabIndex: -1,
436
740
  }, children: (0, jsx_runtime_1.jsx)(EllipsisHorizontalIconOutline_1.EllipsisHorizontalIconOutline, {}) }), (0, jsx_runtime_1.jsx)(menu_1.Menu, Object.assign({ isOpen: isMenuOpen, anchorRef: actionButtonRef, onClose: handleMenuClose }, actionMenuProps, { children: actionMenuItems }))] }))] }) })));
437
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(conditional_wrap_1.ConditionalWrap, { shouldWrap: tooltipContent !== undefined && tooltipContent !== null, wrap: (children) => ((0, jsx_runtime_1.jsxs)(tooltip_1.Tooltip, Object.assign({ type: "simple", hoverDelay: { close: 0, open: 500 }, isDisabled: isMenuOpen, followCursor: "initial" }, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.root, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip.Trigger, Object.assign({ hasButtonWrapper: true }, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.trigger, { children: children })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip.Content, Object.assign({}, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.content, { children: tooltipContent }))] }))), children: itemElement }), hasChildren && ((0, jsx_runtime_1.jsx)(TreeViewContext.Provider, { value: { focusedId, level: level + 1, selectionMode, setFocusedId }, children: (0, jsx_runtime_1.jsx)("div", { role: "group", id: groupId, hidden: !isExpanded, style: { '--tree-level': level - 1 }, children: annotateTreeItems(children) }) }))] }));
741
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(conditional_wrap_1.ConditionalWrap, { shouldWrap: tooltipContent !== undefined && tooltipContent !== null, wrap: (children) => ((0, jsx_runtime_1.jsxs)(tooltip_1.Tooltip, Object.assign({ type: "simple", hoverDelay: { close: 0, open: 500 }, isDisabled: isMenuOpen, followCursor: "initial" }, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.root, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip.Trigger, Object.assign({ hasButtonWrapper: true }, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.trigger, { children: children })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip.Content, Object.assign({}, tooltipProps === null || tooltipProps === void 0 ? void 0 : tooltipProps.content, { children: tooltipContent }))] }))), children: itemElement }), hasChildren && ((0, jsx_runtime_1.jsx)(TreeViewContext.Provider, { value: childContextValue, children: (0, jsx_runtime_1.jsx)("div", { role: "group", id: groupId, hidden: !isExpanded, style: { '--tree-level': level - 1 }, children: annotatedChildren }) }))] }));
438
742
  };
439
743
  /**
440
744
  * A skeleton placeholder for tree items that are being loaded. Renders one or
@@ -446,7 +750,7 @@ const Item = (_a) => {
446
750
  *
447
751
  * @example
448
752
  * ```tsx
449
- * <TreeView.Item title="Lazy folder" hasChildren isLoading={isLoading} isExpanded>
753
+ * <TreeView.Item title="Lazy folder" hasChildren isLoading isExpanded>
450
754
  * {isLoading && <TreeView.SkeletonItem rows={3} />}
451
755
  * </TreeView.Item>
452
756
  * ```
@@ -456,6 +760,20 @@ const SkeletonItem = (_a) => {
456
760
  const { level } = (0, react_2.useContext)(TreeViewContext);
457
761
  return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, "aria-hidden": "true", className: (0, classnames_1.default)('ndl-tree-view-new-skeleton', className), style: Object.assign({ '--tree-level': level - 1 }, style) }, htmlAttributes, restProps, { children: Array.from({ length: rows }, (_unused, index) => ((0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { height: "18px", width: "100%" }, index))) })));
458
762
  };
763
+ /**
764
+ * @remarks
765
+ * Selection can be managed per item, with `isSelected` and `onSelectedChange`
766
+ * on each `TreeView.Item`, or by the tree itself, by passing `selectedIds` (or
767
+ * `defaultSelectedIds`) together with `onSelectedIdsChange`. When the tree
768
+ * manages selection, every item needs an `id`, and each item's own
769
+ * `isSelected` and `onSelectedChange` are ignored.
770
+ *
771
+ * The available selection modes are:
772
+ *
773
+ * - `single` — one item at a time. An unmodified click selects the clicked item and never clears it.
774
+ * - `multiple` — multi-select with checkboxes. An unmodified click toggles an item, and `Shift` repeats that across a range without clearing anything outside it.
775
+ * - `multiple-replace` — multi-select without checkboxes. An unmodified click selects only that item, `Cmd`/`Ctrl` toggles it and moves the anchor, and `Shift` extends from that anchor while retaining its selection snapshot. `Shift` + arrow extends the range.
776
+ */
459
777
  const TreeView = Object.assign(Root, { Item, SkeletonItem });
460
778
  exports.TreeView = TreeView;
461
779
  //# sourceMappingURL=TreeView.js.map