@neo4j-ndl/react 4.21.2 → 4.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/dropzone/stories/dropzone-full.story.js +5 -5
- package/lib/cjs/dropzone/stories/dropzone-full.story.js.map +1 -1
- package/lib/cjs/helpers/index.js +1 -0
- package/lib/cjs/helpers/index.js.map +1 -1
- package/lib/cjs/helpers/platform.js +59 -0
- package/lib/cjs/helpers/platform.js.map +1 -0
- package/lib/cjs/kbd/kbd-utils.js +6 -22
- package/lib/cjs/kbd/kbd-utils.js.map +1 -1
- package/lib/cjs/next/tree-view/TreeView.js +384 -66
- package/lib/cjs/next/tree-view/TreeView.js.map +1 -1
- package/lib/cjs/next/tree-view/selection.js +181 -0
- package/lib/cjs/next/tree-view/selection.js.map +1 -0
- package/lib/cjs/next/tree-view/stories/index.js +13 -1
- package/lib/cjs/next/tree-view/stories/index.js.map +1 -1
- package/lib/cjs/next/tree-view/stories/tree-view-large.story.js +57 -0
- package/lib/cjs/next/tree-view/stories/tree-view-large.story.js.map +1 -0
- package/lib/cjs/next/tree-view/stories/tree-view-lazy.story.js +1 -0
- package/lib/cjs/next/tree-view/stories/tree-view-lazy.story.js.map +1 -1
- package/lib/cjs/next/tree-view/stories/tree-view-multi.story.js +16 -40
- package/lib/cjs/next/tree-view/stories/tree-view-multi.story.js.map +1 -1
- package/lib/cjs/next/tree-view/stories/tree-view-multiple-replace.story.js +38 -0
- package/lib/cjs/next/tree-view/stories/tree-view-multiple-replace.story.js.map +1 -0
- package/lib/cjs/next/tree-view/stories/tree-view-non-selectable-folders.story.js +7 -4
- package/lib/cjs/next/tree-view/stories/tree-view-non-selectable-folders.story.js.map +1 -1
- package/lib/cjs/next/tree-view/stories/tree-view-root-selection.story.js +125 -0
- package/lib/cjs/next/tree-view/stories/tree-view-root-selection.story.js.map +1 -0
- package/lib/cjs/toast/ToastControlled.js +1 -1
- package/lib/cjs/toast/ToastControlled.js.map +1 -1
- package/lib/esm/dropzone/stories/dropzone-full.story.js +5 -5
- package/lib/esm/dropzone/stories/dropzone-full.story.js.map +1 -1
- package/lib/esm/helpers/index.js +1 -0
- package/lib/esm/helpers/index.js.map +1 -1
- package/lib/esm/helpers/platform.js +55 -0
- package/lib/esm/helpers/platform.js.map +1 -0
- package/lib/esm/kbd/kbd-utils.js +2 -18
- package/lib/esm/kbd/kbd-utils.js.map +1 -1
- package/lib/esm/next/tree-view/TreeView.js +385 -67
- package/lib/esm/next/tree-view/TreeView.js.map +1 -1
- package/lib/esm/next/tree-view/selection.js +175 -0
- package/lib/esm/next/tree-view/selection.js.map +1 -0
- package/lib/esm/next/tree-view/stories/index.js +9 -0
- package/lib/esm/next/tree-view/stories/index.js.map +1 -1
- package/lib/esm/next/tree-view/stories/tree-view-large.story.js +55 -0
- package/lib/esm/next/tree-view/stories/tree-view-large.story.js.map +1 -0
- package/lib/esm/next/tree-view/stories/tree-view-lazy.story.js +1 -0
- package/lib/esm/next/tree-view/stories/tree-view-lazy.story.js.map +1 -1
- package/lib/esm/next/tree-view/stories/tree-view-multi.story.js +16 -40
- package/lib/esm/next/tree-view/stories/tree-view-multi.story.js.map +1 -1
- package/lib/esm/next/tree-view/stories/tree-view-multiple-replace.story.js +36 -0
- package/lib/esm/next/tree-view/stories/tree-view-multiple-replace.story.js.map +1 -0
- package/lib/esm/next/tree-view/stories/tree-view-non-selectable-folders.story.js +7 -4
- package/lib/esm/next/tree-view/stories/tree-view-non-selectable-folders.story.js.map +1 -1
- package/lib/esm/next/tree-view/stories/tree-view-root-selection.story.js +123 -0
- package/lib/esm/next/tree-view/stories/tree-view-root-selection.story.js.map +1 -0
- package/lib/esm/toast/ToastControlled.js +1 -1
- package/lib/esm/toast/ToastControlled.js.map +1 -1
- package/lib/types/helpers/index.d.ts +1 -0
- package/lib/types/helpers/index.d.ts.map +1 -1
- package/lib/types/helpers/platform.d.ts +29 -0
- package/lib/types/helpers/platform.d.ts.map +1 -0
- package/lib/types/kbd/kbd-utils.d.ts +2 -5
- package/lib/types/kbd/kbd-utils.d.ts.map +1 -1
- package/lib/types/next/tree-view/TreeView.d.ts +29 -5
- package/lib/types/next/tree-view/TreeView.d.ts.map +1 -1
- package/lib/types/next/tree-view/selection.d.ts +78 -0
- package/lib/types/next/tree-view/selection.d.ts.map +1 -0
- package/lib/types/next/tree-view/stories/index.d.ts +6 -0
- package/lib/types/next/tree-view/stories/index.d.ts.map +1 -1
- package/lib/types/next/tree-view/stories/tree-view-large.story.d.ts +24 -0
- package/lib/types/next/tree-view/stories/tree-view-large.story.d.ts.map +1 -0
- package/lib/types/next/tree-view/stories/tree-view-lazy.story.d.ts.map +1 -1
- package/lib/types/next/tree-view/stories/tree-view-multi.story.d.ts +10 -0
- package/lib/types/next/tree-view/stories/tree-view-multi.story.d.ts.map +1 -1
- package/lib/types/next/tree-view/stories/tree-view-multiple-replace.story.d.ts +29 -0
- package/lib/types/next/tree-view/stories/tree-view-multiple-replace.story.d.ts.map +1 -0
- package/lib/types/next/tree-view/stories/tree-view-non-selectable-folders.story.d.ts +5 -0
- package/lib/types/next/tree-view/stories/tree-view-non-selectable-folders.story.d.ts.map +1 -1
- package/lib/types/next/tree-view/stories/tree-view-root-selection.story.d.ts +24 -0
- package/lib/types/next/tree-view/stories/tree-view-root-selection.story.d.ts.map +1 -0
- package/package.json +2 -2
- package/skills/ndl-react/SKILL.md +1 -1
- package/skills/ndl-react/components/next/tree-view.md +358 -104
|
@@ -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
|
-
|
|
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"]
|
|
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
|
|
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 [
|
|
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.
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
243
|
-
const classes = (0, classnames_1.default)(className, 'ndl-tree-view-new'
|
|
244
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
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
|