Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[base-ui][useSlider] Align externalProps handling #38854

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/pages/base-ui/api/use-slider.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
90 changes: 90 additions & 0 deletions packages/mui-base/src/useSlider/useSlider.test.js
Original file line number Diff line number Diff line change
@@ -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 (
<div {...getRootProps({ 'data-testid': 'test-slider-root', onClick: handleClick })} />
);
}

render(<Test />);

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 (
<div {...getRootProps()}>
<div {...getThumbProps()}>
<input
value={1}
{...getHiddenInputProps({ 'data-testid': 'test-input', ...props.slotProps.input })}
/>
</div>
</div>
);
}

it('forwards external props including event handlers', () => {
const handleClick = spy();
render(
<Test
slotProps={{
input: {
onClick: handleClick,
},
}}
/>,
);

const input = screen.getByTestId('test-input');
expect(input).not.to.equal(null);

fireEvent.click(input);
expect(handleClick.callCount).to.equal(1);
});
});
});
63 changes: 35 additions & 28 deletions packages/mui-base/src/useSlider/useSlider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -282,7 +282,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
const handleRef = useForkRef(ref, handleFocusRef);

const createHandleHiddenInputFocus =
(otherHandlers: Record<string, React.EventHandler<any>>) => (event: React.FocusEvent) => {
(otherHandlers: EventHandlers) => (event: React.FocusEvent) => {
const index = Number(event.currentTarget.getAttribute('data-index'));
handleFocusVisible(event);
if (isFocusVisibleRef.current === true) {
Expand All @@ -292,7 +292,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
otherHandlers?.onFocus?.(event);
};
const createHandleHiddenInputBlur =
(otherHandlers: Record<string, React.EventHandler<any>>) => (event: React.FocusEvent) => {
(otherHandlers: EventHandlers) => (event: React.FocusEvent) => {
handleBlurVisible(event);
if (isFocusVisibleRef.current === false) {
setFocusedThumbIndex(-1);
Expand All @@ -319,7 +319,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
}

const createHandleHiddenInputChange =
(otherHandlers: Record<string, React.EventHandler<any>>) => (event: React.ChangeEvent) => {
(otherHandlers: EventHandlers) => (event: React.ChangeEvent) => {
otherHandlers.onChange?.(event);

const index = Number(event.currentTarget.getAttribute('data-index'));
Expand Down Expand Up @@ -571,8 +571,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
}, [disabled, stopListening]);

const createHandleMouseDown =
(otherHandlers: Record<string, React.EventHandler<any>>) =>
(event: React.MouseEvent<HTMLSpanElement>) => {
(otherHandlers: EventHandlers) => (event: React.MouseEvent<HTMLSpanElement>) => {
otherHandlers.onMouseDown?.(event);
if (disabled) {
return;
Expand Down Expand Up @@ -610,50 +609,55 @@ 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 = <TOther extends EventHandlers = {}>(
otherHandlers: TOther = {} as TOther,
): UseSliderRootSlotProps<TOther> => {
const getRootProps = <ExternalProps extends Record<string, unknown> = {}>(
externalProps: ExternalProps = {} as ExternalProps,
): UseSliderRootSlotProps<ExternalProps> => {
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<string, React.EventHandler<any>>) =>
(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
(otherHandlers: EventHandlers) => (event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
otherHandlers.onMouseOver?.(event);

const index = Number(event.currentTarget.getAttribute('data-index'));
setOpen(index);
};

const createHandleMouseLeave =
(otherHandlers: Record<string, React.EventHandler<any>>) =>
(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
(otherHandlers: EventHandlers) => (event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
otherHandlers.onMouseLeave?.(event);

setOpen(-1);
};

const getThumbProps = <TOther extends EventHandlers = {}>(
otherHandlers: TOther = {} as TOther,
): UseSliderThumbSlotProps<TOther> => {
const getThumbProps = <ExternalProps extends Record<string, unknown> = {}>(
externalProps: ExternalProps = {} as ExternalProps,
): UseSliderThumbSlotProps<ExternalProps> => {
const externalHandlers = extractEventHandlers(externalProps);

const ownEventHandlers = {
onMouseOver: createHandleMouseOver(otherHandlers || {}),
onMouseLeave: createHandleMouseLeave(otherHandlers || {}),
onMouseOver: createHandleMouseOver(externalHandlers || {}),
onMouseLeave: createHandleMouseLeave(externalHandlers || {}),
};

return {
...otherHandlers,
...externalProps,
...externalHandlers,
...ownEventHandlers,
};
};
Expand All @@ -665,17 +669,19 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
};
};

const getHiddenInputProps = <TOther extends EventHandlers = {}>(
otherHandlers: TOther = {} as TOther,
): UseSliderHiddenInputProps<TOther> => {
const getHiddenInputProps = <ExternalProps extends Record<string, unknown> = {}>(
externalProps: ExternalProps = {} as ExternalProps,
): UseSliderHiddenInputProps<ExternalProps> => {
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,
};

Expand All @@ -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,
Expand Down
39 changes: 22 additions & 17 deletions packages/mui-base/src/useSlider/useSlider.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { EventHandlers } from '../utils';

export interface UseSliderParameters {
/**
Expand Down Expand Up @@ -113,15 +112,21 @@ export type UseSliderRootSlotOwnProps = {
ref: React.RefCallback<Element> | null;
};

export type UseSliderRootSlotProps<TOther = {}> = Omit<TOther, keyof UseSliderRootSlotOwnProps> &
export type UseSliderRootSlotProps<ExternalProps = {}> = Omit<
ExternalProps,
keyof UseSliderRootSlotOwnProps
> &
UseSliderRootSlotOwnProps;

export type UseSliderThumbSlotOwnProps = {
onMouseLeave: React.MouseEventHandler;
onMouseOver: React.MouseEventHandler;
};

export type UseSliderThumbSlotProps<TOther = {}> = Omit<TOther, keyof UseSliderThumbSlotOwnProps> &
export type UseSliderThumbSlotProps<ExternalProps = {}> = Omit<
ExternalProps,
keyof UseSliderThumbSlotOwnProps
> &
UseSliderThumbSlotOwnProps;

export type UseSliderHiddenInputOwnProps = {
Expand All @@ -140,8 +145,8 @@ export type UseSliderHiddenInputOwnProps = {
type?: React.InputHTMLAttributes<HTMLInputElement>['type'];
};

export type UseSliderHiddenInputProps<TOther = {}> = Omit<
TOther,
export type UseSliderHiddenInputProps<ExternalProps = {}> = Omit<
ExternalProps,
keyof UseSliderHiddenInputOwnProps
> &
UseSliderHiddenInputOwnProps;
Expand Down Expand Up @@ -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: <TOther extends EventHandlers = {}>(
otherHandlers?: TOther,
) => UseSliderHiddenInputProps<TOther>;
getHiddenInputProps: <ExternalProps extends Record<string, unknown> = {}>(
externalProps?: ExternalProps,
) => UseSliderHiddenInputProps<ExternalProps>;
/**
* 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: <TOther extends EventHandlers = {}>(
otherHandlers?: TOther,
) => UseSliderRootSlotProps<TOther>;
getRootProps: <ExternalProps extends Record<string, unknown> = {}>(
externalProps?: ExternalProps,
) => UseSliderRootSlotProps<ExternalProps>;
/**
* 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: <TOther extends EventHandlers = {}>(
otherHandlers?: TOther,
) => UseSliderThumbSlotProps<TOther>;
getThumbProps: <ExternalProps extends Record<string, unknown> = {}>(
externalProps?: ExternalProps,
) => UseSliderThumbSlotProps<ExternalProps>;
/**
* Resolver for the thumb slot's style prop.
* @param index of the currently moved thumb
Expand Down