@mui/x-tree-view 7.3.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 (63) hide show
  1. package/CHANGELOG.md +79 -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 +1 -4
  12. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +31 -63
  13. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  14. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +32 -5
  15. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +9 -0
  16. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +21 -0
  17. package/internals/useTreeView/useTreeView.types.d.ts +1 -1
  18. package/internals/useTreeView/useTreeViewModels.d.ts +1 -1
  19. package/internals/utils/extractPluginParamsFromProps.d.ts +1 -1
  20. package/internals/utils/publishTreeViewEvent.d.ts +1 -1
  21. package/internals/utils/tree.d.ts +17 -1
  22. package/internals/utils/tree.js +34 -4
  23. package/modern/TreeItem/TreeItem.js +1 -2
  24. package/modern/TreeItem/TreeItemContent.js +1 -2
  25. package/modern/TreeItem/useTreeItemState.js +1 -3
  26. package/modern/TreeItem2/TreeItem2.js +1 -2
  27. package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +1 -3
  28. package/modern/index.js +1 -1
  29. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +1 -4
  30. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +31 -63
  31. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  32. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +21 -0
  33. package/modern/internals/utils/tree.js +34 -4
  34. package/node/RichTreeView/RichTreeView.js +1 -1
  35. package/node/SimpleTreeView/SimpleTreeView.js +1 -1
  36. package/node/TreeItem/TreeItem.js +1 -1
  37. package/node/TreeItem/TreeItemContent.js +1 -1
  38. package/node/TreeItem/useTreeItemState.js +1 -3
  39. package/node/TreeItem2/TreeItem2.js +1 -1
  40. package/node/TreeItem2Icon/TreeItem2Icon.js +1 -1
  41. package/node/TreeView/TreeView.js +1 -1
  42. package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +1 -3
  43. package/node/hooks/useTreeViewApiRef.js +1 -1
  44. package/node/icons/icons.js +1 -1
  45. package/node/index.js +1 -1
  46. package/node/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +1 -1
  47. package/node/internals/TreeViewProvider/TreeViewContext.js +1 -1
  48. package/node/internals/TreeViewProvider/TreeViewProvider.js +1 -1
  49. package/node/internals/TreeViewProvider/useTreeViewContext.js +1 -1
  50. package/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +1 -1
  51. package/node/internals/hooks/useInstanceEventHandler.js +1 -1
  52. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +1 -1
  53. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +2 -5
  54. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
  55. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +1 -1
  56. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +1 -1
  57. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +32 -64
  58. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +82 -94
  59. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +29 -0
  60. package/node/internals/useTreeView/useTreeView.js +1 -1
  61. package/node/internals/useTreeView/useTreeViewModels.js +1 -1
  62. package/node/internals/utils/tree.js +37 -5
  63. package/package.json +3 -5
package/CHANGELOG.md CHANGED
@@ -3,6 +3,83 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 7.3.1
7
+
8
+ _Apr 26, 2024_
9
+
10
+ We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨:
11
+
12
+ - 🎁 Scatter Charts get a [z-axis to allow coloring data points independently from their coordinates](https://mui.com/x/react-charts/scatter/#color-scale)
13
+ - 🌍 Improve Catalan (ca-ES) and Spanish (es-ES) locales on the Date and Time Pickers
14
+ - 🐞 Bugfixes
15
+ - 📚 Documentation improvements
16
+
17
+ ### Data Grid
18
+
19
+ #### `@mui/x-data-grid@7.3.1`
20
+
21
+ - [DataGrid] Fix date filtering for negative timezone offsets (#12836) @cherniavskii
22
+ - [DataGrid] Fix flex column width when used with pinned columns (#12849) @romgrk
23
+ - [DataGrid] Fix group header resize (#12863) @arminmeh
24
+ - [DataGrid] Pass slot props to `columnHeaders` slot (#12768) @cherniavskii
25
+
26
+ #### `@mui/x-data-grid-pro@7.3.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
27
+
28
+ Same changes as in `@mui/x-data-grid@7.3.1`.
29
+
30
+ #### `@mui/x-data-grid-premium@7.3.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
31
+
32
+ Same changes as in `@mui/x-data-grid-pro@7.3.1`.
33
+
34
+ ### Date and Time Pickers
35
+
36
+ #### `@mui/x-date-pickers@7.3.1`
37
+
38
+ - [l10n] Improve Catalan (ca-ES) locale (#12856) @soler1212
39
+ - [l10n] Improve Spanish (es-ES) locale (#12858) @soler1212
40
+
41
+ #### `@mui/x-date-pickers-pro@7.3.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
42
+
43
+ Same changes as in `@mui/x-date-pickers@7.3.1`.
44
+
45
+ ### Charts
46
+
47
+ #### `@mui/x-charts@7.3.1`
48
+
49
+ - [charts] Add documentation on border radius alternative for `BarCharts` (#12859) @JCQuintas
50
+ - [charts] Add z-axis to colorize scatter charts (#12738) @alexfauquette
51
+ - [charts] Fix left/bottomAxis not picking up default axis id (#12894) @JCQuintas
52
+ - [charts] Improve default tooltip content (#12257) @oliviertassinari
53
+ - [charts] Round y values for bar chart (#12846) @alexfauquette
54
+
55
+ ### Tree View
56
+
57
+ #### `@mui/x-tree-view@7.3.1`
58
+
59
+ - [TreeView] Remove un-needed `aria-activedescendant` attribute (#12867) @flaviendelangle
60
+ - [TreeView] Rework the selection internals (#12703) @flaviendelangle
61
+ - [TreeView] Use the order in which the items are displayed for `type-ahead` (#12827) @flaviendelangle
62
+
63
+ ### Docs
64
+
65
+ - [docs] Add demo for styling charts with `sx` props (#12791) @derek-0000
66
+ - [docs] Cover webpack 4 support in migration guide (#12710) @cherniavskii
67
+ - [docs] Document interfaces for charts (#12656) @alexfauquette
68
+ - [docs] Fix Vale regression (#12862) @oliviertassinari
69
+ - [docs] Improve Data Grid migration guide (#12879) @MBilalShafi
70
+ - [docs] Update Column features availability (#12865) @DanailH
71
+
72
+ ### Core
73
+
74
+ - [core] Fix `l10n` GH workflow (#12895) @LukasTy
75
+ - [core] Match Base UI and Toolpad @oliviertassinari
76
+ - [core] Remove redundant `setupFiles` entries in `package.json` (#12899) @LukasTy
77
+ - [core] Use `describeTreeView` for focus tests (#12698) @flaviendelangle
78
+ - [core] Use `describeTreeView` for type-ahead tests (#12811) @flaviendelangle
79
+ - [code-infra] Change package manager to `pnpm` (#11875) @LukasTy
80
+ - [code-infra] Closer sync with eslint config of codebase (#12864) @oliviertassinari
81
+ - [support-infra] Add release announcement to GitHub workflows (#11867) (#12843) @michelengelen
82
+
6
83
  ## 7.3.0
7
84
 
8
85
  _Apr 18, 2024_
@@ -243,7 +320,7 @@ Same changes as in `@mui/x-date-pickers@7.1.1`, plus:
243
320
  - [docs] Fix type arguments in Custom Field page (#12619) @Juneezee
244
321
  - [docs] Fix typo in `getItemId` prop description (#12637) @flaviendelangle
245
322
  - [docs] Make the Charts `margin` usage more visible (#12591) @alexfauquette
246
- - [docs] Match IE 11 spacing with Material UI @oliviertassinari
323
+ - [docs] Match IE 11 spacing with Material UI @oliviertassinari
247
324
  - [docs] Move data grid interfaces to standard API page layout (#12016) @alexfauquette
248
325
  - [docs] Remove ` around @default values (#12158) @alexfauquette
249
326
  - [docs] Remove `day` from the default `dayOfWeekFormatter` function params (#12644) @LukasTy
@@ -506,7 +583,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0`, plus:
506
583
  - [docs] Update links to v6 (#12496) @cherniavskii
507
584
  - [docs] Update links to v7 docs (#12500) @noraleonte
508
585
  - [docs] Update supported versions (#12508) @joserodolfofreitas
509
- - [docs] Update "What's new in MUI X" page #12527 @cherniavskii
586
+ - [docs] Update "What's new in MUI X" page #12527 @cherniavskii
510
587
 
511
588
  ### Core
512
589
 
@@ -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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v7.3.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
@@ -1,3 +1,3 @@
1
1
  import { TreeViewAnyPluginSignature } from '../models';
2
2
  import { TreeViewContextValue } from './TreeViewProvider.types';
3
- export declare const useTreeViewContext: <TPlugins extends readonly TreeViewAnyPluginSignature[]>() => TreeViewContextValue<TPlugins>;
3
+ export declare const useTreeViewContext: <TPlugins extends readonly TreeViewAnyPluginSignature[]>() => NonNullable<TreeViewContextValue<TPlugins>>;
@@ -7,9 +7,9 @@ interface RegistryContainer {
7
7
  }
8
8
  export declare function createUseInstanceEventHandler(registryContainer: RegistryContainer): <Instance extends UseTreeViewInstanceEventsInstance & {
9
9
  $$signature: TreeViewAnyPluginSignature;
10
- }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance["$$signature"]>[E]>) => void;
10
+ }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance['$$signature']>[E]>) => void;
11
11
  export declare const unstable_resetCleanupTracking: () => void;
12
12
  export declare const useInstanceEventHandler: <Instance extends UseTreeViewInstanceEventsInstance & {
13
13
  $$signature: TreeViewAnyPluginSignature;
14
- }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance["$$signature"]>[E]>) => void;
14
+ }, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance['$$signature']>[E]>) => void;
15
15
  export {};
@@ -11,12 +11,6 @@ export interface TreeViewItemMeta {
11
11
  */
12
12
  label?: string;
13
13
  }
14
- export interface TreeViewItemRange {
15
- start?: string | null;
16
- end?: string | null;
17
- next?: string | null;
18
- current?: string;
19
- }
20
14
  export interface TreeViewModel<TValue> {
21
15
  name: string;
22
16
  value: TValue;
@@ -105,12 +105,9 @@ export const useTreeViewFocus = ({
105
105
  instance.focusDefaultItem(event);
106
106
  }
107
107
  };
108
- const focusedItem = instance.getItemMeta(state.focusedItemId);
109
- const activeDescendant = focusedItem ? instance.getTreeItemIdAttribute(focusedItem.id, focusedItem.idAttribute) : null;
110
108
  return {
111
109
  getRootProps: otherHandlers => ({
112
- onFocus: createRootHandleFocus(otherHandlers),
113
- 'aria-activedescendant': activeDescendant ?? undefined
110
+ onFocus: createRootHandleFocus(otherHandlers)
114
111
  }),
115
112
  publicAPI: {
116
113
  focusItem
@@ -5,14 +5,6 @@ import { getFirstNavigableItem, getLastNavigableItem, getNextNavigableItem, getP
5
5
  function isPrintableCharacter(string) {
6
6
  return !!string && string.length === 1 && !!string.match(/\S/);
7
7
  }
8
- function findNextFirstChar(firstChars, startIndex, char) {
9
- for (let i = startIndex; i < firstChars.length; i += 1) {
10
- if (char === firstChars[i]) {
11
- return i;
12
- }
13
- }
14
- return -1;
15
- }
16
8
  export const useTreeViewKeyboardNavigation = ({
17
9
  instance,
18
10
  params,
@@ -35,42 +27,29 @@ export const useTreeViewKeyboardNavigation = ({
35
27
  Object.values(state.items.itemMetaMap).forEach(processItem);
36
28
  firstCharMap.current = newFirstCharMap;
37
29
  }, [state.items.itemMetaMap, params.getItemId, instance]);
38
- const getFirstMatchingItem = (itemId, firstChar) => {
39
- let start;
40
- let index;
41
- const lowercaseChar = firstChar.toLowerCase();
42
- const firstCharIds = [];
43
- const firstChars = [];
44
- // This really only works since the ids are strings
45
- Object.keys(firstCharMap.current).forEach(mapItemId => {
46
- const map = instance.getItemMeta(mapItemId);
47
- const visible = map.parentId ? instance.isItemExpanded(map.parentId) : true;
48
- const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isItemDisabled(mapItemId);
49
- if (visible && !shouldBeSkipped) {
50
- firstCharIds.push(mapItemId);
51
- firstChars.push(firstCharMap.current[mapItemId]);
30
+ const getFirstMatchingItem = (itemId, query) => {
31
+ const cleanQuery = query.toLowerCase();
32
+ const getNextItem = itemIdToCheck => {
33
+ const nextItemId = getNextNavigableItem(instance, itemIdToCheck);
34
+ // We reached the end of the tree, check from the beginning
35
+ if (nextItemId === null) {
36
+ return getFirstNavigableItem(instance);
37
+ }
38
+ return nextItemId;
39
+ };
40
+ let matchingItemId = null;
41
+ let currentItemId = getNextItem(itemId);
42
+ const checkedItems = {};
43
+ // The "!checkedItems[currentItemId]" condition avoids an infinite loop when there is no matching item.
44
+ while (matchingItemId == null && !checkedItems[currentItemId]) {
45
+ if (firstCharMap.current[currentItemId] === cleanQuery) {
46
+ matchingItemId = currentItemId;
47
+ } else {
48
+ checkedItems[currentItemId] = true;
49
+ currentItemId = getNextItem(currentItemId);
52
50
  }
53
- });
54
-
55
- // Get start index for search based on position of currentItem
56
- start = firstCharIds.indexOf(itemId) + 1;
57
- if (start >= firstCharIds.length) {
58
- start = 0;
59
- }
60
-
61
- // Check remaining slots in the menu
62
- index = findNextFirstChar(firstChars, start, lowercaseChar);
63
-
64
- // If not found in remaining slots, check from beginning
65
- if (index === -1) {
66
- index = findNextFirstChar(firstChars, 0, lowercaseChar);
67
- }
68
-
69
- // If a match was found...
70
- if (index > -1) {
71
- return firstCharIds[index];
72
51
  }
73
- return null;
52
+ return matchingItemId;
74
53
  };
75
54
  const canToggleItemSelection = itemId => !params.disableSelection && !instance.isItemDisabled(itemId);
76
55
  const canToggleItemExpansion = itemId => {
@@ -95,9 +74,7 @@ export const useTreeViewKeyboardNavigation = ({
95
74
  {
96
75
  event.preventDefault();
97
76
  if (params.multiSelect && event.shiftKey) {
98
- instance.selectRange(event, {
99
- end: itemId
100
- });
77
+ instance.expandSelectionRange(event, itemId);
101
78
  } else if (params.multiSelect) {
102
79
  instance.selectItem(event, itemId, true);
103
80
  } else {
@@ -136,10 +113,7 @@ export const useTreeViewKeyboardNavigation = ({
136
113
  // Multi select behavior when pressing Shift + ArrowDown
137
114
  // Toggles the selection state of the next item
138
115
  if (params.multiSelect && event.shiftKey && canToggleItemSelection(nextItem)) {
139
- instance.selectRange(event, {
140
- end: nextItem,
141
- current: itemId
142
- }, true);
116
+ instance.selectItemFromArrowNavigation(event, itemId, nextItem);
143
117
  }
144
118
  }
145
119
  break;
@@ -156,10 +130,7 @@ export const useTreeViewKeyboardNavigation = ({
156
130
  // Multi select behavior when pressing Shift + ArrowUp
157
131
  // Toggles the selection state of the previous item
158
132
  if (params.multiSelect && event.shiftKey && canToggleItemSelection(previousItem)) {
159
- instance.selectRange(event, {
160
- end: previousItem,
161
- current: itemId
162
- }, true);
133
+ instance.selectItemFromArrowNavigation(event, itemId, previousItem);
163
134
  }
164
135
  }
165
136
  break;
@@ -202,12 +173,12 @@ export const useTreeViewKeyboardNavigation = ({
202
173
  // Focuses the first item in the tree
203
174
  case key === 'Home':
204
175
  {
205
- instance.focusItem(event, getFirstNavigableItem(instance));
206
-
207
176
  // Multi select behavior when pressing Ctrl + Shift + Home
208
177
  // Selects the focused item and all items up to the first item.
209
178
  if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
210
- instance.rangeSelectToFirst(event, itemId);
179
+ instance.selectRangeFromStartToItem(event, itemId);
180
+ } else {
181
+ instance.focusItem(event, getFirstNavigableItem(instance));
211
182
  }
212
183
  event.preventDefault();
213
184
  break;
@@ -216,12 +187,12 @@ export const useTreeViewKeyboardNavigation = ({
216
187
  // Focuses the last item in the tree
217
188
  case key === 'End':
218
189
  {
219
- instance.focusItem(event, getLastNavigableItem(instance));
220
-
221
190
  // Multi select behavior when pressing Ctrl + Shirt + End
222
191
  // Selects the focused item and all the items down to the last item.
223
192
  if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
224
- instance.rangeSelectToLast(event, itemId);
193
+ instance.selectRangeFromItemToEnd(event, itemId);
194
+ } else {
195
+ instance.focusItem(event, getLastNavigableItem(instance));
225
196
  }
226
197
  event.preventDefault();
227
198
  break;
@@ -239,10 +210,7 @@ export const useTreeViewKeyboardNavigation = ({
239
210
  // Selects all the items
240
211
  case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection:
241
212
  {
242
- instance.selectRange(event, {
243
- start: getFirstNavigableItem(instance),
244
- end: getLastNavigableItem(instance)
245
- });
213
+ instance.selectAllNavigableItems(event);
246
214
  event.preventDefault();
247
215
  break;
248
216
  }
@@ -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
+ };