@mui/x-tree-view 7.12.0 → 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 +151 -2
  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 +10 -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
@@ -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({
@@ -1,12 +1,13 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3
- const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "itemId", "onClick", "onMouseDown", "dragAndDropOverlayProps"];
3
+ const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "itemId", "onClick", "onMouseDown", "dragAndDropOverlayProps", "labelInputProps"];
4
4
  import * as React from 'react';
5
5
  import PropTypes from 'prop-types';
6
6
  import clsx from 'clsx';
7
7
  import Checkbox from '@mui/material/Checkbox';
8
8
  import { useTreeItemState } from './useTreeItemState';
9
9
  import { TreeItem2DragAndDropOverlay } from '../TreeItem2DragAndDropOverlay';
10
+ import { TreeItem2LabelInput } from '../TreeItem2LabelInput';
10
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
12
  /**
12
13
  * @ignore - internal component.
@@ -22,7 +23,8 @@ const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(p
22
23
  itemId,
23
24
  onClick,
24
25
  onMouseDown,
25
- dragAndDropOverlayProps
26
+ dragAndDropOverlayProps,
27
+ labelInputProps
26
28
  } = props,
27
29
  other = _objectWithoutPropertiesLoose(props, _excluded);
28
30
  const {
@@ -30,6 +32,8 @@ const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(p
30
32
  expanded,
31
33
  selected,
32
34
  focused,
35
+ editing,
36
+ editable,
33
37
  disableSelection,
34
38
  checkboxSelection,
35
39
  handleExpansion,
@@ -37,7 +41,10 @@ const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(p
37
41
  handleCheckboxSelection,
38
42
  handleContentClick,
39
43
  preventSelection,
40
- expansionTrigger
44
+ expansionTrigger,
45
+ toggleItemEditing,
46
+ handleSaveItemLabel,
47
+ handleCancelItemLabelEditing
41
48
  } = useTreeItemState(itemId);
42
49
  const icon = iconProp || expansionIcon || displayIcon;
43
50
  const checkboxRef = React.useRef(null);
@@ -62,11 +69,36 @@ const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(p
62
69
  onClick(event);
63
70
  }
64
71
  };
72
+ const handleLabelDoubleClick = event => {
73
+ if (event.defaultMuiPrevented) {
74
+ return;
75
+ }
76
+ toggleItemEditing();
77
+ };
78
+ const handleLabelInputBlur = event => {
79
+ if (event.defaultMuiPrevented) {
80
+ return;
81
+ }
82
+ if (event.target.value) {
83
+ handleSaveItemLabel(event, event.target.value);
84
+ }
85
+ };
86
+ const handleLabelInputKeydown = event => {
87
+ if (event.defaultMuiPrevented) {
88
+ return;
89
+ }
90
+ const target = event.target;
91
+ if (event.key === 'Enter' && target.value) {
92
+ handleSaveItemLabel(event, target.value);
93
+ } else if (event.key === 'Escape') {
94
+ handleCancelItemLabelEditing(event);
95
+ }
96
+ };
65
97
  return (
66
98
  /*#__PURE__*/
67
99
  /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */
68
100
  _jsxs("div", _extends({}, other, {
69
- className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled),
101
+ className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled, editing && classes.editing, editable && classes.editable),
70
102
  onClick: handleClick,
71
103
  onMouseDown: handleMouseDown,
72
104
  ref: ref,
@@ -80,10 +112,17 @@ const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(p
80
112
  disabled: disabled || disableSelection,
81
113
  ref: checkboxRef,
82
114
  tabIndex: -1
83
- }), /*#__PURE__*/_jsx("div", {
84
- className: classes.label,
115
+ }), editing ? /*#__PURE__*/_jsx(TreeItem2LabelInput, _extends({}, labelInputProps, {
116
+ className: classes.labelInput,
117
+ onBlur: handleLabelInputBlur,
118
+ onKeyDown: handleLabelInputKeydown
119
+ })) : /*#__PURE__*/_jsx("div", _extends({
120
+ className: classes.label
121
+ }, editable && {
122
+ onDoubleClick: handleLabelDoubleClick
123
+ }, {
85
124
  children: label
86
- }), dragAndDropOverlayProps && /*#__PURE__*/_jsx(TreeItem2DragAndDropOverlay, _extends({}, dragAndDropOverlayProps))]
125
+ })), dragAndDropOverlayProps && /*#__PURE__*/_jsx(TreeItem2DragAndDropOverlay, _extends({}, dragAndDropOverlayProps))]
87
126
  }))
88
127
  );
89
128
  });
@@ -120,6 +159,7 @@ process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = {
120
159
  /**
121
160
  * The tree item label.
122
161
  */
123
- label: PropTypes.node
162
+ label: PropTypes.node,
163
+ labelInputProps: PropTypes.object
124
164
  } : void 0;
125
165
  export { TreeItemContent };
@@ -3,4 +3,4 @@ import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
3
3
  export function getTreeItemUtilityClass(slot) {
4
4
  return generateUtilityClass('MuiTreeItem', slot);
5
5
  }
6
- export const treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'groupTransition', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label', 'checkbox', 'dragAndDropOverlay']);
6
+ export const treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'groupTransition', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label', 'checkbox', 'labelInput', 'editable', 'editing', 'dragAndDropOverlay']);
@@ -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
  export function useTreeItemState(itemId) {
3
5
  const {
4
6
  instance,
@@ -19,6 +21,8 @@ export function useTreeItemState(itemId) {
19
21
  const focused = instance.isItemFocused(itemId);
20
22
  const selected = instance.isItemSelected(itemId);
21
23
  const disabled = instance.isItemDisabled(itemId);
24
+ const editing = instance?.isItemBeingEdited ? instance?.isItemBeingEdited(itemId) : false;
25
+ const editable = instance.isItemEditable ? instance.isItemEditable(itemId) : false;
22
26
  const handleExpansion = event => {
23
27
  if (!disabled) {
24
28
  if (!focused) {
@@ -79,11 +83,49 @@ export function useTreeItemState(itemId) {
79
83
  event.preventDefault();
80
84
  }
81
85
  };
86
+ const toggleItemEditing = () => {
87
+ if (!hasPlugin(instance, useTreeViewLabel)) {
88
+ return;
89
+ }
90
+ if (instance.isItemEditable(itemId)) {
91
+ if (instance.isItemBeingEdited(itemId)) {
92
+ instance.setEditedItemId(null);
93
+ } else {
94
+ instance.setEditedItemId(itemId);
95
+ }
96
+ }
97
+ };
98
+ const handleSaveItemLabel = (event, label) => {
99
+ if (!hasPlugin(instance, useTreeViewLabel)) {
100
+ return;
101
+ }
102
+
103
+ // As a side effect of `instance.focusItem` called here and in `handleCancelItemLabelEditing` the `labelInput` is blurred
104
+ // The `onBlur` event is triggered, which calls `handleSaveItemLabel` again.
105
+ // To avoid creating an unwanted behavior we need to check if the item is being edited before calling `updateItemLabel`
106
+ // using `instance.isItemBeingEditedRef` instead of `instance.isItemBeingEdited` since the state is not yet updated in this point
107
+ if (instance.isItemBeingEditedRef(itemId)) {
108
+ instance.updateItemLabel(itemId, label);
109
+ toggleItemEditing();
110
+ instance.focusItem(event, itemId);
111
+ }
112
+ };
113
+ const handleCancelItemLabelEditing = event => {
114
+ if (!hasPlugin(instance, useTreeViewLabel)) {
115
+ return;
116
+ }
117
+ if (instance.isItemBeingEditedRef(itemId)) {
118
+ toggleItemEditing();
119
+ instance.focusItem(event, itemId);
120
+ }
121
+ };
82
122
  return {
83
123
  disabled,
84
124
  expanded,
85
125
  selected,
86
126
  focused,
127
+ editable,
128
+ editing,
87
129
  disableSelection,
88
130
  checkboxSelection,
89
131
  handleExpansion,
@@ -91,6 +133,9 @@ export function useTreeItemState(itemId) {
91
133
  handleCheckboxSelection,
92
134
  handleContentClick: onItemClick,
93
135
  preventSelection,
94
- expansionTrigger
136
+ expansionTrigger,
137
+ toggleItemEditing,
138
+ handleSaveItemLabel,
139
+ handleCancelItemLabelEditing
95
140
  };
96
141
  }
@@ -18,6 +18,7 @@ import { getTreeItemUtilityClass } from '../TreeItem';
18
18
  import { TreeItem2Icon } from '../TreeItem2Icon';
19
19
  import { TreeItem2DragAndDropOverlay } from '../TreeItem2DragAndDropOverlay';
20
20
  import { TreeItem2Provider } from '../TreeItem2Provider';
21
+ import { TreeItem2LabelInput } from '../TreeItem2LabelInput';
21
22
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
23
  const useThemeProps = createUseThemeProps('MuiTreeItem2');
23
24
  export const TreeItem2Root = styled('li', {
@@ -104,7 +105,8 @@ export const TreeItem2Content = styled('div', {
104
105
  export const TreeItem2Label = styled('div', {
105
106
  name: 'MuiTreeItem2',
106
107
  slot: 'Label',
107
- overridesResolver: (props, styles) => styles.label
108
+ overridesResolver: (props, styles) => styles.label,
109
+ shouldForwardProp: prop => shouldForwardProp(prop) && prop !== 'editable'
108
110
  })(({
109
111
  theme
110
112
  }) => _extends({
@@ -113,8 +115,18 @@ export const TreeItem2Label = styled('div', {
113
115
  // prevent width + padding to overflow
114
116
  // fixes overflow - see https://github.com/mui/material-ui/issues/27372
115
117
  minWidth: 0,
116
- position: 'relative'
117
- }, theme.typography.body1));
118
+ position: 'relative',
119
+ overflow: 'hidden'
120
+ }, theme.typography.body1, {
121
+ variants: [{
122
+ props: ({
123
+ editable
124
+ }) => editable,
125
+ style: {
126
+ paddingLeft: '2px'
127
+ }
128
+ }]
129
+ }));
118
130
  export const TreeItem2IconContainer = styled('div', {
119
131
  name: 'MuiTreeItem2',
120
132
  slot: 'IconContainer',
@@ -172,6 +184,8 @@ const useUtilityClasses = ownerState => {
172
184
  root: ['root'],
173
185
  content: ['content'],
174
186
  expanded: ['expanded'],
187
+ editing: ['editing'],
188
+ editable: ['editable'],
175
189
  selected: ['selected'],
176
190
  focused: ['focused'],
177
191
  disabled: ['disabled'],
@@ -179,6 +193,7 @@ const useUtilityClasses = ownerState => {
179
193
  checkbox: ['checkbox'],
180
194
  label: ['label'],
181
195
  groupTransition: ['groupTransition'],
196
+ labelInput: ['labelInput'],
182
197
  dragAndDropOverlay: ['dragAndDropOverlay']
183
198
  };
184
199
  return composeClasses(slots, getTreeItemUtilityClass, classes);
@@ -215,6 +230,7 @@ export const TreeItem2 = /*#__PURE__*/React.forwardRef(function TreeItem2(inProp
215
230
  getCheckboxProps,
216
231
  getLabelProps,
217
232
  getGroupTransitionProps,
233
+ getLabelInputProps,
218
234
  getDragAndDropOverlayProps,
219
235
  status
220
236
  } = useTreeItem2({
@@ -244,7 +260,7 @@ export const TreeItem2 = /*#__PURE__*/React.forwardRef(function TreeItem2(inProp
244
260
  getSlotProps: getContentProps,
245
261
  externalSlotProps: slotProps.content,
246
262
  ownerState: {},
247
- className: clsx(classes.content, status.expanded && classes.expanded, status.selected && classes.selected, status.focused && classes.focused, status.disabled && classes.disabled)
263
+ className: clsx(classes.content, status.expanded && classes.expanded, status.selected && classes.selected, status.focused && classes.focused, status.disabled && classes.disabled, status.editing && classes.editing, status.editable && classes.editable)
248
264
  });
249
265
  const IconContainer = slots.iconContainer ?? TreeItem2IconContainer;
250
266
  const iconContainerProps = useSlotProps({
@@ -278,6 +294,14 @@ export const TreeItem2 = /*#__PURE__*/React.forwardRef(function TreeItem2(inProp
278
294
  ownerState: {},
279
295
  className: classes.groupTransition
280
296
  });
297
+ const LabelInput = slots.labelInput ?? TreeItem2LabelInput;
298
+ const labelInputProps = useSlotProps({
299
+ elementType: LabelInput,
300
+ getSlotProps: getLabelInputProps,
301
+ externalSlotProps: slotProps.labelInput,
302
+ ownerState: {},
303
+ className: classes.labelInput
304
+ });
281
305
  const DragAndDropOverlay = slots.dragAndDropOverlay ?? TreeItem2DragAndDropOverlay;
282
306
  const dragAndDropOverlayProps = useSlotProps({
283
307
  elementType: DragAndDropOverlay,
@@ -296,7 +320,7 @@ export const TreeItem2 = /*#__PURE__*/React.forwardRef(function TreeItem2(inProp
296
320
  slots: slots,
297
321
  slotProps: slotProps
298
322
  })
299
- })), /*#__PURE__*/_jsx(Checkbox, _extends({}, checkboxProps)), /*#__PURE__*/_jsx(Label, _extends({}, labelProps)), /*#__PURE__*/_jsx(DragAndDropOverlay, _extends({}, dragAndDropOverlayProps))]
323
+ })), /*#__PURE__*/_jsx(Checkbox, _extends({}, checkboxProps)), status.editing ? /*#__PURE__*/_jsx(LabelInput, _extends({}, labelInputProps)) : /*#__PURE__*/_jsx(Label, _extends({}, labelProps)), /*#__PURE__*/_jsx(DragAndDropOverlay, _extends({}, dragAndDropOverlayProps))]
300
324
  })), children && /*#__PURE__*/_jsx(TreeItem2GroupTransition, _extends({
301
325
  as: GroupTransition
302
326
  }, groupTransitionProps))]
@@ -59,6 +59,8 @@ process.env.NODE_ENV !== "production" ? TreeItem2Icon.propTypes = {
59
59
  slots: PropTypes.object,
60
60
  status: PropTypes.shape({
61
61
  disabled: PropTypes.bool.isRequired,
62
+ editable: PropTypes.bool.isRequired,
63
+ editing: PropTypes.bool.isRequired,
62
64
  expandable: PropTypes.bool.isRequired,
63
65
  expanded: PropTypes.bool.isRequired,
64
66
  focused: PropTypes.bool.isRequired,
@@ -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 @@
1
+ export { TreeItem2LabelInput } from './TreeItem2LabelInput';
@@ -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,
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v7.12.0
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
@@ -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';
@@ -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,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)) {
@@ -0,0 +1 @@
1
+ export { useTreeViewLabel } from './useTreeViewLabel';
@@ -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
+ };