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