diff --git a/docs/pages/base-ui/api/use-input.json b/docs/pages/base-ui/api/use-input.json index ca34c99d4b74f4..dafebdd4c591cc 100644 --- a/docs/pages/base-ui/api/use-input.json +++ b/docs/pages/base-ui/api/use-input.json @@ -46,15 +46,15 @@ }, "getInputProps": { "type": { - "name": "<TOther extends Record<string, any> = {}>(externalProps?: TOther) => UseInputInputSlotProps<TOther>", - "description": "<TOther extends Record<string, any> = {}>(externalProps?: TOther) => UseInputInputSlotProps<TOther>" + "name": "<ExternalProps extends Record<string, any> = {}>(externalProps?: ExternalProps) => UseInputInputSlotProps<ExternalProps>", + "description": "<ExternalProps extends Record<string, any> = {}>(externalProps?: ExternalProps) => UseInputInputSlotProps<ExternalProps>" }, "required": true }, "getRootProps": { "type": { - "name": "<TOther extends Record<string, any> = {}>(externalProps?: TOther) => UseInputRootSlotProps<TOther>", - "description": "<TOther extends Record<string, any> = {}>(externalProps?: TOther) => UseInputRootSlotProps<TOther>" + "name": "<ExternalProps extends Record<string, any> = {}>(externalProps?: ExternalProps) => UseInputRootSlotProps<ExternalProps>", + "description": "<ExternalProps extends Record<string, any> = {}>(externalProps?: ExternalProps) => UseInputRootSlotProps<ExternalProps>" }, "required": true }, diff --git a/packages/mui-base/src/useInput/useInput.test.tsx b/packages/mui-base/src/useInput/useInput.test.tsx index 068ddfe2fd7ca3..8cf1ef0b52f0c2 100644 --- a/packages/mui-base/src/useInput/useInput.test.tsx +++ b/packages/mui-base/src/useInput/useInput.test.tsx @@ -47,4 +47,24 @@ describe('useInput', () => { expect(handleFocus.callCount).to.equal(1); }); }); + + describe('external props', () => { + it('prop getter functions should forward arbitrary props to the corresponding slot', () => { + const rootRef = React.createRef(); + + function Input() { + const { getRootProps, getInputProps } = useInput(); + return ( +
+ +
+ ); + } + const { getByRole } = render(); + + expect(rootRef.current).to.have.attribute('data-testid', 'test-root-slot'); + + expect(getByRole('textbox')).to.have.attribute('data-testid', 'test-input-slot'); + }); + }); }); diff --git a/packages/mui-base/src/useInput/useInput.ts b/packages/mui-base/src/useInput/useInput.ts index 4bdca90201fdeb..48efe21930092f 100644 --- a/packages/mui-base/src/useInput/useInput.ts +++ b/packages/mui-base/src/useInput/useInput.ts @@ -20,7 +20,7 @@ import { * * - [useInput API](https://mui.com/base-ui/react-input/hooks-api/#use-input) */ -export function useInput(parameters: UseInputParameters): UseInputReturnValue { +export function useInput(parameters: UseInputParameters = {}): UseInputReturnValue { const { defaultValue: defaultValueProp, disabled: disabledProp = false, @@ -164,9 +164,9 @@ export function useInput(parameters: UseInputParameters): UseInputReturnValue { otherHandlers.onClick?.(event); }; - const getRootProps = = {}>( - externalProps: TOther = {} as TOther, - ): UseInputRootSlotProps => { + const getRootProps = = {}>( + externalProps: ExternalProps = {} as ExternalProps, + ): UseInputRootSlotProps => { // onBlur, onChange and onFocus are forwarded to the input slot. const propsEventHandlers = extractEventHandlers(parameters, ['onBlur', 'onChange', 'onFocus']); const externalEventHandlers = { ...propsEventHandlers, ...extractEventHandlers(externalProps) }; @@ -178,9 +178,9 @@ export function useInput(parameters: UseInputParameters): UseInputReturnValue { }; }; - const getInputProps = = {}>( - externalProps: TOther = {} as TOther, - ): UseInputInputSlotProps => { + const getInputProps = = {}>( + externalProps: ExternalProps = {} as ExternalProps, + ): UseInputInputSlotProps => { const propsEventHandlers: Record | undefined> = { onBlur, onChange, @@ -190,7 +190,6 @@ export function useInput(parameters: UseInputParameters): UseInputReturnValue { const externalEventHandlers = { ...propsEventHandlers, ...extractEventHandlers(externalProps) }; const mergedEventHandlers = { - ...externalProps, ...externalEventHandlers, onBlur: handleBlur(externalEventHandlers), onChange: handleChange(externalEventHandlers), @@ -201,10 +200,12 @@ export function useInput(parameters: UseInputParameters): UseInputReturnValue { ...mergedEventHandlers, 'aria-invalid': error || undefined, defaultValue: defaultValue as string | number | readonly string[] | undefined, - ref: handleInputRef, value: value as string | number | readonly string[] | undefined, required, disabled, + ...externalProps, + ref: handleInputRef, + ...mergedEventHandlers, }; }; diff --git a/packages/mui-base/src/useInput/useInput.types.ts b/packages/mui-base/src/useInput/useInput.types.ts index 2cd3be29ccbfdc..99ada2624e86a6 100644 --- a/packages/mui-base/src/useInput/useInput.types.ts +++ b/packages/mui-base/src/useInput/useInput.types.ts @@ -33,8 +33,8 @@ export interface UseInputRootSlotOwnProps { onClick: React.MouseEventHandler | undefined; } -export type UseInputRootSlotProps = Omit< - TOther, +export type UseInputRootSlotProps = Omit< + ExternalProps, keyof UseInputRootSlotOwnProps | 'onBlur' | 'onChange' | 'onFocus' > & UseInputRootSlotOwnProps; @@ -51,7 +51,10 @@ export interface UseInputInputSlotOwnProps { disabled: boolean; } -export type UseInputInputSlotProps = Omit & +export type UseInputInputSlotProps = Omit< + ExternalProps, + keyof UseInputInputSlotOwnProps +> & UseInputInputSlotOwnProps; export interface UseInputReturnValue { @@ -76,17 +79,17 @@ export interface UseInputReturnValue { * @param externalProps props for the input slot * @returns props that should be spread on the input slot */ - getInputProps: = {}>( - externalProps?: TOther, - ) => UseInputInputSlotProps; + getInputProps: = {}>( + externalProps?: ExternalProps, + ) => UseInputInputSlotProps; /** * Resolver for the root slot's props. * @param externalProps props for the root slot * @returns props that should be spread on the root slot */ - getRootProps: = {}>( - externalProps?: TOther, - ) => UseInputRootSlotProps; + getRootProps: = {}>( + externalProps?: ExternalProps, + ) => UseInputRootSlotProps; inputRef: React.RefCallback | null; /** * If `true`, the `input` will indicate that it's required.