@mui/x-tree-view 7.12.1 → 7.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/RichTreeView/RichTreeView.js +20 -2
  3. package/RichTreeView/RichTreeView.plugins.d.ts +3 -2
  4. package/RichTreeView/RichTreeView.plugins.js +2 -1
  5. package/TreeItem/TreeItem.js +24 -0
  6. package/TreeItem/TreeItemContent.d.ts +8 -0
  7. package/TreeItem/TreeItemContent.js +48 -8
  8. package/TreeItem/treeItemClasses.d.ts +6 -0
  9. package/TreeItem/treeItemClasses.js +1 -1
  10. package/TreeItem/useTreeItemState.d.ts +6 -0
  11. package/TreeItem/useTreeItemState.js +46 -1
  12. package/TreeItem2/TreeItem2.d.ts +3 -1
  13. package/TreeItem2/TreeItem2.js +29 -5
  14. package/TreeItem2/TreeItem2.types.d.ts +6 -0
  15. package/TreeItem2Icon/TreeItem2Icon.js +2 -0
  16. package/TreeItem2LabelInput/TreeItem2LabelInput.d.ts +2 -0
  17. package/TreeItem2LabelInput/TreeItem2LabelInput.js +20 -0
  18. package/TreeItem2LabelInput/TreeItem2LabelInput.types.d.ts +8 -0
  19. package/TreeItem2LabelInput/TreeItem2LabelInput.types.js +1 -0
  20. package/TreeItem2LabelInput/index.d.ts +2 -0
  21. package/TreeItem2LabelInput/index.js +1 -0
  22. package/TreeItem2LabelInput/package.json +6 -0
  23. package/hooks/useTreeItem2Utils/useTreeItem2Utils.d.ts +5 -1
  24. package/hooks/useTreeItem2Utils/useTreeItem2Utils.js +45 -2
  25. package/hooks/useTreeViewApiRef.d.ts +1 -1
  26. package/index.js +1 -1
  27. package/internals/index.d.ts +2 -0
  28. package/internals/index.js +1 -0
  29. package/internals/models/itemPlugin.d.ts +2 -1
  30. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +4 -1
  31. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +2 -0
  32. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +5 -1
  33. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +2 -0
  34. package/internals/plugins/useTreeViewLabel/index.d.ts +2 -0
  35. package/internals/plugins/useTreeViewLabel/index.js +1 -0
  36. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.d.ts +3 -0
  37. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.d.ts +3 -0
  38. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +44 -0
  39. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +81 -0
  40. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.d.ts +75 -0
  41. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.js +1 -0
  42. package/modern/RichTreeView/RichTreeView.js +20 -2
  43. package/modern/RichTreeView/RichTreeView.plugins.js +2 -1
  44. package/modern/TreeItem/TreeItem.js +24 -0
  45. package/modern/TreeItem/TreeItemContent.js +48 -8
  46. package/modern/TreeItem/treeItemClasses.js +1 -1
  47. package/modern/TreeItem/useTreeItemState.js +46 -1
  48. package/modern/TreeItem2/TreeItem2.js +29 -5
  49. package/modern/TreeItem2Icon/TreeItem2Icon.js +2 -0
  50. package/modern/TreeItem2LabelInput/TreeItem2LabelInput.js +20 -0
  51. package/modern/TreeItem2LabelInput/TreeItem2LabelInput.types.js +1 -0
  52. package/modern/TreeItem2LabelInput/index.js +1 -0
  53. package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +45 -2
  54. package/modern/index.js +1 -1
  55. package/modern/internals/index.js +1 -0
  56. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +4 -1
  57. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +5 -1
  58. package/modern/internals/plugins/useTreeViewLabel/index.js +1 -0
  59. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +44 -0
  60. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +81 -0
  61. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.js +1 -0
  62. package/modern/useTreeItem2/useTreeItem2.js +65 -4
  63. package/node/RichTreeView/RichTreeView.js +20 -2
  64. package/node/RichTreeView/RichTreeView.plugins.js +2 -1
  65. package/node/TreeItem/TreeItem.js +24 -0
  66. package/node/TreeItem/TreeItemContent.js +48 -8
  67. package/node/TreeItem/treeItemClasses.js +1 -1
  68. package/node/TreeItem/useTreeItemState.js +46 -1
  69. package/node/TreeItem2/TreeItem2.js +29 -5
  70. package/node/TreeItem2Icon/TreeItem2Icon.js +2 -0
  71. package/node/TreeItem2LabelInput/TreeItem2LabelInput.js +26 -0
  72. package/node/TreeItem2LabelInput/TreeItem2LabelInput.types.js +5 -0
  73. package/node/TreeItem2LabelInput/index.js +12 -0
  74. package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +45 -2
  75. package/node/index.js +1 -1
  76. package/node/internals/index.js +7 -0
  77. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +4 -1
  78. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +5 -1
  79. package/node/internals/plugins/useTreeViewLabel/index.js +12 -0
  80. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +54 -0
  81. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +91 -0
  82. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.js +5 -0
  83. package/node/useTreeItem2/useTreeItem2.js +65 -4
  84. package/package.json +2 -2
  85. package/useTreeItem2/index.d.ts +1 -1
  86. package/useTreeItem2/useTreeItem2.js +65 -4
  87. package/useTreeItem2/useTreeItem2.types.d.ts +35 -15
@@ -0,0 +1,20 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import { styled } from '../internals/zero-styled';
3
+ const TreeItem2LabelInput = styled('input', {
4
+ name: 'MuiTreeItem2',
5
+ slot: 'LabelInput',
6
+ overridesResolver: (props, styles) => styles.labelInput
7
+ })(({
8
+ theme
9
+ }) => _extends({}, theme.typography.body1, {
10
+ width: '100%',
11
+ backgroundColor: theme.palette.background.paper,
12
+ borderRadius: theme.shape.borderRadius,
13
+ border: 'none',
14
+ padding: '0 2px',
15
+ boxSizing: 'border-box',
16
+ '&:focus': {
17
+ outline: `1px solid ${theme.palette.primary.main}`
18
+ }
19
+ }));
20
+ export { TreeItem2LabelInput };
@@ -0,0 +1,8 @@
1
+ export interface TreeItem2LabelInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
2
+ value?: string;
3
+ onChange?: React.ChangeEventHandler<HTMLInputElement>;
4
+ /**
5
+ * Used to determine if the target of keydown or blur events is the input and prevent the event from propagating to the root.
6
+ */
7
+ 'data-element'?: 'labelInput';
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { TreeItem2LabelInput } from './TreeItem2LabelInput';
2
+ export type { TreeItem2LabelInputProps } from './TreeItem2LabelInput.types';
@@ -0,0 +1 @@
1
+ export { TreeItem2LabelInput } from './TreeItem2LabelInput';
@@ -0,0 +1,6 @@
1
+ {
2
+ "sideEffects": false,
3
+ "module": "./index.js",
4
+ "main": "../node/TreeItem2LabelInput/index.js",
5
+ "types": "./index.d.ts"
6
+ }
@@ -1,9 +1,13 @@
1
1
  import * as React from 'react';
2
+ import { UseTreeViewLabelSignature } from '../../internals/plugins/useTreeViewLabel';
2
3
  import type { UseTreeItem2Status } from '../../useTreeItem2';
3
4
  interface UseTreeItem2Interactions {
4
5
  handleExpansion: (event: React.MouseEvent) => void;
5
6
  handleSelection: (event: React.MouseEvent) => void;
6
7
  handleCheckboxSelection: (event: React.ChangeEvent<HTMLInputElement>) => void;
8
+ toggleItemEditing: () => void;
9
+ handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void;
10
+ handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void;
7
11
  }
8
12
  interface UseTreeItem2UtilsReturnValue {
9
13
  interactions: UseTreeItem2Interactions;
@@ -12,7 +16,7 @@ interface UseTreeItem2UtilsReturnValue {
12
16
  /**
13
17
  * Plugins that `useTreeItem2Utils` can use if they are present, but are not required.
14
18
  */
15
- export type UseTreeItem2UtilsOptionalPlugins = readonly [];
19
+ export type UseTreeItem2UtilsOptionalPlugins = readonly [UseTreeViewLabelSignature];
16
20
  export declare const useTreeItem2Utils: ({ itemId, children, }: {
17
21
  itemId: string;
18
22
  children: React.ReactNode;
@@ -1,4 +1,6 @@
1
1
  import { useTreeViewContext } from '../../internals/TreeViewProvider';
2
+ import { useTreeViewLabel } from '../../internals/plugins/useTreeViewLabel';
3
+ import { hasPlugin } from '../../internals/utils/plugins';
2
4
  const isItemExpandable = reactChildren => {
3
5
  if (Array.isArray(reactChildren)) {
4
6
  return reactChildren.length > 0 && reactChildren.some(isItemExpandable);
@@ -29,7 +31,9 @@ export const useTreeItem2Utils = ({
29
31
  expanded: instance.isItemExpanded(itemId),
30
32
  focused: instance.isItemFocused(itemId),
31
33
  selected: instance.isItemSelected(itemId),
32
- disabled: instance.isItemDisabled(itemId)
34
+ disabled: instance.isItemDisabled(itemId),
35
+ editing: instance?.isItemBeingEdited ? instance?.isItemBeingEdited(itemId) : false,
36
+ editable: instance.isItemEditable ? instance.isItemEditable(itemId) : false
33
37
  };
34
38
  const handleExpansion = event => {
35
39
  if (status.disabled) {
@@ -84,10 +88,49 @@ export const useTreeItem2Utils = ({
84
88
  });
85
89
  }
86
90
  };
91
+ const toggleItemEditing = () => {
92
+ if (!hasPlugin(instance, useTreeViewLabel)) {
93
+ return;
94
+ }
95
+ if (instance.isItemEditable(itemId)) {
96
+ if (instance.isItemBeingEdited(itemId)) {
97
+ instance.setEditedItemId(null);
98
+ } else {
99
+ instance.setEditedItemId(itemId);
100
+ }
101
+ }
102
+ };
103
+ const handleSaveItemLabel = (event, label) => {
104
+ if (!hasPlugin(instance, useTreeViewLabel)) {
105
+ return;
106
+ }
107
+
108
+ // As a side effect of `instance.focusItem` called here and in `handleCancelItemLabelEditing` the `labelInput` is blurred
109
+ // The `onBlur` event is triggered, which calls `handleSaveItemLabel` again.
110
+ // To avoid creating an unwanted behavior we need to check if the item is being edited before calling `updateItemLabel`
111
+ // using `instance.isItemBeingEditedRef` instead of `instance.isItemBeingEdited` since the state is not yet updated in this point
112
+ if (instance.isItemBeingEditedRef(itemId)) {
113
+ instance.updateItemLabel(itemId, label);
114
+ toggleItemEditing();
115
+ instance.focusItem(event, itemId);
116
+ }
117
+ };
118
+ const handleCancelItemLabelEditing = event => {
119
+ if (!hasPlugin(instance, useTreeViewLabel)) {
120
+ return;
121
+ }
122
+ if (instance.isItemBeingEditedRef(itemId)) {
123
+ toggleItemEditing();
124
+ instance.focusItem(event, itemId);
125
+ }
126
+ };
87
127
  const interactions = {
88
128
  handleExpansion,
89
129
  handleSelection,
90
- handleCheckboxSelection
130
+ handleCheckboxSelection,
131
+ toggleItemEditing,
132
+ handleSaveItemLabel,
133
+ handleCancelItemLabelEditing
91
134
  };
92
135
  return {
93
136
  interactions,
@@ -3,4 +3,4 @@ import { TreeViewAnyPluginSignature, TreeViewPublicAPI } from '../internals/mode
3
3
  /**
4
4
  * Hook that instantiates a [[TreeViewApiRef]].
5
5
  */
6
- export declare const useTreeViewApiRef: <TSignatures extends readonly TreeViewAnyPluginSignature[] = readonly [import("../internals").UseTreeViewItemsSignature, import("../internals").UseTreeViewExpansionSignature, import("../internals").UseTreeViewSelectionSignature, import("../internals").UseTreeViewFocusSignature, import("../internals").UseTreeViewKeyboardNavigationSignature, import("../internals").UseTreeViewIconsSignature]>() => React.MutableRefObject<TreeViewPublicAPI<TSignatures> | undefined>;
6
+ export declare const useTreeViewApiRef: <TSignatures extends readonly TreeViewAnyPluginSignature[] = readonly [import("../internals").UseTreeViewItemsSignature, import("../internals").UseTreeViewExpansionSignature, import("../internals").UseTreeViewSelectionSignature, import("../internals").UseTreeViewFocusSignature, import("../internals").UseTreeViewKeyboardNavigationSignature, import("../internals").UseTreeViewIconsSignature, import("../internals").UseTreeViewLabelSignature]>() => React.MutableRefObject<TreeViewPublicAPI<TSignatures> | undefined>;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v7.12.1
2
+ * @mui/x-tree-view v7.13.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -15,6 +15,8 @@ export { useTreeViewIcons } from './plugins/useTreeViewIcons';
15
15
  export type { UseTreeViewIconsSignature, UseTreeViewIconsParameters, } from './plugins/useTreeViewIcons';
16
16
  export { useTreeViewItems, buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID, } from './plugins/useTreeViewItems';
17
17
  export type { UseTreeViewItemsSignature, UseTreeViewItemsParameters, UseTreeViewItemsState, } from './plugins/useTreeViewItems';
18
+ export { useTreeViewLabel } from './plugins/useTreeViewLabel';
19
+ export type { UseTreeViewLabelSignature, UseTreeViewLabelParameters, } from './plugins/useTreeViewLabel';
18
20
  export { useTreeViewJSXItems } from './plugins/useTreeViewJSXItems';
19
21
  export type { UseTreeViewJSXItemsSignature, UseTreeViewJSXItemsParameters, } from './plugins/useTreeViewJSXItems';
20
22
  export { isTargetInDescendants } from './utils/tree';
@@ -11,6 +11,7 @@ export { useTreeViewFocus } from './plugins/useTreeViewFocus';
11
11
  export { useTreeViewKeyboardNavigation } from './plugins/useTreeViewKeyboardNavigation';
12
12
  export { useTreeViewIcons } from './plugins/useTreeViewIcons';
13
13
  export { useTreeViewItems, buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from './plugins/useTreeViewItems';
14
+ export { useTreeViewLabel } from './plugins/useTreeViewLabel';
14
15
  export { useTreeViewJSXItems } from './plugins/useTreeViewJSXItems';
15
16
  export { isTargetInDescendants } from './utils/tree';
16
17
  export { warnOnce } from './utils/warning';
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { EventHandlers } from '@mui/utils';
3
- import type { UseTreeItem2ContentSlotOwnProps, UseTreeItem2DragAndDropOverlaySlotOwnProps, UseTreeItem2RootSlotOwnProps } from '../../useTreeItem2';
3
+ import type { UseTreeItem2ContentSlotOwnProps, UseTreeItem2DragAndDropOverlaySlotOwnProps, UseTreeItem2LabelInputSlotOwnProps, UseTreeItem2RootSlotOwnProps } from '../../useTreeItem2';
4
4
  export interface TreeViewItemPluginSlotPropsEnhancerParams {
5
5
  rootRefObject: React.MutableRefObject<HTMLLIElement | null>;
6
6
  contentRefObject: React.MutableRefObject<HTMLDivElement | null>;
@@ -11,6 +11,7 @@ export interface TreeViewItemPluginSlotPropsEnhancers {
11
11
  root?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2RootSlotOwnProps>;
12
12
  content?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2ContentSlotOwnProps>;
13
13
  dragAndDropOverlay?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2DragAndDropOverlaySlotOwnProps>;
14
+ labelInput?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2LabelInputSlotOwnProps>;
14
15
  }
15
16
  export interface TreeViewItemPluginResponse {
16
17
  /**
@@ -57,8 +57,11 @@ export const useTreeViewExpansion = ({
57
57
  if (params.expansionTrigger) {
58
58
  return params.expansionTrigger;
59
59
  }
60
+ if (instance.isTreeViewEditable) {
61
+ return 'iconContainer';
62
+ }
60
63
  return 'content';
61
- }, [params.expansionTrigger]);
64
+ }, [params.expansionTrigger, instance.isTreeViewEditable]);
62
65
  return {
63
66
  publicAPI: {
64
67
  setItemExpansion
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
3
3
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
4
  import { TreeViewItemId } from '../../../models';
5
+ import { UseTreeViewLabelSignature } from '../useTreeViewLabel';
5
6
  export interface UseTreeViewExpansionPublicAPI {
6
7
  /**
7
8
  * Change the expansion status of a given item.
@@ -83,5 +84,6 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{
83
84
  modelNames: 'expandedItems';
84
85
  contextValue: UseTreeViewExpansionContextValue;
85
86
  dependencies: [UseTreeViewItemsSignature];
87
+ optionalDependencies: [UseTreeViewLabelSignature];
86
88
  }>;
87
89
  export {};
@@ -2,6 +2,8 @@ import * as React from 'react';
2
2
  import { useRtl } from '@mui/system/RtlProvider';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
4
  import { getFirstNavigableItem, getLastNavigableItem, getNextNavigableItem, getPreviousNavigableItem, isTargetInDescendants } from '../../utils/tree';
5
+ import { hasPlugin } from '../../utils/plugins';
6
+ import { useTreeViewLabel } from '../useTreeViewLabel';
5
7
  function isPrintableCharacter(string) {
6
8
  return !!string && string.length === 1 && !!string.match(/\S/);
7
9
  }
@@ -89,7 +91,9 @@ export const useTreeViewKeyboardNavigation = ({
89
91
  // If the focused item has no children, we select it.
90
92
  case key === 'Enter':
91
93
  {
92
- if (canToggleItemExpansion(itemId)) {
94
+ if (hasPlugin(instance, useTreeViewLabel) && instance.isItemEditable(itemId) && !instance.isItemBeingEdited(itemId)) {
95
+ instance.setEditedItemId(itemId);
96
+ } else if (canToggleItemExpansion(itemId)) {
93
97
  instance.toggleItemExpansion(event, itemId);
94
98
  event.preventDefault();
95
99
  } else if (canToggleItemSelection(itemId)) {
@@ -5,6 +5,7 @@ import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
5
5
  import { UseTreeViewFocusSignature } from '../useTreeViewFocus';
6
6
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
7
  import { TreeViewItemId } from '../../../models';
8
+ import { UseTreeViewLabelSignature } from '../useTreeViewLabel';
8
9
  export interface UseTreeViewKeyboardNavigationInstance {
9
10
  /**
10
11
  * Updates the `firstCharMap` to add/remove the first character of some item's labels.
@@ -29,6 +30,7 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
29
30
  UseTreeViewFocusSignature,
30
31
  UseTreeViewExpansionSignature
31
32
  ];
33
+ optionalDependencies: [UseTreeViewLabelSignature];
32
34
  }>;
33
35
  export type TreeViewFirstCharMap = {
34
36
  [itemId: string]: string;
@@ -0,0 +1,2 @@
1
+ export { useTreeViewLabel } from './useTreeViewLabel';
2
+ export type { UseTreeViewLabelSignature, UseTreeViewLabelParameters, } from './useTreeViewLabel.types';
@@ -0,0 +1 @@
1
+ export { useTreeViewLabel } from './useTreeViewLabel';
@@ -0,0 +1,3 @@
1
+ import { TreeViewPlugin } from '../../models';
2
+ import { UseTreeViewLabelSignature } from './useTreeViewLabel.types';
3
+ export declare const useTreeViewLabel: TreeViewPlugin<UseTreeViewLabelSignature>;
@@ -0,0 +1,3 @@
1
+ import { TreeViewItemPlugin } from '../../models';
2
+ export declare const isAndroid: () => boolean;
3
+ export declare const useTreeViewLabelItemPlugin: TreeViewItemPlugin<any>;
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { useTreeViewContext } from '../../TreeViewProvider';
3
+ export const isAndroid = () => navigator.userAgent.toLowerCase().includes('android');
4
+ export const useTreeViewLabelItemPlugin = ({
5
+ props
6
+ }) => {
7
+ const {
8
+ instance
9
+ } = useTreeViewContext();
10
+ const {
11
+ label,
12
+ itemId
13
+ } = props;
14
+ const [labelInputValue, setLabelInputValue] = React.useState(label);
15
+ const isItemBeingEdited = instance.isItemBeingEdited(itemId);
16
+ React.useEffect(() => {
17
+ if (!isItemBeingEdited) {
18
+ setLabelInputValue(label);
19
+ }
20
+ }, [isItemBeingEdited, label]);
21
+ return {
22
+ propsEnhancers: {
23
+ labelInput: ({
24
+ externalEventHandlers
25
+ }) => {
26
+ const editable = instance.isItemEditable(itemId);
27
+ if (!editable) {
28
+ return {};
29
+ }
30
+ const handleInputChange = event => {
31
+ externalEventHandlers.onChange?.(event);
32
+ setLabelInputValue(event.target.value);
33
+ };
34
+ return {
35
+ value: labelInputValue ?? '',
36
+ 'data-element': 'labelInput',
37
+ onChange: handleInputChange,
38
+ autoFocus: true,
39
+ type: 'text'
40
+ };
41
+ }
42
+ }
43
+ };
44
+ };
@@ -0,0 +1,81 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import * as React from 'react';
3
+ import { warnOnce } from '../../utils/warning';
4
+ import { useTreeViewLabelItemPlugin } from './useTreeViewLabel.itemPlugin';
5
+ export const useTreeViewLabel = ({
6
+ instance,
7
+ state,
8
+ setState,
9
+ params,
10
+ experimentalFeatures
11
+ }) => {
12
+ if (process.env.NODE_ENV !== 'production') {
13
+ if (params.isItemEditable && !experimentalFeatures?.labelEditing) {
14
+ warnOnce(['MUI X: The label editing feature requires the `labelEditing` experimental feature to be enabled.', 'You can do it by passing `experimentalFeatures={{ labelEditing: true}}` to the `RichTreeViewPro` component.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/editing/']);
15
+ }
16
+ }
17
+ const editedItemRef = React.useRef(state.editedItemId);
18
+ const isItemBeingEditedRef = itemId => editedItemRef.current === itemId;
19
+ const setEditedItemId = editedItemId => {
20
+ setState(prevState => _extends({}, prevState, {
21
+ editedItemId
22
+ }));
23
+ editedItemRef.current = editedItemId;
24
+ };
25
+ const isItemBeingEdited = itemId => itemId === state.editedItemId;
26
+ const isTreeViewEditable = Boolean(params.isItemEditable) && !!experimentalFeatures.labelEditing;
27
+ const isItemEditable = itemId => {
28
+ if (itemId == null || !isTreeViewEditable) {
29
+ return false;
30
+ }
31
+ const item = instance.getItem(itemId);
32
+ if (!item) {
33
+ return false;
34
+ }
35
+ return typeof params.isItemEditable === 'function' ? params.isItemEditable(item) : Boolean(params.isItemEditable);
36
+ };
37
+ const updateItemLabel = (itemId, label) => {
38
+ if (!label) {
39
+ throw new Error(['MUI X: The Tree View component requires all items to have a `label` property.', 'The label of an item cannot be empty.', itemId].join('\n'));
40
+ }
41
+ setState(prevState => {
42
+ const item = prevState.items.itemMetaMap[itemId];
43
+ if (item.label !== label) {
44
+ return _extends({}, prevState, {
45
+ items: _extends({}, prevState.items, {
46
+ itemMetaMap: _extends({}, prevState.items.itemMetaMap, {
47
+ [itemId]: _extends({}, item, {
48
+ label
49
+ })
50
+ })
51
+ })
52
+ });
53
+ }
54
+ return prevState;
55
+ });
56
+ if (params.onItemLabelChange) {
57
+ params.onItemLabelChange(itemId, label);
58
+ }
59
+ };
60
+ return {
61
+ instance: {
62
+ setEditedItemId,
63
+ isItemBeingEdited,
64
+ updateItemLabel,
65
+ isItemEditable,
66
+ isTreeViewEditable,
67
+ isItemBeingEditedRef
68
+ },
69
+ publicAPI: {
70
+ updateItemLabel
71
+ }
72
+ };
73
+ };
74
+ useTreeViewLabel.itemPlugin = useTreeViewLabelItemPlugin;
75
+ useTreeViewLabel.getInitialState = () => ({
76
+ editedItemId: null
77
+ });
78
+ useTreeViewLabel.params = {
79
+ onItemLabelChange: true,
80
+ isItemEditable: true
81
+ };
@@ -0,0 +1,75 @@
1
+ import { TreeViewPluginSignature } from '../../models';
2
+ import { TreeViewItemId } from '../../../models';
3
+ import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
+ import { TreeItem2LabelInputProps } from '../../../TreeItem2LabelInput';
5
+ export interface UseTreeViewLabelPublicAPI {
6
+ /**
7
+ * Used to update the label of an item.
8
+ * @param {TreeViewItemId} itemId The id of the item to update the label of.
9
+ * @param {string} newLabel The new label of the item.
10
+ */
11
+ updateItemLabel: (itemId: TreeViewItemId, newLabel: string) => void;
12
+ }
13
+ export interface UseTreeViewLabelInstance extends UseTreeViewLabelPublicAPI {
14
+ /**
15
+ * Updates which item is currently being edited.
16
+ * @param {TreeViewItemId} itemId The id of the item that is currently being edited.
17
+ * @returns {void}.
18
+ */
19
+ setEditedItemId: (itemId: TreeViewItemId | null) => void;
20
+ /**
21
+ * Checks if an item is being edited or not.
22
+ * @param {TreeViewItemId} itemId The id of the item to check.
23
+ * @returns {boolean}.
24
+ */
25
+ isItemBeingEdited: (itemId: TreeViewItemId) => boolean;
26
+ /**
27
+ * Checks if an item is being edited or not.
28
+ * Purely internal use, used to avoid unnecessarily calling `updateItemLabel` twice.
29
+ * @param {TreeViewItemId} itemId The id of the item to check.
30
+ * @returns {boolean}.
31
+ */
32
+ isItemBeingEditedRef: (itemId: TreeViewItemId) => boolean;
33
+ /**
34
+ * Determines if a given item is editable.
35
+ * @param {TreeViewItemId} itemId The id of the item to check.
36
+ * @returns {boolean} `true` if the item is editable.
37
+ */
38
+ isItemEditable: (itemId: TreeViewItemId) => boolean;
39
+ /**
40
+ * Set to `true` if the tree view is editable.
41
+ */
42
+ isTreeViewEditable: boolean;
43
+ }
44
+ export interface UseTreeViewLabelParameters<R extends {}> {
45
+ /**
46
+ * Callback fired when the label of an item changes.
47
+ * @param {TreeViewItemId} itemId The id of the item that was edited.
48
+ * @param {string} newLabel The new label of the items.
49
+ */
50
+ onItemLabelChange?: (itemId: TreeViewItemId, newLabel: string) => void;
51
+ /**
52
+ * Determines if a given item is editable or not.
53
+ * Make sure to also enable the `labelEditing` experimental feature:
54
+ * `<RichTreeViewPro experimentalFeatures={{ labelEditing: true }} />`.
55
+ * By default, the items are not editable.
56
+ * @template R
57
+ * @param {R} item The item to check.
58
+ * @returns {boolean} `true` if the item is editable.
59
+ */
60
+ isItemEditable?: boolean | ((item: R) => boolean);
61
+ }
62
+ export interface UseTreeViewLabelState {
63
+ editedItemId: string | null;
64
+ }
65
+ export type UseTreeViewLabelSignature = TreeViewPluginSignature<{
66
+ params: UseTreeViewLabelParameters<any>;
67
+ defaultizedParams: UseTreeViewLabelParameters<any>;
68
+ publicAPI: UseTreeViewLabelPublicAPI;
69
+ instance: UseTreeViewLabelInstance;
70
+ state: UseTreeViewLabelState;
71
+ experimentalFeatures: 'labelEditing';
72
+ dependencies: [UseTreeViewItemsSignature];
73
+ }>;
74
+ export interface UseTreeItem2LabelInputSlotPropsFromItemsReordering extends TreeItem2LabelInputProps {
75
+ }
@@ -140,7 +140,8 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
140
140
  getItemOrderedChildrenIds: PropTypes.func.isRequired,
141
141
  getItemTree: PropTypes.func.isRequired,
142
142
  selectItem: PropTypes.func.isRequired,
143
- setItemExpansion: PropTypes.func.isRequired
143
+ setItemExpansion: PropTypes.func.isRequired,
144
+ updateItemLabel: PropTypes.func.isRequired
144
145
  })
145
146
  }),
146
147
  /**
@@ -191,7 +192,8 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
191
192
  * the feature will be fully disabled and any property / method call will not have any effect.
192
193
  */
193
194
  experimentalFeatures: PropTypes.shape({
194
- indentationAtItemLevel: PropTypes.bool
195
+ indentationAtItemLevel: PropTypes.bool,
196
+ labelEditing: PropTypes.bool
195
197
  }),
196
198
  /**
197
199
  * Used to determine the id of a given item.
@@ -223,6 +225,16 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
223
225
  * @returns {boolean} `true` if the item should be disabled.
224
226
  */
225
227
  isItemDisabled: PropTypes.func,
228
+ /**
229
+ * Determines if a given item is editable or not.
230
+ * Make sure to also enable the `labelEditing` experimental feature:
231
+ * `<RichTreeViewPro experimentalFeatures={{ labelEditing: true }} />`.
232
+ * By default, the items are not editable.
233
+ * @template R
234
+ * @param {R} item The item to check.
235
+ * @returns {boolean} `true` if the item is editable.
236
+ */
237
+ isItemEditable: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
226
238
  /**
227
239
  * Horizontal indentation between an item and its children.
228
240
  * Examples: 24, "24px", "2rem", "2em".
@@ -260,6 +272,12 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
260
272
  * @param {string} itemId The id of the focused item.
261
273
  */
262
274
  onItemFocus: PropTypes.func,
275
+ /**
276
+ * Callback fired when the label of an item changes.
277
+ * @param {TreeViewItemId} itemId The id of the item that was edited.
278
+ * @param {string} newLabel The new label of the items.
279
+ */
280
+ onItemLabelChange: PropTypes.func,
263
281
  /**
264
282
  * Callback fired when a tree item is selected or deselected.
265
283
  * @param {React.SyntheticEvent} event The DOM event that triggered the change.
@@ -4,6 +4,7 @@ import { useTreeViewSelection } from '../internals/plugins/useTreeViewSelection'
4
4
  import { useTreeViewFocus } from '../internals/plugins/useTreeViewFocus';
5
5
  import { useTreeViewKeyboardNavigation } from '../internals/plugins/useTreeViewKeyboardNavigation';
6
6
  import { useTreeViewIcons } from '../internals/plugins/useTreeViewIcons';
7
- export const RICH_TREE_VIEW_PLUGINS = [useTreeViewItems, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewIcons];
7
+ import { useTreeViewLabel } from '../internals/plugins/useTreeViewLabel';
8
+ export const RICH_TREE_VIEW_PLUGINS = [useTreeViewItems, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewIcons, useTreeViewLabel];
8
9
 
9
10
  // We can't infer this type from the plugin, otherwise we would lose the generics.
@@ -25,6 +25,7 @@ import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons';
25
25
  import { TreeItem2Provider } from '../TreeItem2Provider';
26
26
  import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext';
27
27
  import { useTreeItemState } from './useTreeItemState';
28
+ import { isTargetInDescendants } from '../internals/utils/tree';
28
29
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
29
30
  const useThemeProps = createUseThemeProps('MuiTreeItem');
30
31
  const useUtilityClasses = ownerState => {
@@ -41,6 +42,9 @@ const useUtilityClasses = ownerState => {
41
42
  iconContainer: ['iconContainer'],
42
43
  checkbox: ['checkbox'],
43
44
  label: ['label'],
45
+ labelInput: ['labelInput'],
46
+ editing: ['editing'],
47
+ editable: ['editable'],
44
48
  groupTransition: ['groupTransition']
45
49
  };
46
50
  return composeClasses(slots, getTreeItemUtilityClass, classes);
@@ -207,6 +211,7 @@ export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps,
207
211
  focused,
208
212
  selected,
209
213
  disabled,
214
+ editing,
210
215
  handleExpansion
211
216
  } = useTreeItemState(itemId);
212
217
  const {
@@ -317,10 +322,19 @@ export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps,
317
322
  }
318
323
  function handleBlur(event) {
319
324
  onBlur?.(event);
325
+ if (editing ||
326
+ // we can exit the editing state by clicking outside the input (within the tree item) or by pressing Enter or Escape -> we don't want to remove the focused item from the state in these cases
327
+ // we can also exit the editing state by clicking on the root itself -> want to remove the focused item from the state in this case
328
+ event.relatedTarget && isTargetInDescendants(event.relatedTarget, rootRefObject.current) && (event.target && event.target?.dataset?.element === 'labelInput' && isTargetInDescendants(event.target, rootRefObject.current) || event.relatedTarget?.dataset?.element === 'labelInput')) {
329
+ return;
330
+ }
320
331
  instance.removeFocusedItem();
321
332
  }
322
333
  const handleKeyDown = event => {
323
334
  onKeyDown?.(event);
335
+ if (event.target?.dataset?.element === 'labelInput') {
336
+ return;
337
+ }
324
338
  instance.handleItemKeyDown(event, itemId);
325
339
  };
326
340
  const idAttribute = instance.getTreeItemIdAttribute(itemId, id);
@@ -340,6 +354,11 @@ export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps,
340
354
  contentRefObject,
341
355
  externalEventHandlers: {}
342
356
  }) ?? {};
357
+ const enhancedLabelInputProps = propsEnhancers.labelInput?.({
358
+ rootRefObject,
359
+ contentRefObject,
360
+ externalEventHandlers: {}
361
+ }) ?? {};
343
362
  return /*#__PURE__*/_jsx(TreeItem2Provider, {
344
363
  itemId: itemId,
345
364
  children: /*#__PURE__*/_jsxs(TreeItemRoot, _extends({
@@ -368,8 +387,11 @@ export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps,
368
387
  selected: classes.selected,
369
388
  focused: classes.focused,
370
389
  disabled: classes.disabled,
390
+ editable: classes.editable,
391
+ editing: classes.editing,
371
392
  iconContainer: classes.iconContainer,
372
393
  label: classes.label,
394
+ labelInput: classes.labelInput,
373
395
  checkbox: classes.checkbox
374
396
  },
375
397
  label: label,
@@ -382,6 +404,8 @@ export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps,
382
404
  ownerState: ownerState
383
405
  }, ContentProps, enhancedContentProps, enhancedDragAndDropOverlayProps.action == null ? {} : {
384
406
  dragAndDropOverlayProps: enhancedDragAndDropOverlayProps
407
+ }, enhancedLabelInputProps.value == null ? {} : {
408
+ labelInputProps: enhancedLabelInputProps
385
409
  }, {
386
410
  ref: handleContentRef
387
411
  })), children && /*#__PURE__*/_jsx(TreeItemGroup, _extends({