diff --git a/packages/mui-utils/src/resolveProps.test.ts b/packages/mui-utils/src/resolveProps.test.ts index eb0b1943284297..491428ffc22a3a 100644 --- a/packages/mui-utils/src/resolveProps.test.ts +++ b/packages/mui-utils/src/resolveProps.test.ts @@ -44,4 +44,51 @@ describe('resolveProps', () => { foo: '', }); }); + + it('merge components and componentsProps props', () => { + expect( + resolveProps( + { components: { Input: 'Input' }, componentsProps: { input: { className: 'input' } } }, + { + components: { Root: 'Root' }, + componentsProps: { root: { className: 'root' }, input: { style: { color: 'red' } } }, + }, + ), + ).to.deep.equal({ + components: { Root: 'Root', Input: 'Input' }, + componentsProps: { + root: { className: 'root' }, + input: { className: 'input', style: { color: 'red' } }, + }, + }); + }); + + it('merge slots and slotProps props', () => { + expect( + resolveProps( + { slots: { input: 'input' }, slotProps: { input: { className: 'input' } } }, + { + slots: { root: 'root' }, + slotProps: { root: { className: 'root' }, input: { style: { color: 'red' } } }, + }, + ), + ).to.deep.equal({ + slots: { root: 'root', input: 'input' }, + slotProps: { + root: { className: 'root' }, + input: { className: 'input', style: { color: 'red' } }, + }, + }); + }); + + it('should not merge props that are not intended', () => { + expect( + resolveProps( + { notTheSlotProps: { style: { color: 'red' } } }, + { notTheSlotProps: { className: 'input' } }, + ), + ).to.deep.equal({ + notTheSlotProps: { className: 'input' }, + }); + }); }); diff --git a/packages/mui-utils/src/resolveProps.ts b/packages/mui-utils/src/resolveProps.ts index 35d46f9ec35809..fef2dbdb86f049 100644 --- a/packages/mui-utils/src/resolveProps.ts +++ b/packages/mui-utils/src/resolveProps.ts @@ -4,14 +4,43 @@ * @param {object} props * @returns {object} resolved props */ -export default function resolveProps>( - defaultProps: T, - props: T, -) { +export default function resolveProps< + T extends { + components?: Record; + componentsProps?: Record; + slots?: Record; + slotProps?: Record; + } & Record, +>(defaultProps: T, props: T) { const output = { ...props }; - Object.keys(defaultProps).forEach((propName: keyof T) => { - if (output[propName] === undefined) { + (Object.keys(defaultProps) as Array).forEach((propName) => { + if (propName.toString().match(/^(components|slots)$/)) { + output[propName] = { + ...(defaultProps[propName] as any), + ...(output[propName] as any), + }; + } else if (propName.toString().match(/^(componentsProps|slotProps)$/)) { + const defaultSlotProps = (defaultProps[propName] || {}) as T[keyof T]; + const slotProps = props[propName] as {} as T[keyof T]; + output[propName] = {} as T[keyof T]; + + if (!slotProps || !Object.keys(slotProps)) { + // Reduce the iteration if the slot props is empty + output[propName] = defaultSlotProps; + } else if (!defaultSlotProps || !Object.keys(defaultSlotProps)) { + // Reduce the iteration if the default slot props is empty + output[propName] = slotProps; + } else { + output[propName] = { ...slotProps }; + Object.keys(defaultSlotProps).forEach((slotPropName) => { + (output[propName] as Record)[slotPropName] = resolveProps( + (defaultSlotProps as Record)[slotPropName], + (slotProps as Record)[slotPropName], + ); + }); + } + } else if (output[propName] === undefined) { output[propName] = defaultProps[propName]; } });