From 7a01f9a258302b9196dad6763e5688747306a80e Mon Sep 17 00:00:00 2001 From: delangle Date: Wed, 3 Apr 2024 14:27:35 +0200 Subject: [PATCH 1/3] [TreeView] Return instance and publicAPI methods from plugin and populate the main objects inside useTreeView --- .../headless/LogExpandedItems.js | 2 ++ .../headless/LogExpandedItems.tsx | 2 ++ .../rich-tree-view/headless/headless.md | 20 +++++++++++-- .../useTreeViewInstanceEvents.ts | 15 +++++----- .../src/internals/models/plugin.ts | 7 +++-- .../useTreeViewExpansion.ts | 12 +++++++- .../useTreeViewFocus/useTreeViewFocus.ts | 18 +++++++++-- .../plugins/useTreeViewId/useTreeViewId.ts | 10 +++---- .../useTreeViewJSXNodes.tsx | 14 +++++++++ .../useTreeViewKeyboardNavigation.ts | 17 ++++++++--- .../useTreeViewNodes/useTreeViewNodes.ts | 18 +++++++++-- .../useTreeViewSelection.ts | 14 +++++++++ .../src/internals/useTreeView/useTreeView.ts | 30 +++++++++++-------- .../useTreeView/useTreeView.utils.ts | 21 +------------ 14 files changed, 139 insertions(+), 61 deletions(-) diff --git a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.js b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.js index fbccfb57e3c2..fec5970b3b5d 100644 --- a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.js +++ b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.js @@ -21,6 +21,8 @@ const useTreeViewLogExpanded = ({ params, models }) => { params.logMessage(`Expanded items: ${expandedStr}`); } }, [expandedStr]); // eslint-disable-line react-hooks/exhaustive-deps + + return {}; }; // Sets the default value of this plugin parameters. diff --git a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx index c8decec9b3ca..325a1c3386f6 100644 --- a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx +++ b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx @@ -52,6 +52,8 @@ const useTreeViewLogExpanded: TreeViewPlugin = ({ params.logMessage(`Expanded items: ${expandedStr}`); } }, [expandedStr]); // eslint-disable-line react-hooks/exhaustive-deps + + return {}; }; // Sets the default value of this plugin parameters. diff --git a/docs/data/tree-view/rich-tree-view/headless/headless.md b/docs/data/tree-view/rich-tree-view/headless/headless.md index 6d920b03d2c5..c003a9fa5f69 100644 --- a/docs/data/tree-view/rich-tree-view/headless/headless.md +++ b/docs/data/tree-view/rich-tree-view/headless/headless.md @@ -28,6 +28,8 @@ A custom plugins contains 2 required elements: React.useEffect(() => { console.log(params.customParam); }); + + return {}; }; ``` @@ -48,6 +50,8 @@ const useCustomPlugin = ({ params }) => { React.useEffect(() => { console.log(params.customParam); }); + + return {}; }; useCustomPlugin.params = { customParam: true }; @@ -100,6 +104,8 @@ const useCustomPlugin = ({ models }) => { const updateCustomModel = (newValue) => models.customModel.setControlledValue(newValue); + + return {}; }; ``` @@ -141,11 +147,15 @@ The Tree View instance is an object accessible in all the plugins and in the `Tr It is the main way a plugin can provide features to the rest of the component. ```ts -const useCustomPlugin = ({ models, instance }) => { +const useCustomPlugin = ({ models }) => { const toggleCustomModel = () => models.customModel.setValue(!models.customModel.value); - populateInstance(instance, { toggleCustomModel }); + return { + instance: { + toggleCustomModel, + }, + }; }; ``` @@ -169,7 +179,11 @@ const useCustomPlugin = () => { publishTreeViewEvent(instance, 'toggleCustomModel', { value: newValue }); }; - populateInstance(instance, { toggleCustomModel }); + return { + instance: { + toggleCustomModel, + }, + }; }; ``` diff --git a/packages/x-tree-view/src/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.ts b/packages/x-tree-view/src/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.ts index 9dd96f72c8bc..0357b4096b3d 100644 --- a/packages/x-tree-view/src/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.ts +++ b/packages/x-tree-view/src/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import { EventManager } from '../../utils/EventManager'; import type { TreeViewPlugin } from '../../models'; -import { populateInstance } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types'; import type { TreeViewEventListener } from '../../models/events'; @@ -9,9 +8,7 @@ const isSyntheticEvent = (event: any): event is React.SyntheticEvent => { return event.isPropagationStopped !== undefined; }; -export const useTreeViewInstanceEvents: TreeViewPlugin = ({ - instance, -}) => { +export const useTreeViewInstanceEvents: TreeViewPlugin = () => { const [eventManager] = React.useState(() => new EventManager()); const publishEvent = React.useCallback( @@ -38,10 +35,12 @@ export const useTreeViewInstanceEvents: TreeViewPlugin(instance, { - $$publishEvent: publishEvent, - $$subscribeEvent: subscribeEvent, - }); + return { + instance: { + $$publishEvent: publishEvent, + $$subscribeEvent: subscribeEvent, + }, + }; }; useTreeViewInstanceEvents.params = {}; diff --git a/packages/x-tree-view/src/internals/models/plugin.ts b/packages/x-tree-view/src/internals/models/plugin.ts index 0587d6b19e2c..e08de9c222fb 100644 --- a/packages/x-tree-view/src/internals/models/plugin.ts +++ b/packages/x-tree-view/src/internals/models/plugin.ts @@ -8,7 +8,6 @@ import { TreeViewItemId } from '../../models'; export interface TreeViewPluginOptions { instance: TreeViewUsedInstance; - publicAPI: TreeViewUsedPublicAPI; params: TreeViewUsedDefaultizedParams; state: TreeViewUsedState; slots: TSignature['slots']; @@ -30,7 +29,9 @@ type TreeViewResponse = { getRootProps?: ( otherHandlers: TOther, ) => React.HTMLAttributes; -} & OptionalIfEmpty<'contextValue', TSignature['contextValue']>; +} & OptionalIfEmpty<'publicAPI', TSignature['publicAPI']> & + OptionalIfEmpty<'instance', TSignature['instance']> & + OptionalIfEmpty<'contextValue', TSignature['contextValue']>; export type TreeViewPluginSignature< T extends { @@ -149,7 +150,7 @@ export type TreeItemWrapper = (params: { }) => React.ReactNode; export type TreeViewPlugin = { - (options: TreeViewPluginOptions): void | TreeViewResponse; + (options: TreeViewPluginOptions): TreeViewResponse; getDefaultizedParams?: ( params: TreeViewUsedParams, ) => TSignature['defaultizedParams']; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts index c97d903ed116..fbf842f356ea 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import { TreeViewPlugin } from '../../models'; -import { populateInstance } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types'; export const useTreeViewExpansion: TreeViewPlugin = ({ @@ -72,12 +71,23 @@ export const useTreeViewExpansion: TreeViewPlugin } }; +<<<<<<< Updated upstream populateInstance(instance, { isNodeExpanded, isNodeExpandable, toggleNodeExpansion, expandAllSiblings, }); +======= + return { + instance: { + isItemExpanded, + isItemExpandable, + toggleItemExpansion, + expandAllSiblings, + }, + }; +>>>>>>> Stashed changes }; useTreeViewExpansion.models = { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts index 68d3337ec937..24fc6bcafe79 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts @@ -3,7 +3,6 @@ import useEventCallback from '@mui/utils/useEventCallback'; import { EventHandlers } from '@mui/base/utils'; import ownerDocument from '@mui/utils/ownerDocument'; import { TreeViewPlugin, TreeViewUsedInstance } from '../../models'; -import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewFocusSignature } from './useTreeViewFocus.types'; import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; import { getActiveElement } from '../../utils/utils'; @@ -33,7 +32,6 @@ const useTabbableItemId = ( export const useTreeViewFocus: TreeViewPlugin = ({ instance, - publicAPI, params, state, setState, @@ -119,6 +117,7 @@ export const useTreeViewFocus: TreeViewPlugin = ({ const canItemBeTabbed = (itemId: string) => itemId === tabbableItemId; +<<<<<<< Updated upstream populateInstance(instance, { isNodeFocused, canItemBeTabbed, @@ -134,6 +133,11 @@ export const useTreeViewFocus: TreeViewPlugin = ({ useInstanceEventHandler(instance, 'removeNode', ({ id }) => { if (state.focusedNodeId === id) { instance.focusDefaultNode(null); +======= + useInstanceEventHandler(instance, 'removeItem', ({ id }) => { + if (state.focusedItemId === id) { + instance.focusDefaultItem(null); +>>>>>>> Stashed changes } }); @@ -156,6 +160,16 @@ export const useTreeViewFocus: TreeViewPlugin = ({ onFocus: createHandleFocus(otherHandlers), 'aria-activedescendant': activeDescendant ?? undefined, }), + publicAPI: { + focusItem, + }, + instance: { + isItemFocused, + canItemBeTabbed, + focusItem, + focusDefaultItem, + removeFocusedItem, + }, }; }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewId/useTreeViewId.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewId/useTreeViewId.ts index 2e97b58bd0dd..a0572cc19b23 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewId/useTreeViewId.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewId/useTreeViewId.ts @@ -1,10 +1,9 @@ import * as React from 'react'; import useId from '@mui/utils/useId'; import { TreeViewPlugin } from '../../models'; -import { populateInstance } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewIdSignature } from './useTreeViewId.types'; -export const useTreeViewId: TreeViewPlugin = ({ instance, params }) => { +export const useTreeViewId: TreeViewPlugin = ({ params }) => { const treeId = useId(params.id); const getTreeItemId = React.useCallback( @@ -12,14 +11,13 @@ export const useTreeViewId: TreeViewPlugin = ({ instance [treeId], ); - populateInstance(instance, { - getTreeItemId, - }); - return { getRootProps: () => ({ id: treeId, }), + instance: { + getTreeItemId, + }, }; }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx index 7ac7809b31e2..ccc1e9122cf6 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx @@ -2,8 +2,12 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import useForkRef from '@mui/utils/useForkRef'; import { TreeViewItemPlugin, TreeViewNode, TreeViewPlugin } from '../../models'; +<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx import { populateInstance } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewJSXNodesSignature } from './useTreeViewJSXNodes.types'; +======= +import { UseTreeViewJSXItemsSignature } from './useTreeViewJSXItems.types'; +>>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext'; import { @@ -75,11 +79,21 @@ export const useTreeViewJSXNodes: TreeViewPlugin = }; }); +<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx populateInstance(instance, { insertJSXNode, removeJSXNode, mapFirstCharFromJSX, }); +======= + return { + instance: { + insertJSXItem, + removeJSXItem, + mapFirstCharFromJSX, + }, + }; +>>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx }; const useTreeViewJSXNodesItemPlugin: TreeViewItemPlugin = ({ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts index 762573b4dada..f1b800e5fe5a 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -3,11 +3,18 @@ import { useTheme } from '@mui/material/styles'; import useEventCallback from '@mui/utils/useEventCallback'; import { TreeViewPlugin } from '../../models'; import { +<<<<<<< Updated upstream getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance, +======= + getFirstItem, + getLastItem, + getNextItem, + getPreviousItem, +>>>>>>> Stashed changes } from '../../useTreeView/useTreeView.utils'; import { TreeViewFirstCharMap, @@ -303,10 +310,12 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< } }; - populateInstance(instance, { - updateFirstCharMap, - handleItemKeyDown, - }); + return { + instance: { + updateFirstCharMap, + handleItemKeyDown, + }, + }; }; useTreeViewKeyboardNavigation.params = {}; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts index 39a63d0281d8..5a8f6a74748a 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts @@ -1,6 +1,5 @@ import * as React from 'react'; import { TreeViewPlugin } from '../../models'; -import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils'; import { UseTreeViewNodesSignature, UseTreeViewNodesDefaultizedParameters, @@ -93,7 +92,6 @@ const updateNodesState = ({ export const useTreeViewNodes: TreeViewPlugin = ({ instance, - publicAPI, params, state, setState, @@ -198,6 +196,7 @@ export const useTreeViewNodes: TreeViewPlugin = ({ return state.nodes.nodeTree.map(getPropsFromItemId); }; +<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts populateInstance(instance, { getNode, getItem, @@ -211,7 +210,22 @@ export const useTreeViewNodes: TreeViewPlugin = ({ getItem, }); +======= +>>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.ts return { + publicAPI: { + getItem, + }, + instance: { + getNode, + getItem, + getItemsToRender, + getChildrenIds, + getNavigableChildrenIds, + isItemDisabled, + preventItemUpdates, + areItemUpdatesPrevented, + }, contextValue: { disabledItemsFocusable: params.disabledItemsFocusable }, }; }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts index c382816220cb..f873347de705 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts @@ -1,11 +1,15 @@ import * as React from 'react'; import { TreeViewPlugin, TreeViewItemRange } from '../../models'; +<<<<<<< Updated upstream import { populateInstance, getNextNode, getFirstNode, getLastNode, } from '../../useTreeView/useTreeView.utils'; +======= +import { getNextItem, getFirstItem, getLastItem } from '../../useTreeView/useTreeView.utils'; +>>>>>>> Stashed changes import { UseTreeViewSelectionSignature } from './useTreeViewSelection.types'; import { findOrderInTremauxTree } from './useTreeViewSelection.utils'; @@ -187,6 +191,7 @@ export const useTreeViewSelection: TreeViewPlugin }); }; +<<<<<<< Updated upstream populateInstance(instance, { isNodeSelected, selectNode, @@ -195,10 +200,19 @@ export const useTreeViewSelection: TreeViewPlugin rangeSelectToFirst, }); +======= +>>>>>>> Stashed changes return { getRootProps: () => ({ 'aria-multiselectable': params.multiSelect, }), + instance: { + isItemSelected, + selectItem, + selectRange, + rangeSelectToLast, + rangeSelectToFirst, + }, contextValue: { selection: { multiSelect: params.multiSelect, diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts index 17f7d3769f4a..5c71251849dc 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts @@ -86,23 +86,29 @@ export const useTreeView = ; const runPlugin = (plugin: TreeViewPlugin) => { - const pluginResponse = - plugin({ - instance, - publicAPI, - params, - slots: params.slots, - slotProps: params.slotProps, - state, - setState, - rootRef: innerRootRef, - models, - }) || {}; + const pluginResponse = plugin({ + instance, + params, + slots: params.slots, + slotProps: params.slotProps, + state, + setState, + rootRef: innerRootRef, + models, + }); if (pluginResponse.getRootProps) { rootPropsGetters.push(pluginResponse.getRootProps); } + if (pluginResponse.publicAPI) { + Object.assign(publicAPI, pluginResponse.publicAPI); + } + + if (pluginResponse.instance) { + Object.assign(instance, pluginResponse.instance); + } + if (pluginResponse.contextValue) { Object.assign(contextValue, pluginResponse.contextValue); } diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts index 6e374cb568bb..b8265006da10 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts @@ -1,9 +1,4 @@ -import { - TreeViewAnyPluginSignature, - TreeViewInstance, - TreeViewUsedInstance, - TreeViewUsedPublicAPI, -} from '../models'; +import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewUsedPublicAPI } from '../models'; import type { UseTreeViewExpansionSignature } from '../plugins/useTreeViewExpansion'; import type { UseTreeViewNodesSignature } from '../plugins/useTreeViewNodes'; @@ -69,17 +64,3 @@ export const getLastNode = ( export const getFirstNode = (instance: TreeViewInstance<[UseTreeViewNodesSignature]>) => instance.getNavigableChildrenIds(null)[0]; - -export const populateInstance = ( - instance: TreeViewUsedInstance, - methods: T['instance'], -) => { - Object.assign(instance, methods); -}; - -export const populatePublicAPI = ( - publicAPI: TreeViewUsedPublicAPI, - methods: T['publicAPI'], -) => { - Object.assign(publicAPI, methods); -}; From 78413e11417b010ca8c13381d934f98c98b47e6a Mon Sep 17 00:00:00 2001 From: delangle Date: Wed, 3 Apr 2024 14:39:02 +0200 Subject: [PATCH 2/3] Work --- .../useTreeViewExpansion.test.tsx | 304 ++++++++++++++++++ .../useTreeViewExpansion.ts | 21 +- .../useTreeViewExpansion.types.ts | 10 +- .../useTreeViewFocus/useTreeViewFocus.ts | 86 ++--- .../useTreeViewFocus.types.ts | 12 +- .../plugins/useTreeViewItems/index.ts | 6 + .../useTreeViewItems.test.tsx} | 2 +- .../useTreeViewItems.ts} | 107 +++--- .../useTreeViewItems.types.ts | 107 ++++++ .../plugins/useTreeViewJSXItems/index.ts | 6 + .../useTreeViewJSXItems.tsx} | 59 ++-- .../useTreeViewJSXItems.types.ts | 20 ++ .../plugins/useTreeViewJSXNodes/index.ts | 6 - .../useTreeViewJSXNodes.types.ts | 20 -- .../useTreeViewKeyboardNavigation.test.tsx | 53 +++ .../useTreeViewKeyboardNavigation.ts | 96 +++--- .../useTreeViewKeyboardNavigation.types.ts | 4 +- .../plugins/useTreeViewNodes/index.ts | 6 - .../useTreeViewNodes.types.ts | 96 ------ .../useTreeViewSelection.ts | 81 ++--- .../useTreeViewSelection.types.ts | 12 +- .../useTreeViewSelection.utils.ts | 14 +- 22 files changed, 713 insertions(+), 415 deletions(-) create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewItems/index.ts rename packages/x-tree-view/src/internals/plugins/{useTreeViewNodes/useTreeViewNodes.test.tsx => useTreeViewItems/useTreeViewItems.test.tsx} (98%) rename packages/x-tree-view/src/internals/plugins/{useTreeViewNodes/useTreeViewNodes.ts => useTreeViewItems/useTreeViewItems.ts} (68%) create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/index.ts rename packages/x-tree-view/src/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.tsx => useTreeViewJSXItems/useTreeViewJSXItems.tsx} (64%) create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.ts delete mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/index.ts delete mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.ts create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx delete mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewNodes/index.ts delete mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.ts diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx new file mode 100644 index 000000000000..fcc5bad1dd9b --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -0,0 +1,304 @@ +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; +import { UseTreeViewExpansionSignature } from '@mui/x-tree-view/internals'; +import { act, fireEvent } from '@mui-internal/test-utils'; +import { + TreeItem2, + TreeItem2Props, + UseTreeItem2ContentSlotOwnProps, + useTreeItem2Utils, +} from '@mui/x-tree-view'; +import * as React from 'react'; + +/** + * All tests related to keyboard navigation (e.g.: expanding using "Enter" and "ArrowRight") + * are located in the `useTreeViewKeyboardNavigation.test.tsx` file. + */ +describeTreeView( + 'useTreeViewExpansion plugin', + ({ render, setup }) => { + describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => { + it('should not expand items when no default state and no control state are defined', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(response.isItemExpanded('1')).to.equal(false); + expect(response.getAllItemRoots()).to.have.length(2); + }); + + it('should use the default state when defined', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); + + expect(response.isItemExpanded('1')).to.equal(true); + expect(response.getAllItemRoots()).to.have.length(3); + }); + + it('should use the control state when defined', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expandedItems: ['1'], + }); + + expect(response.isItemExpanded('1')).to.equal(true); + expect(response.getItemRoot('1.1')).toBeVisible(); + }); + + it('should use the control state upon the default state when both are defined', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expandedItems: ['1'], + defaultExpandedItems: ['2'], + }); + + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should react to control state update', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + expandedItems: [], + }); + + response.setProps({ expandedItems: ['1'] }); + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to empty list)', () => { + const onExpandedItemsChange = spy(); + + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onExpandedItemsChange, + }); + + fireEvent.click(response.getItemContent('1')); + act(() => { + response.getRoot().focus(); + }); + + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['1']); + }); + + it('should call the onExpandedItemsChange callback when the model is updated (add expanded item no non-empty list)', () => { + const onExpandedItemsChange = spy(); + + const response = render({ + items: [ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + ], + onExpandedItemsChange, + defaultExpandedItems: ['1'], + }); + + fireEvent.click(response.getItemContent('2')); + act(() => { + response.getRoot().focus(); + }); + + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['2', '1']); + }); + + it('should call the onExpandedItemsChange callback when the model is updated (remove expanded item)', () => { + const onExpandedItemsChange = spy(); + + const response = render({ + items: [ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + ], + onExpandedItemsChange, + defaultExpandedItems: ['1'], + }); + + fireEvent.click(response.getItemContent('1')); + act(() => { + response.getRoot().focus(); + }); + + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal([]); + }); + + it('should warn when switching from controlled to uncontrolled', () => { + const response = render({ + items: [{ id: '1' }], + expandedItems: [], + }); + + expect(() => { + response.setProps({ expandedItems: undefined }); + }).toErrorDev( + 'MUI X: A component is changing the controlled expandedItems state of TreeView to be uncontrolled.', + ); + }); + + it('should warn and not react to update when updating the default state', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); + + expect(() => { + response.setProps({ defaultExpandedItems: ['2'] }); + expect(response.isItemExpanded('1')).to.equal(true); + expect(response.isItemExpanded('2')).to.equal(false); + }).toErrorDev( + 'MUI X: A component is changing the default expandedItems state of an uncontrolled TreeView after being initialized. To suppress this warning opt to use a controlled TreeView.', + ); + }); + }); + + describe('click interactions', () => { + it('should expand collapsed item when clicking on an item content', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemContent('1')); + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should collapse expanded item when clicking on an item content', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); + + expect(response.isItemExpanded('1')).to.equal(true); + fireEvent.click(response.getItemContent('1')); + expect(response.isItemExpanded('1')).to.equal(false); + }); + + it('should not expand collapsed item when clicking on a disabled item content', () => { + const response = render({ + items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemContent('1')); + expect(response.isItemExpanded('1')).to.equal(false); + }); + + it('should not collapse expanded item when clicking on a disabled item', () => { + const response = render({ + items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); + + expect(response.isItemExpanded('1')).to.equal(true); + fireEvent.click(response.getItemContent('1')); + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should expand collapsed item when clicking on an item label', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemLabel('1')); + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should expand collapsed item when clicking on an item icon container', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemIconContainer('1')); + expect(response.isItemExpanded('1')).to.equal(true); + }); + + it('should be able to limit the expansion to the icon', function test() { + // This test is not relevant for the TreeItem component. + // We could create the equivalent test for it, + // but it's not worth the effort given the complexity of the old behavior override. + if (!setup.includes('TreeItem2')) { + this.skip(); + } + + const CustomTreeItem = React.forwardRef(function MyTreeItem( + props: TreeItem2Props, + ref: React.Ref, + ) { + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + + const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => { + event.defaultMuiPrevented = true; + interactions.handleSelection(event); + }; + + const handleIconContainerClick = (event: React.MouseEvent) => { + interactions.handleExpansion(event); + }; + + return ( + + ); + }); + + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + slots: { item: CustomTreeItem }, + }); + + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemContent('1')); + expect(response.isItemExpanded('1')).to.equal(false); + fireEvent.click(response.getItemIconContainer('1')); + expect(response.isItemExpanded('1')).to.equal(true); + }); + }); + + describe('onItemExpansionToggle prop', () => { + it('should call the onItemExpansionToggle callback when expanding an item', () => { + const onItemExpansionToggle = spy(); + + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onItemExpansionToggle, + }); + + fireEvent.click(response.getItemContent('1')); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true); + }); + + it('should call the onItemExpansionToggle callback when collapsing an item', () => { + const onItemExpansionToggle = spy(); + + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + onItemExpansionToggle, + }); + + fireEvent.click(response.getItemContent('1')); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false); + }); + }); + }, +); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts index fbf842f356ea..8eeb154fcc99 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.ts @@ -13,7 +13,7 @@ export const useTreeViewExpansion: TreeViewPlugin models.expandedItems.setControlledValue(value); }; - const isNodeExpanded = React.useCallback( + const isItemExpanded = React.useCallback( (itemId: string) => { return Array.isArray(models.expandedItems.value) ? models.expandedItems.value.indexOf(itemId) !== -1 @@ -22,12 +22,12 @@ export const useTreeViewExpansion: TreeViewPlugin [models.expandedItems.value], ); - const isNodeExpandable = React.useCallback( + const isItemExpandable = React.useCallback( (itemId: string) => !!instance.getNode(itemId)?.expandable, [instance], ); - const toggleNodeExpansion = useEventCallback( + const toggleItemExpansion = useEventCallback( (event: React.SyntheticEvent, itemId: string | null) => { if (itemId == null) { return; @@ -55,7 +55,7 @@ export const useTreeViewExpansion: TreeViewPlugin const siblings = instance.getChildrenIds(node.parentId); const diff = siblings.filter( - (child) => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child), + (child) => instance.isItemExpandable(child) && !instance.isItemExpanded(child), ); const newExpanded = models.expandedItems.value.concat(diff); @@ -71,14 +71,6 @@ export const useTreeViewExpansion: TreeViewPlugin } }; -<<<<<<< Updated upstream - populateInstance(instance, { - isNodeExpanded, - isNodeExpandable, - toggleNodeExpansion, - expandAllSiblings, - }); -======= return { instance: { isItemExpanded, @@ -87,7 +79,6 @@ export const useTreeViewExpansion: TreeViewPlugin expandAllSiblings, }, }; ->>>>>>> Stashed changes }; useTreeViewExpansion.models = { @@ -96,11 +87,11 @@ useTreeViewExpansion.models = { }, }; -const DEFAULT_EXPANDED_NODES: string[] = []; +const DEFAULT_EXPANDED_ITEMS: string[] = []; useTreeViewExpansion.getDefaultizedParams = (params) => ({ ...params, - defaultExpandedItems: params.defaultExpandedItems ?? DEFAULT_EXPANDED_NODES, + defaultExpandedItems: params.defaultExpandedItems ?? DEFAULT_EXPANDED_ITEMS, }); useTreeViewExpansion.params = { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts index 1a66b9dd86fd..9f261541492a 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts @@ -1,11 +1,11 @@ import * as React from 'react'; import { DefaultizedProps, TreeViewPluginSignature } from '../../models'; -import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; export interface UseTreeViewExpansionInstance { - isNodeExpanded: (itemId: string) => boolean; - isNodeExpandable: (itemId: string) => boolean; - toggleNodeExpansion: (event: React.SyntheticEvent, value: string) => void; + isItemExpanded: (itemId: string) => boolean; + isItemExpandable: (itemId: string) => boolean; + toggleItemExpansion: (event: React.SyntheticEvent, value: string) => void; expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void; } @@ -50,5 +50,5 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{ defaultizedParams: UseTreeViewExpansionDefaultizedParameters; instance: UseTreeViewExpansionInstance; modelNames: 'expandedItems'; - dependantPlugins: [UseTreeViewNodesSignature]; + dependantPlugins: [UseTreeViewItemsSignature]; }>; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts index 24fc6bcafe79..6b3662198e30 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts @@ -13,7 +13,7 @@ const useTabbableItemId = ( ) => { const isItemVisible = (itemId: string) => { const node = instance.getNode(itemId); - return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + return node && (node.parentId == null || instance.isItemExpanded(node.parentId)); }; let tabbableItemId: string | null | undefined; @@ -41,9 +41,9 @@ export const useTreeViewFocus: TreeViewPlugin = ({ const tabbableItemId = useTabbableItemId(instance, models.selectedItems.value); const setFocusedItemId = useEventCallback((itemId: React.SetStateAction) => { - const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedNodeId) : itemId; - if (state.focusedNodeId !== cleanItemId) { - setState((prevState) => ({ ...prevState, focusedNodeId: cleanItemId })); + const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedItemId) : itemId; + if (state.focusedItemId !== cleanItemId) { + setState((prevState) => ({ ...prevState, focusedItemId: cleanItemId })); } }); @@ -54,14 +54,14 @@ export const useTreeViewFocus: TreeViewPlugin = ({ [rootRef], ); - const isNodeFocused = React.useCallback( - (itemId: string) => state.focusedNodeId === itemId && isTreeViewFocused(), - [state.focusedNodeId, isTreeViewFocused], + const isItemFocused = React.useCallback( + (itemId: string) => state.focusedItemId === itemId && isTreeViewFocused(), + [state.focusedItemId, isTreeViewFocused], ); - const isNodeVisible = (itemId: string) => { + const isItemVisible = (itemId: string) => { const node = instance.getNode(itemId); - return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + return node && (node.parentId == null || instance.isItemExpanded(node.parentId)); }; const innerFocusItem = (event: React.SyntheticEvent | null, itemId: string) => { @@ -77,39 +77,41 @@ export const useTreeViewFocus: TreeViewPlugin = ({ } }; - const focusItem = useEventCallback((event: React.SyntheticEvent, nodeId: string) => { - // If we receive a nodeId, and it is visible, the focus will be set to it - if (isNodeVisible(nodeId)) { - innerFocusItem(event, nodeId); + const focusItem = useEventCallback((event: React.SyntheticEvent, itemId: string) => { + // If we receive an itemId, and it is visible, the focus will be set to it + if (isItemVisible(itemId)) { + innerFocusItem(event, itemId); } }); - const focusDefaultNode = useEventCallback((event: React.SyntheticEvent | null) => { - let nodeToFocusId: string | null | undefined; + const focusDefaultItem = useEventCallback((event: React.SyntheticEvent | null) => { + let itemToFocusId: string | null | undefined; if (Array.isArray(models.selectedItems.value)) { - nodeToFocusId = models.selectedItems.value.find(isNodeVisible); - } else if (models.selectedItems.value != null && isNodeVisible(models.selectedItems.value)) { - nodeToFocusId = models.selectedItems.value; + itemToFocusId = models.selectedItems.value.find(isItemVisible); + } else if (models.selectedItems.value != null && isItemVisible(models.selectedItems.value)) { + itemToFocusId = models.selectedItems.value; } - if (nodeToFocusId == null) { - nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; + if (itemToFocusId == null) { + itemToFocusId = instance.getNavigableChildrenIds(null)[0]; } - innerFocusItem(event, nodeToFocusId); + innerFocusItem(event, itemToFocusId); }); const removeFocusedItem = useEventCallback(() => { - if (state.focusedNodeId == null) { + if (state.focusedItemId == null) { return; } - const node = instance.getNode(state.focusedNodeId); - const itemElement = document.getElementById( - instance.getTreeItemId(state.focusedNodeId, node.idAttribute), - ); - if (itemElement) { - itemElement.blur(); + const node = instance.getNode(state.focusedItemId); + if (node) { + const itemElement = document.getElementById( + instance.getTreeItemId(state.focusedItemId, node.idAttribute), + ); + if (itemElement) { + itemElement.blur(); + } } setFocusedItemId(null); @@ -117,27 +119,9 @@ export const useTreeViewFocus: TreeViewPlugin = ({ const canItemBeTabbed = (itemId: string) => itemId === tabbableItemId; -<<<<<<< Updated upstream - populateInstance(instance, { - isNodeFocused, - canItemBeTabbed, - focusItem, - focusDefaultNode, - removeFocusedItem, - }); - - populatePublicAPI(publicAPI, { - focusItem, - }); - - useInstanceEventHandler(instance, 'removeNode', ({ id }) => { - if (state.focusedNodeId === id) { - instance.focusDefaultNode(null); -======= useInstanceEventHandler(instance, 'removeItem', ({ id }) => { if (state.focusedItemId === id) { instance.focusDefaultItem(null); ->>>>>>> Stashed changes } }); @@ -146,13 +130,13 @@ export const useTreeViewFocus: TreeViewPlugin = ({ otherHandlers.onFocus?.(event); // if the event bubbled (which is React specific) we don't want to steal focus if (event.target === event.currentTarget) { - instance.focusDefaultNode(event); + instance.focusDefaultItem(event); } }; - const focusedNode = instance.getNode(state.focusedNodeId!); - const activeDescendant = focusedNode - ? instance.getTreeItemId(focusedNode.id, focusedNode.idAttribute) + const focusedItem = instance.getNode(state.focusedItemId!); + const activeDescendant = focusedItem + ? instance.getTreeItemId(focusedItem.id, focusedItem.idAttribute) : null; return { @@ -173,7 +157,7 @@ export const useTreeViewFocus: TreeViewPlugin = ({ }; }; -useTreeViewFocus.getInitialState = () => ({ focusedNodeId: null }); +useTreeViewFocus.getInitialState = () => ({ focusedItemId: null }); useTreeViewFocus.params = { onItemFocus: true, diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts index 2b9fe5f6f1e6..1a5e571f1317 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts @@ -1,15 +1,15 @@ import * as React from 'react'; import { TreeViewPluginSignature } from '../../models'; import { UseTreeViewIdSignature } from '../useTreeViewId/useTreeViewId.types'; -import type { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import type { UseTreeViewItemsSignature } from '../useTreeViewItems'; import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; export interface UseTreeViewFocusInstance { - isNodeFocused: (itemId: string) => boolean; + isItemFocused: (itemId: string) => boolean; canItemBeTabbed: (itemId: string) => boolean; - focusItem: (event: React.SyntheticEvent, nodeId: string) => void; - focusDefaultNode: (event: React.SyntheticEvent | null) => void; + focusItem: (event: React.SyntheticEvent, itemId: string) => void; + focusDefaultItem: (event: React.SyntheticEvent | null) => void; removeFocusedItem: () => void; } @@ -28,7 +28,7 @@ export interface UseTreeViewFocusParameters { export type UseTreeViewFocusDefaultizedParameters = UseTreeViewFocusParameters; export interface UseTreeViewFocusState { - focusedNodeId: string | null; + focusedItemId: string | null; } export type UseTreeViewFocusSignature = TreeViewPluginSignature<{ @@ -39,7 +39,7 @@ export type UseTreeViewFocusSignature = TreeViewPluginSignature<{ state: UseTreeViewFocusState; dependantPlugins: [ UseTreeViewIdSignature, - UseTreeViewNodesSignature, + UseTreeViewItemsSignature, UseTreeViewSelectionSignature, UseTreeViewExpansionSignature, ]; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/index.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/index.ts new file mode 100644 index 000000000000..63c1a5d694ce --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/index.ts @@ -0,0 +1,6 @@ +export { useTreeViewItems } from './useTreeViewItems'; +export type { + UseTreeViewItemsSignature, + UseTreeViewItemsParameters, + UseTreeViewItemsDefaultizedParameters, +} from './useTreeViewItems.types'; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx similarity index 98% rename from packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.test.tsx rename to packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx index 28f3e2b1f398..e1f098e05799 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx @@ -5,7 +5,7 @@ import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { TreeItem } from '@mui/x-tree-view/TreeItem'; -describe('useTreeViewNodes', () => { +describe('useTreeViewItems', () => { const { render } = createRenderer(); it('should throw an error when two items have the same ID (items prop approach)', function test() { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.ts similarity index 68% rename from packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts rename to packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.ts index 5a8f6a74748a..314ec01142ae 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.ts @@ -1,25 +1,25 @@ import * as React from 'react'; import { TreeViewPlugin } from '../../models'; import { - UseTreeViewNodesSignature, - UseTreeViewNodesDefaultizedParameters, + UseTreeViewItemsSignature, + UseTreeViewItemsDefaultizedParameters, TreeViewNodeMap, TreeViewItemIdAndChildren, - UseTreeViewNodesState, + UseTreeViewItemsState, TreeViewItemMap, -} from './useTreeViewNodes.types'; +} from './useTreeViewItems.types'; import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; import { TreeViewBaseItem } from '../../../models'; -const updateNodesState = ({ +const updateItemsState = ({ items, isItemDisabled, getItemLabel, getItemId, }: Pick< - UseTreeViewNodesDefaultizedParameters, + UseTreeViewItemsDefaultizedParameters, 'items' | 'isItemDisabled' | 'getItemLabel' | 'getItemId' ->): UseTreeViewNodesState['nodes'] => { +>): UseTreeViewItemsState['items'] => { const nodeMap: TreeViewNodeMap = {}; const itemMap: TreeViewItemMap = {}; @@ -46,7 +46,7 @@ const updateNodesState = ({ [ 'MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', - `Tow items were provided with the same id in the \`items\` prop: "${id}"`, + `Two items were provided with the same id in the \`items\` prop: "${id}"`, ].join('\n'), ); } @@ -90,42 +90,42 @@ const updateNodesState = ({ }; }; -export const useTreeViewNodes: TreeViewPlugin = ({ +export const useTreeViewItems: TreeViewPlugin = ({ instance, params, state, setState, }) => { const getNode = React.useCallback( - (itemId: string) => state.nodes.nodeMap[itemId], - [state.nodes.nodeMap], + (itemId: string) => state.items.nodeMap[itemId], + [state.items.nodeMap], ); const getItem = React.useCallback( - (itemId: string) => state.nodes.itemMap[itemId], - [state.nodes.itemMap], + (itemId: string) => state.items.itemMap[itemId], + [state.items.itemMap], ); - const isNodeDisabled = React.useCallback( + const isItemDisabled = React.useCallback( (itemId: string | null): itemId is string => { if (itemId == null) { return false; } - let item = instance.getNode(itemId); + let node = instance.getNode(itemId); - // This can be called before the item has been added to the node map. - if (!item) { + // This can be called before the item has been added to the item map. + if (!node) { return false; } - if (item.disabled) { + if (node.disabled) { return true; } - while (item.parentId != null) { - item = instance.getNode(item.parentId); - if (item.disabled) { + while (node.parentId != null) { + node = instance.getNode(node.parentId); + if (node.disabled) { return true; } } @@ -137,38 +137,49 @@ export const useTreeViewNodes: TreeViewPlugin = ({ const getChildrenIds = React.useCallback( (itemId: string | null) => - Object.values(state.nodes.nodeMap) + Object.values(state.items.nodeMap) .filter((item) => item.parentId === itemId) .sort((a, b) => a.index - b.index) .map((child) => child.id), - [state.nodes.nodeMap], + [state.items.nodeMap], ); const getNavigableChildrenIds = (itemId: string | null) => { let childrenIds = instance.getChildrenIds(itemId); if (!params.disabledItemsFocusable) { - childrenIds = childrenIds.filter((item) => !instance.isNodeDisabled(item)); + childrenIds = childrenIds.filter((item) => !instance.isItemDisabled(item)); } return childrenIds; }; + const areItemUpdatesPreventedRef = React.useRef(false); + const preventItemUpdates = React.useCallback(() => { + areItemUpdatesPreventedRef.current = true; + }, []); + + const areItemUpdatesPrevented = React.useCallback(() => areItemUpdatesPreventedRef.current, []); + React.useEffect(() => { + if (instance.areItemUpdatesPrevented()) { + return; + } + setState((prevState) => { - const newState = updateNodesState({ + const newState = updateItemsState({ items: params.items, isItemDisabled: params.isItemDisabled, getItemId: params.getItemId, getItemLabel: params.getItemLabel, }); - Object.values(prevState.nodes.nodeMap).forEach((node) => { - if (!newState.nodeMap[node.id]) { - publishTreeViewEvent(instance, 'removeNode', { id: node.id }); + Object.values(prevState.items.nodeMap).forEach((item) => { + if (!newState.nodeMap[item.id]) { + publishTreeViewEvent(instance, 'removeItem', { id: item.id }); } }); - return { ...prevState, nodes: newState }; + return { ...prevState, items: newState }; }); }, [ instance, @@ -179,39 +190,23 @@ export const useTreeViewNodes: TreeViewPlugin = ({ params.getItemLabel, ]); - const getNodesToRender = () => { + const getItemsToRender = () => { const getPropsFromItemId = ({ id, children, - }: TreeViewItemIdAndChildren): ReturnType[number] => { - const node = state.nodes.nodeMap[id]; + }: TreeViewItemIdAndChildren): ReturnType[number] => { + const item = state.items.nodeMap[id]; return { - label: node.label!, - itemId: node.id, - id: node.idAttribute, + label: item.label!, + itemId: item.id, + id: item.idAttribute, children: children?.map(getPropsFromItemId), }; }; - return state.nodes.nodeTree.map(getPropsFromItemId); + return state.items.nodeTree.map(getPropsFromItemId); }; -<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.ts - populateInstance(instance, { - getNode, - getItem, - getNodesToRender, - getChildrenIds, - getNavigableChildrenIds, - isNodeDisabled, - }); - - populatePublicAPI(publicAPI, { - getItem, - }); - -======= ->>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.ts return { publicAPI: { getItem, @@ -230,8 +225,8 @@ export const useTreeViewNodes: TreeViewPlugin = ({ }; }; -useTreeViewNodes.getInitialState = (params) => ({ - nodes: updateNodesState({ +useTreeViewItems.getInitialState = (params) => ({ + items: updateItemsState({ items: params.items, isItemDisabled: params.isItemDisabled, getItemId: params.getItemId, @@ -239,12 +234,12 @@ useTreeViewNodes.getInitialState = (params) => ({ }), }); -useTreeViewNodes.getDefaultizedParams = (params) => ({ +useTreeViewItems.getDefaultizedParams = (params) => ({ ...params, disabledItemsFocusable: params.disabledItemsFocusable ?? false, }); -useTreeViewNodes.params = { +useTreeViewItems.params = { disabledItemsFocusable: true, items: true, isItemDisabled: true, diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts new file mode 100644 index 000000000000..465f6bbbd070 --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts @@ -0,0 +1,107 @@ +import { TreeViewNode, DefaultizedProps, TreeViewPluginSignature } from '../../models'; +import { TreeViewItemId } from '../../../models'; + +interface TreeViewItemProps { + label: string; + itemId: string; + id: string | undefined; + children?: TreeViewItemProps[]; +} + +export interface UseTreeViewItemsInstance { + getNode: (itemId: string) => TreeViewNode; + getItem: (itemId: string) => R; + getItemsToRender: () => TreeViewItemProps[]; + getChildrenIds: (itemId: string | null) => string[]; + getNavigableChildrenIds: (itemId: string | null) => string[]; + isItemDisabled: (itemId: string | null) => itemId is string; + /** + * Freeze any future update to the state based on the `items` prop. + * This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth. + */ + preventItemUpdates: () => void; + /** + * Check if the updates to the state based on the `items` prop are prevented. + * This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth. + * @returns {boolean} `true` if the updates to the state based on the `items` prop are prevented. + */ + areItemUpdatesPrevented: () => boolean; +} + +export interface UseTreeViewItemsPublicAPI + extends Pick, 'getItem'> {} + +export interface UseTreeViewItemsParameters { + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable?: boolean; + items: readonly R[]; + /** + * Used to determine if a given item should be disabled. + * @template R + * @param {R} item The item to check. + * @returns {boolean} `true` if the item should be disabled. + */ + isItemDisabled?: (item: R) => boolean; + /** + * Used to determine the string label for a given item. + * + * @template R + * @param {R} item The item to check. + * @returns {string} The label of the item. + * @default (item) => item.label + */ + getItemLabel?: (item: R) => string; + /** + * Used to determine the id of a given item. + * + * @template R + * @param {R} item The item to check. + * @returns {string} The id of the item. + * @default (item) => item.id + */ + getItemId?: (item: R) => TreeViewItemId; +} + +export type UseTreeViewItemsDefaultizedParameters = DefaultizedProps< + UseTreeViewItemsParameters, + 'disabledItemsFocusable' +>; + +interface UseTreeViewItemsEventLookup { + removeItem: { + params: { id: string }; + }; +} + +export interface TreeViewItemIdAndChildren { + id: TreeViewItemId; + children?: TreeViewItemIdAndChildren[]; +} + +export interface UseTreeViewItemsState { + items: { + nodeTree: TreeViewItemIdAndChildren[]; + nodeMap: TreeViewNodeMap; + itemMap: TreeViewItemMap; + }; +} + +interface UseTreeViewItemsContextValue + extends Pick, 'disabledItemsFocusable'> {} + +export type UseTreeViewItemsSignature = TreeViewPluginSignature<{ + params: UseTreeViewItemsParameters; + defaultizedParams: UseTreeViewItemsDefaultizedParameters; + instance: UseTreeViewItemsInstance; + publicAPI: UseTreeViewItemsPublicAPI; + events: UseTreeViewItemsEventLookup; + state: UseTreeViewItemsState; + contextValue: UseTreeViewItemsContextValue; +}>; + +export type TreeViewNodeMap = { [itemId: string]: TreeViewNode }; + +export type TreeViewItemMap = { [itemId: string]: R }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/index.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/index.ts new file mode 100644 index 000000000000..1eff39d296b7 --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/index.ts @@ -0,0 +1,6 @@ +export { useTreeViewJSXItems } from './useTreeViewJSXItems'; +export type { + UseTreeViewJSXItemsSignature, + UseTreeViewItemsParameters, + UseTreeViewItemsDefaultizedParameters, +} from './useTreeViewJSXItems.types'; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx similarity index 64% rename from packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx rename to packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx index ccc1e9122cf6..b46ca31d199f 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx @@ -2,12 +2,7 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import useForkRef from '@mui/utils/useForkRef'; import { TreeViewItemPlugin, TreeViewNode, TreeViewPlugin } from '../../models'; -<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx -import { populateInstance } from '../../useTreeView/useTreeView.utils'; -import { UseTreeViewJSXNodesSignature } from './useTreeViewJSXNodes.types'; -======= import { UseTreeViewJSXItemsSignature } from './useTreeViewJSXItems.types'; ->>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext'; import { @@ -18,50 +13,52 @@ import { import type { TreeItemProps } from '../../../TreeItem'; import type { TreeItem2Props } from '../../../TreeItem2'; -export const useTreeViewJSXNodes: TreeViewPlugin = ({ +export const useTreeViewJSXItems: TreeViewPlugin = ({ instance, setState, }) => { - const insertJSXNode = useEventCallback((node: TreeViewNode) => { + instance.preventItemUpdates(); + + const insertJSXItem = useEventCallback((item: TreeViewNode) => { setState((prevState) => { - if (prevState.nodes.nodeMap[node.id] != null) { + if (prevState.items.nodeMap[item.id] != null) { throw new Error( [ 'MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', - `Tow items were provided with the same id in the \`items\` prop: "${node.id}"`, + `Two items were provided with the same id in the \`items\` prop: "${item.id}"`, ].join('\n'), ); } return { ...prevState, - nodes: { - ...prevState.nodes, - nodeMap: { ...prevState.nodes.nodeMap, [node.id]: node }, + items: { + ...prevState.items, + nodeMap: { ...prevState.items.nodeMap, [item.id]: item }, // For `SimpleTreeView`, we don't have a proper `item` object, so we create a very basic one. - itemMap: { ...prevState.nodes.itemMap, [node.id]: { id: node.id, label: node.label } }, + itemMap: { ...prevState.items.itemMap, [item.id]: { id: item.id, label: item.label } }, }, }; }); }); - const removeJSXNode = useEventCallback((itemId: string) => { + const removeJSXItem = useEventCallback((itemId: string) => { setState((prevState) => { - const newNodeMap = { ...prevState.nodes.nodeMap }; - const newItemMap = { ...prevState.nodes.itemMap }; + const newNodeMap = { ...prevState.items.nodeMap }; + const newItemMap = { ...prevState.items.itemMap }; delete newNodeMap[itemId]; delete newItemMap[itemId]; return { ...prevState, - nodes: { - ...prevState.nodes, + items: { + ...prevState.items, nodeMap: newNodeMap, itemMap: newItemMap, }, }; }); - publishTreeViewEvent(instance, 'removeNode', { id: itemId }); + publishTreeViewEvent(instance, 'removeItem', { id: itemId }); }); const mapFirstCharFromJSX = useEventCallback((itemId: string, firstChar: string) => { @@ -79,13 +76,6 @@ export const useTreeViewJSXNodes: TreeViewPlugin = }; }); -<<<<<<< Updated upstream:packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.tsx - populateInstance(instance, { - insertJSXNode, - removeJSXNode, - mapFirstCharFromJSX, - }); -======= return { instance: { insertJSXItem, @@ -93,17 +83,16 @@ export const useTreeViewJSXNodes: TreeViewPlugin = mapFirstCharFromJSX, }, }; ->>>>>>> Stashed changes:packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx }; -const useTreeViewJSXNodesItemPlugin: TreeViewItemPlugin = ({ +const useTreeViewJSXItemsItemPlugin: TreeViewItemPlugin = ({ props, rootRef, contentRef, }) => { const { children, disabled = false, label, itemId, id } = props; - const { instance } = useTreeViewContext<[UseTreeViewJSXNodesSignature]>(); + const { instance } = useTreeViewContext<[UseTreeViewJSXItemsSignature]>(); const isExpandable = (reactChildren: React.ReactNode) => { if (Array.isArray(reactChildren)) { @@ -131,9 +120,9 @@ const useTreeViewJSXNodesItemPlugin: TreeViewItemPlugin { - // On the first render a node's index will be -1. We want to wait for the real index. + // On the first render a item's index will be -1. We want to wait for the real index. if (index !== -1) { - instance.insertJSXNode({ + instance.insertJSXItem({ id: itemId, idAttribute: id, index, @@ -142,7 +131,7 @@ const useTreeViewJSXNodesItemPlugin: TreeViewItemPlugin instance.removeJSXNode(itemId); + return () => instance.removeJSXItem(itemId); } return undefined; @@ -164,10 +153,10 @@ const useTreeViewJSXNodesItemPlugin: TreeViewItemPlugin ( +useTreeViewJSXItems.wrapItem = ({ children, itemId }) => ( {children} ); -useTreeViewJSXNodes.params = {}; +useTreeViewJSXItems.params = {}; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.ts new file mode 100644 index 000000000000..c219453ac6da --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.ts @@ -0,0 +1,20 @@ +import { TreeViewNode, TreeViewPluginSignature } from '../../models'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; +import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation'; + +export interface UseTreeViewItemsInstance { + insertJSXItem: (item: TreeViewNode) => void; + removeJSXItem: (itemId: string) => void; + mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void; +} + +export interface UseTreeViewItemsParameters {} + +export interface UseTreeViewItemsDefaultizedParameters {} + +export type UseTreeViewJSXItemsSignature = TreeViewPluginSignature<{ + params: UseTreeViewItemsParameters; + defaultizedParams: UseTreeViewItemsDefaultizedParameters; + instance: UseTreeViewItemsInstance; + dependantPlugins: [UseTreeViewItemsSignature, UseTreeViewKeyboardNavigationSignature]; +}>; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/index.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/index.ts deleted file mode 100644 index 2aeb78d6ca7a..000000000000 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { useTreeViewJSXNodes } from './useTreeViewJSXNodes'; -export type { - UseTreeViewJSXNodesSignature, - UseTreeViewNodesParameters, - UseTreeViewNodesDefaultizedParameters, -} from './useTreeViewJSXNodes.types'; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.ts deleted file mode 100644 index 7567e90b10c4..000000000000 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TreeViewNode, TreeViewPluginSignature } from '../../models'; -import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; -import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation'; - -export interface UseTreeViewNodesInstance { - insertJSXNode: (node: TreeViewNode) => void; - removeJSXNode: (itemId: string) => void; - mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void; -} - -export interface UseTreeViewNodesParameters {} - -export interface UseTreeViewNodesDefaultizedParameters {} - -export type UseTreeViewJSXNodesSignature = TreeViewPluginSignature<{ - params: UseTreeViewNodesParameters; - defaultizedParams: UseTreeViewNodesDefaultizedParameters; - instance: UseTreeViewNodesInstance; - dependantPlugins: [UseTreeViewNodesSignature, UseTreeViewKeyboardNavigationSignature]; -}>; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx new file mode 100644 index 000000000000..970bed10f721 --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { act, createRenderer, fireEvent } from '@mui-internal/test-utils'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; + +describe('useTreeViewKeyboardNavigation', () => { + const { render } = createRenderer(); + + it('should work after adding / removing items', () => { + const { getByRole, setProps } = render( + , + ); + + act(() => { + getByRole('treeitem', { name: 'one' }).focus(); + }); + + fireEvent.keyDown(getByRole('treeitem', { name: 'one' }), { key: 'f' }); + expect(getByRole('treeitem', { name: 'four' })).toHaveFocus(); + + setProps({ + items: [ + { id: 'one', label: 'one' }, + { id: 'two', label: 'two' }, + { id: 'three', label: 'three' }, + ], + }); + expect(getByRole('treeitem', { name: 'one' })).toHaveFocus(); + + fireEvent.keyDown(getByRole('treeitem', { name: 'one' }), { key: 't' }); + expect(getByRole('treeitem', { name: 'two' })).toHaveFocus(); + + setProps({ + items: [ + { id: 'one', label: 'one' }, + { id: 'two', label: 'two' }, + { id: 'three', label: 'three' }, + { id: 'four', label: 'four' }, + ], + }); + expect(getByRole('treeitem', { name: 'two' })).toHaveFocus(); + + fireEvent.keyDown(getByRole('treeitem', { name: 'two' }), { key: 'f' }); + expect(getByRole('treeitem', { name: 'four' })).toHaveFocus(); + }); +}); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts index f1b800e5fe5a..c62d5a280a95 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -1,26 +1,17 @@ import * as React from 'react'; import { useTheme } from '@mui/material/styles'; import useEventCallback from '@mui/utils/useEventCallback'; -import { TreeViewPlugin } from '../../models'; +import { TreeViewNode, TreeViewPlugin } from '../../models'; import { -<<<<<<< Updated upstream - getFirstNode, - getLastNode, - getNextNode, - getPreviousNode, - populateInstance, -======= getFirstItem, getLastItem, getNextItem, getPreviousItem, ->>>>>>> Stashed changes } from '../../useTreeView/useTreeView.utils'; import { TreeViewFirstCharMap, UseTreeViewKeyboardNavigationSignature, } from './useTreeViewKeyboardNavigation.types'; -import { TreeViewBaseItem } from '../../../models'; import { MuiCancellableEvent } from '../../models/MuiCancellableEvent'; function isPrintableCharacter(string: string) { @@ -38,36 +29,31 @@ function findNextFirstChar(firstChars: string[], startIndex: number, char: strin export const useTreeViewKeyboardNavigation: TreeViewPlugin< UseTreeViewKeyboardNavigationSignature -> = ({ instance, params }) => { +> = ({ instance, params, state }) => { const theme = useTheme(); const isRTL = theme.direction === 'rtl'; const firstCharMap = React.useRef({}); - const hasFirstCharMapBeenUpdatedImperatively = React.useRef(false); const updateFirstCharMap = useEventCallback( (callback: (firstCharMap: TreeViewFirstCharMap) => TreeViewFirstCharMap) => { - hasFirstCharMapBeenUpdatedImperatively.current = true; firstCharMap.current = callback(firstCharMap.current); }, ); React.useEffect(() => { - if (hasFirstCharMapBeenUpdatedImperatively.current) { + if (instance.areItemUpdatesPrevented()) { return; } const newFirstCharMap: { [itemId: string]: string } = {}; - const processItem = (item: TreeViewBaseItem) => { - const getItemId = params.getItemId; - const itemId = getItemId ? getItemId(item) : (item as { id: string }).id; - newFirstCharMap[itemId] = instance.getNode(itemId).label!.substring(0, 1).toLowerCase(); - item.children?.forEach(processItem); + const processItem = (node: TreeViewNode) => { + newFirstCharMap[node.id] = node.label!.substring(0, 1).toLowerCase(); }; - params.items.forEach(processItem); + Object.values(state.items.nodeMap).forEach(processItem); firstCharMap.current = newFirstCharMap; - }, [params.items, params.getItemId, instance]); + }, [state.items.nodeMap, params.getItemId, instance]); const getFirstMatchingItem = (itemId: string, firstChar: string) => { let start: number; @@ -79,10 +65,10 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // This really only works since the ids are strings Object.keys(firstCharMap.current).forEach((mapItemId) => { const map = instance.getNode(mapItemId); - const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; + const visible = map.parentId ? instance.isItemExpanded(map.parentId) : true; const shouldBeSkipped = params.disabledItemsFocusable ? false - : instance.isNodeDisabled(mapItemId); + : instance.isItemDisabled(mapItemId); if (visible && !shouldBeSkipped) { firstCharIds.push(mapItemId); @@ -113,10 +99,10 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< }; const canToggleItemSelection = (itemId: string) => - !params.disableSelection && !instance.isNodeDisabled(itemId); + !params.disableSelection && !instance.isItemDisabled(itemId); const canToggleItemExpansion = (itemId: string) => { - return !instance.isNodeDisabled(itemId) && instance.isNodeExpandable(itemId); + return !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId); }; // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction @@ -137,31 +123,31 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // eslint-disable-next-line default-case switch (true) { - // Select the node when pressing "Space" + // Select the item when pressing "Space" case key === ' ' && canToggleItemSelection(itemId): { event.preventDefault(); if (params.multiSelect && event.shiftKey) { instance.selectRange(event, { end: itemId }); } else if (params.multiSelect) { - instance.selectNode(event, itemId, true); + instance.selectItem(event, itemId, true); } else { - instance.selectNode(event, itemId); + instance.selectItem(event, itemId); } break; } - // If the focused node has children, we expand it. - // If the focused node has no children, we select it. + // If the focused item has children, we expand it. + // If the focused item has no children, we select it. case key === 'Enter': { if (canToggleItemExpansion(itemId)) { - instance.toggleNodeExpansion(event, itemId); + instance.toggleItemExpansion(event, itemId); event.preventDefault(); } else if (canToggleItemSelection(itemId)) { if (params.multiSelect) { event.preventDefault(); - instance.selectNode(event, itemId, true); - } else if (!instance.isNodeSelected(itemId)) { - instance.selectNode(event, itemId); + instance.selectItem(event, itemId, true); + } else if (!instance.isItemSelected(itemId)) { + instance.selectItem(event, itemId); event.preventDefault(); } } @@ -171,7 +157,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Focus the next focusable item case key === 'ArrowDown': { - const nextItem = getNextNode(instance, itemId); + const nextItem = getNextItem(instance, itemId); if (nextItem) { event.preventDefault(); instance.focusItem(event, nextItem); @@ -195,7 +181,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Focuses the previous focusable item case key === 'ArrowUp': { - const previousItem = getPreviousNode(instance, itemId); + const previousItem = getPreviousItem(instance, itemId); if (previousItem) { event.preventDefault(); instance.focusItem(event, previousItem); @@ -220,14 +206,14 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // If the focused item is expanded, we move the focus to its first child // If the focused item is collapsed and has children, we expand it case (key === 'ArrowRight' && !isRTL) || (key === 'ArrowLeft' && isRTL): { - if (instance.isNodeExpanded(itemId)) { - const nextNodeId = getNextNode(instance, itemId); - if (nextNodeId) { - instance.focusItem(event, nextNodeId); + if (instance.isItemExpanded(itemId)) { + const nextItemId = getNextItem(instance, itemId); + if (nextItemId) { + instance.focusItem(event, nextItemId); event.preventDefault(); } } else if (canToggleItemExpansion(itemId)) { - instance.toggleNodeExpansion(event, itemId); + instance.toggleItemExpansion(event, itemId); event.preventDefault(); } @@ -237,8 +223,8 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // If the focused item is expanded, we collapse it // If the focused item is collapsed and has a parent, we move the focus to this parent case (key === 'ArrowLeft' && !isRTL) || (key === 'ArrowRight' && isRTL): { - if (canToggleItemExpansion(itemId) && instance.isNodeExpanded(itemId)) { - instance.toggleNodeExpansion(event, itemId); + if (canToggleItemExpansion(itemId) && instance.isItemExpanded(itemId)) { + instance.toggleItemExpansion(event, itemId); event.preventDefault(); } else { const parent = instance.getNode(itemId).parentId; @@ -251,12 +237,12 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< break; } - // Focuses the first node in the tree + // Focuses the first item in the tree case key === 'Home': { - instance.focusItem(event, getFirstNode(instance)); + instance.focusItem(event, getFirstItem(instance)); // Multi select behavior when pressing Ctrl + Shift + Home - // Selects the focused node and all nodes up to the first node. + // Selects the focused item and all items up to the first item. if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { instance.rangeSelectToFirst(event, itemId); } @@ -267,7 +253,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Focuses the last item in the tree case key === 'End': { - instance.focusItem(event, getLastNode(instance)); + instance.focusItem(event, getLastItem(instance)); // Multi select behavior when pressing Ctrl + Shirt + End // Selects the focused item and all the items down to the last item. @@ -287,11 +273,11 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< } // Multi select behavior when pressing Ctrl + a - // Selects all the nodes + // Selects all the items case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection: { instance.selectRange(event, { - start: getFirstNode(instance), - end: getLastNode(instance), + start: getFirstItem(instance), + end: getLastItem(instance), }); event.preventDefault(); break; @@ -300,9 +286,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Type-ahead // TODO: Support typing multiple characters case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key): { - const matchingNode = getFirstMatchingItem(itemId, key); - if (matchingNode != null) { - instance.focusItem(event, matchingNode); + const matchingItem = getFirstMatchingItem(itemId, key); + if (matchingItem != null) { + instance.focusItem(event, matchingItem); event.preventDefault(); } break; @@ -314,8 +300,8 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< instance: { updateFirstCharMap, handleItemKeyDown, - }, - }; + } + } }; useTreeViewKeyboardNavigation.params = {}; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts index 729b8a875c47..053eeaaf43e6 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { TreeViewPluginSignature } from '../../models'; -import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; import { UseTreeViewFocusSignature } from '../useTreeViewFocus'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; @@ -17,7 +17,7 @@ export interface UseTreeViewKeyboardNavigationInstance { export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{ instance: UseTreeViewKeyboardNavigationInstance; dependantPlugins: [ - UseTreeViewNodesSignature, + UseTreeViewItemsSignature, UseTreeViewSelectionSignature, UseTreeViewFocusSignature, UseTreeViewExpansionSignature, diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/index.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/index.ts deleted file mode 100644 index 35b6c06d11a6..000000000000 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { useTreeViewNodes } from './useTreeViewNodes'; -export type { - UseTreeViewNodesSignature, - UseTreeViewNodesParameters, - UseTreeViewNodesDefaultizedParameters, -} from './useTreeViewNodes.types'; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.ts deleted file mode 100644 index 55a5bc1c52c9..000000000000 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { TreeViewNode, DefaultizedProps, TreeViewPluginSignature } from '../../models'; -import { TreeViewItemId } from '../../../models'; - -interface TreeViewNodeProps { - label: string; - itemId: string; - id: string | undefined; - children?: TreeViewNodeProps[]; -} - -export interface UseTreeViewNodesInstance { - getNode: (itemId: string) => TreeViewNode; - getItem: (itemId: string) => R; - getNodesToRender: () => TreeViewNodeProps[]; - getChildrenIds: (itemId: string | null) => string[]; - getNavigableChildrenIds: (itemId: string | null) => string[]; - isNodeDisabled: (itemId: string | null) => itemId is string; -} - -export interface UseTreeViewNodesPublicAPI - extends Pick, 'getItem'> {} - -export interface UseTreeViewNodesParameters { - /** - * If `true`, will allow focus on disabled items. - * @default false - */ - disabledItemsFocusable?: boolean; - items: readonly R[]; - /** - * Used to determine if a given item should be disabled. - * @template R - * @param {R} item The item to check. - * @returns {boolean} `true` if the item should be disabled. - */ - isItemDisabled?: (item: R) => boolean; - /** - * Used to determine the string label for a given item. - * - * @template R - * @param {R} item The item to check. - * @returns {string} The label of the item. - * @default `(item) => item.label` - */ - getItemLabel?: (item: R) => string; - /** - * Used to determine the string label for a given item. - * - * @template R - * @param {R} item The item to check. - * @returns {string} The id of the item. - * @default `(item) => item.id` - */ - getItemId?: (item: R) => TreeViewItemId; -} - -export type UseTreeViewNodesDefaultizedParameters = DefaultizedProps< - UseTreeViewNodesParameters, - 'disabledItemsFocusable' ->; - -interface UseTreeViewNodesEventLookup { - removeNode: { - params: { id: string }; - }; -} - -export interface TreeViewItemIdAndChildren { - id: TreeViewItemId; - children?: TreeViewItemIdAndChildren[]; -} - -export interface UseTreeViewNodesState { - nodes: { - nodeTree: TreeViewItemIdAndChildren[]; - nodeMap: TreeViewNodeMap; - itemMap: TreeViewItemMap; - }; -} - -interface UseTreeViewNodesContextValue - extends Pick, 'disabledItemsFocusable'> {} - -export type UseTreeViewNodesSignature = TreeViewPluginSignature<{ - params: UseTreeViewNodesParameters; - defaultizedParams: UseTreeViewNodesDefaultizedParameters; - instance: UseTreeViewNodesInstance; - publicAPI: UseTreeViewNodesPublicAPI; - events: UseTreeViewNodesEventLookup; - state: UseTreeViewNodesState; - contextValue: UseTreeViewNodesContextValue; -}>; - -export type TreeViewNodeMap = { [itemId: string]: TreeViewNode }; - -export type TreeViewItemMap = { [itemId: string]: R }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts index f873347de705..61e3c44c67fc 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts @@ -1,15 +1,11 @@ import * as React from 'react'; import { TreeViewPlugin, TreeViewItemRange } from '../../models'; -<<<<<<< Updated upstream import { populateInstance, - getNextNode, - getFirstNode, - getLastNode, + getNextItem, + getFirstItem, + getLastItem, } from '../../useTreeView/useTreeView.utils'; -======= -import { getNextItem, getFirstItem, getLastItem } from '../../useTreeView/useTreeView.utils'; ->>>>>>> Stashed changes import { UseTreeViewSelectionSignature } from './useTreeViewSelection.types'; import { findOrderInTremauxTree } from './useTreeViewSelection.utils'; @@ -18,7 +14,7 @@ export const useTreeViewSelection: TreeViewPlugin params, models, }) => { - const lastSelectedNode = React.useRef(null); + const lastSelectedItem = React.useRef(null); const lastSelectionWasRange = React.useRef(false); const currentRangeSelection = React.useRef([]); @@ -29,7 +25,7 @@ export const useTreeViewSelection: TreeViewPlugin if (params.onItemSelectionToggle) { if (params.multiSelect) { const addedItems = (newSelectedItems as string[]).filter( - (itemId) => !instance.isNodeSelected(itemId), + (itemId) => !instance.isItemSelected(itemId), ); const removedItems = (models.selectedItems.value as string[]).filter( (itemId) => !(newSelectedItems as string[]).includes(itemId), @@ -59,12 +55,12 @@ export const useTreeViewSelection: TreeViewPlugin models.selectedItems.setControlledValue(newSelectedItems); }; - const isNodeSelected = (itemId: string) => + const isItemSelected = (itemId: string) => Array.isArray(models.selectedItems.value) ? models.selectedItems.value.indexOf(itemId) !== -1 : models.selectedItems.value === itemId; - const selectNode = (event: React.SyntheticEvent, itemId: string, multiple = false) => { + const selectItem = (event: React.SyntheticEvent, itemId: string, multiple = false) => { if (params.disableSelection) { return; } @@ -84,28 +80,28 @@ export const useTreeViewSelection: TreeViewPlugin const newSelected = params.multiSelect ? [itemId] : itemId; setSelectedItems(event, newSelected); } - lastSelectedNode.current = itemId; + lastSelectedItem.current = itemId; lastSelectionWasRange.current = false; currentRangeSelection.current = []; }; - const getNodesInRange = (nodeAId: string, nodeBId: string) => { - const [first, last] = findOrderInTremauxTree(instance, nodeAId, nodeBId); - const nodes = [first]; + const getItemsInRange = (itemAId: string, itemBId: string) => { + const [first, last] = findOrderInTremauxTree(instance, itemAId, itemBId); + const items = [first]; let current = first; while (current !== last) { - current = getNextNode(instance, current)!; - nodes.push(current); + current = getNextItem(instance, current)!; + items.push(current); } - return nodes; + return items; }; - const handleRangeArrowSelect = (event: React.SyntheticEvent, nodes: TreeViewItemRange) => { + const handleRangeArrowSelect = (event: React.SyntheticEvent, items: TreeViewItemRange) => { let base = (models.selectedItems.value as string[]).slice(); - const { start, next, current } = nodes; + const { start, next, current } = items; if (!next || !current) { return; @@ -134,29 +130,29 @@ export const useTreeViewSelection: TreeViewPlugin const handleRangeSelect = ( event: React.SyntheticEvent, - nodes: { start: string; end: string }, + items: { start: string; end: string }, ) => { let base = (models.selectedItems.value as string[]).slice(); - const { start, end } = nodes; - // If last selection was a range selection ignore nodes that were selected. + const { start, end } = items; + // If last selection was a range selection ignore items that were selected. if (lastSelectionWasRange.current) { base = base.filter((id) => currentRangeSelection.current.indexOf(id) === -1); } - let range = getNodesInRange(start, end); - range = range.filter((node) => !instance.isNodeDisabled(node)); + let range = getItemsInRange(start, end); + range = range.filter((item) => !instance.isItemDisabled(item)); currentRangeSelection.current = range; let newSelected = base.concat(range); newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i); setSelectedItems(event, newSelected); }; - const selectRange = (event: React.SyntheticEvent, nodes: TreeViewItemRange, stacked = false) => { + const selectRange = (event: React.SyntheticEvent, items: TreeViewItemRange, stacked = false) => { if (params.disableSelection) { return; } - const { start = lastSelectedNode.current, end, current } = nodes; + const { start = lastSelectedItem.current, end, current } = items; if (stacked) { handleRangeArrowSelect(event, { start, next: end, current }); } else if (start != null && end != null) { @@ -166,42 +162,31 @@ export const useTreeViewSelection: TreeViewPlugin }; const rangeSelectToFirst = (event: React.KeyboardEvent, itemId: string) => { - if (!lastSelectedNode.current) { - lastSelectedNode.current = itemId; + if (!lastSelectedItem.current) { + lastSelectedItem.current = itemId; } - const start = lastSelectionWasRange.current ? lastSelectedNode.current : itemId; + const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId; instance.selectRange(event, { start, - end: getFirstNode(instance), + end: getFirstItem(instance), }); }; const rangeSelectToLast = (event: React.KeyboardEvent, itemId: string) => { - if (!lastSelectedNode.current) { - lastSelectedNode.current = itemId; + if (!lastSelectedItem.current) { + lastSelectedItem.current = itemId; } - const start = lastSelectionWasRange.current ? lastSelectedNode.current : itemId; + const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId; instance.selectRange(event, { start, - end: getLastNode(instance), + end: getLastItem(instance), }); }; -<<<<<<< Updated upstream - populateInstance(instance, { - isNodeSelected, - selectNode, - selectRange, - rangeSelectToLast, - rangeSelectToFirst, - }); - -======= ->>>>>>> Stashed changes return { getRootProps: () => ({ 'aria-multiselectable': params.multiSelect, @@ -227,14 +212,14 @@ useTreeViewSelection.models = { }, }; -const DEFAULT_SELECTED_NODES: string[] = []; +const DEFAULT_SELECTED_ITEMS: string[] = []; useTreeViewSelection.getDefaultizedParams = (params) => ({ ...params, disableSelection: params.disableSelection ?? false, multiSelect: params.multiSelect ?? false, defaultSelectedItems: - params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_NODES : null), + params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_ITEMS : null), }); useTreeViewSelection.params = { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts index 37d6324909ce..474a7b8f82e4 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts @@ -1,12 +1,12 @@ import * as React from 'react'; import type { DefaultizedProps, TreeViewItemRange, TreeViewPluginSignature } from '../../models'; -import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; export interface UseTreeViewSelectionInstance { - isNodeSelected: (itemId: string) => boolean; - selectNode: (event: React.SyntheticEvent, itemId: string, multiple?: boolean) => void; - selectRange: (event: React.SyntheticEvent, nodes: TreeViewItemRange, stacked?: boolean) => void; + isItemSelected: (itemId: string) => boolean; + selectItem: (event: React.SyntheticEvent, itemId: string, multiple?: boolean) => void; + selectRange: (event: React.SyntheticEvent, items: TreeViewItemRange, stacked?: boolean) => void; rangeSelectToFirst: (event: React.KeyboardEvent, itemId: string) => void; rangeSelectToLast: (event: React.KeyboardEvent, itemId: string) => void; } @@ -76,8 +76,8 @@ export type UseTreeViewSelectionSignature = TreeViewPluginSignature<{ contextValue: UseTreeViewSelectionContextValue; modelNames: 'selectedItems'; dependantPlugins: [ - UseTreeViewNodesSignature, + UseTreeViewItemsSignature, UseTreeViewExpansionSignature, - UseTreeViewNodesSignature, + UseTreeViewItemsSignature, ]; }>; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts index 7bfd556d6ba9..48553f5c6471 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts @@ -1,22 +1,22 @@ import { TreeViewInstance } from '../../models'; -import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; /** * This is used to determine the start and end of a selection range so - * we can get the nodes between the two border nodes. + * we can get the items between the two border items. * - * It finds the nodes' common ancestor using + * It finds the items' common ancestor using * a naive implementation of a lowest common ancestor algorithm * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). - * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB - * so we can compare their indexes to work out which node comes first in a depth first search. + * Then compares the ancestor's 2 children that are ancestors of itemA and ItemB + * so we can compare their indexes to work out which item comes first in a depth first search. * (https://en.wikipedia.org/wiki/Depth-first_search) * - * Another way to put it is which node is shallower in a trémaux tree + * Another way to put it is which item is shallower in a trémaux tree * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree */ export const findOrderInTremauxTree = ( - instance: TreeViewInstance<[UseTreeViewNodesSignature]>, + instance: TreeViewInstance<[UseTreeViewItemsSignature]>, nodeAId: string, nodeBId: string, ) => { From 73b7d34a5ce0a4492344ce9639354141b69ff08a Mon Sep 17 00:00:00 2001 From: delangle Date: Wed, 3 Apr 2024 14:57:25 +0200 Subject: [PATCH 3/3] Fix --- .../x-tree-view/src/internals/useTreeView/useTreeView.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts index 7ce6d1c915ce..6a0dc3b36b70 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.utils.ts @@ -1,4 +1,4 @@ -import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewUsedPublicAPI } from '../models'; +import { TreeViewInstance } from '../models'; import type { UseTreeViewExpansionSignature } from '../plugins/useTreeViewExpansion'; import type { UseTreeViewItemsSignature } from '../plugins/useTreeViewItems';