From c3c1491717196240b1b11c4afa343e4caf663d67 Mon Sep 17 00:00:00 2001 From: delangle Date: Wed, 10 Jul 2024 08:31:33 +0200 Subject: [PATCH 1/2] [TreeView] Add utility function to check if an optional plugin is present --- .../src/internals/corePlugins/corePlugins.ts | 7 ++++- .../useTreeViewOptionalPlugins/index.ts | 2 ++ .../useTreeViewOptionalPlugins.ts | 17 +++++++++++ .../useTreeViewOptionalPlugins.types.ts | 9 ++++++ .../src/internals/models/plugin.ts | 1 + .../useTreeViewKeyboardNavigation.ts | 29 ++++++++++++------- .../useTreeViewKeyboardNavigation.types.ts | 2 +- .../src/internals/useTreeView/useTreeView.ts | 1 + .../src/internals/utils/plugins.ts | 12 ++++++++ 9 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/index.ts create mode 100644 packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.ts create mode 100644 packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.types.ts create mode 100644 packages/x-tree-view/src/internals/utils/plugins.ts diff --git a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts index 1ecfb88d8e25..f6aa4c84a219 100644 --- a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts +++ b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts @@ -1,4 +1,5 @@ import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; +import { useTreeViewOptionalPlugins } from './useTreeViewOptionalPlugins'; import { useTreeViewId, UseTreeViewIdParameters } from './useTreeViewId'; import { ConvertPluginsIntoSignatures } from '../models'; @@ -6,7 +7,11 @@ import { ConvertPluginsIntoSignatures } from '../models'; * Internal plugins that create the tools used by the other plugins. * These plugins are used by the tree view components. */ -export const TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents, useTreeViewId] as const; +export const TREE_VIEW_CORE_PLUGINS = [ + useTreeViewInstanceEvents, + useTreeViewOptionalPlugins, + useTreeViewId, +] as const; export type TreeViewCorePluginSignatures = ConvertPluginsIntoSignatures< typeof TREE_VIEW_CORE_PLUGINS diff --git a/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/index.ts b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/index.ts new file mode 100644 index 000000000000..a10084128f63 --- /dev/null +++ b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/index.ts @@ -0,0 +1,2 @@ +export { useTreeViewOptionalPlugins } from './useTreeViewOptionalPlugins'; +export type { UseTreeViewOptionalPluginsSignature } from './useTreeViewOptionalPlugins.types'; diff --git a/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.ts b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.ts new file mode 100644 index 000000000000..6eadb1c88684 --- /dev/null +++ b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.ts @@ -0,0 +1,17 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewOptionalPluginsSignature } from './useTreeViewOptionalPlugins.types'; + +export const useTreeViewOptionalPlugins: TreeViewPlugin = ({ + plugins, +}) => { + const pluginSet = new Set(plugins); + const getAvailablePlugins = () => pluginSet; + + return { + instance: { + getAvailablePlugins, + }, + }; +}; + +useTreeViewOptionalPlugins.params = {}; diff --git a/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.types.ts b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.types.ts new file mode 100644 index 000000000000..a88f14e9e041 --- /dev/null +++ b/packages/x-tree-view/src/internals/corePlugins/useTreeViewOptionalPlugins/useTreeViewOptionalPlugins.types.ts @@ -0,0 +1,9 @@ +import { TreeViewPlugin, TreeViewAnyPluginSignature, TreeViewPluginSignature } from '../../models'; + +interface UseTreeViewOptionalPluginsInstance { + getAvailablePlugins: () => Set>; +} + +export type UseTreeViewOptionalPluginsSignature = TreeViewPluginSignature<{ + instance: UseTreeViewOptionalPluginsInstance; +}>; diff --git a/packages/x-tree-view/src/internals/models/plugin.ts b/packages/x-tree-view/src/internals/models/plugin.ts index 5cbe4e634f60..03d91239fbcb 100644 --- a/packages/x-tree-view/src/internals/models/plugin.ts +++ b/packages/x-tree-view/src/internals/models/plugin.ts @@ -16,6 +16,7 @@ export interface TreeViewPluginOptions; setState: React.Dispatch>>; rootRef: React.RefObject; + plugins: TreeViewPlugin[]; } type TreeViewModelsInitializer = { 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 2a414205c2e9..68d2a0db92c1 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -13,6 +13,8 @@ import { UseTreeViewKeyboardNavigationSignature, } from './useTreeViewKeyboardNavigation.types'; import { MuiCancellableEvent } from '../../models/MuiCancellableEvent'; +import { hasPlugin } from '../../utils/plugins'; +import { useTreeViewFocus } from '../useTreeViewFocus'; function isPrintableCharacter(string: string) { return !!string && string.length === 1 && !!string.match(/\S/); @@ -77,9 +79,8 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const canToggleItemSelection = (itemId: string) => !params.disableSelection && !instance.isItemDisabled(itemId); - const canToggleItemExpansion = (itemId: string) => { - return !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId); - }; + const canToggleItemExpansion = (itemId: string) => + !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId); // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction const handleItemKeyDown = ( @@ -142,7 +143,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const nextItem = getNextNavigableItem(instance, itemId); if (nextItem) { event.preventDefault(); - instance.focusItem(event, nextItem); + if (hasPlugin(instance, useTreeViewFocus)) { + instance.focusItem(event, nextItem); + } // Multi select behavior when pressing Shift + ArrowDown // Toggles the selection state of the next item @@ -159,7 +162,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const previousItem = getPreviousNavigableItem(instance, itemId); if (previousItem) { event.preventDefault(); - instance.focusItem(event, previousItem); + if (hasPlugin(instance, useTreeViewFocus)) { + instance.focusItem(event, previousItem); + } // Multi select behavior when pressing Shift + ArrowUp // Toggles the selection state of the previous item @@ -177,7 +182,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< if (instance.isItemExpanded(itemId)) { const nextItemId = getNextNavigableItem(instance, itemId); if (nextItemId) { - instance.focusItem(event, nextItemId); + if (hasPlugin(instance, useTreeViewFocus)) { + instance.focusItem(event, nextItemId); + } event.preventDefault(); } } else if (canToggleItemExpansion(itemId)) { @@ -197,7 +204,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< } else { const parent = instance.getItemMeta(itemId).parentId; if (parent) { - instance.focusItem(event, parent); + if (hasPlugin(instance, useTreeViewFocus)) { + instance.focusItem(event, parent); + } event.preventDefault(); } } @@ -211,7 +220,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Selects the focused item and all items up to the first item. if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { instance.selectRangeFromStartToItem(event, itemId); - } else { + } else if (hasPlugin(instance, useTreeViewFocus)) { instance.focusItem(event, getFirstNavigableItem(instance)); } @@ -225,7 +234,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Selects the focused item and all the items down to the last item. if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { instance.selectRangeFromItemToEnd(event, itemId); - } else { + } else if (hasPlugin(instance, useTreeViewFocus)) { instance.focusItem(event, getLastNavigableItem(instance)); } @@ -252,7 +261,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // TODO: Support typing multiple characters case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key): { const matchingItem = getFirstMatchingItem(itemId, key); - if (matchingItem != null) { + if (matchingItem != null && hasPlugin(instance, useTreeViewFocus)) { instance.focusItem(event, matchingItem); event.preventDefault(); } 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 8572a6e75fc2..51f88ed7d35f 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 @@ -32,9 +32,9 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{ dependencies: [ UseTreeViewItemsSignature, UseTreeViewSelectionSignature, - UseTreeViewFocusSignature, UseTreeViewExpansionSignature, ]; + optionalDependencies: [UseTreeViewFocusSignature]; }>; export type TreeViewFirstCharMap = { [itemId: string]: string }; diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts index 2742cda627d8..fef78939ab09 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts @@ -157,6 +157,7 @@ export const useTreeView = < setState, rootRef: innerRootRef, models, + plugins, }); if (pluginResponse.getRootProps) { diff --git a/packages/x-tree-view/src/internals/utils/plugins.ts b/packages/x-tree-view/src/internals/utils/plugins.ts new file mode 100644 index 000000000000..6975427d0002 --- /dev/null +++ b/packages/x-tree-view/src/internals/utils/plugins.ts @@ -0,0 +1,12 @@ +import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewPlugin } from '../models'; + +export const hasPlugin = < + TSignature extends TreeViewAnyPluginSignature, + TInstance extends TreeViewInstance<[], [TSignature]>, +>( + instance: TInstance, + plugin: TreeViewPlugin, +): instance is Omit & TSignature['instance'] => { + const plugins = instance.getAvailablePlugins(); + return plugins.has(plugin as any); +}; From 85fa798a861fecad7f8488cd1df78503e7eedfaa Mon Sep 17 00:00:00 2001 From: delangle Date: Thu, 11 Jul 2024 10:15:54 +0200 Subject: [PATCH 2/2] Undo example --- .../useTreeViewKeyboardNavigation.ts | 29 +++++++------------ .../useTreeViewKeyboardNavigation.types.ts | 2 +- 2 files changed, 11 insertions(+), 20 deletions(-) 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 68d2a0db92c1..2a414205c2e9 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -13,8 +13,6 @@ import { UseTreeViewKeyboardNavigationSignature, } from './useTreeViewKeyboardNavigation.types'; import { MuiCancellableEvent } from '../../models/MuiCancellableEvent'; -import { hasPlugin } from '../../utils/plugins'; -import { useTreeViewFocus } from '../useTreeViewFocus'; function isPrintableCharacter(string: string) { return !!string && string.length === 1 && !!string.match(/\S/); @@ -79,8 +77,9 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const canToggleItemSelection = (itemId: string) => !params.disableSelection && !instance.isItemDisabled(itemId); - const canToggleItemExpansion = (itemId: string) => - !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId); + const canToggleItemExpansion = (itemId: string) => { + return !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId); + }; // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction const handleItemKeyDown = ( @@ -143,9 +142,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const nextItem = getNextNavigableItem(instance, itemId); if (nextItem) { event.preventDefault(); - if (hasPlugin(instance, useTreeViewFocus)) { - instance.focusItem(event, nextItem); - } + instance.focusItem(event, nextItem); // Multi select behavior when pressing Shift + ArrowDown // Toggles the selection state of the next item @@ -162,9 +159,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< const previousItem = getPreviousNavigableItem(instance, itemId); if (previousItem) { event.preventDefault(); - if (hasPlugin(instance, useTreeViewFocus)) { - instance.focusItem(event, previousItem); - } + instance.focusItem(event, previousItem); // Multi select behavior when pressing Shift + ArrowUp // Toggles the selection state of the previous item @@ -182,9 +177,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< if (instance.isItemExpanded(itemId)) { const nextItemId = getNextNavigableItem(instance, itemId); if (nextItemId) { - if (hasPlugin(instance, useTreeViewFocus)) { - instance.focusItem(event, nextItemId); - } + instance.focusItem(event, nextItemId); event.preventDefault(); } } else if (canToggleItemExpansion(itemId)) { @@ -204,9 +197,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< } else { const parent = instance.getItemMeta(itemId).parentId; if (parent) { - if (hasPlugin(instance, useTreeViewFocus)) { - instance.focusItem(event, parent); - } + instance.focusItem(event, parent); event.preventDefault(); } } @@ -220,7 +211,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Selects the focused item and all items up to the first item. if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { instance.selectRangeFromStartToItem(event, itemId); - } else if (hasPlugin(instance, useTreeViewFocus)) { + } else { instance.focusItem(event, getFirstNavigableItem(instance)); } @@ -234,7 +225,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // Selects the focused item and all the items down to the last item. if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) { instance.selectRangeFromItemToEnd(event, itemId); - } else if (hasPlugin(instance, useTreeViewFocus)) { + } else { instance.focusItem(event, getLastNavigableItem(instance)); } @@ -261,7 +252,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // TODO: Support typing multiple characters case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key): { const matchingItem = getFirstMatchingItem(itemId, key); - if (matchingItem != null && hasPlugin(instance, useTreeViewFocus)) { + if (matchingItem != null) { instance.focusItem(event, matchingItem); event.preventDefault(); } 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 51f88ed7d35f..8572a6e75fc2 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 @@ -32,9 +32,9 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{ dependencies: [ UseTreeViewItemsSignature, UseTreeViewSelectionSignature, + UseTreeViewFocusSignature, UseTreeViewExpansionSignature, ]; - optionalDependencies: [UseTreeViewFocusSignature]; }>; export type TreeViewFirstCharMap = { [itemId: string]: string };