@mui/x-tree-view 7.2.0 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +204 -2
  2. package/TreeItem/TreeItem.js +1 -2
  3. package/TreeItem/TreeItemContent.js +1 -2
  4. package/TreeItem/useTreeItemState.js +1 -3
  5. package/TreeItem2/TreeItem2.js +1 -2
  6. package/hooks/useTreeItem2Utils/useTreeItem2Utils.js +1 -3
  7. package/index.js +1 -1
  8. package/internals/TreeViewProvider/useTreeViewContext.d.ts +1 -1
  9. package/internals/hooks/useInstanceEventHandler.d.ts +2 -2
  10. package/internals/models/treeView.d.ts +0 -6
  11. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +6 -5
  12. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +7 -7
  13. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +31 -63
  14. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  15. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +32 -5
  16. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +9 -0
  17. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +21 -0
  18. package/internals/useTreeView/useTreeView.types.d.ts +1 -1
  19. package/internals/useTreeView/useTreeViewModels.d.ts +1 -1
  20. package/internals/utils/extractPluginParamsFromProps.d.ts +1 -1
  21. package/internals/utils/publishTreeViewEvent.d.ts +1 -1
  22. package/internals/utils/tree.d.ts +17 -1
  23. package/internals/utils/tree.js +34 -4
  24. package/modern/TreeItem/TreeItem.js +1 -2
  25. package/modern/TreeItem/TreeItemContent.js +1 -2
  26. package/modern/TreeItem/useTreeItemState.js +1 -3
  27. package/modern/TreeItem2/TreeItem2.js +1 -2
  28. package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +1 -3
  29. package/modern/index.js +1 -1
  30. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +6 -5
  31. package/modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +7 -7
  32. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +31 -63
  33. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  34. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +21 -0
  35. package/modern/internals/utils/tree.js +34 -4
  36. package/node/RichTreeView/RichTreeView.js +1 -1
  37. package/node/SimpleTreeView/SimpleTreeView.js +1 -1
  38. package/node/TreeItem/TreeItem.js +1 -1
  39. package/node/TreeItem/TreeItemContent.js +1 -1
  40. package/node/TreeItem/useTreeItemState.js +1 -3
  41. package/node/TreeItem2/TreeItem2.js +1 -1
  42. package/node/TreeItem2Icon/TreeItem2Icon.js +1 -1
  43. package/node/TreeView/TreeView.js +1 -1
  44. package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +1 -3
  45. package/node/hooks/useTreeViewApiRef.js +1 -1
  46. package/node/icons/icons.js +1 -1
  47. package/node/index.js +1 -1
  48. package/node/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +1 -1
  49. package/node/internals/TreeViewProvider/TreeViewContext.js +1 -1
  50. package/node/internals/TreeViewProvider/TreeViewProvider.js +1 -1
  51. package/node/internals/TreeViewProvider/useTreeViewContext.js +1 -1
  52. package/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +1 -1
  53. package/node/internals/hooks/useInstanceEventHandler.js +1 -1
  54. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +1 -1
  55. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +7 -6
  56. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
  57. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +1 -1
  58. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +8 -8
  59. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +32 -64
  60. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  61. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +29 -0
  62. package/node/internals/useTreeView/useTreeView.js +1 -1
  63. package/node/internals/useTreeView/useTreeViewModels.js +1 -1
  64. package/node/internals/utils/tree.js +37 -5
  65. package/package.json +3 -5
@@ -1,14 +1,25 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
- import { getFirstNavigableItem, getLastNavigableItem, getNavigableItemsInRange } from '../../utils/tree';
3
+ import { findOrderInTremauxTree, getAllNavigableItems, getFirstNavigableItem, getLastNavigableItem, getNonDisabledItemsInRange } from '../../utils/tree';
4
+ import { convertSelectedItemsToArray, getLookupFromArray } from './useTreeViewSelection.utils';
4
5
  export const useTreeViewSelection = ({
5
6
  instance,
6
7
  params,
7
8
  models
8
9
  }) => {
9
10
  const lastSelectedItem = React.useRef(null);
10
- const lastSelectionWasRange = React.useRef(false);
11
- const currentRangeSelection = React.useRef([]);
11
+ const lastSelectedRange = React.useRef({});
12
+ const selectedItemsMap = React.useMemo(() => {
13
+ const temp = new Map();
14
+ if (Array.isArray(models.selectedItems.value)) {
15
+ models.selectedItems.value.forEach(id => {
16
+ temp.set(id, true);
17
+ });
18
+ } else if (models.selectedItems.value != null) {
19
+ temp.set(models.selectedItems.value, true);
20
+ }
21
+ return temp;
22
+ }, [models.selectedItems.value]);
12
23
  const setSelectedItems = (event, newSelectedItems) => {
13
24
  if (params.onItemSelectionToggle) {
14
25
  if (params.multiSelect) {
@@ -34,115 +45,90 @@ export const useTreeViewSelection = ({
34
45
  }
35
46
  models.selectedItems.setControlledValue(newSelectedItems);
36
47
  };
37
- const isItemSelected = itemId => Array.isArray(models.selectedItems.value) ? models.selectedItems.value.indexOf(itemId) !== -1 : models.selectedItems.value === itemId;
48
+ const isItemSelected = itemId => selectedItemsMap.has(itemId);
38
49
  const selectItem = (event, itemId, multiple = false) => {
39
50
  if (params.disableSelection) {
40
51
  return;
41
52
  }
53
+ let newSelected;
42
54
  if (multiple) {
43
- if (Array.isArray(models.selectedItems.value)) {
44
- let newSelected;
45
- if (models.selectedItems.value.indexOf(itemId) !== -1) {
46
- newSelected = models.selectedItems.value.filter(id => id !== itemId);
47
- } else {
48
- newSelected = [itemId].concat(models.selectedItems.value);
49
- }
50
- setSelectedItems(event, newSelected);
55
+ const cleanSelectedItems = convertSelectedItemsToArray(models.selectedItems.value);
56
+ if (instance.isItemSelected(itemId)) {
57
+ newSelected = cleanSelectedItems.filter(id => id !== itemId);
58
+ } else {
59
+ newSelected = [itemId].concat(cleanSelectedItems);
51
60
  }
52
61
  } else {
53
- const newSelected = params.multiSelect ? [itemId] : itemId;
54
- setSelectedItems(event, newSelected);
62
+ newSelected = params.multiSelect ? [itemId] : itemId;
55
63
  }
64
+ setSelectedItems(event, newSelected);
56
65
  lastSelectedItem.current = itemId;
57
- lastSelectionWasRange.current = false;
58
- currentRangeSelection.current = [];
66
+ lastSelectedRange.current = {};
59
67
  };
60
- const handleRangeArrowSelect = (event, items) => {
61
- let base = models.selectedItems.value.slice();
62
- const {
63
- start,
64
- next,
65
- current
66
- } = items;
67
- if (!next || !current) {
68
+ const selectRange = (event, [start, end]) => {
69
+ if (params.disableSelection || !params.multiSelect) {
68
70
  return;
69
71
  }
70
- if (currentRangeSelection.current.indexOf(current) === -1) {
71
- currentRangeSelection.current = [];
72
+ let newSelectedItems = convertSelectedItemsToArray(models.selectedItems.value).slice();
73
+
74
+ // If the last selection was a range selection,
75
+ // remove the items that were part of the last range from the model
76
+ if (Object.keys(lastSelectedRange.current).length > 0) {
77
+ newSelectedItems = newSelectedItems.filter(id => !lastSelectedRange.current[id]);
72
78
  }
73
- if (lastSelectionWasRange.current) {
74
- if (currentRangeSelection.current.indexOf(next) !== -1) {
75
- base = base.filter(id => id === start || id !== current);
76
- currentRangeSelection.current = currentRangeSelection.current.filter(id => id === start || id !== current);
77
- } else {
78
- base.push(next);
79
- currentRangeSelection.current.push(next);
80
- }
81
- } else {
82
- base.push(next);
83
- currentRangeSelection.current.push(current, next);
84
- }
85
- setSelectedItems(event, base);
79
+
80
+ // Add to the model the items that are part of the new range and not already part of the model.
81
+ const selectedItemsLookup = getLookupFromArray(newSelectedItems);
82
+ const range = getNonDisabledItemsInRange(instance, start, end);
83
+ const itemsToAddToModel = range.filter(id => !selectedItemsLookup[id]);
84
+ newSelectedItems = newSelectedItems.concat(itemsToAddToModel);
85
+ setSelectedItems(event, newSelectedItems);
86
+ lastSelectedRange.current = getLookupFromArray(range);
86
87
  };
87
- const handleRangeSelect = (event, items) => {
88
- let base = models.selectedItems.value.slice();
89
- const {
90
- start,
91
- end
92
- } = items;
93
- // If last selection was a range selection ignore items that were selected.
94
- if (lastSelectionWasRange.current) {
95
- base = base.filter(id => currentRangeSelection.current.indexOf(id) === -1);
88
+ const expandSelectionRange = (event, itemId) => {
89
+ if (lastSelectedItem.current != null) {
90
+ const [start, end] = findOrderInTremauxTree(instance, itemId, lastSelectedItem.current);
91
+ selectRange(event, [start, end]);
96
92
  }
97
- let range = getNavigableItemsInRange(instance, start, end);
98
- range = range.filter(item => !instance.isItemDisabled(item));
99
- currentRangeSelection.current = range;
100
- let newSelected = base.concat(range);
101
- newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i);
102
- setSelectedItems(event, newSelected);
103
93
  };
104
- const selectRange = (event, items, stacked = false) => {
105
- if (params.disableSelection) {
94
+ const selectRangeFromStartToItem = (event, itemId) => {
95
+ selectRange(event, [getFirstNavigableItem(instance), itemId]);
96
+ };
97
+ const selectRangeFromItemToEnd = (event, itemId) => {
98
+ selectRange(event, [itemId, getLastNavigableItem(instance)]);
99
+ };
100
+ const selectAllNavigableItems = event => {
101
+ if (params.disableSelection || !params.multiSelect) {
106
102
  return;
107
103
  }
108
- const {
109
- start = lastSelectedItem.current,
110
- end,
111
- current
112
- } = items;
113
- if (stacked) {
114
- handleRangeArrowSelect(event, {
115
- start,
116
- next: end,
117
- current
118
- });
119
- } else if (start != null && end != null) {
120
- handleRangeSelect(event, {
121
- start,
122
- end
123
- });
124
- }
125
- lastSelectionWasRange.current = true;
104
+ const navigableItems = getAllNavigableItems(instance);
105
+ setSelectedItems(event, navigableItems);
106
+ lastSelectedRange.current = getLookupFromArray(navigableItems);
126
107
  };
127
- const rangeSelectToFirst = (event, itemId) => {
128
- if (!lastSelectedItem.current) {
129
- lastSelectedItem.current = itemId;
108
+ const selectItemFromArrowNavigation = (event, currentItem, nextItem) => {
109
+ if (params.disableSelection || !params.multiSelect) {
110
+ return;
130
111
  }
131
- const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId;
132
- instance.selectRange(event, {
133
- start,
134
- end: getFirstNavigableItem(instance)
135
- });
136
- };
137
- const rangeSelectToLast = (event, itemId) => {
138
- if (!lastSelectedItem.current) {
139
- lastSelectedItem.current = itemId;
112
+ let newSelectedItems = convertSelectedItemsToArray(models.selectedItems.value).slice();
113
+ if (Object.keys(lastSelectedRange.current).length === 0) {
114
+ newSelectedItems.push(nextItem);
115
+ lastSelectedRange.current = {
116
+ [currentItem]: true,
117
+ [nextItem]: true
118
+ };
119
+ } else {
120
+ if (!lastSelectedRange.current[currentItem]) {
121
+ lastSelectedRange.current = {};
122
+ }
123
+ if (lastSelectedRange.current[nextItem]) {
124
+ newSelectedItems = newSelectedItems.filter(id => id !== currentItem);
125
+ delete lastSelectedRange.current[currentItem];
126
+ } else {
127
+ newSelectedItems.push(nextItem);
128
+ lastSelectedRange.current[nextItem] = true;
129
+ }
140
130
  }
141
- const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId;
142
- instance.selectRange(event, {
143
- start,
144
- end: getLastNavigableItem(instance)
145
- });
131
+ setSelectedItems(event, newSelectedItems);
146
132
  };
147
133
  return {
148
134
  getRootProps: () => ({
@@ -151,9 +137,11 @@ export const useTreeViewSelection = ({
151
137
  instance: {
152
138
  isItemSelected,
153
139
  selectItem,
154
- selectRange,
155
- rangeSelectToLast,
156
- rangeSelectToFirst
140
+ selectAllNavigableItems,
141
+ expandSelectionRange,
142
+ selectRangeFromStartToItem,
143
+ selectRangeFromItemToEnd,
144
+ selectItemFromArrowNavigation
157
145
  },
158
146
  contextValue: {
159
147
  selection: {
@@ -1,13 +1,40 @@
1
1
  import * as React from 'react';
2
- import type { DefaultizedProps, TreeViewItemRange, TreeViewPluginSignature } from '../../models';
2
+ import type { DefaultizedProps, TreeViewPluginSignature } from '../../models';
3
3
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
4
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
5
5
  export interface UseTreeViewSelectionInstance {
6
6
  isItemSelected: (itemId: string) => boolean;
7
- selectItem: (event: React.SyntheticEvent, itemId: string, multiple?: boolean) => void;
8
- selectRange: (event: React.SyntheticEvent, items: TreeViewItemRange, stacked?: boolean) => void;
9
- rangeSelectToFirst: (event: React.KeyboardEvent, itemId: string) => void;
10
- rangeSelectToLast: (event: React.KeyboardEvent, itemId: string) => void;
7
+ selectItem: (event: React.SyntheticEvent, itemId: string, keepExistingSelection?: boolean) => void;
8
+ /**
9
+ * Select all the navigable items in the tree.
10
+ * @param {React.SyntheticEvent} event The event source of the callback.
11
+ */
12
+ selectAllNavigableItems: (event: React.SyntheticEvent) => void;
13
+ /**
14
+ * Expand the current selection range up to the given item.
15
+ * @param {React.SyntheticEvent} event The event source of the callback.
16
+ * @param {string} itemId The id of the item to expand the selection to.
17
+ */
18
+ expandSelectionRange: (event: React.SyntheticEvent, itemId: string) => void;
19
+ /**
20
+ * Expand the current selection range from the first navigable item to the given item.
21
+ * @param {React.SyntheticEvent} event The event source of the callback.
22
+ * @param {string} itemId The id of the item up to which the selection range should be expanded.
23
+ */
24
+ selectRangeFromStartToItem: (event: React.SyntheticEvent, itemId: string) => void;
25
+ /**
26
+ * Expand the current selection range from the given item to the last navigable item.
27
+ * @param {React.SyntheticEvent} event The event source of the callback.
28
+ * @param {string} itemId The id of the item from which the selection range should be expanded.
29
+ */
30
+ selectRangeFromItemToEnd: (event: React.SyntheticEvent, itemId: string) => void;
31
+ /**
32
+ * Update the selection when navigating with ArrowUp / ArrowDown keys.
33
+ * @param {React.SyntheticEvent} event The event source of the callback.
34
+ * @param {string} currentItemId The id of the active item before the keyboard navigation.
35
+ * @param {string} nextItemId The id of the active item after the keyboard navigation.
36
+ */
37
+ selectItemFromArrowNavigation: (event: React.SyntheticEvent, currentItemId: string, nextItemId: string) => void;
11
38
  }
12
39
  type TreeViewSelectionValue<Multiple extends boolean | undefined> = Multiple extends true ? string[] : string | null;
13
40
  export interface UseTreeViewSelectionParameters<Multiple extends boolean | undefined> {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Transform the `selectedItems` model to be an array if it was a string or null.
3
+ * @param {string[] | string | null} model The raw model.
4
+ * @returns {string[]} The converted model.
5
+ */
6
+ export declare const convertSelectedItemsToArray: (model: string[] | string | null) => string[];
7
+ export declare const getLookupFromArray: (array: string[]) => {
8
+ [itemId: string]: boolean;
9
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Transform the `selectedItems` model to be an array if it was a string or null.
3
+ * @param {string[] | string | null} model The raw model.
4
+ * @returns {string[]} The converted model.
5
+ */
6
+ export const convertSelectedItemsToArray = model => {
7
+ if (Array.isArray(model)) {
8
+ return model;
9
+ }
10
+ if (model != null) {
11
+ return [model];
12
+ }
13
+ return [];
14
+ };
15
+ export const getLookupFromArray = array => {
16
+ const lookup = {};
17
+ array.forEach(itemId => {
18
+ lookup[itemId] = true;
19
+ });
20
+ return lookup;
21
+ };
@@ -11,7 +11,7 @@ export interface UseTreeViewBaseParameters<TPlugins extends readonly TreeViewPlu
11
11
  slotProps: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'slotProps'>;
12
12
  }
13
13
  export type UseTreeViewDefaultizedParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> = UseTreeViewBaseParameters<TPlugins> & MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'defaultizedParams'>;
14
- export interface UseTreeViewRootSlotProps extends Pick<React.HTMLAttributes<HTMLUListElement>, 'onFocus' | 'onBlur' | 'onKeyDown' | 'id' | 'aria-activedescendant' | 'aria-multiselectable' | 'role' | 'tabIndex'> {
14
+ export interface UseTreeViewRootSlotProps extends Pick<React.HTMLAttributes<HTMLUListElement>, 'onFocus' | 'onBlur' | 'onKeyDown' | 'id' | 'aria-multiselectable' | 'role' | 'tabIndex'> {
15
15
  ref: React.Ref<HTMLUListElement>;
16
16
  }
17
17
  export interface UseTreeViewReturnValue<TPlugins extends readonly TreeViewAnyPluginSignature[]> {
@@ -3,4 +3,4 @@ import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignature
3
3
  * Implements the same behavior as `useControlled` but for several models.
4
4
  * The controlled models are never stored in the state and the state is only updated if the model is not controlled.
5
5
  */
6
- export declare const useTreeViewModels: <TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]>(plugins: TPlugins, props: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, "defaultizedParams">) => MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, "models">;
6
+ export declare const useTreeViewModels: <TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]>(plugins: TPlugins, props: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'defaultizedParams'>) => MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, "models">;
@@ -8,7 +8,7 @@ export declare const extractPluginParamsFromProps: <TPlugins extends readonly Tr
8
8
  }>({ props: { slots, slotProps, apiRef, ...props }, plugins, rootRef, }: {
9
9
  props: TProps;
10
10
  plugins: TPlugins;
11
- rootRef?: React.Ref<HTMLUListElement> | undefined;
11
+ rootRef?: React.Ref<HTMLUListElement>;
12
12
  }) => {
13
13
  pluginParams: UseTreeViewBaseParameters<TPlugins> & MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, "params">;
14
14
  slots: TSlots | undefined;
@@ -2,4 +2,4 @@ import { UseTreeViewInstanceEventsInstance } from '../corePlugins/useTreeViewIns
2
2
  import { TreeViewAnyPluginSignature, TreeViewUsedEvents } from '../models';
3
3
  export declare const publishTreeViewEvent: <Instance extends UseTreeViewInstanceEventsInstance & {
4
4
  $$signature: TreeViewAnyPluginSignature;
5
- }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, params: TreeViewUsedEvents<Instance["$$signature"]>[E]["params"]) => void;
5
+ }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, params: TreeViewUsedEvents<Instance['$$signature']>[E]['params']) => void;
@@ -5,4 +5,20 @@ export declare const getPreviousNavigableItem: (instance: TreeViewInstance<[UseT
5
5
  export declare const getNextNavigableItem: (instance: TreeViewInstance<[UseTreeViewExpansionSignature, UseTreeViewItemsSignature]>, itemId: string) => string | null;
6
6
  export declare const getLastNavigableItem: (instance: TreeViewInstance<[UseTreeViewExpansionSignature, UseTreeViewItemsSignature]>) => string;
7
7
  export declare const getFirstNavigableItem: (instance: TreeViewInstance<[UseTreeViewItemsSignature]>) => string;
8
- export declare const getNavigableItemsInRange: (instance: TreeViewInstance<[UseTreeViewItemsSignature, UseTreeViewExpansionSignature]>, itemAId: string, itemBId: string) => string[];
8
+ /**
9
+ * This is used to determine the start and end of a selection range so
10
+ * we can get the items between the two border items.
11
+ *
12
+ * It finds the items' common ancestor using
13
+ * a naive implementation of a lowest common ancestor algorithm
14
+ * (https://en.wikipedia.org/wiki/Lowest_common_ancestor).
15
+ * Then compares the ancestor's 2 children that are ancestors of itemA and ItemB
16
+ * so we can compare their indexes to work out which item comes first in a depth first search.
17
+ * (https://en.wikipedia.org/wiki/Depth-first_search)
18
+ *
19
+ * Another way to put it is which item is shallower in a trémaux tree
20
+ * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree
21
+ */
22
+ export declare const findOrderInTremauxTree: (instance: TreeViewInstance<[UseTreeViewItemsSignature]>, itemAId: string, itemBId: string) => string[];
23
+ export declare const getNonDisabledItemsInRange: (instance: TreeViewInstance<[UseTreeViewItemsSignature, UseTreeViewExpansionSignature]>, itemAId: string, itemBId: string) => string[];
24
+ export declare const getAllNavigableItems: (instance: TreeViewInstance<[UseTreeViewItemsSignature, UseTreeViewExpansionSignature]>) => string[];
@@ -84,7 +84,7 @@ export const getFirstNavigableItem = instance => instance.getItemOrderedChildren
84
84
  * Another way to put it is which item is shallower in a trémaux tree
85
85
  * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree
86
86
  */
87
- const findOrderInTremauxTree = (instance, itemAId, itemBId) => {
87
+ export const findOrderInTremauxTree = (instance, itemAId, itemBId) => {
88
88
  if (itemAId === itemBId) {
89
89
  return [itemAId, itemBId];
90
90
  }
@@ -125,13 +125,43 @@ const findOrderInTremauxTree = (instance, itemAId, itemBId) => {
125
125
  const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1];
126
126
  return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [itemAId, itemBId] : [itemBId, itemAId];
127
127
  };
128
- export const getNavigableItemsInRange = (instance, itemAId, itemBId) => {
128
+ export const getNonDisabledItemsInRange = (instance, itemAId, itemBId) => {
129
+ const getNextItem = itemId => {
130
+ // If the item is expanded and has some children, return the first of them.
131
+ if (instance.isItemExpandable(itemId) && instance.isItemExpanded(itemId)) {
132
+ return instance.getItemOrderedChildrenIds(itemId)[0];
133
+ }
134
+ let itemMeta = instance.getItemMeta(itemId);
135
+ while (itemMeta != null) {
136
+ // Try to find the first navigable sibling after the current item.
137
+ const siblings = instance.getItemOrderedChildrenIds(itemMeta.parentId);
138
+ const currentItemIndex = instance.getItemIndex(itemMeta.id);
139
+ if (currentItemIndex < siblings.length - 1) {
140
+ return siblings[currentItemIndex + 1];
141
+ }
142
+
143
+ // If the item is the last of its siblings, go up a level to the parent and try again.
144
+ itemMeta = instance.getItemMeta(itemMeta.parentId);
145
+ }
146
+ throw new Error('Invalid range');
147
+ };
129
148
  const [first, last] = findOrderInTremauxTree(instance, itemAId, itemBId);
130
149
  const items = [first];
131
150
  let current = first;
132
151
  while (current !== last) {
133
- current = getNextNavigableItem(instance, current);
134
- items.push(current);
152
+ current = getNextItem(current);
153
+ if (!instance.isItemDisabled(current)) {
154
+ items.push(current);
155
+ }
135
156
  }
136
157
  return items;
158
+ };
159
+ export const getAllNavigableItems = instance => {
160
+ let item = getFirstNavigableItem(instance);
161
+ const navigableItems = [];
162
+ while (item != null) {
163
+ navigableItems.push(item);
164
+ item = getNextNavigableItem(instance, item);
165
+ }
166
+ return navigableItems;
137
167
  };
@@ -19,8 +19,7 @@ import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses';
19
19
  import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext';
20
20
  import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons';
21
21
  import { TreeItem2Provider } from '../TreeItem2Provider';
22
- import { jsx as _jsx } from "react/jsx-runtime";
23
- import { jsxs as _jsxs } from "react/jsx-runtime";
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
23
  const useUtilityClasses = ownerState => {
25
24
  const {
26
25
  classes
@@ -5,8 +5,7 @@ import * as React from 'react';
5
5
  import PropTypes from 'prop-types';
6
6
  import clsx from 'clsx';
7
7
  import { useTreeItemState } from './useTreeItemState';
8
- import { jsx as _jsx } from "react/jsx-runtime";
9
- import { jsxs as _jsxs } from "react/jsx-runtime";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
9
  /**
11
10
  * @ignore - internal component.
12
11
  */
@@ -32,9 +32,7 @@ export function useTreeItemState(itemId) {
32
32
  const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
33
33
  if (multiple) {
34
34
  if (event.shiftKey) {
35
- instance.selectRange(event, {
36
- end: itemId
37
- });
35
+ instance.expandSelectionRange(event, itemId);
38
36
  } else {
39
37
  instance.selectItem(event, itemId, true);
40
38
  }
@@ -14,8 +14,7 @@ import { unstable_useTreeItem2 as useTreeItem2 } from '../useTreeItem2';
14
14
  import { getTreeItemUtilityClass, treeItemClasses } from '../TreeItem';
15
15
  import { TreeItem2Icon } from '../TreeItem2Icon';
16
16
  import { TreeItem2Provider } from '../TreeItem2Provider';
17
- import { jsx as _jsx } from "react/jsx-runtime";
18
- import { jsxs as _jsxs } from "react/jsx-runtime";
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
18
  export const TreeItem2Root = styled('li', {
20
19
  name: 'MuiTreeItem2',
21
20
  slot: 'Root',
@@ -40,9 +40,7 @@ export const useTreeItem2Utils = ({
40
40
  const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
41
41
  if (multiple) {
42
42
  if (event.shiftKey) {
43
- instance.selectRange(event, {
44
- end: itemId
45
- });
43
+ instance.expandSelectionRange(event, itemId);
46
44
  } else {
47
45
  instance.selectItem(event, itemId, true);
48
46
  }
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v7.2.0
2
+ * @mui/x-tree-view v7.3.1
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -94,19 +94,20 @@ export const useTreeViewFocus = ({
94
94
  instance.focusDefaultItem(null);
95
95
  }
96
96
  });
97
- const createHandleFocus = otherHandlers => event => {
97
+ const createRootHandleFocus = otherHandlers => event => {
98
98
  otherHandlers.onFocus?.(event);
99
+ if (event.defaultMuiPrevented) {
100
+ return;
101
+ }
102
+
99
103
  // if the event bubbled (which is React specific) we don't want to steal focus
100
104
  if (event.target === event.currentTarget) {
101
105
  instance.focusDefaultItem(event);
102
106
  }
103
107
  };
104
- const focusedItem = instance.getItemMeta(state.focusedItemId);
105
- const activeDescendant = focusedItem ? instance.getTreeItemIdAttribute(focusedItem.id, focusedItem.idAttribute) : null;
106
108
  return {
107
109
  getRootProps: otherHandlers => ({
108
- onFocus: createHandleFocus(otherHandlers),
109
- 'aria-activedescendant': activeDescendant ?? undefined
110
+ onFocus: createRootHandleFocus(otherHandlers)
110
111
  }),
111
112
  publicAPI: {
112
113
  focusItem
@@ -86,6 +86,12 @@ export const useTreeViewJSXItems = ({
86
86
  }
87
87
  };
88
88
  };
89
+ const isItemExpandable = reactChildren => {
90
+ if (Array.isArray(reactChildren)) {
91
+ return reactChildren.length > 0 && reactChildren.some(isItemExpandable);
92
+ }
93
+ return Boolean(reactChildren);
94
+ };
89
95
  const useTreeViewJSXItemsItemPlugin = ({
90
96
  props,
91
97
  rootRef,
@@ -110,13 +116,7 @@ const useTreeViewJSXItemsItemPlugin = ({
110
116
  unregisterChild,
111
117
  parentId
112
118
  } = parentContext;
113
- const isExpandable = reactChildren => {
114
- if (Array.isArray(reactChildren)) {
115
- return reactChildren.length > 0 && reactChildren.some(isExpandable);
116
- }
117
- return Boolean(reactChildren);
118
- };
119
- const expandable = isExpandable(children);
119
+ const expandable = isItemExpandable(children);
120
120
  const pluginContentRef = React.useRef(null);
121
121
  const handleContentRef = useForkRef(pluginContentRef, contentRef);
122
122