@mui/x-tree-view 7.0.0-beta.3 → 7.0.0-beta.5

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 (147) hide show
  1. package/CHANGELOG.md +357 -22
  2. package/RichTreeView/RichTreeView.js +8 -0
  3. package/RichTreeView/RichTreeView.types.d.ts +7 -1
  4. package/SimpleTreeView/SimpleTreeView.js +8 -0
  5. package/SimpleTreeView/SimpleTreeView.types.d.ts +7 -1
  6. package/TreeItem/TreeItem.js +1 -1
  7. package/TreeItem/TreeItem.types.d.ts +1 -1
  8. package/TreeView/TreeView.js +8 -0
  9. package/hooks/index.d.ts +1 -0
  10. package/hooks/index.js +1 -0
  11. package/hooks/package.json +6 -0
  12. package/hooks/useTreeViewApiRef.d.ts +6 -0
  13. package/hooks/useTreeViewApiRef.js +5 -0
  14. package/index.d.ts +1 -0
  15. package/index.js +3 -2
  16. package/internals/TreeViewProvider/DescendantProvider.js +1 -11
  17. package/internals/hooks/useTimeout.d.ts +5 -3
  18. package/internals/hooks/useTimeout.js +13 -5
  19. package/internals/models/helpers.d.ts +1 -0
  20. package/internals/models/plugin.d.ts +12 -0
  21. package/internals/models/treeView.d.ts +1 -0
  22. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +40 -22
  23. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +5 -0
  24. package/internals/useTreeView/useTreeView.d.ts +2 -0
  25. package/internals/useTreeView/useTreeView.js +12 -0
  26. package/internals/useTreeView/useTreeView.types.d.ts +2 -1
  27. package/internals/useTreeView/useTreeView.utils.d.ts +2 -1
  28. package/internals/useTreeView/useTreeView.utils.js +3 -0
  29. package/internals/utils/extractPluginParamsFromProps.d.ts +3 -2
  30. package/internals/utils/extractPluginParamsFromProps.js +5 -3
  31. package/internals/utils/utils.d.ts +1 -0
  32. package/internals/utils/utils.js +10 -0
  33. package/modern/RichTreeView/RichTreeView.js +8 -0
  34. package/modern/SimpleTreeView/SimpleTreeView.js +8 -0
  35. package/modern/TreeItem/TreeItem.js +1 -1
  36. package/modern/TreeView/TreeView.js +8 -0
  37. package/modern/hooks/index.js +1 -0
  38. package/modern/hooks/useTreeViewApiRef.js +5 -0
  39. package/modern/index.js +3 -2
  40. package/modern/internals/TreeViewProvider/DescendantProvider.js +1 -11
  41. package/modern/internals/hooks/useTimeout.js +13 -5
  42. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +40 -22
  43. package/modern/internals/useTreeView/useTreeView.js +12 -0
  44. package/modern/internals/useTreeView/useTreeView.utils.js +3 -0
  45. package/modern/internals/utils/extractPluginParamsFromProps.js +5 -3
  46. package/modern/internals/utils/utils.js +10 -0
  47. package/node/RichTreeView/RichTreeView.js +8 -0
  48. package/node/SimpleTreeView/SimpleTreeView.js +8 -0
  49. package/node/TreeItem/TreeItem.js +1 -1
  50. package/node/TreeView/TreeView.js +8 -0
  51. package/node/hooks/index.js +12 -0
  52. package/node/hooks/useTreeViewApiRef.js +14 -0
  53. package/node/index.js +13 -1
  54. package/node/internals/TreeViewProvider/DescendantProvider.js +1 -10
  55. package/node/internals/hooks/useTimeout.js +13 -4
  56. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +39 -21
  57. package/node/internals/useTreeView/useTreeView.js +13 -0
  58. package/node/internals/useTreeView/useTreeView.utils.js +6 -2
  59. package/node/internals/utils/extractPluginParamsFromProps.js +5 -3
  60. package/node/internals/utils/utils.js +17 -0
  61. package/package.json +1 -1
  62. package/legacy/RichTreeView/RichTreeView.js +0 -255
  63. package/legacy/RichTreeView/RichTreeView.types.js +0 -1
  64. package/legacy/RichTreeView/index.js +0 -3
  65. package/legacy/RichTreeView/richTreeViewClasses.js +0 -6
  66. package/legacy/SimpleTreeView/SimpleTreeView.js +0 -190
  67. package/legacy/SimpleTreeView/SimpleTreeView.plugins.js +0 -6
  68. package/legacy/SimpleTreeView/SimpleTreeView.types.js +0 -1
  69. package/legacy/SimpleTreeView/index.js +0 -3
  70. package/legacy/SimpleTreeView/simpleTreeViewClasses.js +0 -6
  71. package/legacy/TreeItem/TreeItem.js +0 -360
  72. package/legacy/TreeItem/TreeItem.types.js +0 -1
  73. package/legacy/TreeItem/TreeItemContent.js +0 -95
  74. package/legacy/TreeItem/index.js +0 -4
  75. package/legacy/TreeItem/treeItemClasses.js +0 -6
  76. package/legacy/TreeItem/useTreeItemState.js +0 -58
  77. package/legacy/TreeView/TreeView.js +0 -165
  78. package/legacy/TreeView/TreeView.types.js +0 -1
  79. package/legacy/TreeView/index.js +0 -3
  80. package/legacy/TreeView/treeViewClasses.js +0 -6
  81. package/legacy/icons/icons.js +0 -9
  82. package/legacy/icons/index.js +0 -1
  83. package/legacy/index.js +0 -14
  84. package/legacy/internals/TreeViewProvider/DescendantProvider.js +0 -199
  85. package/legacy/internals/TreeViewProvider/TreeViewContext.js +0 -8
  86. package/legacy/internals/TreeViewProvider/TreeViewProvider.js +0 -19
  87. package/legacy/internals/TreeViewProvider/TreeViewProvider.types.js +0 -1
  88. package/legacy/internals/TreeViewProvider/index.js +0 -1
  89. package/legacy/internals/TreeViewProvider/useTreeViewContext.js +0 -9
  90. package/legacy/internals/corePlugins/corePlugins.js +0 -6
  91. package/legacy/internals/corePlugins/index.js +0 -1
  92. package/legacy/internals/corePlugins/useTreeViewInstanceEvents/index.js +0 -1
  93. package/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +0 -40
  94. package/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js +0 -1
  95. package/legacy/internals/hooks/useInstanceEventHandler.js +0 -87
  96. package/legacy/internals/hooks/useLazyRef.js +0 -11
  97. package/legacy/internals/hooks/useOnMount.js +0 -7
  98. package/legacy/internals/hooks/useTimeout.js +0 -38
  99. package/legacy/internals/index.js +0 -4
  100. package/legacy/internals/models/MuiCancellableEvent.js +0 -1
  101. package/legacy/internals/models/events.js +0 -1
  102. package/legacy/internals/models/helpers.js +0 -1
  103. package/legacy/internals/models/index.js +0 -3
  104. package/legacy/internals/models/plugin.js +0 -1
  105. package/legacy/internals/models/treeView.js +0 -1
  106. package/legacy/internals/plugins/defaultPlugins.js +0 -10
  107. package/legacy/internals/plugins/index.js +0 -1
  108. package/legacy/internals/plugins/useTreeViewExpansion/index.js +0 -1
  109. package/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +0 -81
  110. package/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js +0 -1
  111. package/legacy/internals/plugins/useTreeViewFocus/index.js +0 -1
  112. package/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +0 -103
  113. package/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js +0 -1
  114. package/legacy/internals/plugins/useTreeViewIcons/index.js +0 -1
  115. package/legacy/internals/plugins/useTreeViewIcons/useTreeViewIcons.js +0 -21
  116. package/legacy/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.js +0 -1
  117. package/legacy/internals/plugins/useTreeViewId/index.js +0 -1
  118. package/legacy/internals/plugins/useTreeViewId/useTreeViewId.js +0 -24
  119. package/legacy/internals/plugins/useTreeViewId/useTreeViewId.types.js +0 -1
  120. package/legacy/internals/plugins/useTreeViewJSXNodes/index.js +0 -1
  121. package/legacy/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +0 -130
  122. package/legacy/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.js +0 -1
  123. package/legacy/internals/plugins/useTreeViewKeyboardNavigation/index.js +0 -1
  124. package/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +0 -284
  125. package/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js +0 -1
  126. package/legacy/internals/plugins/useTreeViewNodes/index.js +0 -1
  127. package/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +0 -161
  128. package/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js +0 -1
  129. package/legacy/internals/plugins/useTreeViewSelection/index.js +0 -1
  130. package/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +0 -219
  131. package/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js +0 -1
  132. package/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -55
  133. package/legacy/internals/useTreeView/index.js +0 -1
  134. package/legacy/internals/useTreeView/useTreeView.js +0 -111
  135. package/legacy/internals/useTreeView/useTreeView.types.js +0 -1
  136. package/legacy/internals/useTreeView/useTreeView.utils.js +0 -46
  137. package/legacy/internals/useTreeView/useTreeViewModels.js +0 -75
  138. package/legacy/internals/utils/EventManager.js +0 -91
  139. package/legacy/internals/utils/cleanupTracking/CleanupTracking.js +0 -1
  140. package/legacy/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js +0 -29
  141. package/legacy/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js +0 -52
  142. package/legacy/internals/utils/extractPluginParamsFromProps.js +0 -36
  143. package/legacy/internals/utils/publishTreeViewEvent.js +0 -3
  144. package/legacy/internals/utils/warning.js +0 -15
  145. package/legacy/models/index.js +0 -1
  146. package/legacy/models/items.js +0 -1
  147. package/legacy/themeAugmentation/index.js +0 -3
@@ -8,17 +8,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
8
8
  /** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md
9
9
  * Modified slightly to suit our purposes.
10
10
  */
11
-
12
- // To replace with .findIndex() once we stop IE11 support.
13
11
  import { jsx as _jsx } from "react/jsx-runtime";
14
- function findIndex(array, comp) {
15
- for (let i = 0; i < array.length; i += 1) {
16
- if (comp(array[i])) {
17
- return i;
18
- }
19
- }
20
- return -1;
21
- }
22
12
  function binaryFindElement(array, element) {
23
13
  let start = 0;
24
14
  let end = array.length - 1;
@@ -84,7 +74,7 @@ export function useDescendant(descendant) {
84
74
  // index on the following render, and we will re-register descendants
85
75
  // so that everything is up-to-date before the user interacts with a
86
76
  // collection.
87
- const index = findIndex(descendants, item => item.element === descendant.element);
77
+ const index = descendants.findIndex(item => item.element === descendant.element);
88
78
  const previousDescendants = usePrevious(descendants);
89
79
 
90
80
  // We also need to re-register descendants any time ANY of the other
@@ -1,9 +1,11 @@
1
- declare class Timeout {
1
+ export declare class Timeout {
2
2
  static create(): Timeout;
3
- currentId: number;
3
+ currentId: ReturnType<typeof setTimeout> | null;
4
+ /**
5
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
6
+ */
4
7
  start(delay: number, fn: Function): void;
5
8
  clear: () => void;
6
9
  disposeEffect: () => () => void;
7
10
  }
8
11
  export declare function useTimeout(): Timeout;
9
- export {};
@@ -1,12 +1,14 @@
1
+ 'use client';
2
+
1
3
  import { useLazyRef } from './useLazyRef';
2
4
  import { useOnMount } from './useOnMount';
3
- class Timeout {
5
+ export class Timeout {
4
6
  constructor() {
5
- this.currentId = 0;
7
+ this.currentId = null;
6
8
  this.clear = () => {
7
- if (this.currentId !== 0) {
9
+ if (this.currentId !== null) {
8
10
  clearTimeout(this.currentId);
9
- this.currentId = 0;
11
+ this.currentId = null;
10
12
  }
11
13
  };
12
14
  this.disposeEffect = () => {
@@ -16,9 +18,15 @@ class Timeout {
16
18
  static create() {
17
19
  return new Timeout();
18
20
  }
21
+ /**
22
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
23
+ */
19
24
  start(delay, fn) {
20
25
  this.clear();
21
- this.currentId = setTimeout(fn, delay);
26
+ this.currentId = setTimeout(() => {
27
+ this.currentId = null;
28
+ fn();
29
+ }, delay);
22
30
  }
23
31
  }
24
32
  export function useTimeout() {
@@ -7,6 +7,7 @@ export type ConvertPluginsIntoSignatures<TPlugins extends readonly any[]> = TPlu
7
7
  export interface MergePlugins<TPlugins extends readonly any[]> {
8
8
  state: MergePluginsProperty<TPlugins, 'state'>;
9
9
  instance: MergePluginsProperty<TPlugins, 'instance'>;
10
+ publicAPI: MergePluginsProperty<TPlugins, 'publicAPI'>;
10
11
  params: MergePluginsProperty<TPlugins, 'params'>;
11
12
  defaultizedParams: MergePluginsProperty<TPlugins, 'defaultizedParams'>;
12
13
  dependantPlugins: MergePluginsProperty<TPlugins, 'dependantPlugins'>;
@@ -7,6 +7,7 @@ import type { TreeViewCorePluginsSignature } from '../corePlugins';
7
7
  import type { TreeItemProps } from '../../TreeItem';
8
8
  export interface TreeViewPluginOptions<TSignature extends TreeViewAnyPluginSignature> {
9
9
  instance: TreeViewUsedInstance<TSignature>;
10
+ publicAPI: TreeViewUsedPublicAPI<TSignature>;
10
11
  params: TreeViewUsedDefaultizedParams<TSignature>;
11
12
  state: TreeViewUsedState<TSignature>;
12
13
  slots: TSignature['slots'];
@@ -27,6 +28,7 @@ export type TreeViewPluginSignature<T extends {
27
28
  params?: {};
28
29
  defaultizedParams?: {};
29
30
  instance?: {};
31
+ publicAPI?: {};
30
32
  events?: {
31
33
  [key in keyof T['events']]: TreeViewEventLookupElement;
32
34
  };
@@ -50,6 +52,9 @@ export type TreeViewPluginSignature<T extends {
50
52
  instance: T extends {
51
53
  instance: {};
52
54
  } ? T['instance'] : {};
55
+ publicAPI: T extends {
56
+ publicAPI: {};
57
+ } ? T['publicAPI'] : {};
53
58
  events: T extends {
54
59
  events: {};
55
60
  } ? T['events'] : {};
@@ -86,6 +91,7 @@ export type TreeViewAnyPluginSignature = {
86
91
  slots: any;
87
92
  slotProps: any;
88
93
  models: any;
94
+ publicAPI: any;
89
95
  };
90
96
  type TreeViewUsedPlugins<TSignature extends TreeViewAnyPluginSignature> = [
91
97
  TreeViewCorePluginsSignature,
@@ -99,6 +105,12 @@ export type TreeViewUsedInstance<TSignature extends TreeViewAnyPluginSignature>
99
105
  */
100
106
  $$signature: TSignature;
101
107
  };
108
+ export type TreeViewUsedPublicAPI<TSignature extends TreeViewAnyPluginSignature> = TSignature['publicAPI'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'publicAPI'> & {
109
+ /**
110
+ * Private property only defined in TypeScript to be able to access the plugin signature from the publicAPI object.
111
+ */
112
+ $$signature: TSignature;
113
+ };
102
114
  type TreeViewUsedState<TSignature extends TreeViewAnyPluginSignature> = TSignature['state'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'state'>;
103
115
  type RemoveSetValue<Models extends Record<string, TreeViewModel<any>>> = {
104
116
  [K in keyof Models]: Omit<Models[K], 'setValue'>;
@@ -24,3 +24,4 @@ export interface TreeViewModel<TValue> {
24
24
  setControlledValue: (value: TValue | ((prevValue: TValue) => TValue)) => void;
25
25
  }
26
26
  export type TreeViewInstance<TSignatures extends readonly TreeViewAnyPluginSignature[]> = MergePluginsProperty<TSignatures, 'instance'>;
27
+ export type TreeViewPublicAPI<TSignatures extends readonly TreeViewAnyPluginSignature[]> = MergePluginsProperty<TSignatures, 'publicAPI'>;
@@ -2,10 +2,12 @@ import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
4
  import ownerDocument from '@mui/utils/ownerDocument';
5
- import { populateInstance } from '../../useTreeView/useTreeView.utils';
5
+ import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils';
6
6
  import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
7
+ import { getActiveElement } from '../../utils/utils';
7
8
  export const useTreeViewFocus = ({
8
9
  instance,
10
+ publicAPI,
9
11
  params,
10
12
  state,
11
13
  setState,
@@ -14,19 +16,45 @@ export const useTreeViewFocus = ({
14
16
  }) => {
15
17
  const setFocusedNodeId = useEventCallback(nodeId => {
16
18
  const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId;
17
- setState(prevState => _extends({}, prevState, {
18
- focusedNodeId: cleanNodeId
19
- }));
19
+ if (state.focusedNodeId !== cleanNodeId) {
20
+ setState(prevState => _extends({}, prevState, {
21
+ focusedNodeId: cleanNodeId
22
+ }));
23
+ }
20
24
  });
21
- const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]);
25
+ const isTreeViewFocused = React.useCallback(() => !!rootRef.current && rootRef.current === getActiveElement(ownerDocument(rootRef.current)), [rootRef]);
26
+ const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId && isTreeViewFocused(), [state.focusedNodeId, isTreeViewFocused]);
27
+ const isNodeVisible = nodeId => {
28
+ const node = instance.getNode(nodeId);
29
+ return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
30
+ };
22
31
  const focusNode = useEventCallback((event, nodeId) => {
23
- if (nodeId) {
32
+ // if we receive a nodeId, and it is visible, the focus will be set to it
33
+ if (nodeId && isNodeVisible(nodeId)) {
34
+ if (!isTreeViewFocused()) {
35
+ instance.focusRoot();
36
+ }
24
37
  setFocusedNodeId(nodeId);
25
38
  if (params.onNodeFocus) {
26
39
  params.onNodeFocus(event, nodeId);
27
40
  }
28
41
  }
29
42
  });
43
+ const focusDefaultNode = useEventCallback(event => {
44
+ let nodeToFocusId;
45
+ if (Array.isArray(models.selectedNodes.value)) {
46
+ nodeToFocusId = models.selectedNodes.value.find(isNodeVisible);
47
+ } else if (models.selectedNodes.value != null && isNodeVisible(models.selectedNodes.value)) {
48
+ nodeToFocusId = models.selectedNodes.value;
49
+ }
50
+ if (nodeToFocusId == null) {
51
+ nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
52
+ }
53
+ setFocusedNodeId(nodeToFocusId);
54
+ if (params.onNodeFocus) {
55
+ params.onNodeFocus(event, nodeToFocusId);
56
+ }
57
+ });
30
58
  const focusRoot = useEventCallback(() => {
31
59
  var _rootRef$current;
32
60
  (_rootRef$current = rootRef.current) == null || _rootRef$current.focus({
@@ -36,7 +64,11 @@ export const useTreeViewFocus = ({
36
64
  populateInstance(instance, {
37
65
  isNodeFocused,
38
66
  focusNode,
39
- focusRoot
67
+ focusRoot,
68
+ focusDefaultNode
69
+ });
70
+ populatePublicAPI(publicAPI, {
71
+ focusNode
40
72
  });
41
73
  useInstanceEventHandler(instance, 'removeNode', ({
42
74
  id
@@ -51,23 +83,9 @@ export const useTreeViewFocus = ({
51
83
  const createHandleFocus = otherHandlers => event => {
52
84
  var _otherHandlers$onFocu;
53
85
  (_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event);
54
-
55
86
  // if the event bubbled (which is React specific) we don't want to steal focus
56
87
  if (event.target === event.currentTarget) {
57
- const isNodeVisible = nodeId => {
58
- const node = instance.getNode(nodeId);
59
- return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
60
- };
61
- let nodeToFocusId;
62
- if (Array.isArray(models.selectedNodes.value)) {
63
- nodeToFocusId = models.selectedNodes.value.find(isNodeVisible);
64
- } else if (models.selectedNodes.value != null && isNodeVisible(models.selectedNodes.value)) {
65
- nodeToFocusId = models.selectedNodes.value;
66
- }
67
- if (nodeToFocusId == null) {
68
- nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
69
- }
70
- instance.focusNode(event, nodeToFocusId);
88
+ instance.focusDefaultNode(event);
71
89
  }
72
90
  };
73
91
  const createHandleBlur = otherHandlers => event => {
@@ -7,8 +7,12 @@ import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
7
  export interface UseTreeViewFocusInstance {
8
8
  isNodeFocused: (nodeId: string) => boolean;
9
9
  focusNode: (event: React.SyntheticEvent, nodeId: string | null) => void;
10
+ focusDefaultNode: (event: React.SyntheticEvent) => void;
10
11
  focusRoot: () => void;
11
12
  }
13
+ export interface UseTreeViewFocusPublicAPI {
14
+ focusNode: (event: React.SyntheticEvent, nodeId: string | null) => void;
15
+ }
12
16
  export interface UseTreeViewFocusParameters {
13
17
  /**
14
18
  * Callback fired when tree items are focused.
@@ -26,6 +30,7 @@ export type UseTreeViewFocusSignature = TreeViewPluginSignature<{
26
30
  params: UseTreeViewFocusParameters;
27
31
  defaultizedParams: UseTreeViewFocusDefaultizedParameters;
28
32
  instance: UseTreeViewFocusInstance;
33
+ publicAPI: UseTreeViewFocusPublicAPI;
29
34
  state: UseTreeViewFocusState;
30
35
  dependantPlugins: [
31
36
  UseTreeViewIdSignature,
@@ -1,3 +1,5 @@
1
+ import * as React from 'react';
1
2
  import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures } from '../models';
2
3
  import { UseTreeViewParameters, UseTreeViewReturnValue } from './useTreeView.types';
4
+ export declare function useTreeViewApiInitialization<T>(inputApiRef: React.MutableRefObject<T> | undefined): T;
3
5
  export declare const useTreeView: <Plugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]>(inParams: UseTreeViewParameters<Plugins>) => UseTreeViewReturnValue<ConvertPluginsIntoSignatures<Plugins>>;
@@ -3,6 +3,16 @@ import * as React from 'react';
3
3
  import useForkRef from '@mui/utils/useForkRef';
4
4
  import { useTreeViewModels } from './useTreeViewModels';
5
5
  import { TREE_VIEW_CORE_PLUGINS } from '../corePlugins';
6
+ export function useTreeViewApiInitialization(inputApiRef) {
7
+ const fallbackPublicApiRef = React.useRef({});
8
+ if (inputApiRef) {
9
+ if (inputApiRef.current == null) {
10
+ inputApiRef.current = {};
11
+ }
12
+ return inputApiRef.current;
13
+ }
14
+ return fallbackPublicApiRef.current;
15
+ }
6
16
  export const useTreeView = inParams => {
7
17
  const plugins = [...TREE_VIEW_CORE_PLUGINS, ...inParams.plugins];
8
18
  const params = plugins.reduce((acc, plugin) => {
@@ -14,6 +24,7 @@ export const useTreeView = inParams => {
14
24
  const models = useTreeViewModels(plugins, params);
15
25
  const instanceRef = React.useRef({});
16
26
  const instance = instanceRef.current;
27
+ const publicAPI = useTreeViewApiInitialization(inParams.apiRef);
17
28
  const innerRootRef = React.useRef(null);
18
29
  const handleRootRef = useForkRef(innerRootRef, inParams.rootRef);
19
30
  const [state, setState] = React.useState(() => {
@@ -32,6 +43,7 @@ export const useTreeView = inParams => {
32
43
  const runPlugin = plugin => {
33
44
  const pluginResponse = plugin({
34
45
  instance,
46
+ publicAPI,
35
47
  params,
36
48
  slots: params.slots,
37
49
  slotProps: params.slotProps,
@@ -1,9 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { EventHandlers } from '@mui/base/utils';
3
3
  import type { TreeViewContextValue } from '../TreeViewProvider';
4
- import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewInstance } from '../models';
4
+ import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewInstance, TreeViewPublicAPI } from '../models';
5
5
  export type UseTreeViewParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> = UseTreeViewBaseParameters<TPlugins> & MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'params'>;
6
6
  export interface UseTreeViewBaseParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> {
7
+ apiRef: React.MutableRefObject<TreeViewPublicAPI<ConvertPluginsIntoSignatures<TPlugins>>> | undefined;
7
8
  rootRef?: React.Ref<HTMLUListElement> | undefined;
8
9
  plugins: TPlugins;
9
10
  slots: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'slots'>;
@@ -1,4 +1,4 @@
1
- import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewUsedInstance } from '../models';
1
+ import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewUsedInstance, TreeViewUsedPublicAPI } from '../models';
2
2
  import type { UseTreeViewExpansionSignature } from '../plugins/useTreeViewExpansion';
3
3
  import type { UseTreeViewNodesSignature } from '../plugins/useTreeViewNodes';
4
4
  export declare const getPreviousNode: (instance: TreeViewInstance<[UseTreeViewNodesSignature, UseTreeViewExpansionSignature]>, nodeId: string) => string | null;
@@ -6,3 +6,4 @@ export declare const getNextNode: (instance: TreeViewInstance<[UseTreeViewExpans
6
6
  export declare const getLastNode: (instance: TreeViewInstance<[UseTreeViewExpansionSignature, UseTreeViewNodesSignature]>) => string;
7
7
  export declare const getFirstNode: (instance: TreeViewInstance<[UseTreeViewNodesSignature]>) => string;
8
8
  export declare const populateInstance: <T extends TreeViewAnyPluginSignature>(instance: TreeViewUsedInstance<T>, methods: T["instance"]) => void;
9
+ export declare const populatePublicAPI: <T extends TreeViewAnyPluginSignature>(publicAPI: TreeViewUsedPublicAPI<T>, methods: T["publicAPI"]) => void;
@@ -40,4 +40,7 @@ export const getLastNode = instance => {
40
40
  export const getFirstNode = instance => instance.getNavigableChildrenIds(null)[0];
41
41
  export const populateInstance = (instance, methods) => {
42
42
  Object.assign(instance, methods);
43
+ };
44
+ export const populatePublicAPI = (publicAPI, methods) => {
45
+ Object.assign(publicAPI, methods);
43
46
  };
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react';
2
- import { ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewPlugin } from '../models';
2
+ import { ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewPlugin, TreeViewPublicAPI } from '../models';
3
3
  import { UseTreeViewBaseParameters } from '../useTreeView/useTreeView.types';
4
4
  export declare const extractPluginParamsFromProps: <TPlugins extends readonly TreeViewPlugin<any>[], TSlots extends MergePluginsProperty<TPlugins, "slots">, TSlotProps extends MergePluginsProperty<TPlugins, "slotProps">, TProps extends {
5
5
  slots?: TSlots | undefined;
6
6
  slotProps?: TSlotProps | undefined;
7
- }>({ props: { slots, slotProps, ...props }, plugins, rootRef, }: {
7
+ apiRef?: React.MutableRefObject<TreeViewPublicAPI<ConvertPluginsIntoSignatures<TPlugins>> | undefined> | undefined;
8
+ }>({ props: { slots, slotProps, apiRef, ...props }, plugins, rootRef, }: {
8
9
  props: TProps;
9
10
  plugins: TPlugins;
10
11
  rootRef?: React.Ref<HTMLUListElement> | undefined;
@@ -1,10 +1,11 @@
1
1
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2
- const _excluded = ["slots", "slotProps"];
2
+ const _excluded = ["slots", "slotProps", "apiRef"];
3
3
  export const extractPluginParamsFromProps = _ref => {
4
4
  let {
5
5
  props: {
6
6
  slots,
7
- slotProps
7
+ slotProps,
8
+ apiRef
8
9
  },
9
10
  plugins,
10
11
  rootRef
@@ -18,7 +19,8 @@ export const extractPluginParamsFromProps = _ref => {
18
19
  plugins,
19
20
  rootRef,
20
21
  slots: slots != null ? slots : {},
21
- slotProps: slotProps != null ? slotProps : {}
22
+ slotProps: slotProps != null ? slotProps : {},
23
+ apiRef
22
24
  };
23
25
  const otherProps = {};
24
26
  Object.keys(props).forEach(propName => {
@@ -0,0 +1 @@
1
+ export declare const getActiveElement: (root?: Document | ShadowRoot) => Element | null;
@@ -0,0 +1,10 @@
1
+ export const getActiveElement = (root = document) => {
2
+ const activeEl = root.activeElement;
3
+ if (!activeEl) {
4
+ return null;
5
+ }
6
+ if (activeEl.shadowRoot) {
7
+ return getActiveElement(activeEl.shadowRoot);
8
+ }
9
+ return activeEl;
10
+ };
@@ -132,6 +132,14 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
132
132
  // | These PropTypes are generated from the TypeScript type definitions |
133
133
  // | To update them edit the TypeScript types and run "yarn proptypes" |
134
134
  // ----------------------------------------------------------------------
135
+ /**
136
+ * The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
137
+ */
138
+ apiRef: PropTypes.shape({
139
+ current: PropTypes.shape({
140
+ focusNode: PropTypes.func.isRequired
141
+ })
142
+ }),
135
143
  /**
136
144
  * Override or extend the styles applied to the component.
137
145
  */
@@ -90,6 +90,14 @@ process.env.NODE_ENV !== "production" ? SimpleTreeView.propTypes = {
90
90
  // | These PropTypes are generated from the TypeScript type definitions |
91
91
  // | To update them edit the TypeScript types and run "yarn proptypes" |
92
92
  // ----------------------------------------------------------------------
93
+ /**
94
+ * The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
95
+ */
96
+ apiRef: PropTypes.shape({
97
+ current: PropTypes.shape({
98
+ focusNode: PropTypes.func.isRequired
99
+ })
100
+ }),
93
101
  /**
94
102
  * The content of the component.
95
103
  */
@@ -360,7 +360,7 @@ TreeItem.propTypes = {
360
360
  TransitionComponent: PropTypes.elementType,
361
361
  /**
362
362
  * Props applied to the transition element.
363
- * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component.
363
+ * By default, the element is based on this [`Transition`](https://reactcommunity.org/react-transition-group/transition/) component.
364
364
  */
365
365
  TransitionProps: PropTypes.object
366
366
  };
@@ -64,6 +64,14 @@ process.env.NODE_ENV !== "production" ? TreeView.propTypes = {
64
64
  // | These PropTypes are generated from the TypeScript type definitions |
65
65
  // | To update them edit the TypeScript types and run "yarn proptypes" |
66
66
  // ----------------------------------------------------------------------
67
+ /**
68
+ * The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
69
+ */
70
+ apiRef: PropTypes.shape({
71
+ current: PropTypes.shape({
72
+ focusNode: PropTypes.func.isRequired
73
+ })
74
+ }),
67
75
  /**
68
76
  * The content of the component.
69
77
  */
@@ -0,0 +1 @@
1
+ export { useTreeViewApiRef } from './useTreeViewApiRef';
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Hook that instantiates a [[TreeViewApiRef]].
4
+ */
5
+ export const useTreeViewApiRef = () => React.useRef(undefined);
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v7.0.0-beta.3
2
+ * @mui/x-tree-view v7.0.0-beta.5
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -11,4 +11,5 @@ export * from './SimpleTreeView';
11
11
  export * from './RichTreeView';
12
12
  export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler';
13
13
  export * from './models';
14
- export * from './icons';
14
+ export * from './icons';
15
+ export * from './hooks';
@@ -8,17 +8,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
8
8
  /** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md
9
9
  * Modified slightly to suit our purposes.
10
10
  */
11
-
12
- // To replace with .findIndex() once we stop IE11 support.
13
11
  import { jsx as _jsx } from "react/jsx-runtime";
14
- function findIndex(array, comp) {
15
- for (let i = 0; i < array.length; i += 1) {
16
- if (comp(array[i])) {
17
- return i;
18
- }
19
- }
20
- return -1;
21
- }
22
12
  function binaryFindElement(array, element) {
23
13
  let start = 0;
24
14
  let end = array.length - 1;
@@ -84,7 +74,7 @@ export function useDescendant(descendant) {
84
74
  // index on the following render, and we will re-register descendants
85
75
  // so that everything is up-to-date before the user interacts with a
86
76
  // collection.
87
- const index = findIndex(descendants, item => item.element === descendant.element);
77
+ const index = descendants.findIndex(item => item.element === descendant.element);
88
78
  const previousDescendants = usePrevious(descendants);
89
79
 
90
80
  // We also need to re-register descendants any time ANY of the other
@@ -1,12 +1,14 @@
1
+ 'use client';
2
+
1
3
  import { useLazyRef } from './useLazyRef';
2
4
  import { useOnMount } from './useOnMount';
3
- class Timeout {
5
+ export class Timeout {
4
6
  constructor() {
5
- this.currentId = 0;
7
+ this.currentId = null;
6
8
  this.clear = () => {
7
- if (this.currentId !== 0) {
9
+ if (this.currentId !== null) {
8
10
  clearTimeout(this.currentId);
9
- this.currentId = 0;
11
+ this.currentId = null;
10
12
  }
11
13
  };
12
14
  this.disposeEffect = () => {
@@ -16,9 +18,15 @@ class Timeout {
16
18
  static create() {
17
19
  return new Timeout();
18
20
  }
21
+ /**
22
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
23
+ */
19
24
  start(delay, fn) {
20
25
  this.clear();
21
- this.currentId = setTimeout(fn, delay);
26
+ this.currentId = setTimeout(() => {
27
+ this.currentId = null;
28
+ fn();
29
+ }, delay);
22
30
  }
23
31
  }
24
32
  export function useTimeout() {
@@ -2,10 +2,12 @@ import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
4
  import ownerDocument from '@mui/utils/ownerDocument';
5
- import { populateInstance } from '../../useTreeView/useTreeView.utils';
5
+ import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils';
6
6
  import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
7
+ import { getActiveElement } from '../../utils/utils';
7
8
  export const useTreeViewFocus = ({
8
9
  instance,
10
+ publicAPI,
9
11
  params,
10
12
  state,
11
13
  setState,
@@ -14,19 +16,45 @@ export const useTreeViewFocus = ({
14
16
  }) => {
15
17
  const setFocusedNodeId = useEventCallback(nodeId => {
16
18
  const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId;
17
- setState(prevState => _extends({}, prevState, {
18
- focusedNodeId: cleanNodeId
19
- }));
19
+ if (state.focusedNodeId !== cleanNodeId) {
20
+ setState(prevState => _extends({}, prevState, {
21
+ focusedNodeId: cleanNodeId
22
+ }));
23
+ }
20
24
  });
21
- const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]);
25
+ const isTreeViewFocused = React.useCallback(() => !!rootRef.current && rootRef.current === getActiveElement(ownerDocument(rootRef.current)), [rootRef]);
26
+ const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId && isTreeViewFocused(), [state.focusedNodeId, isTreeViewFocused]);
27
+ const isNodeVisible = nodeId => {
28
+ const node = instance.getNode(nodeId);
29
+ return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
30
+ };
22
31
  const focusNode = useEventCallback((event, nodeId) => {
23
- if (nodeId) {
32
+ // if we receive a nodeId, and it is visible, the focus will be set to it
33
+ if (nodeId && isNodeVisible(nodeId)) {
34
+ if (!isTreeViewFocused()) {
35
+ instance.focusRoot();
36
+ }
24
37
  setFocusedNodeId(nodeId);
25
38
  if (params.onNodeFocus) {
26
39
  params.onNodeFocus(event, nodeId);
27
40
  }
28
41
  }
29
42
  });
43
+ const focusDefaultNode = useEventCallback(event => {
44
+ let nodeToFocusId;
45
+ if (Array.isArray(models.selectedNodes.value)) {
46
+ nodeToFocusId = models.selectedNodes.value.find(isNodeVisible);
47
+ } else if (models.selectedNodes.value != null && isNodeVisible(models.selectedNodes.value)) {
48
+ nodeToFocusId = models.selectedNodes.value;
49
+ }
50
+ if (nodeToFocusId == null) {
51
+ nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
52
+ }
53
+ setFocusedNodeId(nodeToFocusId);
54
+ if (params.onNodeFocus) {
55
+ params.onNodeFocus(event, nodeToFocusId);
56
+ }
57
+ });
30
58
  const focusRoot = useEventCallback(() => {
31
59
  rootRef.current?.focus({
32
60
  preventScroll: true
@@ -35,7 +63,11 @@ export const useTreeViewFocus = ({
35
63
  populateInstance(instance, {
36
64
  isNodeFocused,
37
65
  focusNode,
38
- focusRoot
66
+ focusRoot,
67
+ focusDefaultNode
68
+ });
69
+ populatePublicAPI(publicAPI, {
70
+ focusNode
39
71
  });
40
72
  useInstanceEventHandler(instance, 'removeNode', ({
41
73
  id
@@ -49,23 +81,9 @@ export const useTreeViewFocus = ({
49
81
  });
50
82
  const createHandleFocus = otherHandlers => event => {
51
83
  otherHandlers.onFocus?.(event);
52
-
53
84
  // if the event bubbled (which is React specific) we don't want to steal focus
54
85
  if (event.target === event.currentTarget) {
55
- const isNodeVisible = nodeId => {
56
- const node = instance.getNode(nodeId);
57
- return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
58
- };
59
- let nodeToFocusId;
60
- if (Array.isArray(models.selectedNodes.value)) {
61
- nodeToFocusId = models.selectedNodes.value.find(isNodeVisible);
62
- } else if (models.selectedNodes.value != null && isNodeVisible(models.selectedNodes.value)) {
63
- nodeToFocusId = models.selectedNodes.value;
64
- }
65
- if (nodeToFocusId == null) {
66
- nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
67
- }
68
- instance.focusNode(event, nodeToFocusId);
86
+ instance.focusDefaultNode(event);
69
87
  }
70
88
  };
71
89
  const createHandleBlur = otherHandlers => event => {