diff --git a/docs/pages/base-ui/api/use-slider.json b/docs/pages/base-ui/api/use-slider.json index 9978166f327161..620535067170fd 100644 --- a/docs/pages/base-ui/api/use-slider.json +++ b/docs/pages/base-ui/api/use-slider.json @@ -62,22 +62,22 @@ }, "getHiddenInputProps": { "type": { - "name": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderHiddenInputProps<TOther>", - "description": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderHiddenInputProps<TOther>" + "name": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderHiddenInputProps<ExternalProps>", + "description": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderHiddenInputProps<ExternalProps>" }, "required": true }, "getRootProps": { "type": { - "name": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderRootSlotProps<TOther>", - "description": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderRootSlotProps<TOther>" + "name": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderRootSlotProps<ExternalProps>", + "description": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderRootSlotProps<ExternalProps>" }, "required": true }, "getThumbProps": { "type": { - "name": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderThumbSlotProps<TOther>", - "description": "<TOther extends EventHandlers = {}>(otherHandlers?: TOther) => UseSliderThumbSlotProps<TOther>" + "name": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderThumbSlotProps<ExternalProps>", + "description": "<ExternalProps extends Record<string, unknown> = {}>(externalProps?: ExternalProps) => UseSliderThumbSlotProps<ExternalProps>" }, "required": true }, diff --git a/packages/mui-base/src/useSlider/useSlider.test.js b/packages/mui-base/src/useSlider/useSlider.test.js new file mode 100644 index 00000000000000..b717f0e934b57e --- /dev/null +++ b/packages/mui-base/src/useSlider/useSlider.test.js @@ -0,0 +1,90 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { createRenderer, screen, fireEvent } from '@mui-internal/test-utils'; +import { useSlider } from './useSlider'; + +describe('useSlider', () => { + const { render } = createRenderer(); + describe('getRootProps', () => { + it('forwards external props including event handlers', () => { + const rootRef = React.createRef(); + + const handleClick = spy(); + + function Test() { + const { getRootProps } = useSlider({ + rootRef, + marks: [ + { + label: 'One', + value: 1, + }, + ], + }); + + return ( +
+ ); + } + + render(); + + const slider = screen.getByTestId('test-slider-root'); + expect(slider).not.to.equal(null); + expect(rootRef.current).to.deep.equal(slider); + + fireEvent.click(slider); + expect(handleClick.callCount).to.equal(1); + }); + }); + + describe('getHiddenInputProps', () => { + function Test( + props = { + slotProps: { + input: {}, + }, + }, + ) { + const { getRootProps, getThumbProps, getHiddenInputProps } = useSlider({ + marks: [ + { + label: 'One', + value: 1, + }, + ], + }); + + return ( +
+
+ +
+
+ ); + } + + it('forwards external props including event handlers', () => { + const handleClick = spy(); + render( + , + ); + + const input = screen.getByTestId('test-input'); + expect(input).not.to.equal(null); + + fireEvent.click(input); + expect(handleClick.callCount).to.equal(1); + }); + }); +}); diff --git a/packages/mui-base/src/useSlider/useSlider.ts b/packages/mui-base/src/useSlider/useSlider.ts index 1a382af52cc029..219d18666c51df 100644 --- a/packages/mui-base/src/useSlider/useSlider.ts +++ b/packages/mui-base/src/useSlider/useSlider.ts @@ -17,7 +17,7 @@ import { UseSliderRootSlotProps, UseSliderThumbSlotProps, } from './useSlider.types'; -import { areArraysEqual, EventHandlers } from '../utils'; +import { areArraysEqual, EventHandlers, extractEventHandlers } from '../utils'; const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2; @@ -282,7 +282,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue const handleRef = useForkRef(ref, handleFocusRef); const createHandleHiddenInputFocus = - (otherHandlers: Record>) => (event: React.FocusEvent) => { + (otherHandlers: EventHandlers) => (event: React.FocusEvent) => { const index = Number(event.currentTarget.getAttribute('data-index')); handleFocusVisible(event); if (isFocusVisibleRef.current === true) { @@ -292,7 +292,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue otherHandlers?.onFocus?.(event); }; const createHandleHiddenInputBlur = - (otherHandlers: Record>) => (event: React.FocusEvent) => { + (otherHandlers: EventHandlers) => (event: React.FocusEvent) => { handleBlurVisible(event); if (isFocusVisibleRef.current === false) { setFocusedThumbIndex(-1); @@ -319,7 +319,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue } const createHandleHiddenInputChange = - (otherHandlers: Record>) => (event: React.ChangeEvent) => { + (otherHandlers: EventHandlers) => (event: React.ChangeEvent) => { otherHandlers.onChange?.(event); const index = Number(event.currentTarget.getAttribute('data-index')); @@ -571,8 +571,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue }, [disabled, stopListening]); const createHandleMouseDown = - (otherHandlers: Record>) => - (event: React.MouseEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent) => { otherHandlers.onMouseDown?.(event); if (disabled) { return; @@ -610,26 +609,29 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue const trackOffset = valueToPercent(range ? values[0] : min, min, max); const trackLeap = valueToPercent(values[values.length - 1], min, max) - trackOffset; - const getRootProps = ( - otherHandlers: TOther = {} as TOther, - ): UseSliderRootSlotProps => { + const getRootProps = = {}>( + externalProps: ExternalProps = {} as ExternalProps, + ): UseSliderRootSlotProps => { + const externalHandlers = extractEventHandlers(externalProps); + const ownEventHandlers = { - onMouseDown: createHandleMouseDown(otherHandlers || {}), + onMouseDown: createHandleMouseDown(externalHandlers || {}), }; const mergedEventHandlers = { - ...otherHandlers, + ...externalHandlers, ...ownEventHandlers, }; + return { + ...externalProps, ref: handleRef, ...mergedEventHandlers, }; }; const createHandleMouseOver = - (otherHandlers: Record>) => - (event: React.MouseEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent) => { otherHandlers.onMouseOver?.(event); const index = Number(event.currentTarget.getAttribute('data-index')); @@ -637,23 +639,25 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue }; const createHandleMouseLeave = - (otherHandlers: Record>) => - (event: React.MouseEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent) => { otherHandlers.onMouseLeave?.(event); setOpen(-1); }; - const getThumbProps = ( - otherHandlers: TOther = {} as TOther, - ): UseSliderThumbSlotProps => { + const getThumbProps = = {}>( + externalProps: ExternalProps = {} as ExternalProps, + ): UseSliderThumbSlotProps => { + const externalHandlers = extractEventHandlers(externalProps); + const ownEventHandlers = { - onMouseOver: createHandleMouseOver(otherHandlers || {}), - onMouseLeave: createHandleMouseLeave(otherHandlers || {}), + onMouseOver: createHandleMouseOver(externalHandlers || {}), + onMouseLeave: createHandleMouseLeave(externalHandlers || {}), }; return { - ...otherHandlers, + ...externalProps, + ...externalHandlers, ...ownEventHandlers, }; }; @@ -665,17 +669,19 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue }; }; - const getHiddenInputProps = ( - otherHandlers: TOther = {} as TOther, - ): UseSliderHiddenInputProps => { + const getHiddenInputProps = = {}>( + externalProps: ExternalProps = {} as ExternalProps, + ): UseSliderHiddenInputProps => { + const externalHandlers = extractEventHandlers(externalProps); + const ownEventHandlers = { - onChange: createHandleHiddenInputChange(otherHandlers || {}), - onFocus: createHandleHiddenInputFocus(otherHandlers || {}), - onBlur: createHandleHiddenInputBlur(otherHandlers || {}), + onChange: createHandleHiddenInputChange(externalHandlers || {}), + onFocus: createHandleHiddenInputFocus(externalHandlers || {}), + onBlur: createHandleHiddenInputBlur(externalHandlers || {}), }; const mergedEventHandlers = { - ...otherHandlers, + ...externalHandlers, ...ownEventHandlers, }; @@ -691,6 +697,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue max: parameters.max, step: parameters.step === null && parameters.marks ? 'any' : parameters.step ?? undefined, disabled, + ...externalProps, ...mergedEventHandlers, style: { ...visuallyHidden, diff --git a/packages/mui-base/src/useSlider/useSlider.types.ts b/packages/mui-base/src/useSlider/useSlider.types.ts index 95eaa44b22d11f..0319f6384b9c8a 100644 --- a/packages/mui-base/src/useSlider/useSlider.types.ts +++ b/packages/mui-base/src/useSlider/useSlider.types.ts @@ -1,5 +1,4 @@ import * as React from 'react'; -import { EventHandlers } from '../utils'; export interface UseSliderParameters { /** @@ -113,7 +112,10 @@ export type UseSliderRootSlotOwnProps = { ref: React.RefCallback | null; }; -export type UseSliderRootSlotProps = Omit & +export type UseSliderRootSlotProps = Omit< + ExternalProps, + keyof UseSliderRootSlotOwnProps +> & UseSliderRootSlotOwnProps; export type UseSliderThumbSlotOwnProps = { @@ -121,7 +123,10 @@ export type UseSliderThumbSlotOwnProps = { onMouseOver: React.MouseEventHandler; }; -export type UseSliderThumbSlotProps = Omit & +export type UseSliderThumbSlotProps = Omit< + ExternalProps, + keyof UseSliderThumbSlotOwnProps +> & UseSliderThumbSlotOwnProps; export type UseSliderHiddenInputOwnProps = { @@ -140,8 +145,8 @@ export type UseSliderHiddenInputOwnProps = { type?: React.InputHTMLAttributes['type']; }; -export type UseSliderHiddenInputProps = Omit< - TOther, +export type UseSliderHiddenInputProps = Omit< + ExternalProps, keyof UseSliderHiddenInputOwnProps > & UseSliderHiddenInputOwnProps; @@ -190,28 +195,28 @@ export interface UseSliderReturnValue { focusedThumbIndex: number; /** * Resolver for the hidden input slot's props. - * @param otherHandlers props for the hidden input slot + * @param externalProps props for the hidden input slot * @returns props that should be spread on the hidden input slot */ - getHiddenInputProps: ( - otherHandlers?: TOther, - ) => UseSliderHiddenInputProps; + getHiddenInputProps: = {}>( + externalProps?: ExternalProps, + ) => UseSliderHiddenInputProps; /** * Resolver for the root slot's props. - * @param otherHandlers props for the root slot + * @param externalProps props for the root slot * @returns props that should be spread on the root slot */ - getRootProps: ( - otherHandlers?: TOther, - ) => UseSliderRootSlotProps; + getRootProps: = {}>( + externalProps?: ExternalProps, + ) => UseSliderRootSlotProps; /** * Resolver for the thumb slot's props. - * @param otherHandlers props for the thumb slot + * @param externalProps props for the thumb slot * @returns props that should be spread on the thumb slot */ - getThumbProps: ( - otherHandlers?: TOther, - ) => UseSliderThumbSlotProps; + getThumbProps: = {}>( + externalProps?: ExternalProps, + ) => UseSliderThumbSlotProps; /** * Resolver for the thumb slot's style prop. * @param index of the currently moved thumb