Skip to content

Commit

Permalink
[TreeView] Improve typing to support optional dependencies in plugins…
Browse files Browse the repository at this point in the history
… and in the item (#13523)
  • Loading branch information
flaviendelangle authored Jun 19, 2024
1 parent 59e3a45 commit 6f68422
Show file tree
Hide file tree
Showing 18 changed files with 109 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type TreeViewLogExpandedSignature = TreeViewPluginSignature<{
// The parameters of this plugin as they are passed to the plugin after calling `plugin.getDefaultizedParams`
defaultizedParams: TreeViewLogExpandedDefaultizedParameters;
// Dependencies of this plugin (we need the expansion plugin to access its model)
dependantPlugins: [UseTreeViewExpansionSignature];
dependencies: [UseTreeViewExpansionSignature];
}>;

const useTreeViewLogExpanded: TreeViewPlugin<TreeViewLogExpandedSignature> = ({
Expand Down
4 changes: 2 additions & 2 deletions docs/data/tree-view/rich-tree-view/headless/headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{
// The name of the models defined by your plugin
modelNames: UseCustomPluginModelNames;
// The plugins this plugin needs to work correctly
dependantPlugins: UseCustomPluginDependantPlugins;
dependencies: UseCustomPluginDependantPlugins;
}>;
```

Expand Down Expand Up @@ -317,7 +317,7 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{
contextValue: { customPlugin: { enabled: boolean } };
modelNames: 'customModel';
// We want to have access to the expansion models and methods of the expansion plugin.
dependantPlugins: [UseTreeViewExpansionSignature];
dependencies: [UseTreeViewExpansionSignature];
}>;
```

Expand Down
9 changes: 7 additions & 2 deletions packages/x-tree-view/src/TreeItem/TreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import { unstable_composeClasses as composeClasses } from '@mui/base';
import { styled, createUseThemeProps } from '../internals/zero-styled';
import { TreeItemContent } from './TreeItemContent';
import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses';
import { TreeItemMinimalPlugins, TreeItemOwnerState, TreeItemProps } from './TreeItem.types';
import {
TreeItemMinimalPlugins,
TreeItemOptionalPlugins,
TreeItemOwnerState,
TreeItemProps,
} from './TreeItem.types';
import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext';
import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons';
import { TreeItem2Provider } from '../TreeItem2Provider';
Expand Down Expand Up @@ -185,7 +190,7 @@ export const TreeItem = React.forwardRef(function TreeItem(
disabledItemsFocusable,
indentationAtItemLevel,
instance,
} = useTreeViewContext<TreeItemMinimalPlugins>();
} = useTreeViewContext<TreeItemMinimalPlugins, TreeItemOptionalPlugins>();
const depthContext = React.useContext(TreeViewItemDepthContext);

const props = useThemeProps({ props: inProps, name: 'MuiTreeItem' });
Expand Down
8 changes: 8 additions & 0 deletions packages/x-tree-view/src/TreeItem/TreeItem.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export interface TreeItemOwnerState extends TreeItemProps {
indentationAtItemLevel: boolean;
}

/**
* Plugins that need to be present in the Tree View in order for `TreeItem` to work correctly.
*/
export type TreeItemMinimalPlugins = readonly [
UseTreeViewIconsSignature,
UseTreeViewSelectionSignature,
Expand All @@ -122,3 +125,8 @@ export type TreeItemMinimalPlugins = readonly [
UseTreeViewKeyboardNavigationSignature,
UseTreeViewIdSignature,
];

/**
* Plugins that `TreeItem` can use if they are present, but are not required.
*/
export type TreeItemOptionalPlugins = readonly [];
4 changes: 3 additions & 1 deletion packages/x-tree-view/src/TreeItem/useTreeItemState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ type UseTreeItemStateMinimalPlugins = readonly [
UseTreeViewItemsSignature,
];

type UseTreeItemStateOptionalPlugins = readonly [];

export function useTreeItemState(itemId: string) {
const {
instance,
selection: { multiSelect, checkboxSelection, disableSelection },
} = useTreeViewContext<UseTreeItemStateMinimalPlugins>();
} = useTreeViewContext<UseTreeItemStateMinimalPlugins, UseTreeItemStateOptionalPlugins>();

const expandable = instance.isItemExpandable(itemId);
const expanded = instance.isItemExpanded(itemId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ const isItemExpandable = (reactChildren: React.ReactNode) => {
return Boolean(reactChildren);
};

/**
* Plugins that need to be present in the Tree View in order for `useTreeItem2Utils` to work correctly.
*/
type UseTreeItem2UtilsMinimalPlugins = readonly [
UseTreeViewSelectionSignature,
UseTreeViewExpansionSignature,
UseTreeViewItemsSignature,
UseTreeViewFocusSignature,
];

/**
* Plugins that `useTreeItem2Utils` can use if they are present, but are not required.
*/
export type UseTreeItem2UtilsOptionalPlugins = readonly [];

export const useTreeItem2Utils = ({
itemId,
children,
Expand All @@ -41,7 +49,7 @@ export const useTreeItem2Utils = ({
const {
instance,
selection: { multiSelect },
} = useTreeViewContext<UseTreeItem2UtilsMinimalPlugins>();
} = useTreeViewContext<UseTreeItem2UtilsMinimalPlugins, UseTreeItem2UtilsOptionalPlugins>();

const status: UseTreeItem2Status = {
expandable: isItemExpandable(children),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ export type TreeViewItemPluginsRunner = <TProps extends {}>(
props: TProps,
) => Required<TreeViewItemPluginResponse>;

export type TreeViewContextValue<TSignatures extends readonly TreeViewAnyPluginSignature[]> =
MergeSignaturesProperty<TSignatures, 'contextValue'> & {
instance: TreeViewInstance<TSignatures>;
publicAPI: TreeViewPublicAPI<TSignatures>;
export type TreeViewContextValue<
TSignatures extends readonly TreeViewAnyPluginSignature[],
TOptionalSignatures extends readonly TreeViewAnyPluginSignature[] = [],
> = MergeSignaturesProperty<TSignatures, 'contextValue'> &
Partial<MergeSignaturesProperty<TOptionalSignatures, 'contextValue'>> & {
instance: TreeViewInstance<TSignatures, TOptionalSignatures>;
publicAPI: TreeViewPublicAPI<TSignatures, TOptionalSignatures>;
rootRef: React.RefObject<HTMLUListElement>;
wrapItem: TreeItemWrapper<TSignatures>;
wrapRoot: TreeRootWrapper<TSignatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import { TreeViewAnyPluginSignature } from '../models';
import { TreeViewContext } from './TreeViewContext';
import { TreeViewContextValue } from './TreeViewProvider.types';

export const useTreeViewContext = <TSignatures extends readonly TreeViewAnyPluginSignature[]>() => {
const context = React.useContext(TreeViewContext) as TreeViewContextValue<TSignatures>;
export const useTreeViewContext = <
TSignatures extends readonly TreeViewAnyPluginSignature[],
TOptionalSignatures extends readonly TreeViewAnyPluginSignature[] = [],
>() => {
const context = React.useContext(TreeViewContext) as TreeViewContextValue<
TSignatures,
TOptionalSignatures
>;
if (context == null) {
throw new Error(
[
Expand Down
54 changes: 32 additions & 22 deletions packages/x-tree-view/src/internals/models/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export type TreeViewPluginSignature<
slotProps?: { [key in keyof T['slotProps']]: {} | (() => {}) };
modelNames?: keyof T['defaultizedParams'];
experimentalFeatures?: string;
dependantPlugins?: readonly TreeViewAnyPluginSignature[];
dependencies?: readonly TreeViewAnyPluginSignature[];
optionalDependencies?: readonly TreeViewAnyPluginSignature[];
},
> = {
params: T extends { params: {} } ? T['params'] : {};
Expand All @@ -67,15 +68,19 @@ export type TreeViewPluginSignature<
}
: {};
experimentalFeatures: T['experimentalFeatures'];
dependantPlugins: T extends { dependantPlugins: Array<any> } ? T['dependantPlugins'] : [];
dependencies: T extends { dependencies: Array<any> } ? T['dependencies'] : [];
optionalDependencies: T extends { optionalDependencies: Array<any> }
? T['optionalDependencies']
: [];
};

export type TreeViewAnyPluginSignature = {
state: any;
instance: any;
params: any;
defaultizedParams: any;
dependantPlugins: any;
dependencies: any;
optionalDependencies: any;
events: any;
contextValue: any;
slots: any;
Expand All @@ -85,43 +90,48 @@ export type TreeViewAnyPluginSignature = {
publicAPI: any;
};

type TreeViewUsedPlugins<TSignature extends TreeViewAnyPluginSignature> = [
type TreeViewRequiredPlugins<TSignature extends TreeViewAnyPluginSignature> = [
...TreeViewCorePluginSignatures,
...TSignature['dependantPlugins'],
...TSignature['dependencies'],
];

type PluginPropertyWithDependencies<
TSignature extends TreeViewAnyPluginSignature,
TProperty extends keyof TreeViewAnyPluginSignature,
> = TSignature[TProperty] &
MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, TProperty> &
Partial<MergeSignaturesProperty<TSignature['optionalDependencies'], TProperty>>;

export type TreeViewUsedParams<TSignature extends TreeViewAnyPluginSignature> =
TSignature['params'] & MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'params'>;
PluginPropertyWithDependencies<TSignature, 'params'>;

type TreeViewUsedDefaultizedParams<TSignature extends TreeViewAnyPluginSignature> =
TSignature['defaultizedParams'] &
MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'defaultizedParams'>;
PluginPropertyWithDependencies<TSignature, 'defaultizedParams'>;

export type TreeViewUsedInstance<TSignature extends TreeViewAnyPluginSignature> =
TSignature['instance'] &
MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'instance'> & {
/**
* Private property only defined in TypeScript to be able to access the plugin signature from the instance object.
*/
$$signature: TSignature;
};
PluginPropertyWithDependencies<TSignature, 'instance'> & {
/**
* Private property only defined in TypeScript to be able to access the plugin signature from the instance object.
*/
$$signature: TSignature;
};

type TreeViewUsedState<TSignature extends TreeViewAnyPluginSignature> = TSignature['state'] &
MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'state'>;
type TreeViewUsedState<TSignature extends TreeViewAnyPluginSignature> =
PluginPropertyWithDependencies<TSignature, 'state'>;

type TreeViewUsedExperimentalFeatures<TSignature extends TreeViewAnyPluginSignature> =
TreeViewExperimentalFeatures<[TSignature, ...TSignature['dependantPlugins']]>;
TreeViewExperimentalFeatures<[TSignature, ...TSignature['dependencies']]>;

type RemoveSetValue<Models extends Record<string, TreeViewModel<any>>> = {
[K in keyof Models]: Omit<Models[K], 'setValue'>;
};

export type TreeViewUsedModels<TSignature extends TreeViewAnyPluginSignature> =
TSignature['models'] &
RemoveSetValue<MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'models'>>;
RemoveSetValue<MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, 'models'>>;

export type TreeViewUsedEvents<TSignature extends TreeViewAnyPluginSignature> =
TSignature['events'] & MergeSignaturesProperty<TreeViewUsedPlugins<TSignature>, 'events'>;
TSignature['events'] & MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, 'events'>;

export interface TreeViewItemPluginOptions<TProps extends {}> extends TreeViewItemPluginResponse {
props: TProps;
Expand Down Expand Up @@ -167,11 +177,11 @@ export type TreeViewPlugin<TSignature extends TreeViewAnyPluginSignature> = {
* @param {{ nodeId: TreeViewItemId; children: React.ReactNode; }} params The params of the item.
* @returns {React.ReactNode} The wrapped item.
*/
wrapItem?: TreeItemWrapper<[TSignature, ...TSignature['dependantPlugins']]>;
wrapItem?: TreeItemWrapper<[TSignature, ...TSignature['dependencies']]>;
/**
* Render function used to add React wrappers around the TreeView.
* @param {{ children: React.ReactNode; }} params The params of the root.
* @returns {React.ReactNode} The wrapped root.
*/
wrapRoot?: TreeRootWrapper<[TSignature, ...TSignature['dependantPlugins']]>;
wrapRoot?: TreeRootWrapper<[TSignature, ...TSignature['dependencies']]>;
};
14 changes: 10 additions & 4 deletions packages/x-tree-view/src/internals/models/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ export interface TreeViewModel<TValue> {
setControlledValue: (value: TValue | ((prevValue: TValue) => TValue)) => void;
}

export type TreeViewInstance<TSignatures extends readonly TreeViewAnyPluginSignature[]> =
MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'instance'>;
export type TreeViewInstance<
TSignatures extends readonly TreeViewAnyPluginSignature[],
TOptionalSignatures extends readonly TreeViewAnyPluginSignature[] = [],
> = MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'instance'> &
Partial<MergeSignaturesProperty<TOptionalSignatures, 'instance'>>;

export type TreeViewPublicAPI<TSignatures extends readonly TreeViewAnyPluginSignature[]> =
MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'publicAPI'>;
export type TreeViewPublicAPI<
TSignatures extends readonly TreeViewAnyPluginSignature[],
TOptionalSignatures extends readonly TreeViewAnyPluginSignature[] = [],
> = MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'publicAPI'> &
Partial<MergeSignaturesProperty<TOptionalSignatures, 'instance'>>;

export type TreeViewExperimentalFeatures<
TSignatures extends readonly TreeViewAnyPluginSignature[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,5 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{
instance: UseTreeViewExpansionInstance;
publicAPI: UseTreeViewExpansionPublicAPI;
modelNames: 'expandedItems';
dependantPlugins: [UseTreeViewItemsSignature];
dependencies: [UseTreeViewItemsSignature];
}>;
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export type UseTreeViewFocusSignature = TreeViewPluginSignature<{
instance: UseTreeViewFocusInstance;
publicAPI: UseTreeViewFocusPublicAPI;
state: UseTreeViewFocusState;
dependantPlugins: [
dependencies: [
UseTreeViewIdSignature,
UseTreeViewItemsSignature,
UseTreeViewSelectionSignature,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@ export type UseTreeViewIconsSignature = TreeViewPluginSignature<{
contextValue: UseTreeViewIconsContextValue;
slots: UseTreeViewIconsSlots;
slotProps: UseTreeViewIconsSlotProps;
dependantPlugins: [UseTreeViewItemsSignature, UseTreeViewSelectionSignature];
dependencies: [UseTreeViewItemsSignature, UseTreeViewSelectionSignature];
}>;
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ export type UseTreeViewJSXItemsSignature = TreeViewPluginSignature<{
params: UseTreeViewJSXItemsParameters;
defaultizedParams: UseTreeViewItemsDefaultizedParameters;
instance: UseTreeViewItemsInstance;
dependantPlugins: [UseTreeViewItemsSignature, UseTreeViewKeyboardNavigationSignature];
dependencies: [UseTreeViewItemsSignature, UseTreeViewKeyboardNavigationSignature];
}>;
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface UseTreeViewKeyboardNavigationInstance {

export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
instance: UseTreeViewKeyboardNavigationInstance;
dependantPlugins: [
dependencies: [
UseTreeViewItemsSignature,
UseTreeViewSelectionSignature,
UseTreeViewFocusSignature,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export type UseTreeViewSelectionSignature = TreeViewPluginSignature<{
instance: UseTreeViewSelectionInstance;
contextValue: UseTreeViewSelectionContextValue;
modelNames: 'selectedItems';
dependantPlugins: [
dependencies: [
UseTreeViewItemsSignature,
UseTreeViewExpansionSignature,
UseTreeViewItemsSignature,
Expand Down
6 changes: 4 additions & 2 deletions packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UseTreeItemIconContainerSlotProps,
UseTreeItem2CheckboxSlotProps,
UseTreeItem2MinimalPlugins,
UseTreeItem2OptionalPlugins,
} from './useTreeItem2.types';
import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext';
import { MuiCancellableEvent } from '../internals/models/MuiCancellableEvent';
Expand All @@ -19,17 +20,18 @@ import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'

export const useTreeItem2 = <
TSignatures extends UseTreeItem2MinimalPlugins = UseTreeItem2MinimalPlugins,
TOptionalSignatures extends UseTreeItem2OptionalPlugins = UseTreeItem2OptionalPlugins,
>(
parameters: UseTreeItem2Parameters,
): UseTreeItem2ReturnValue<TSignatures> => {
): UseTreeItem2ReturnValue<TSignatures, TOptionalSignatures> => {
const {
runItemPlugins,
selection: { multiSelect, disableSelection, checkboxSelection },
disabledItemsFocusable,
indentationAtItemLevel,
instance,
publicAPI,
} = useTreeViewContext<TSignatures>();
} = useTreeViewContext<TSignatures, TOptionalSignatures>();
const depthContext = React.useContext(TreeViewItemDepthContext);

const { id, itemId, label, children, rootRef } = parameters;
Expand Down
Loading

0 comments on commit 6f68422

Please sign in to comment.