@mui/x-tree-view 9.10.0 → 9.11.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/CHANGELOG.md +224 -0
- package/README.md +1 -11
- package/RichTreeView/RichTreeView.js +2 -0
- package/RichTreeView/RichTreeView.mjs +2 -0
- package/SimpleTreeView/SimpleTreeView.js +1 -0
- package/SimpleTreeView/SimpleTreeView.mjs +1 -0
- package/TreeItemIcon/TreeItemIcon.js +1 -0
- package/TreeItemIcon/TreeItemIcon.mjs +1 -0
- package/hooks/useTreeItemUtils/useTreeItemUtils.js +2 -0
- package/hooks/useTreeItemUtils/useTreeItemUtils.mjs +2 -0
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internals/MinimalTreeViewStore/MinimalTreeViewStore.d.mts +2 -1
- package/internals/MinimalTreeViewStore/MinimalTreeViewStore.d.ts +2 -1
- package/internals/RichTreeViewStore/RichTreeViewStore.d.mts +8 -2
- package/internals/RichTreeViewStore/RichTreeViewStore.d.ts +8 -2
- package/internals/RichTreeViewStore/RichTreeViewStore.js +5 -2
- package/internals/RichTreeViewStore/RichTreeViewStore.mjs +5 -2
- package/internals/plugins/items/TreeViewItemsPlugin.d.mts +27 -0
- package/internals/plugins/items/TreeViewItemsPlugin.d.ts +27 -0
- package/internals/plugins/items/TreeViewItemsPlugin.js +81 -35
- package/internals/plugins/items/TreeViewItemsPlugin.mjs +82 -36
- package/internals/plugins/items/utils.d.mts +28 -0
- package/internals/plugins/items/utils.d.ts +28 -0
- package/internals/plugins/items/utils.js +49 -0
- package/internals/plugins/items/utils.mjs +48 -0
- package/internals/plugins/selection/TreeViewSelectionPlugin.d.mts +15 -0
- package/internals/plugins/selection/TreeViewSelectionPlugin.d.ts +15 -0
- package/internals/plugins/selection/TreeViewSelectionPlugin.js +66 -17
- package/internals/plugins/selection/TreeViewSelectionPlugin.mjs +66 -17
- package/internals/plugins/selection/itemPlugin.js +13 -36
- package/internals/plugins/selection/itemPlugin.mjs +14 -37
- package/internals/plugins/selection/selectors.d.mts +11 -0
- package/internals/plugins/selection/selectors.d.ts +11 -0
- package/internals/plugins/selection/selectors.js +56 -3
- package/internals/plugins/selection/selectors.mjs +56 -3
- package/models/items.d.mts +2 -1
- package/models/items.d.ts +2 -1
- package/package.json +3 -3
- package/useTreeItem/useTreeItem.types.d.mts +4 -0
- package/useTreeItem/useTreeItem.types.d.ts +4 -0
|
@@ -41,10 +41,16 @@ class TreeViewSelectionPlugin {
|
|
|
41
41
|
} else {
|
|
42
42
|
cleanModel = newModel;
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
// The store is updated before the callbacks are fired,
|
|
46
|
+
// so that the selection selectors and the `getItemSelection` API method
|
|
47
|
+
// return the new selection status when called from `onItemSelectionToggle` or `onSelectedItemsChange`.
|
|
48
|
+
if (selectedItems === undefined) {
|
|
49
|
+
this.store.set('selectedItems', cleanModel);
|
|
50
|
+
}
|
|
44
51
|
if (onItemSelectionToggle) {
|
|
45
52
|
if (isMultiSelectEnabled) {
|
|
46
53
|
const changes = getAddedAndRemovedItems({
|
|
47
|
-
store: this.store,
|
|
48
54
|
newModel: cleanModel,
|
|
49
55
|
oldModel: oldModel
|
|
50
56
|
});
|
|
@@ -65,9 +71,6 @@ class TreeViewSelectionPlugin {
|
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
|
-
if (selectedItems === undefined) {
|
|
69
|
-
this.store.set('selectedItems', cleanModel);
|
|
70
|
-
}
|
|
71
74
|
onSelectedItemsChange?.(event, cleanModel);
|
|
72
75
|
};
|
|
73
76
|
selectRange = (event, [start, end]) => {
|
|
@@ -91,12 +94,39 @@ class TreeViewSelectionPlugin {
|
|
|
91
94
|
this.setSelectedItems(event, newSelectedItems);
|
|
92
95
|
this.lastSelectedRange = getLookupFromArray(range);
|
|
93
96
|
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the selection status of an item.
|
|
100
|
+
* An item that is not selected is `indeterminate` when some of its selectable descendants are selected.
|
|
101
|
+
* @param {TreeViewItemId} itemId The id of the item to get the selection status of.
|
|
102
|
+
* @returns {TreeViewItemSelectionStatus} The selection status of the item.
|
|
103
|
+
*/
|
|
104
|
+
getItemSelection = itemId => _selectors.selectionSelectors.itemSelectionStatus(this.store.state, itemId);
|
|
94
105
|
buildPublicAPI = () => {
|
|
95
106
|
return {
|
|
107
|
+
getItemSelection: this.getItemSelection,
|
|
96
108
|
setItemSelection: this.setItemSelection
|
|
97
109
|
};
|
|
98
110
|
};
|
|
99
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Select the items added below a selected parent when the selection propagates to the descendants.
|
|
114
|
+
* @param {TreeViewItemId | null} parentId The id of the item the new items were added to.
|
|
115
|
+
* @param {TreeViewItemId[]} newItemIds The ids of the items that were just added.
|
|
116
|
+
*/
|
|
117
|
+
propagateSelectionToNewItems = (parentId, newItemIds) => {
|
|
118
|
+
const {
|
|
119
|
+
selectionPropagation = _empty.EMPTY_OBJECT
|
|
120
|
+
} = this.store.parameters;
|
|
121
|
+
if (parentId == null || newItemIds.length === 0 || !selectionPropagation.descendants || !_selectors.selectionSelectors.isMultiSelectEnabled(this.store.state) || !_selectors.selectionSelectors.isItemSelected(this.store.state, parentId)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only propagate to the new items, the rest of the parent's subtree is already up to date.
|
|
126
|
+
const newModel = _selectors.selectionSelectors.selectedItems(this.store.state).concat(newItemIds);
|
|
127
|
+
this.setSelectedItems(null, newModel, newItemIds);
|
|
128
|
+
};
|
|
129
|
+
|
|
100
130
|
/**
|
|
101
131
|
* Select or deselect an item.
|
|
102
132
|
* @param {object} parameters The parameters of the method.
|
|
@@ -116,7 +146,7 @@ class TreeViewSelectionPlugin {
|
|
|
116
146
|
}
|
|
117
147
|
let newSelected;
|
|
118
148
|
const isMultiSelectEnabled = _selectors.selectionSelectors.isMultiSelectEnabled(this.store.state);
|
|
119
|
-
if (keepExistingSelection) {
|
|
149
|
+
if (keepExistingSelection && isMultiSelectEnabled) {
|
|
120
150
|
const oldSelected = _selectors.selectionSelectors.selectedItems(this.store.state);
|
|
121
151
|
const isSelectedBefore = _selectors.selectionSelectors.isItemSelected(this.store.state, itemId);
|
|
122
152
|
if (isSelectedBefore && (shouldBeSelected === false || shouldBeSelected == null)) {
|
|
@@ -199,7 +229,9 @@ class TreeViewSelectionPlugin {
|
|
|
199
229
|
}
|
|
200
230
|
let newSelectedItems = _selectors.selectionSelectors.selectedItems(this.store.state).slice();
|
|
201
231
|
if (Object.keys(this.lastSelectedRange).length === 0) {
|
|
202
|
-
|
|
232
|
+
if (!_selectors.selectionSelectors.isItemSelected(this.store.state, nextItem)) {
|
|
233
|
+
newSelectedItems.push(nextItem);
|
|
234
|
+
}
|
|
203
235
|
this.lastSelectedRange = {
|
|
204
236
|
[currentItem]: true,
|
|
205
237
|
[nextItem]: true
|
|
@@ -212,7 +244,9 @@ class TreeViewSelectionPlugin {
|
|
|
212
244
|
newSelectedItems = newSelectedItems.filter(id => id !== currentItem);
|
|
213
245
|
delete this.lastSelectedRange[currentItem];
|
|
214
246
|
} else {
|
|
215
|
-
|
|
247
|
+
if (!_selectors.selectionSelectors.isItemSelected(this.store.state, nextItem)) {
|
|
248
|
+
newSelectedItems.push(nextItem);
|
|
249
|
+
}
|
|
216
250
|
this.lastSelectedRange[nextItem] = true;
|
|
217
251
|
}
|
|
218
252
|
}
|
|
@@ -233,7 +267,6 @@ function propagateSelection({
|
|
|
233
267
|
let shouldRegenerateModel = false;
|
|
234
268
|
const newModelLookup = getLookupFromArray(newModel);
|
|
235
269
|
const changes = getAddedAndRemovedItems({
|
|
236
|
-
store,
|
|
237
270
|
newModel,
|
|
238
271
|
oldModel
|
|
239
272
|
});
|
|
@@ -250,20 +283,27 @@ function propagateSelection({
|
|
|
250
283
|
if (selectionPropagation.descendants) {
|
|
251
284
|
const selectDescendants = itemId => {
|
|
252
285
|
if (itemId !== addedItemId) {
|
|
253
|
-
|
|
254
|
-
|
|
286
|
+
if (_selectors.selectionSelectors.canItemBeSelected(store.state, itemId)) {
|
|
287
|
+
shouldRegenerateModel = true;
|
|
288
|
+
newModelLookup[itemId] = true;
|
|
289
|
+
}
|
|
255
290
|
}
|
|
256
291
|
_items.itemsSelectors.itemOrderedChildrenIds(store.state, itemId).forEach(selectDescendants);
|
|
257
292
|
};
|
|
258
293
|
selectDescendants(addedItemId);
|
|
259
294
|
}
|
|
260
295
|
if (selectionPropagation.parents) {
|
|
261
|
-
const
|
|
296
|
+
const checkAllSelectableDescendantsSelected = itemId => {
|
|
297
|
+
if (!_selectors.selectionSelectors.canItemBeSelected(store.state, itemId)) {
|
|
298
|
+
// Non-selectable items don't count; still recurse for isItemSelectionDisabled case
|
|
299
|
+
const children = _items.itemsSelectors.itemOrderedChildrenIds(store.state, itemId);
|
|
300
|
+
return children.every(checkAllSelectableDescendantsSelected);
|
|
301
|
+
}
|
|
262
302
|
if (!newModelLookup[itemId]) {
|
|
263
303
|
return false;
|
|
264
304
|
}
|
|
265
305
|
const children = _items.itemsSelectors.itemOrderedChildrenIds(store.state, itemId);
|
|
266
|
-
return children.every(
|
|
306
|
+
return children.every(checkAllSelectableDescendantsSelected);
|
|
267
307
|
};
|
|
268
308
|
const selectParents = itemId => {
|
|
269
309
|
const parentId = _items.itemsSelectors.itemParentId(store.state, itemId);
|
|
@@ -271,9 +311,11 @@ function propagateSelection({
|
|
|
271
311
|
return;
|
|
272
312
|
}
|
|
273
313
|
const siblings = _items.itemsSelectors.itemOrderedChildrenIds(store.state, parentId);
|
|
274
|
-
if (siblings.every(
|
|
275
|
-
|
|
276
|
-
|
|
314
|
+
if (siblings.every(checkAllSelectableDescendantsSelected)) {
|
|
315
|
+
if (_selectors.selectionSelectors.canItemBeSelected(store.state, parentId)) {
|
|
316
|
+
shouldRegenerateModel = true;
|
|
317
|
+
newModelLookup[parentId] = true;
|
|
318
|
+
}
|
|
277
319
|
selectParents(parentId);
|
|
278
320
|
}
|
|
279
321
|
};
|
|
@@ -304,8 +346,11 @@ function propagateSelection({
|
|
|
304
346
|
});
|
|
305
347
|
return shouldRegenerateModel ? Object.keys(newModelLookup) : newModel;
|
|
306
348
|
}
|
|
349
|
+
|
|
350
|
+
// This method only diffs the two models it receives,
|
|
351
|
+
// it must not read the selection from the store,
|
|
352
|
+
// otherwise the result would depend on whether the store has already been updated or not.
|
|
307
353
|
function getAddedAndRemovedItems({
|
|
308
|
-
store,
|
|
309
354
|
oldModel,
|
|
310
355
|
newModel
|
|
311
356
|
}) {
|
|
@@ -313,8 +358,12 @@ function getAddedAndRemovedItems({
|
|
|
313
358
|
newModel.forEach(id => {
|
|
314
359
|
newModelMap.set(id, true);
|
|
315
360
|
});
|
|
361
|
+
const oldModelMap = new Map();
|
|
362
|
+
oldModel.forEach(id => {
|
|
363
|
+
oldModelMap.set(id, true);
|
|
364
|
+
});
|
|
316
365
|
return {
|
|
317
|
-
added: newModel.filter(itemId => !
|
|
366
|
+
added: newModel.filter(itemId => !oldModelMap.has(itemId)),
|
|
318
367
|
removed: oldModel.filter(itemId => !newModelMap.has(itemId))
|
|
319
368
|
};
|
|
320
369
|
}
|
|
@@ -34,10 +34,16 @@ export class TreeViewSelectionPlugin {
|
|
|
34
34
|
} else {
|
|
35
35
|
cleanModel = newModel;
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
// The store is updated before the callbacks are fired,
|
|
39
|
+
// so that the selection selectors and the `getItemSelection` API method
|
|
40
|
+
// return the new selection status when called from `onItemSelectionToggle` or `onSelectedItemsChange`.
|
|
41
|
+
if (selectedItems === undefined) {
|
|
42
|
+
this.store.set('selectedItems', cleanModel);
|
|
43
|
+
}
|
|
37
44
|
if (onItemSelectionToggle) {
|
|
38
45
|
if (isMultiSelectEnabled) {
|
|
39
46
|
const changes = getAddedAndRemovedItems({
|
|
40
|
-
store: this.store,
|
|
41
47
|
newModel: cleanModel,
|
|
42
48
|
oldModel: oldModel
|
|
43
49
|
});
|
|
@@ -58,9 +64,6 @@ export class TreeViewSelectionPlugin {
|
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
|
-
if (selectedItems === undefined) {
|
|
62
|
-
this.store.set('selectedItems', cleanModel);
|
|
63
|
-
}
|
|
64
67
|
onSelectedItemsChange?.(event, cleanModel);
|
|
65
68
|
};
|
|
66
69
|
selectRange = (event, [start, end]) => {
|
|
@@ -84,12 +87,39 @@ export class TreeViewSelectionPlugin {
|
|
|
84
87
|
this.setSelectedItems(event, newSelectedItems);
|
|
85
88
|
this.lastSelectedRange = getLookupFromArray(range);
|
|
86
89
|
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the selection status of an item.
|
|
93
|
+
* An item that is not selected is `indeterminate` when some of its selectable descendants are selected.
|
|
94
|
+
* @param {TreeViewItemId} itemId The id of the item to get the selection status of.
|
|
95
|
+
* @returns {TreeViewItemSelectionStatus} The selection status of the item.
|
|
96
|
+
*/
|
|
97
|
+
getItemSelection = itemId => selectionSelectors.itemSelectionStatus(this.store.state, itemId);
|
|
87
98
|
buildPublicAPI = () => {
|
|
88
99
|
return {
|
|
100
|
+
getItemSelection: this.getItemSelection,
|
|
89
101
|
setItemSelection: this.setItemSelection
|
|
90
102
|
};
|
|
91
103
|
};
|
|
92
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Select the items added below a selected parent when the selection propagates to the descendants.
|
|
107
|
+
* @param {TreeViewItemId | null} parentId The id of the item the new items were added to.
|
|
108
|
+
* @param {TreeViewItemId[]} newItemIds The ids of the items that were just added.
|
|
109
|
+
*/
|
|
110
|
+
propagateSelectionToNewItems = (parentId, newItemIds) => {
|
|
111
|
+
const {
|
|
112
|
+
selectionPropagation = EMPTY_OBJECT
|
|
113
|
+
} = this.store.parameters;
|
|
114
|
+
if (parentId == null || newItemIds.length === 0 || !selectionPropagation.descendants || !selectionSelectors.isMultiSelectEnabled(this.store.state) || !selectionSelectors.isItemSelected(this.store.state, parentId)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Only propagate to the new items, the rest of the parent's subtree is already up to date.
|
|
119
|
+
const newModel = selectionSelectors.selectedItems(this.store.state).concat(newItemIds);
|
|
120
|
+
this.setSelectedItems(null, newModel, newItemIds);
|
|
121
|
+
};
|
|
122
|
+
|
|
93
123
|
/**
|
|
94
124
|
* Select or deselect an item.
|
|
95
125
|
* @param {object} parameters The parameters of the method.
|
|
@@ -109,7 +139,7 @@ export class TreeViewSelectionPlugin {
|
|
|
109
139
|
}
|
|
110
140
|
let newSelected;
|
|
111
141
|
const isMultiSelectEnabled = selectionSelectors.isMultiSelectEnabled(this.store.state);
|
|
112
|
-
if (keepExistingSelection) {
|
|
142
|
+
if (keepExistingSelection && isMultiSelectEnabled) {
|
|
113
143
|
const oldSelected = selectionSelectors.selectedItems(this.store.state);
|
|
114
144
|
const isSelectedBefore = selectionSelectors.isItemSelected(this.store.state, itemId);
|
|
115
145
|
if (isSelectedBefore && (shouldBeSelected === false || shouldBeSelected == null)) {
|
|
@@ -192,7 +222,9 @@ export class TreeViewSelectionPlugin {
|
|
|
192
222
|
}
|
|
193
223
|
let newSelectedItems = selectionSelectors.selectedItems(this.store.state).slice();
|
|
194
224
|
if (Object.keys(this.lastSelectedRange).length === 0) {
|
|
195
|
-
|
|
225
|
+
if (!selectionSelectors.isItemSelected(this.store.state, nextItem)) {
|
|
226
|
+
newSelectedItems.push(nextItem);
|
|
227
|
+
}
|
|
196
228
|
this.lastSelectedRange = {
|
|
197
229
|
[currentItem]: true,
|
|
198
230
|
[nextItem]: true
|
|
@@ -205,7 +237,9 @@ export class TreeViewSelectionPlugin {
|
|
|
205
237
|
newSelectedItems = newSelectedItems.filter(id => id !== currentItem);
|
|
206
238
|
delete this.lastSelectedRange[currentItem];
|
|
207
239
|
} else {
|
|
208
|
-
|
|
240
|
+
if (!selectionSelectors.isItemSelected(this.store.state, nextItem)) {
|
|
241
|
+
newSelectedItems.push(nextItem);
|
|
242
|
+
}
|
|
209
243
|
this.lastSelectedRange[nextItem] = true;
|
|
210
244
|
}
|
|
211
245
|
}
|
|
@@ -225,7 +259,6 @@ function propagateSelection({
|
|
|
225
259
|
let shouldRegenerateModel = false;
|
|
226
260
|
const newModelLookup = getLookupFromArray(newModel);
|
|
227
261
|
const changes = getAddedAndRemovedItems({
|
|
228
|
-
store,
|
|
229
262
|
newModel,
|
|
230
263
|
oldModel
|
|
231
264
|
});
|
|
@@ -242,20 +275,27 @@ function propagateSelection({
|
|
|
242
275
|
if (selectionPropagation.descendants) {
|
|
243
276
|
const selectDescendants = itemId => {
|
|
244
277
|
if (itemId !== addedItemId) {
|
|
245
|
-
|
|
246
|
-
|
|
278
|
+
if (selectionSelectors.canItemBeSelected(store.state, itemId)) {
|
|
279
|
+
shouldRegenerateModel = true;
|
|
280
|
+
newModelLookup[itemId] = true;
|
|
281
|
+
}
|
|
247
282
|
}
|
|
248
283
|
itemsSelectors.itemOrderedChildrenIds(store.state, itemId).forEach(selectDescendants);
|
|
249
284
|
};
|
|
250
285
|
selectDescendants(addedItemId);
|
|
251
286
|
}
|
|
252
287
|
if (selectionPropagation.parents) {
|
|
253
|
-
const
|
|
288
|
+
const checkAllSelectableDescendantsSelected = itemId => {
|
|
289
|
+
if (!selectionSelectors.canItemBeSelected(store.state, itemId)) {
|
|
290
|
+
// Non-selectable items don't count; still recurse for isItemSelectionDisabled case
|
|
291
|
+
const children = itemsSelectors.itemOrderedChildrenIds(store.state, itemId);
|
|
292
|
+
return children.every(checkAllSelectableDescendantsSelected);
|
|
293
|
+
}
|
|
254
294
|
if (!newModelLookup[itemId]) {
|
|
255
295
|
return false;
|
|
256
296
|
}
|
|
257
297
|
const children = itemsSelectors.itemOrderedChildrenIds(store.state, itemId);
|
|
258
|
-
return children.every(
|
|
298
|
+
return children.every(checkAllSelectableDescendantsSelected);
|
|
259
299
|
};
|
|
260
300
|
const selectParents = itemId => {
|
|
261
301
|
const parentId = itemsSelectors.itemParentId(store.state, itemId);
|
|
@@ -263,9 +303,11 @@ function propagateSelection({
|
|
|
263
303
|
return;
|
|
264
304
|
}
|
|
265
305
|
const siblings = itemsSelectors.itemOrderedChildrenIds(store.state, parentId);
|
|
266
|
-
if (siblings.every(
|
|
267
|
-
|
|
268
|
-
|
|
306
|
+
if (siblings.every(checkAllSelectableDescendantsSelected)) {
|
|
307
|
+
if (selectionSelectors.canItemBeSelected(store.state, parentId)) {
|
|
308
|
+
shouldRegenerateModel = true;
|
|
309
|
+
newModelLookup[parentId] = true;
|
|
310
|
+
}
|
|
269
311
|
selectParents(parentId);
|
|
270
312
|
}
|
|
271
313
|
};
|
|
@@ -296,8 +338,11 @@ function propagateSelection({
|
|
|
296
338
|
});
|
|
297
339
|
return shouldRegenerateModel ? Object.keys(newModelLookup) : newModel;
|
|
298
340
|
}
|
|
341
|
+
|
|
342
|
+
// This method only diffs the two models it receives,
|
|
343
|
+
// it must not read the selection from the store,
|
|
344
|
+
// otherwise the result would depend on whether the store has already been updated or not.
|
|
299
345
|
function getAddedAndRemovedItems({
|
|
300
|
-
store,
|
|
301
346
|
oldModel,
|
|
302
347
|
newModel
|
|
303
348
|
}) {
|
|
@@ -305,8 +350,12 @@ function getAddedAndRemovedItems({
|
|
|
305
350
|
newModel.forEach(id => {
|
|
306
351
|
newModelMap.set(id, true);
|
|
307
352
|
});
|
|
353
|
+
const oldModelMap = new Map();
|
|
354
|
+
oldModel.forEach(id => {
|
|
355
|
+
oldModelMap.set(id, true);
|
|
356
|
+
});
|
|
308
357
|
return {
|
|
309
|
-
added: newModel.filter(itemId => !
|
|
358
|
+
added: newModel.filter(itemId => !oldModelMap.has(itemId)),
|
|
310
359
|
removed: oldModel.filter(itemId => !newModelMap.has(itemId))
|
|
311
360
|
};
|
|
312
361
|
}
|
|
@@ -8,38 +8,6 @@ var _store = require("@mui/x-internals/store");
|
|
|
8
8
|
var _TreeViewProvider = require("../../TreeViewProvider");
|
|
9
9
|
var _selectors = require("../items/selectors");
|
|
10
10
|
var _selectors2 = require("./selectors");
|
|
11
|
-
const selectorCheckboxSelectionStatus = (0, _store.createSelector)((state, itemId) => {
|
|
12
|
-
if (_selectors2.selectionSelectors.isItemSelected(state, itemId)) {
|
|
13
|
-
return 'checked';
|
|
14
|
-
}
|
|
15
|
-
let hasSelectedDescendant = false;
|
|
16
|
-
let hasUnSelectedDescendant = false;
|
|
17
|
-
const traverseDescendants = itemToTraverseId => {
|
|
18
|
-
if (itemToTraverseId !== itemId) {
|
|
19
|
-
if (_selectors2.selectionSelectors.isItemSelected(state, itemToTraverseId)) {
|
|
20
|
-
hasSelectedDescendant = true;
|
|
21
|
-
} else {
|
|
22
|
-
hasUnSelectedDescendant = true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
_selectors.itemsSelectors.itemOrderedChildrenIds(state, itemToTraverseId).forEach(traverseDescendants);
|
|
26
|
-
};
|
|
27
|
-
traverseDescendants(itemId);
|
|
28
|
-
const shouldSelectBasedOnDescendants = _selectors2.selectionSelectors.propagationRules(state).parents;
|
|
29
|
-
if (shouldSelectBasedOnDescendants) {
|
|
30
|
-
if (hasSelectedDescendant && hasUnSelectedDescendant) {
|
|
31
|
-
return 'indeterminate';
|
|
32
|
-
}
|
|
33
|
-
if (hasSelectedDescendant && !hasUnSelectedDescendant) {
|
|
34
|
-
return 'checked';
|
|
35
|
-
}
|
|
36
|
-
return 'empty';
|
|
37
|
-
}
|
|
38
|
-
if (hasSelectedDescendant) {
|
|
39
|
-
return 'indeterminate';
|
|
40
|
-
}
|
|
41
|
-
return 'empty';
|
|
42
|
-
});
|
|
43
11
|
const useSelectionItemPlugin = ({
|
|
44
12
|
props
|
|
45
13
|
}) => {
|
|
@@ -52,19 +20,28 @@ const useSelectionItemPlugin = ({
|
|
|
52
20
|
const isCheckboxSelectionEnabled = (0, _store.useStore)(store, _selectors2.selectionSelectors.isCheckboxSelectionEnabled);
|
|
53
21
|
const isFeatureEnabledForItem = (0, _store.useStore)(store, _selectors2.selectionSelectors.isFeatureEnabledForItem, itemId);
|
|
54
22
|
const canItemBeSelected = (0, _store.useStore)(store, _selectors2.selectionSelectors.canItemBeSelected, itemId);
|
|
55
|
-
const
|
|
23
|
+
const isItemDisabled = (0, _store.useStore)(store, _selectors.itemsSelectors.isItemDisabled, itemId);
|
|
24
|
+
const isItemSelectable = (0, _store.useStore)(store, _selectors2.selectionSelectors.isItemSelectable, itemId);
|
|
25
|
+
const selectionStatus = (0, _store.useStore)(store, _selectors2.selectionSelectors.itemSelectionStatus, itemId);
|
|
26
|
+
|
|
27
|
+
// An item is "inherently not selectable" when disabled or excluded via isItemSelectionDisabled,
|
|
28
|
+
// regardless of the global disableSelection flag. Such items must not have aria-checked.
|
|
29
|
+
const isItemInherentlyNotSelectable = isItemDisabled || !isItemSelectable;
|
|
56
30
|
return {
|
|
57
31
|
propsEnhancers: {
|
|
58
32
|
root: () => {
|
|
59
33
|
// https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
|
|
60
34
|
let ariaChecked;
|
|
61
|
-
if (
|
|
35
|
+
if (isItemInherentlyNotSelectable) {
|
|
36
|
+
// - if the tree contains nodes that are not selectable, aria-checked is not present on those nodes.
|
|
37
|
+
ariaChecked = undefined;
|
|
38
|
+
} else if (selectionStatus === 'selected') {
|
|
62
39
|
// - each selected node has aria-checked set to true.
|
|
63
40
|
ariaChecked = true;
|
|
64
41
|
} else if (selectionStatus === 'indeterminate') {
|
|
65
42
|
ariaChecked = 'mixed';
|
|
66
43
|
} else if (!canItemBeSelected) {
|
|
67
|
-
//
|
|
44
|
+
// disableSelection=true with an unselected item: aria-checked is not present.
|
|
68
45
|
ariaChecked = undefined;
|
|
69
46
|
} else {
|
|
70
47
|
// - all nodes that are selectable but not selected have aria-checked set to false.
|
|
@@ -93,7 +70,7 @@ const useSelectionItemPlugin = ({
|
|
|
93
70
|
onChange: handleChange,
|
|
94
71
|
visible: isCheckboxSelectionEnabled && isFeatureEnabledForItem,
|
|
95
72
|
disabled: !canItemBeSelected,
|
|
96
|
-
checked: selectionStatus === '
|
|
73
|
+
checked: selectionStatus === 'selected',
|
|
97
74
|
indeterminate: selectionStatus === 'indeterminate'
|
|
98
75
|
};
|
|
99
76
|
}
|
|
@@ -1,39 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useStore } from '@mui/x-internals/store';
|
|
2
2
|
import { useTreeViewContext } from "../../TreeViewProvider/index.mjs";
|
|
3
3
|
import { itemsSelectors } from "../items/selectors.mjs";
|
|
4
4
|
import { selectionSelectors } from "./selectors.mjs";
|
|
5
|
-
const selectorCheckboxSelectionStatus = createSelector((state, itemId) => {
|
|
6
|
-
if (selectionSelectors.isItemSelected(state, itemId)) {
|
|
7
|
-
return 'checked';
|
|
8
|
-
}
|
|
9
|
-
let hasSelectedDescendant = false;
|
|
10
|
-
let hasUnSelectedDescendant = false;
|
|
11
|
-
const traverseDescendants = itemToTraverseId => {
|
|
12
|
-
if (itemToTraverseId !== itemId) {
|
|
13
|
-
if (selectionSelectors.isItemSelected(state, itemToTraverseId)) {
|
|
14
|
-
hasSelectedDescendant = true;
|
|
15
|
-
} else {
|
|
16
|
-
hasUnSelectedDescendant = true;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
itemsSelectors.itemOrderedChildrenIds(state, itemToTraverseId).forEach(traverseDescendants);
|
|
20
|
-
};
|
|
21
|
-
traverseDescendants(itemId);
|
|
22
|
-
const shouldSelectBasedOnDescendants = selectionSelectors.propagationRules(state).parents;
|
|
23
|
-
if (shouldSelectBasedOnDescendants) {
|
|
24
|
-
if (hasSelectedDescendant && hasUnSelectedDescendant) {
|
|
25
|
-
return 'indeterminate';
|
|
26
|
-
}
|
|
27
|
-
if (hasSelectedDescendant && !hasUnSelectedDescendant) {
|
|
28
|
-
return 'checked';
|
|
29
|
-
}
|
|
30
|
-
return 'empty';
|
|
31
|
-
}
|
|
32
|
-
if (hasSelectedDescendant) {
|
|
33
|
-
return 'indeterminate';
|
|
34
|
-
}
|
|
35
|
-
return 'empty';
|
|
36
|
-
});
|
|
37
5
|
export const useSelectionItemPlugin = ({
|
|
38
6
|
props
|
|
39
7
|
}) => {
|
|
@@ -46,19 +14,28 @@ export const useSelectionItemPlugin = ({
|
|
|
46
14
|
const isCheckboxSelectionEnabled = useStore(store, selectionSelectors.isCheckboxSelectionEnabled);
|
|
47
15
|
const isFeatureEnabledForItem = useStore(store, selectionSelectors.isFeatureEnabledForItem, itemId);
|
|
48
16
|
const canItemBeSelected = useStore(store, selectionSelectors.canItemBeSelected, itemId);
|
|
49
|
-
const
|
|
17
|
+
const isItemDisabled = useStore(store, itemsSelectors.isItemDisabled, itemId);
|
|
18
|
+
const isItemSelectable = useStore(store, selectionSelectors.isItemSelectable, itemId);
|
|
19
|
+
const selectionStatus = useStore(store, selectionSelectors.itemSelectionStatus, itemId);
|
|
20
|
+
|
|
21
|
+
// An item is "inherently not selectable" when disabled or excluded via isItemSelectionDisabled,
|
|
22
|
+
// regardless of the global disableSelection flag. Such items must not have aria-checked.
|
|
23
|
+
const isItemInherentlyNotSelectable = isItemDisabled || !isItemSelectable;
|
|
50
24
|
return {
|
|
51
25
|
propsEnhancers: {
|
|
52
26
|
root: () => {
|
|
53
27
|
// https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
|
|
54
28
|
let ariaChecked;
|
|
55
|
-
if (
|
|
29
|
+
if (isItemInherentlyNotSelectable) {
|
|
30
|
+
// - if the tree contains nodes that are not selectable, aria-checked is not present on those nodes.
|
|
31
|
+
ariaChecked = undefined;
|
|
32
|
+
} else if (selectionStatus === 'selected') {
|
|
56
33
|
// - each selected node has aria-checked set to true.
|
|
57
34
|
ariaChecked = true;
|
|
58
35
|
} else if (selectionStatus === 'indeterminate') {
|
|
59
36
|
ariaChecked = 'mixed';
|
|
60
37
|
} else if (!canItemBeSelected) {
|
|
61
|
-
//
|
|
38
|
+
// disableSelection=true with an unselected item: aria-checked is not present.
|
|
62
39
|
ariaChecked = undefined;
|
|
63
40
|
} else {
|
|
64
41
|
// - all nodes that are selectable but not selected have aria-checked set to false.
|
|
@@ -87,7 +64,7 @@ export const useSelectionItemPlugin = ({
|
|
|
87
64
|
onChange: handleChange,
|
|
88
65
|
visible: isCheckboxSelectionEnabled && isFeatureEnabledForItem,
|
|
89
66
|
disabled: !canItemBeSelected,
|
|
90
|
-
checked: selectionStatus === '
|
|
67
|
+
checked: selectionStatus === 'selected',
|
|
91
68
|
indeterminate: selectionStatus === 'indeterminate'
|
|
92
69
|
};
|
|
93
70
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TreeViewItemSelectionStatus } from "../../../models/index.mjs";
|
|
1
2
|
import type { MinimalTreeViewState } from "../../MinimalTreeViewStore/index.mjs";
|
|
2
3
|
export declare const selectionSelectors: {
|
|
3
4
|
/**
|
|
@@ -32,6 +33,16 @@ export declare const selectionSelectors: {
|
|
|
32
33
|
* Checks whether an item is selected.
|
|
33
34
|
*/
|
|
34
35
|
isItemSelected: (args_0: MinimalTreeViewState<any, any>, itemId: string) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Gets the selection status of an item.
|
|
38
|
+
* An item that is not selected is `indeterminate` when some of its selectable descendants are selected.
|
|
39
|
+
* When `selectionPropagation.parents` is enabled, an item whose selectable descendants are all selected is `selected`.
|
|
40
|
+
*/
|
|
41
|
+
itemSelectionStatus: (state: MinimalTreeViewState<any, any>, itemId: string) => TreeViewItemSelectionStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Checks whether an item is indeterminate (it is not selected but some of its selectable descendants are).
|
|
44
|
+
*/
|
|
45
|
+
isItemIndeterminate: (args_0: MinimalTreeViewState<any, any>, _itemId: string) => boolean;
|
|
35
46
|
/**
|
|
36
47
|
* Checks whether the selection feature is enabled for an item.
|
|
37
48
|
* Returns `true` when selection is enabled on the Tree View and the item is selectable (even if the item is disabled).
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TreeViewItemSelectionStatus } from "../../../models/index.js";
|
|
1
2
|
import type { MinimalTreeViewState } from "../../MinimalTreeViewStore/index.js";
|
|
2
3
|
export declare const selectionSelectors: {
|
|
3
4
|
/**
|
|
@@ -32,6 +33,16 @@ export declare const selectionSelectors: {
|
|
|
32
33
|
* Checks whether an item is selected.
|
|
33
34
|
*/
|
|
34
35
|
isItemSelected: (args_0: MinimalTreeViewState<any, any>, itemId: string) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Gets the selection status of an item.
|
|
38
|
+
* An item that is not selected is `indeterminate` when some of its selectable descendants are selected.
|
|
39
|
+
* When `selectionPropagation.parents` is enabled, an item whose selectable descendants are all selected is `selected`.
|
|
40
|
+
*/
|
|
41
|
+
itemSelectionStatus: (state: MinimalTreeViewState<any, any>, itemId: string) => TreeViewItemSelectionStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Checks whether an item is indeterminate (it is not selected but some of its selectable descendants are).
|
|
44
|
+
*/
|
|
45
|
+
isItemIndeterminate: (args_0: MinimalTreeViewState<any, any>, _itemId: string) => boolean;
|
|
35
46
|
/**
|
|
36
47
|
* Checks whether the selection feature is enabled for an item.
|
|
37
48
|
* Returns `true` when selection is enabled on the Tree View and the item is selectable (even if the item is disabled).
|
|
@@ -23,6 +23,49 @@ const selectedItemsMapSelector = (0, _store.createSelectorMemoized)(selectedItem
|
|
|
23
23
|
return selectedItemsMap;
|
|
24
24
|
});
|
|
25
25
|
const isItemSelectableSelector = (0, _store.createSelector)((state, itemId) => state.itemMetaLookup[itemId]?.selectable ?? true);
|
|
26
|
+
const isItemSelectedSelector = (0, _store.createSelector)(selectedItemsMapSelector, (selectedItemsMap, itemId) => selectedItemsMap.has(itemId));
|
|
27
|
+
const canItemBeSelectedSelector = (0, _store.createSelector)(_selectors.itemsSelectors.isItemDisabled, isItemSelectableSelector, state => !state.disableSelection, (isItemDisabled, isItemSelectable, isSelectionEnabled, _itemId) => isSelectionEnabled && !isItemDisabled && isItemSelectable);
|
|
28
|
+
const propagationRulesSelector = (0, _store.createSelector)(state => state.selectionPropagation);
|
|
29
|
+
const itemSelectionStatusSelector = (0, _store.createSelector)((state, itemId) => {
|
|
30
|
+
if (isItemSelectedSelector(state, itemId)) {
|
|
31
|
+
return 'selected';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Only the descendants can make an unselected item `indeterminate`, so leaves can be resolved
|
|
35
|
+
// without traversing the tree.
|
|
36
|
+
if (_selectors.itemsSelectors.itemOrderedChildrenIds(state, itemId).length === 0) {
|
|
37
|
+
return 'unselected';
|
|
38
|
+
}
|
|
39
|
+
let hasSelectedDescendant = false;
|
|
40
|
+
let hasUnSelectedDescendant = false;
|
|
41
|
+
const traverseDescendants = itemToTraverseId => {
|
|
42
|
+
if (itemToTraverseId !== itemId) {
|
|
43
|
+
if (canItemBeSelectedSelector(state, itemToTraverseId)) {
|
|
44
|
+
if (isItemSelectedSelector(state, itemToTraverseId)) {
|
|
45
|
+
hasSelectedDescendant = true;
|
|
46
|
+
} else {
|
|
47
|
+
hasUnSelectedDescendant = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
_selectors.itemsSelectors.itemOrderedChildrenIds(state, itemToTraverseId).forEach(traverseDescendants);
|
|
52
|
+
};
|
|
53
|
+
traverseDescendants(itemId);
|
|
54
|
+
const shouldSelectBasedOnDescendants = propagationRulesSelector(state).parents;
|
|
55
|
+
if (shouldSelectBasedOnDescendants) {
|
|
56
|
+
if (hasSelectedDescendant && hasUnSelectedDescendant) {
|
|
57
|
+
return 'indeterminate';
|
|
58
|
+
}
|
|
59
|
+
if (hasSelectedDescendant && !hasUnSelectedDescendant) {
|
|
60
|
+
return 'selected';
|
|
61
|
+
}
|
|
62
|
+
return 'unselected';
|
|
63
|
+
}
|
|
64
|
+
if (hasSelectedDescendant) {
|
|
65
|
+
return 'indeterminate';
|
|
66
|
+
}
|
|
67
|
+
return 'unselected';
|
|
68
|
+
});
|
|
26
69
|
const selectionSelectors = exports.selectionSelectors = {
|
|
27
70
|
/**
|
|
28
71
|
* Gets the selected items as provided to the component.
|
|
@@ -51,11 +94,21 @@ const selectionSelectors = exports.selectionSelectors = {
|
|
|
51
94
|
/**
|
|
52
95
|
* Gets the selection propagation rules.
|
|
53
96
|
*/
|
|
54
|
-
propagationRules:
|
|
97
|
+
propagationRules: propagationRulesSelector,
|
|
55
98
|
/**
|
|
56
99
|
* Checks whether an item is selected.
|
|
57
100
|
*/
|
|
58
|
-
isItemSelected:
|
|
101
|
+
isItemSelected: isItemSelectedSelector,
|
|
102
|
+
/**
|
|
103
|
+
* Gets the selection status of an item.
|
|
104
|
+
* An item that is not selected is `indeterminate` when some of its selectable descendants are selected.
|
|
105
|
+
* When `selectionPropagation.parents` is enabled, an item whose selectable descendants are all selected is `selected`.
|
|
106
|
+
*/
|
|
107
|
+
itemSelectionStatus: itemSelectionStatusSelector,
|
|
108
|
+
/**
|
|
109
|
+
* Checks whether an item is indeterminate (it is not selected but some of its selectable descendants are).
|
|
110
|
+
*/
|
|
111
|
+
isItemIndeterminate: (0, _store.createSelector)(itemSelectionStatusSelector, (selectionStatus, _itemId) => selectionStatus === 'indeterminate'),
|
|
59
112
|
/**
|
|
60
113
|
* Checks whether the selection feature is enabled for an item.
|
|
61
114
|
* Returns `true` when selection is enabled on the Tree View and the item is selectable (even if the item is disabled).
|
|
@@ -64,7 +117,7 @@ const selectionSelectors = exports.selectionSelectors = {
|
|
|
64
117
|
/**
|
|
65
118
|
* Checks whether an item can be selected (if selection is enabled, if the item is not disabled, and if the item is selectable).
|
|
66
119
|
*/
|
|
67
|
-
canItemBeSelected:
|
|
120
|
+
canItemBeSelected: canItemBeSelectedSelector,
|
|
68
121
|
/**
|
|
69
122
|
* Checks whether an item is selectable based on the `isItemSelectionDisabled` prop.
|
|
70
123
|
*/
|