Skip to content

Commit

Permalink
[TreeView] Allow the plugins to enrich the props passed to the item s…
Browse files Browse the repository at this point in the history
…lots (mui#13953)
  • Loading branch information
flaviendelangle authored and DungTiger committed Jul 23, 2024
1 parent fe100c7 commit f03f787
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 19 deletions.
1 change: 1 addition & 0 deletions packages/x-tree-view/src/internals/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './helpers';
export * from './plugin';
export * from './itemPlugin';
export * from './treeView';
49 changes: 49 additions & 0 deletions packages/x-tree-view/src/internals/models/itemPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from 'react';
import { EventHandlers } from '@mui/base/utils';
import type {
UseTreeItem2ContentSlotOwnProps,
UseTreeItem2RootSlotOwnProps,
} from '../../useTreeItem2';

export interface TreeViewItemPluginSlotPropsEnhancerParams {
rootRefObject: React.MutableRefObject<HTMLLIElement | null>;
contentRefObject: React.MutableRefObject<HTMLDivElement | null>;
externalEventHandlers: EventHandlers;
}

type TreeViewItemPluginSlotPropsEnhancer<TSlotProps> = (
params: TreeViewItemPluginSlotPropsEnhancerParams,
) => Partial<TSlotProps>;

export interface TreeViewItemPluginSlotPropsEnhancers {
root?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2RootSlotOwnProps>;
content?: TreeViewItemPluginSlotPropsEnhancer<UseTreeItem2ContentSlotOwnProps>;
}

export interface TreeViewItemPluginResponse {
/**
* Root of the `content` slot enriched by the plugin.
*/
contentRef?: React.RefCallback<HTMLElement> | null;
/**
* Ref of the `root` slot enriched by the plugin
*/
rootRef?: React.RefCallback<HTMLLIElement> | null;
/**
* Callback to enhance the slot props of the Tree Item.
*
* Not all slots are enabled by default,
* if a new plugin needs to pass to an unconfigured slot,
* it just needs to be added to `TreeViewItemPluginSlotPropsEnhancers`
*/
propsEnhancers?: TreeViewItemPluginSlotPropsEnhancers;
}

export interface TreeViewItemPluginOptions<TProps extends {}>
extends Omit<TreeViewItemPluginResponse, 'propsEnhancers'> {
props: TProps;
}

export type TreeViewItemPlugin<TProps extends {}> = (
options: TreeViewItemPluginOptions<TProps>,
) => void | TreeViewItemPluginResponse;
20 changes: 1 addition & 19 deletions packages/x-tree-view/src/internals/models/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TreeViewExperimentalFeatures, TreeViewInstance, TreeViewModel } from '.
import type { MergeSignaturesProperty, OptionalIfEmpty } from './helpers';
import { TreeViewEventLookupElement } from './events';
import type { TreeViewCorePluginSignatures } from '../corePlugins';
import { TreeViewItemPlugin } from './itemPlugin';
import { TreeViewItemId } from '../../models';

export interface TreeViewPluginOptions<TSignature extends TreeViewAnyPluginSignature> {
Expand Down Expand Up @@ -136,25 +137,6 @@ export type TreeViewUsedModels<TSignature extends TreeViewAnyPluginSignature> =
export type TreeViewUsedEvents<TSignature extends TreeViewAnyPluginSignature> =
TSignature['events'] & MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, 'events'>;

export interface TreeViewItemPluginOptions<TProps extends {}> extends TreeViewItemPluginResponse {
props: TProps;
}

export interface TreeViewItemPluginResponse {
/**
* Root of the `content` slot enriched by the plugin.
*/
contentRef?: React.RefCallback<HTMLElement> | null;
/**
* Ref of the `root` slot enriched by the plugin
*/
rootRef?: React.RefCallback<HTMLLIElement> | null;
}

export type TreeViewItemPlugin<TProps extends {}> = (
options: TreeViewItemPluginOptions<TProps>,
) => void | TreeViewItemPluginResponse;

export type TreeItemWrapper<TSignatures extends readonly TreeViewAnyPluginSignature[]> = (params: {
itemId: TreeViewItemId;
children: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
TreeViewAnyPluginSignature,
TreeViewInstance,
TreeViewPublicAPI,
TreeViewItemPluginSlotPropsEnhancers,
TreeViewItemPluginSlotPropsEnhancerParams,
} from '../models';
import { TreeViewCorePluginSignatures } from '../corePlugins';

Expand All @@ -24,6 +26,9 @@ export const useTreeViewBuildContext = <TSignatures extends readonly TreeViewAny
const runItemPlugins: TreeViewItemPluginsRunner = (itemPluginProps) => {
let finalRootRef: React.RefCallback<HTMLLIElement> | null = null;
let finalContentRef: React.RefCallback<HTMLElement> | null = null;
const pluginPropEnhancers: TreeViewItemPluginSlotPropsEnhancers[] = [];
const pluginPropEnhancersNames: { [key in keyof TreeViewItemPluginSlotPropsEnhancers]?: true } =
{};

plugins.forEach((plugin) => {
if (!plugin.itemPlugin) {
Expand All @@ -41,11 +46,47 @@ export const useTreeViewBuildContext = <TSignatures extends readonly TreeViewAny
if (itemPluginResponse?.contentRef) {
finalContentRef = itemPluginResponse.contentRef;
}
if (itemPluginResponse?.propsEnhancers) {
pluginPropEnhancers.push(itemPluginResponse.propsEnhancers);

// Prepare a list of all the slots which are enhanced by at least one plugin
Object.keys(itemPluginResponse.propsEnhancers).forEach((propsEnhancerName) => {
pluginPropEnhancersNames[
propsEnhancerName as keyof TreeViewItemPluginSlotPropsEnhancers
] = true;
});
}
});

const resolvePropsEnhancer =
(currentSlotName: keyof TreeViewItemPluginSlotPropsEnhancers) =>
(currentSlotParams: TreeViewItemPluginSlotPropsEnhancerParams) => {
const enhancedProps = {};
pluginPropEnhancers.forEach((propsEnhancersForCurrentPlugin) => {
const propsEnhancerForCurrentPluginAndSlot =
propsEnhancersForCurrentPlugin[currentSlotName];
if (propsEnhancerForCurrentPluginAndSlot != null) {
Object.assign(enhancedProps, propsEnhancerForCurrentPluginAndSlot(currentSlotParams));
}
});

return enhancedProps;
};

const propsEnhancers = Object.fromEntries(
Object.keys(pluginPropEnhancersNames).map(
(propEnhancerName) =>
[
propEnhancerName,
resolvePropsEnhancer(propEnhancerName as keyof TreeViewItemPluginSlotPropsEnhancers),
] as const,
),
);

return {
contentRef: finalContentRef,
rootRef: finalRootRef,
propsEnhancers,
};
};

Expand Down
4 changes: 4 additions & 0 deletions packages/x-tree-view/src/useTreeItem2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ export type {
UseTreeItem2Parameters,
UseTreeItem2ReturnValue,
UseTreeItem2Status,
UseTreeItem2RootSlotOwnProps,
UseTreeItem2ContentSlotOwnProps,
UseTreeItem2LabelSlotOwnProps,
UseTreeItem2IconContainerSlotOwnProps,
UseTreeItem2GroupTransitionSlotOwnProps,
} from './useTreeItem2.types';
4 changes: 4 additions & 0 deletions scripts/x-tree-view.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@
{ "name": "unstable_resetCleanupTracking", "kind": "Variable" },
{ "name": "unstable_useTreeItem2", "kind": "Variable" },
{ "name": "UseTreeItem2ContentSlotOwnProps", "kind": "Interface" },
{ "name": "UseTreeItem2GroupTransitionSlotOwnProps", "kind": "Interface" },
{ "name": "UseTreeItem2IconContainerSlotOwnProps", "kind": "Interface" },
{ "name": "UseTreeItem2LabelSlotOwnProps", "kind": "Interface" },
{ "name": "UseTreeItem2Parameters", "kind": "Interface" },
{ "name": "UseTreeItem2ReturnValue", "kind": "Interface" },
{ "name": "UseTreeItem2RootSlotOwnProps", "kind": "Interface" },
{ "name": "UseTreeItem2Status", "kind": "Interface" },
{ "name": "useTreeItem2Utils", "kind": "Variable" },
{ "name": "useTreeItemState", "kind": "Function" },
Expand Down
1 change: 1 addition & 0 deletions test/utils/tree-view/fakeContextValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const getFakeContextValue = (
runItemPlugins: () => ({
rootRef: null,
contentRef: null,
propsEnhancers: {},
}),
wrapItem: ({ children }) => children,
wrapRoot: ({ children }) => children,
Expand Down

0 comments on commit f03f787

Please sign in to comment.