{
* @default 'primary'
*/
color?: 'primary' | 'secondary';
+ /**
+ * The components used for each slot inside the Slider.
+ * Either a string to use a HTML element or a component.
+ */
+ components?: {
+ Root?: React.ElementType;
+ Track?: React.ElementType;
+ Rail?: React.ElementType;
+ Thumb?: React.ElementType;
+ Mark?: React.ElementType;
+ MarkLabel?: React.ElementType;
+ ValueLabel?: React.ElementType;
+ };
+ /**
+ * The props used for each slot inside the Slider.
+ */
+ componentsProps?: {
+ root?: {
+ state?: Omit['props'], 'components' | 'componentsProps'>;
+ as: React.ElementType;
+ };
+ track?: {
+ state?: Omit['props'], 'components' | 'componentsProps'>;
+ as?: React.ElementType;
+ };
+ rail?: {
+ state?: Omit['props'], 'components' | 'componentsProps'>;
+ as?: React.ElementType;
+ };
+ thumb?: {
+ state?: Omit['props'], 'components' | 'componentsProps'> & {
+ active?: boolean;
+ focusVisible?: boolean;
+ };
+ as?: React.ElementType;
+ };
+ mark?: {
+ state?: Omit['props'], 'components' | 'componentsProps'> & {
+ markActive?: boolean;
+ };
+ as?: React.ElementType;
+ };
+ markLabel?: {
+ state?: Omit['props'], 'components' | 'componentsProps'> & {
+ markLabelActive?: boolean;
+ };
+ as?: React.ElementType;
+ };
+ valueLabel?: {
+ state?: Omit['props'], 'components' | 'componentsProps'> & {
+ index?: number;
+ open?: boolean;
+ };
+ as?: React.ElementType;
+ };
+ };
/**
* The default element value. Use when the component is not controlled.
*/
@@ -101,6 +157,10 @@ export interface SliderTypeMap
{
* @returns {string}
*/
getAriaValueText?: (value: number, index: number) => string;
+ /**
+ * Indicates whether the theme context has rtl direction. It is set automatically.
+ */
+ isRtl?: boolean;
/**
* Marks indicate predetermined values to which the user can move the slider.
* If `true` the marks will be spaced according the value of the `step` prop.
@@ -203,6 +263,7 @@ export interface SliderTypeMap
{
};
defaultComponent: D;
}
+
/**
*
* Demos:
@@ -222,4 +283,20 @@ export type SliderProps<
P = {}
> = OverrideProps, D>;
+type SliderRootProps = NonNullable['root'];
+type SliderMarkProps = NonNullable['mark'];
+type SliderMarkLabelProps = NonNullable['markLabel'];
+type SliderRailProps = NonNullable['rail'];
+type SliderTrackProps = NonNullable['track'];
+type SliderThumbProps = NonNullable['thumb'];
+type SliderValueLabel = NonNullable['valueLabel'];
+
+export const SliderRoot: React.FC;
+export const SliderMark: React.FC;
+export const SliderMarkLabel: React.FC;
+export const SliderRail: React.FC;
+export const SliderTrack: React.FC;
+export const SliderThumb: React.FC;
+export const SliderValueLabel: React.FC;
+
export default Slider;
diff --git a/packages/material-ui/src/Slider/Slider.js b/packages/material-ui/src/Slider/Slider.js
index 2831fcb1282846..7087170000936b 100644
--- a/packages/material-ui/src/Slider/Slider.js
+++ b/packages/material-ui/src/Slider/Slider.js
@@ -1,153 +1,81 @@
import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { chainPropTypes } from '@material-ui/utils';
-import withStyles from '../styles/withStyles';
-import useTheme from '../styles/useTheme';
+import { propsToClassKey } from '@material-ui/styles';
+import useThemeProps from '../styles/useThemeProps';
import { fade, lighten, darken } from '../styles/colorManipulator';
-import useIsFocusVisible from '../utils/useIsFocusVisible';
-import useEnhancedEffect from '../utils/useEnhancedEffect';
-import ownerDocument from '../utils/ownerDocument';
-import useEventCallback from '../utils/useEventCallback';
-import useForkRef from '../utils/useForkRef';
import capitalize from '../utils/capitalize';
-import useControlled from '../utils/useControlled';
-import ValueLabel from './ValueLabel';
+import SliderBase from './SliderBase';
+import muiStyled from '../styles/muiStyled';
-function asc(a, b) {
- return a - b;
-}
-
-function clamp(value, min, max) {
- return Math.min(Math.max(min, value), max);
-}
-
-function findClosest(values, currentValue) {
- const { index: closestIndex } = values.reduce((acc, value, index) => {
- const distance = Math.abs(currentValue - value);
-
- if (acc === null || distance < acc.distance || distance === acc.distance) {
- return {
- distance,
- index,
- };
- }
-
- return acc;
- }, null);
- return closestIndex;
-}
-
-function trackFinger(event, touchId) {
- if (touchId.current !== undefined && event.changedTouches) {
- for (let i = 0; i < event.changedTouches.length; i += 1) {
- const touch = event.changedTouches[i];
- if (touch.identifier === touchId.current) {
- return {
- x: touch.clientX,
- y: touch.clientY,
- };
- }
- }
+const overridesResolver = (props, styles, name) => {
+ const {
+ color = 'primary',
+ marks: marksProp = false,
+ max = 100,
+ min = 0,
+ orientation = 'horizontal',
+ step = 1,
+ track = 'normal',
+ } = props;
- return false;
- }
+ const marks =
+ marksProp === true && step !== null
+ ? [...Array(Math.floor((max - min) / step) + 1)].map((_, index) => ({
+ value: min + step * index,
+ }))
+ : marksProp || [];
- return {
- x: event.clientX,
- y: event.clientY,
+ const marked = marks.length > 0 && marks.some((mark) => mark.label);
+
+ const styleOverrides = {
+ ...styles.root,
+ ...styles[`color${capitalize(color)}`],
+ '&.Mui-disabled': styles.disabled,
+ ...(marked && styles.marked),
+ ...(orientation === 'vertical' && styles.vertical),
+ ...(track === 'inverted' && styles.trackInverted),
+ ...(track === false && styles.trackFalse),
+ [`.& ${name}-rail`]: styles.rail,
+ [`.& ${name}-track`]: styles.track,
+ [`.& ${name}-mark`]: styles.mark,
+ [`.& ${name}-markLabel`]: styles.markLabel,
+ [`.& ${name}-valueLabel`]: styles.valueLabel,
+ [`.& ${name}-thumb`]: {
+ ...styles.thumb,
+ ...styles[`thumbColor${capitalize(color)}`],
+ '&.Mui-disabled': styles.disabled,
+ },
};
-}
-
-function valueToPercent(value, min, max) {
- return ((value - min) * 100) / (max - min);
-}
-function percentToValue(percent, min, max) {
- return (max - min) * percent + min;
-}
-
-function getDecimalPrecision(num) {
- // This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
- // When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
- if (Math.abs(num) < 1) {
- const parts = num.toExponential().split('e-');
- const matissaDecimalPart = parts[0].split('.')[1];
- return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);
- }
-
- const decimalPart = num.toString().split('.')[1];
- return decimalPart ? decimalPart.length : 0;
-}
-
-function roundValueToStep(value, step, min) {
- const nearest = Math.round((value - min) / step) * step + min;
- return Number(nearest.toFixed(getDecimalPrecision(step)));
-}
-
-function setValueIndex({ values, source, newValue, index }) {
- // Performance shortcut
- if (source[index] === newValue) {
- return source;
- }
-
- const output = values.slice();
- output[index] = newValue;
- return output;
-}
-
-function focusThumb({ sliderRef, activeIndex, setActive }) {
- const doc = ownerDocument(sliderRef.current);
- if (
- !sliderRef.current.contains(doc.activeElement) ||
- Number(doc.activeElement.getAttribute('data-index')) !== activeIndex
- ) {
- sliderRef.current.querySelector(`[role="slider"][data-index="${activeIndex}"]`).focus();
- }
+ return styleOverrides;
+};
- if (setActive) {
- setActive(activeIndex);
+const variantsResolver = (props, styles, theme, name) => {
+ const { state = {} } = props;
+ let variantsStyles = {};
+ if (theme && theme.components && theme.components[name] && theme.components[name].variants) {
+ const themeVariants = theme.components[name].variants;
+ themeVariants.forEach((themeVariant) => {
+ let isMatch = true;
+ Object.keys(themeVariant.props).forEach((key) => {
+ if (state[key] !== themeVariant.props[key]) {
+ isMatch = false;
+ }
+ });
+ if (isMatch) {
+ variantsStyles = { ...variantsStyles, ...styles[propsToClassKey(themeVariant.props)] };
+ }
+ });
}
-}
-const axisProps = {
- horizontal: {
- offset: (percent) => ({ left: `${percent}%` }),
- leap: (percent) => ({ width: `${percent}%` }),
- },
- 'horizontal-reverse': {
- offset: (percent) => ({ right: `${percent}%` }),
- leap: (percent) => ({ width: `${percent}%` }),
- },
- vertical: {
- offset: (percent) => ({ bottom: `${percent}%` }),
- leap: (percent) => ({ height: `${percent}%` }),
- },
+ return variantsStyles;
};
-const Identity = (x) => x;
-
-// TODO: remove support for Safari < 13.
-// https://caniuse.com/#search=touch-action
-//
-// Safari, on iOS, supports touch action since v13.
-// Over 80% of the iOS phones are compatible
-// in August 2020.
-let cachedSupportsTouchActionNone;
-function doesSupportTouchActionNone() {
- if (cachedSupportsTouchActionNone === undefined) {
- const element = document.createElement('div');
- element.style.touchAction = 'none';
- document.body.appendChild(element);
- cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none';
- element.parentElement.removeChild(element);
- }
- return cachedSupportsTouchActionNone;
-}
-
-export const styles = (theme) => ({
- /* Styles applied to the root element. */
- root: {
+export const SliderRoot = muiStyled(
+ 'div',
+ {},
+ { muiName: 'MuiSlider', overridesResolver, variantsResolver },
+)((props) => {
+ return {
height: 2,
width: '100%',
boxSizing: 'content-box',
@@ -155,901 +83,218 @@ export const styles = (theme) => ({
display: 'inline-block',
position: 'relative',
cursor: 'pointer',
- // Disable scroll capabilities.
touchAction: 'none',
- color: theme.palette.primary.main,
+ color: props.theme.palette.primary.main,
WebkitTapHighlightColor: 'transparent',
- '&$disabled': {
+ ...(props.state.color === 'secondary' && {
+ color: props.theme.palette.secondary.main,
+ }),
+ '&.Mui-disabled': {
pointerEvents: 'none',
cursor: 'default',
- color: theme.palette.grey[400],
+ color: props.theme.palette.grey[400],
},
- '&$vertical': {
+ ...(props.state.orientation === 'vertical' && {
width: 2,
height: '100%',
padding: '0 13px',
- },
+ }),
// The primary input mechanism of the device includes a pointing device of limited accuracy.
'@media (pointer: coarse)': {
// Reach 42px touch target, about ~8mm on screen.
padding: '20px 0',
- '&$vertical': {
+ ...(props.state.orientation === 'vertical' && {
padding: '0 20px',
- },
+ }),
},
'@media print': {
colorAdjust: 'exact',
},
- },
- /* Styles applied to the root element if `color="primary"`. */
- colorPrimary: {
- // TODO v5: move the style here
- },
- /* Styles applied to the root element if `color="secondary"`. */
- colorSecondary: {
- color: theme.palette.secondary.main,
- },
- /* Styles applied to the root element if `marks` is provided with at least one label. */
- marked: {
- marginBottom: 20,
- '&$vertical': {
- marginBottom: 'auto',
- marginRight: 20,
- },
- },
- /* Pseudo-class applied to the root element if `orientation="vertical"`. */
- vertical: {},
- /* Pseudo-class applied to the root and thumb element if `disabled={true}`. */
- disabled: {},
- /* Styles applied to the rail element. */
- rail: {
- display: 'block',
- position: 'absolute',
- width: '100%',
- height: 2,
- borderRadius: 1,
- backgroundColor: 'currentColor',
- opacity: 0.38,
- '$vertical &': {
- height: '100%',
- width: 2,
- },
- },
- /* Styles applied to the track element. */
- track: {
- display: 'block',
- position: 'absolute',
- height: 2,
- borderRadius: 1,
- backgroundColor: 'currentColor',
- '$vertical &': {
- width: 2,
- },
- },
- /* Styles applied to the track element if `track={false}`. */
- trackFalse: {
- '& $track': {
- display: 'none',
- },
- },
- /* Styles applied to the track element if `track="inverted"`. */
- trackInverted: {
- '& $track': {
- backgroundColor:
- // Same logic as the LinearProgress track color
- theme.palette.type === 'light'
- ? lighten(theme.palette.primary.main, 0.62)
- : darken(theme.palette.primary.main, 0.5),
+ ...(props.state.marked && {
+ marginBottom: 20,
+ ...(props.state.orientation === 'vertical' && {
+ marginBottom: 'auto',
+ marginRight: 20,
+ }),
+ }),
+
+ '& .MuiSlider-rail': {
+ display: 'block',
+ position: 'absolute',
+ width: '100%',
+ height: 2,
+ borderRadius: 1,
+ backgroundColor: 'currentColor',
+ opacity: 0.38,
+ ...(props.state.orientation === 'vertical' && {
+ height: '100%',
+ width: 2,
+ }),
+ ...(props.state.track === 'inverted' && {
+ opacity: 1,
+ }),
},
- '& $rail': {
- opacity: 1,
+
+ '& .MuiSlider-track': {
+ display: 'block',
+ position: 'absolute',
+ height: 2,
+ borderRadius: 1,
+ backgroundColor: 'currentColor',
+ ...(props.state.orientation === 'vertical' && {
+ width: 2,
+ }),
+ ...(props.state.track === false && {
+ display: 'none',
+ }),
+ ...(props.state.track === 'inverted' && {
+ backgroundColor:
+ // Same logic as the LinearProgress track color
+ props.theme.palette.type === 'light'
+ ? lighten(props.theme.palette.primary.main, 0.62)
+ : darken(props.theme.palette.primary.main, 0.5),
+ }),
},
- },
- /* Styles applied to the thumb element. */
- thumb: {
- position: 'absolute',
- width: 12,
- height: 12,
- marginLeft: -6,
- marginTop: -5,
- boxSizing: 'border-box',
- borderRadius: '50%',
- outline: 0,
- backgroundColor: 'currentColor',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- transition: theme.transitions.create(['box-shadow'], {
- duration: theme.transitions.duration.shortest,
- }),
- '&::after': {
+
+ '& .MuiSlider-thumb': {
position: 'absolute',
- content: '""',
+ width: 12,
+ height: 12,
+ marginLeft: -6,
+ marginTop: -5,
+ boxSizing: 'border-box',
borderRadius: '50%',
- // reach 42px hit target (2 * 15 + thumb diameter)
- left: -15,
- top: -15,
- right: -15,
- bottom: -15,
- },
- '&$focusVisible,&:hover': {
- boxShadow: `0px 0px 0px 8px ${fade(theme.palette.primary.main, 0.16)}`,
- '@media (hover: none)': {
- boxShadow: 'none',
+ outline: 0,
+ backgroundColor: 'currentColor',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ transition: props.theme.transitions.create(['box-shadow'], {
+ duration: props.theme.transitions.duration.shortest,
+ }),
+ '::after': {
+ position: 'absolute',
+ content: '""',
+ borderRadius: '50%',
+ // reach 42px hit target (2 * 15 + thumb diameter)
+ left: -15,
+ top: -15,
+ right: -15,
+ bottom: -15,
},
- },
- '&$active': {
- boxShadow: `0px 0px 0px 14px ${fade(theme.palette.primary.main, 0.16)}`,
- },
- '&$disabled': {
- width: 8,
- height: 8,
- marginLeft: -4,
- marginTop: -3,
- '&:hover': {
- boxShadow: 'none',
+ ':hover': {
+ boxShadow: `0px 0px 0px 8px ${fade(props.theme.palette.primary.main, 0.16)}`,
+ '@media (hover: none)': {
+ boxShadow: 'none',
+ },
},
- },
- '$vertical &': {
- marginLeft: -5,
- marginBottom: -6,
- },
- '$vertical &$disabled': {
- marginLeft: -3,
- marginBottom: -4,
- },
- },
- /* Styles applied to the thumb element if `color="primary"`. */
- thumbColorPrimary: {
- // TODO v5: move the style here
- },
- /* Styles applied to the thumb element if `color="secondary"`. */
- thumbColorSecondary: {
- '&$focusVisible,&:hover': {
- boxShadow: `0px 0px 0px 8px ${fade(theme.palette.secondary.main, 0.16)}`,
- },
- '&$active': {
- boxShadow: `0px 0px 0px 14px ${fade(theme.palette.secondary.main, 0.16)}`,
- },
- },
- /* Pseudo-class applied to the thumb element if it's active. */
- active: {},
- /* Pseudo-class applied to the thumb element if keyboard focused. */
- focusVisible: {},
- /* Styles applied to the thumb label element. */
- valueLabel: {
- // IE 11 centering bug, to remove from the customization demos once no longer supported
- left: 'calc(-50% - 4px)',
- },
- /* Styles applied to the mark element. */
- mark: {
- position: 'absolute',
- width: 2,
- height: 2,
- borderRadius: 1,
- backgroundColor: 'currentColor',
- },
- /* Styles applied to the mark element if active (depending on the value). */
- markActive: {
- backgroundColor: theme.palette.background.paper,
- opacity: 0.8,
- },
- /* Styles applied to the mark label element. */
- markLabel: {
- ...theme.typography.body2,
- color: theme.palette.text.secondary,
- position: 'absolute',
- top: 26,
- transform: 'translateX(-50%)',
- whiteSpace: 'nowrap',
- '$vertical &': {
- top: 'auto',
- left: 26,
- transform: 'translateY(50%)',
- },
- '@media (pointer: coarse)': {
- top: 40,
- '$vertical &': {
- left: 31,
+ '&.Mui-focusVisible': {
+ boxShadow: `0px 0px 0px 8px ${fade(props.theme.palette.primary.main, 0.16)}`,
+ '@media (hover: none)': {
+ boxShadow: 'none',
+ },
+ },
+ '&.Mui-active': {
+ boxShadow: `0px 0px 0px 14px ${fade(props.theme.palette.primary.main, 0.16)}`,
+ },
+ '&.Mui-disabled': {
+ width: 8,
+ height: 8,
+ marginLeft: -4,
+ marginTop: -3,
+ ':hover': {
+ boxShadow: 'none',
+ },
},
+ ...(props.state.orientation === 'vertical' && {
+ marginLeft: -5,
+ marginBottom: -6,
+ }),
+ ...(props.state.orientation === 'vertical' && {
+ '&.Mui-disabled': {
+ marginLeft: -3,
+ marginBottom: -4,
+ },
+ }),
+ ...(props.state.color === 'secondary' && {
+ ':hover': {
+ boxShadow: `0px 0px 0px 8px ${fade(props.theme.palette.secondary.main, 0.16)}`,
+ },
+ '&.Mui-focusVisible': {
+ boxShadow: `0px 0px 0px 8px ${fade(props.theme.palette.secondary.main, 0.16)}`,
+ },
+ '&.Mui-active': {
+ boxShadow: `0px 0px 0px 14px ${fade(props.theme.palette.secondary.main, 0.16)}`,
+ },
+ }),
},
- },
- /* Styles applied to the mark label element if active (depending on the value). */
- markLabelActive: {
- color: theme.palette.text.primary,
- },
-});
-const Slider = React.forwardRef(function Slider(props, ref) {
- const {
- 'aria-label': ariaLabel,
- 'aria-labelledby': ariaLabelledby,
- 'aria-valuetext': ariaValuetext,
- classes,
- className,
- color = 'primary',
- component: Component = 'span',
- defaultValue,
- disabled = false,
- getAriaLabel,
- getAriaValueText,
- marks: marksProp = false,
- max = 100,
- min = 0,
- name,
- onChange,
- onChangeCommitted,
- onMouseDown,
- orientation = 'horizontal',
- scale = Identity,
- step = 1,
- ThumbComponent = 'span',
- track = 'normal',
- value: valueProp,
- ValueLabelComponent = ValueLabel,
- valueLabelDisplay = 'off',
- valueLabelFormat = Identity,
- ...other
- } = props;
- const theme = useTheme();
- const touchId = React.useRef();
- // We can't use the :active browser pseudo-classes.
- // - The active state isn't triggered when clicking on the rail.
- // - The active state isn't transfered when inversing a range slider.
- const [active, setActive] = React.useState(-1);
- const [open, setOpen] = React.useState(-1);
-
- const [valueDerived, setValueState] = useControlled({
- controlled: valueProp,
- default: defaultValue,
- name: 'Slider',
- });
-
- const range = Array.isArray(valueDerived);
- let values = range ? valueDerived.slice().sort(asc) : [valueDerived];
- values = values.map((value) => clamp(value, min, max));
- const marks =
- marksProp === true && step !== null
- ? [...Array(Math.floor((max - min) / step) + 1)].map((_, index) => ({
- value: min + step * index,
- }))
- : marksProp || [];
-
- const {
- isFocusVisibleRef,
- onBlur: handleBlurVisible,
- onFocus: handleFocusVisible,
- ref: focusVisibleRef,
- } = useIsFocusVisible();
- const [focusVisible, setFocusVisible] = React.useState(-1);
-
- const sliderRef = React.useRef();
- const handleFocusRef = useForkRef(focusVisibleRef, sliderRef);
- const handleRef = useForkRef(ref, handleFocusRef);
-
- const handleFocus = useEventCallback((event) => {
- const index = Number(event.currentTarget.getAttribute('data-index'));
- handleFocusVisible(event);
- if (isFocusVisibleRef.current === true) {
- setFocusVisible(index);
- }
- setOpen(index);
- });
- const handleBlur = useEventCallback((event) => {
- handleBlurVisible(event);
- if (isFocusVisibleRef.current === false) {
- setFocusVisible(-1);
- }
- setOpen(-1);
- });
- const handleMouseOver = useEventCallback((event) => {
- const index = Number(event.currentTarget.getAttribute('data-index'));
- setOpen(index);
- });
- const handleMouseLeave = useEventCallback(() => {
- setOpen(-1);
- });
-
- useEnhancedEffect(() => {
- if (disabled && sliderRef.current.contains(document.activeElement)) {
- // This is necessary because Firefox and Safari will keep focus
- // on a disabled element:
- // https://codesandbox.io/s/mui-pr-22247-forked-h151h?file=/src/App.js
- document.activeElement.blur();
- }
- }, [disabled]);
-
- if (disabled && active !== -1) {
- setActive(-1);
- }
- if (disabled && focusVisible !== -1) {
- setFocusVisible(-1);
- }
-
- const isRtl = theme.direction === 'rtl';
-
- const handleKeyDown = useEventCallback((event) => {
- const index = Number(event.currentTarget.getAttribute('data-index'));
- const value = values[index];
- const tenPercents = (max - min) / 10;
- const marksValues = marks.map((mark) => mark.value);
- const marksIndex = marksValues.indexOf(value);
- let newValue;
- const increaseKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
- const decreaseKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
-
- switch (event.key) {
- case 'Home':
- newValue = min;
- break;
- case 'End':
- newValue = max;
- break;
- case 'PageUp':
- if (step) {
- newValue = value + tenPercents;
- }
- break;
- case 'PageDown':
- if (step) {
- newValue = value - tenPercents;
- }
- break;
- case increaseKey:
- case 'ArrowUp':
- if (step) {
- newValue = value + step;
- } else {
- newValue = marksValues[marksIndex + 1] || marksValues[marksValues.length - 1];
- }
- break;
- case decreaseKey:
- case 'ArrowDown':
- if (step) {
- newValue = value - step;
- } else {
- newValue = marksValues[marksIndex - 1] || marksValues[0];
- }
- break;
- default:
- return;
- }
-
- // Prevent scroll of the page
- event.preventDefault();
-
- if (step) {
- newValue = roundValueToStep(newValue, step, min);
- }
-
- newValue = clamp(newValue, min, max);
-
- if (range) {
- const previousValue = newValue;
- newValue = setValueIndex({
- values,
- source: valueDerived,
- newValue,
- index,
- }).sort(asc);
- focusThumb({ sliderRef, activeIndex: newValue.indexOf(previousValue) });
- }
-
- setValueState(newValue);
- setFocusVisible(index);
-
- if (onChange) {
- onChange(event, newValue);
- }
- if (onChangeCommitted) {
- onChangeCommitted(event, newValue);
- }
- });
-
- const previousIndex = React.useRef();
- let axis = orientation;
- if (isRtl && orientation === 'horizontal') {
- axis += '-reverse';
- }
-
- const getFingerNewValue = ({ finger, move = false, values: values2, source }) => {
- const { current: slider } = sliderRef;
- const { width, height, bottom, left } = slider.getBoundingClientRect();
- let percent;
-
- if (axis.indexOf('vertical') === 0) {
- percent = (bottom - finger.y) / height;
- } else {
- percent = (finger.x - left) / width;
- }
-
- if (axis.indexOf('-reverse') !== -1) {
- percent = 1 - percent;
- }
-
- let newValue;
- newValue = percentToValue(percent, min, max);
- if (step) {
- newValue = roundValueToStep(newValue, step, min);
- } else {
- const marksValues = marks.map((mark) => mark.value);
- const closestIndex = findClosest(marksValues, newValue);
- newValue = marksValues[closestIndex];
- }
-
- newValue = clamp(newValue, min, max);
- let activeIndex = 0;
-
- if (range) {
- if (!move) {
- activeIndex = findClosest(values2, newValue);
- } else {
- activeIndex = previousIndex.current;
- }
+ '& .MuiSlider-valueLabel': {
+ // IE 11 centering bug, to remove from the customization demos once no longer supported
+ left: 'calc(-50% - 4px)',
+ },
- const previousValue = newValue;
- newValue = setValueIndex({
- values: values2,
- source,
- newValue,
- index: activeIndex,
- }).sort(asc);
- activeIndex = newValue.indexOf(previousValue);
- previousIndex.current = activeIndex;
- }
+ '& .MuiSlider-mark': {
+ position: 'absolute',
+ width: 2,
+ height: 2,
+ borderRadius: 1,
+ backgroundColor: 'currentColor',
+ '&.MuiSlider-markActive': {
+ backgroundColor: props.theme.palette.background.paper,
+ opacity: 0.8,
+ },
+ },
- return { newValue, activeIndex };
+ '& .MuiSlider-markLabel': {
+ ...props.theme.typography.body2,
+ color: props.theme.palette.text.secondary,
+ position: 'absolute',
+ top: 26,
+ transform: 'translateX(-50%)',
+ whiteSpace: 'nowrap',
+ ...(props.state.orientation === 'vertical' && {
+ top: 'auto',
+ left: 26,
+ transform: 'translateY(50%)',
+ }),
+ '@media (pointer: coarse)': {
+ top: 40,
+ ...(props.state.orientation === 'vertical' && {
+ left: 31,
+ }),
+ },
+ '&.MuiSlider-markLabelActive': {
+ color: props.theme.palette.text.primary,
+ },
+ },
};
+});
- const handleTouchMove = useEventCallback((nativeEvent) => {
- const finger = trackFinger(nativeEvent, touchId);
-
- if (!finger) {
- return;
- }
-
- // Cancel move in case some other element consumed a mouseup event and it was not fired.
- if (nativeEvent.type === 'mousemove' && nativeEvent.buttons === 0) {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- handleTouchEnd(nativeEvent);
- return;
- }
-
- const { newValue, activeIndex } = getFingerNewValue({
- finger,
- move: true,
- values,
- source: valueDerived,
- });
-
- focusThumb({ sliderRef, activeIndex, setActive });
- setValueState(newValue);
-
- if (onChange) {
- onChange(nativeEvent, newValue);
- }
- });
-
- const handleTouchEnd = useEventCallback((nativeEvent) => {
- const finger = trackFinger(nativeEvent, touchId);
-
- if (!finger) {
- return;
- }
-
- const { newValue } = getFingerNewValue({ finger, values, source: valueDerived });
-
- setActive(-1);
- if (nativeEvent.type === 'touchend') {
- setOpen(-1);
- }
-
- if (onChangeCommitted) {
- onChangeCommitted(nativeEvent, newValue);
- }
-
- touchId.current = undefined;
-
- const doc = ownerDocument(sliderRef.current);
- doc.removeEventListener('mousemove', handleTouchMove);
- doc.removeEventListener('mouseup', handleTouchEnd);
- doc.removeEventListener('touchmove', handleTouchMove);
- doc.removeEventListener('touchend', handleTouchEnd);
- });
-
- const handleTouchStart = useEventCallback((event) => {
- // If touch-action: none; is not supported we need to prevent the scroll manually.
- if (!doesSupportTouchActionNone()) {
- event.preventDefault();
- }
-
- const touch = event.changedTouches[0];
- if (touch != null) {
- // A number that uniquely identifies the current finger in the touch session.
- touchId.current = touch.identifier;
- }
- const finger = trackFinger(event, touchId);
- const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived });
- focusThumb({ sliderRef, activeIndex, setActive });
-
- setValueState(newValue);
-
- if (onChange) {
- onChange(event, newValue);
- }
-
- const doc = ownerDocument(sliderRef.current);
- doc.addEventListener('touchmove', handleTouchMove);
- doc.addEventListener('touchend', handleTouchEnd);
- });
-
- React.useEffect(() => {
- const { current: slider } = sliderRef;
- slider.addEventListener('touchstart', handleTouchStart, {
- passive: doesSupportTouchActionNone(),
- });
-
- const doc = ownerDocument(slider);
-
- return () => {
- slider.removeEventListener('touchstart', handleTouchStart, {
- passive: doesSupportTouchActionNone(),
- });
-
- doc.removeEventListener('mousemove', handleTouchMove);
- doc.removeEventListener('mouseup', handleTouchEnd);
- doc.removeEventListener('touchmove', handleTouchMove);
- doc.removeEventListener('touchend', handleTouchEnd);
- };
- }, [handleTouchEnd, handleTouchMove, handleTouchStart]);
-
- React.useEffect(() => {
- if (disabled) {
- const doc = ownerDocument(sliderRef.current);
- doc.removeEventListener('mousemove', handleTouchMove);
- doc.removeEventListener('mouseup', handleTouchEnd);
- doc.removeEventListener('touchmove', handleTouchMove);
- doc.removeEventListener('touchend', handleTouchEnd);
- }
- }, [disabled, handleTouchEnd, handleTouchMove]);
-
- const handleMouseDown = useEventCallback((event) => {
- if (onMouseDown) {
- onMouseDown(event);
- }
-
- event.preventDefault();
- const finger = trackFinger(event, touchId);
- const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived });
- focusThumb({ sliderRef, activeIndex, setActive });
-
- setValueState(newValue);
-
- if (onChange) {
- onChange(event, newValue);
- }
-
- const doc = ownerDocument(sliderRef.current);
- doc.addEventListener('mousemove', handleTouchMove);
- doc.addEventListener('mouseup', handleTouchEnd);
- });
-
- const trackOffset = valueToPercent(range ? values[0] : min, min, max);
- const trackLeap = valueToPercent(values[values.length - 1], min, max) - trackOffset;
- const trackStyle = {
- ...axisProps[axis].offset(trackOffset),
- ...axisProps[axis].leap(trackLeap),
+const getComponentProps = (components, componentsProps, name) => {
+ const slotProps = componentsProps[name] || {};
+ return {
+ as: components[name],
+ ...slotProps,
};
+};
+const Slider = React.forwardRef(function Slider(inputProps, ref) {
+ const props = useThemeProps({ props: inputProps, name: 'MuiSlider' });
+ const { components = {}, componentsProps = {}, ...other } = props;
return (
- 0 && marks.some((mark) => mark.label),
- [classes.vertical]: orientation === 'vertical',
- [classes.trackInverted]: track === 'inverted',
- [classes.trackFalse]: track === false,
- },
- className,
- )}
- onMouseDown={handleMouseDown}
+
-
-
-
- {marks.map((mark, index) => {
- const percent = valueToPercent(mark.value, min, max);
- const style = axisProps[axis].offset(percent);
-
- let markActive;
- if (track === false) {
- markActive = values.indexOf(mark.value) !== -1;
- } else {
- markActive =
- (track === 'normal' &&
- (range
- ? mark.value >= values[0] && mark.value <= values[values.length - 1]
- : mark.value <= values[0])) ||
- (track === 'inverted' &&
- (range
- ? mark.value <= values[0] || mark.value >= values[values.length - 1]
- : mark.value >= values[0]));
- }
-
- return (
-
-
- {mark.label != null ? (
-
- {mark.label}
-
- ) : null}
-
- );
- })}
- {values.map((value, index) => {
- const percent = valueToPercent(value, min, max);
- const style = axisProps[axis].offset(percent);
-
- return (
-
-
-
- );
- })}
-
+ components={{
+ Root: SliderRoot,
+ ...components,
+ }}
+ componentsProps={{
+ root: getComponentProps(components, componentsProps, 'root'),
+ }}
+ ref={ref}
+ />
);
});
-Slider.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the d.ts file and run "yarn proptypes" |
- // ----------------------------------------------------------------------
- /**
- * The label of the slider.
- */
- 'aria-label': chainPropTypes(PropTypes.string, (props) => {
- const range = Array.isArray(props.value || props.defaultValue);
-
- if (range && props['aria-label'] != null) {
- return new Error(
- 'Material-UI: You need to use the `getAriaLabel` prop instead of `aria-label` when using a range slider.',
- );
- }
-
- return null;
- }),
- /**
- * The id of the element containing a label for the slider.
- */
- 'aria-labelledby': PropTypes.string,
- /**
- * A string value that provides a user-friendly name for the current value of the slider.
- */
- 'aria-valuetext': chainPropTypes(PropTypes.string, (props) => {
- const range = Array.isArray(props.value || props.defaultValue);
-
- if (range && props['aria-valuetext'] != null) {
- return new Error(
- 'Material-UI: You need to use the `getAriaValueText` prop instead of `aria-valuetext` when using a range slider.',
- );
- }
-
- return null;
- }),
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * Override or extend the styles applied to the component.
- */
- classes: PropTypes.object,
- /**
- * @ignore
- */
- className: PropTypes.string,
- /**
- * The color of the component. It supports those theme colors that make sense for this component.
- * @default 'primary'
- */
- color: PropTypes.oneOf(['primary', 'secondary']),
- /**
- * The component used for the root node.
- * Either a string to use a HTML element or a component.
- */
- component: PropTypes.elementType,
- /**
- * The default element value. Use when the component is not controlled.
- */
- defaultValue: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
- /**
- * If `true`, the slider will be disabled.
- * @default false
- */
- disabled: PropTypes.bool,
- /**
- * Accepts a function which returns a string value that provides a user-friendly name for the thumb labels of the slider.
- *
- * @param {number} index The thumb label's index to format.
- * @returns {string}
- */
- getAriaLabel: PropTypes.func,
- /**
- * Accepts a function which returns a string value that provides a user-friendly name for the current value of the slider.
- *
- * @param {number} value The thumb label's value to format.
- * @param {number} index The thumb label's index to format.
- * @returns {string}
- */
- getAriaValueText: PropTypes.func,
- /**
- * Marks indicate predetermined values to which the user can move the slider.
- * If `true` the marks will be spaced according the value of the `step` prop.
- * If an array, it should contain objects with `value` and an optional `label` keys.
- * @default false
- */
- marks: PropTypes.oneOfType([
- PropTypes.arrayOf(
- PropTypes.shape({
- label: PropTypes.node,
- value: PropTypes.number.isRequired,
- }),
- ),
- PropTypes.bool,
- ]),
- /**
- * The maximum allowed value of the slider.
- * Should not be equal to min.
- * @default 100
- */
- max: PropTypes.number,
- /**
- * The minimum allowed value of the slider.
- * Should not be equal to max.
- * @default 0
- */
- min: PropTypes.number,
- /**
- * Name attribute of the hidden `input` element.
- */
- name: PropTypes.string,
- /**
- * Callback function that is fired when the slider's value changed.
- *
- * @param {object} event The event source of the callback. **Warning**: This is a generic event not a change event.
- * @param {number | number[]} value The new value.
- */
- onChange: PropTypes.func,
- /**
- * Callback function that is fired when the `mouseup` is triggered.
- *
- * @param {object} event The event source of the callback. **Warning**: This is a generic event not a change event.
- * @param {number | number[]} value The new value.
- */
- onChangeCommitted: PropTypes.func,
- /**
- * @ignore
- */
- onMouseDown: PropTypes.func,
- /**
- * The slider orientation.
- * @default 'horizontal'
- */
- orientation: PropTypes.oneOf(['horizontal', 'vertical']),
- /**
- * A transformation function, to change the scale of the slider.
- * @default (x) => x
- */
- scale: PropTypes.func,
- /**
- * The granularity with which the slider can step through values. (A "discrete" slider.)
- * The `min` prop serves as the origin for the valid values.
- * We recommend (max - min) to be evenly divisible by the step.
- *
- * When step is `null`, the thumb can only be slid onto marks provided with the `marks` prop.
- * @default 1
- */
- step: PropTypes.number,
- /**
- * The component used to display the value label.
- * @default 'span'
- */
- ThumbComponent: PropTypes.elementType,
- /**
- * The track presentation:
- *
- * - `normal` the track will render a bar representing the slider value.
- * - `inverted` the track will render a bar representing the remaining slider value.
- * - `false` the track will render without a bar.
- * @default 'normal'
- */
- track: PropTypes.oneOf(['inverted', 'normal', false]),
- /**
- * The value of the slider.
- * For ranged sliders, provide an array with two values.
- */
- value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
- /**
- * The value label component.
- * @default ValueLabel
- */
- ValueLabelComponent: PropTypes.elementType,
- /**
- * Controls when the value label is displayed:
- *
- * - `auto` the value label will display when the thumb is hovered or focused.
- * - `on` will display persistently.
- * - `off` will never display.
- * @default 'off'
- */
- valueLabelDisplay: PropTypes.oneOf(['auto', 'off', 'on']),
- /**
- * The format function the value label's value.
- *
- * When a function is provided, it should have the following signature:
- *
- * - {number} value The value label's value to format
- * - {number} index The value label's index to format
- * @default (x) => x
- */
- valueLabelFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
-};
-
-export default withStyles(styles, { name: 'MuiSlider' })(Slider);
+export default Slider;
diff --git a/packages/material-ui/src/Slider/SliderBase.d.ts b/packages/material-ui/src/Slider/SliderBase.d.ts
new file mode 100644
index 00000000000000..e823b9f45ab642
--- /dev/null
+++ b/packages/material-ui/src/Slider/SliderBase.d.ts
@@ -0,0 +1,6 @@
+import { OverridableComponent } from '../OverridableComponent';
+import { SliderTypeMap } from './Slider';
+
+declare const SliderBase: OverridableComponent;
+
+export default SliderBase;
diff --git a/packages/material-ui/src/Slider/SliderBase.js b/packages/material-ui/src/Slider/SliderBase.js
new file mode 100644
index 00000000000000..bd3d9d2ff71db3
--- /dev/null
+++ b/packages/material-ui/src/Slider/SliderBase.js
@@ -0,0 +1,914 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+import { chainPropTypes } from '@material-ui/utils';
+import useIsFocusVisible from '../utils/useIsFocusVisible';
+import useEnhancedEffect from '../utils/useEnhancedEffect';
+import ownerDocument from '../utils/ownerDocument';
+import useEventCallback from '../utils/useEventCallback';
+import useForkRef from '../utils/useForkRef';
+import capitalize from '../utils/capitalize';
+import useControlled from '../utils/useControlled';
+import ValueLabelComponent from './ValueLabel';
+
+function asc(a, b) {
+ return a - b;
+}
+
+function clamp(value, min, max) {
+ return Math.min(Math.max(min, value), max);
+}
+
+function findClosest(values, currentValue) {
+ const { index: closestIndex } = values.reduce((acc, value, index) => {
+ const distance = Math.abs(currentValue - value);
+
+ if (acc === null || distance < acc.distance || distance === acc.distance) {
+ return {
+ distance,
+ index,
+ };
+ }
+
+ return acc;
+ }, null);
+ return closestIndex;
+}
+
+function trackFinger(event, touchId) {
+ if (touchId.current !== undefined && event.changedTouches) {
+ for (let i = 0; i < event.changedTouches.length; i += 1) {
+ const touch = event.changedTouches[i];
+ if (touch.identifier === touchId.current) {
+ return {
+ x: touch.clientX,
+ y: touch.clientY,
+ };
+ }
+ }
+
+ return false;
+ }
+
+ return {
+ x: event.clientX,
+ y: event.clientY,
+ };
+}
+
+function valueToPercent(value, min, max) {
+ return ((value - min) * 100) / (max - min);
+}
+
+function percentToValue(percent, min, max) {
+ return (max - min) * percent + min;
+}
+
+function getDecimalPrecision(num) {
+ // This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
+ // When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
+ if (Math.abs(num) < 1) {
+ const parts = num.toExponential().split('e-');
+ const matissaDecimalPart = parts[0].split('.')[1];
+ return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);
+ }
+
+ const decimalPart = num.toString().split('.')[1];
+ return decimalPart ? decimalPart.length : 0;
+}
+
+function roundValueToStep(value, step, min) {
+ const nearest = Math.round((value - min) / step) * step + min;
+ return Number(nearest.toFixed(getDecimalPrecision(step)));
+}
+
+function setValueIndex({ values, source, newValue, index }) {
+ // Performance shortcut
+ if (source[index] === newValue) {
+ return source;
+ }
+
+ const output = values.slice();
+ output[index] = newValue;
+ return output;
+}
+
+function focusThumb({ sliderRef, activeIndex, setActive }) {
+ const doc = ownerDocument(sliderRef.current);
+ if (
+ !sliderRef.current.contains(doc.activeElement) ||
+ Number(doc.activeElement.getAttribute('data-index')) !== activeIndex
+ ) {
+ sliderRef.current.querySelector(`[role="slider"][data-index="${activeIndex}"]`).focus();
+ }
+
+ if (setActive) {
+ setActive(activeIndex);
+ }
+}
+
+const axisProps = {
+ horizontal: {
+ offset: (percent) => ({ left: `${percent}%` }),
+ leap: (percent) => ({ width: `${percent}%` }),
+ },
+ 'horizontal-reverse': {
+ offset: (percent) => ({ right: `${percent}%` }),
+ leap: (percent) => ({ width: `${percent}%` }),
+ },
+ vertical: {
+ offset: (percent) => ({ bottom: `${percent}%` }),
+ leap: (percent) => ({ height: `${percent}%` }),
+ },
+};
+
+const Identity = (x) => x;
+
+// TODO: remove support for Safari < 13.
+// https://caniuse.com/#search=touch-action
+//
+// Safari, on iOS, supports touch action since v13.
+// Over 80% of the iOS phones are compatible
+// in August 2020.
+let cachedSupportsTouchActionNone;
+function doesSupportTouchActionNone() {
+ if (cachedSupportsTouchActionNone === undefined) {
+ const element = document.createElement('div');
+ element.style.touchAction = 'none';
+ document.body.appendChild(element);
+ cachedSupportsTouchActionNone = window.getComputedStyle(element).touchAction === 'none';
+ element.parentElement.removeChild(element);
+ }
+ return cachedSupportsTouchActionNone;
+}
+
+const getUtilityClass = (name) => {
+ return `MuiSlider-${name}`;
+};
+
+const useSliderClasses = (props) => {
+ const { color, disabled, marked, orientation, track } = props;
+
+ const utilityClasses = {
+ root: clsx(getUtilityClass('root'), getUtilityClass(`color${capitalize(color)}`), {
+ ['Mui-disabled']: disabled,
+ [getUtilityClass('marked')]: marked,
+ [getUtilityClass('vertical')]: orientation === 'vertical',
+ [getUtilityClass('trackInverted')]: track === 'inverted',
+ [getUtilityClass('trackFalse')]: track === false,
+ }),
+ rail: getUtilityClass('rail'),
+ track: getUtilityClass('track'),
+ mark: getUtilityClass('mark'),
+ markLabel: getUtilityClass('markLabel'),
+ valueLabel: getUtilityClass('valueLabel'),
+ thumb: clsx(getUtilityClass('thumb'), getUtilityClass(`thumbColor${capitalize(color)}`), {
+ ['Mui-disabled']: disabled,
+ }),
+ };
+
+ return utilityClasses;
+};
+
+const isComponent = (element) => typeof element !== 'string';
+
+const Slider = React.forwardRef(function Slider(props, ref) {
+ const {
+ 'aria-label': ariaLabel,
+ 'aria-labelledby': ariaLabelledby,
+ 'aria-valuetext': ariaValuetext,
+ classes = {},
+ className,
+ color = 'primary',
+ component: Component = 'span',
+ defaultValue,
+ disabled = false,
+ getAriaLabel,
+ getAriaValueText,
+ marks: marksProp = false,
+ max = 100,
+ min = 0,
+ name,
+ onChange,
+ onChangeCommitted,
+ onMouseDown,
+ orientation = 'horizontal',
+ scale = Identity,
+ step = 1,
+ track = 'normal',
+ value: valueProp,
+ valueLabelDisplay = 'off',
+ valueLabelFormat = Identity,
+ isRtl = false,
+ components = {},
+ componentsProps = {},
+ ...other
+ } = props;
+
+ const touchId = React.useRef();
+ // We can't use the :active browser pseudo-classes.
+ // - The active state isn't triggered when clicking on the rail.
+ // - The active state isn't transfered when inversing a range slider.
+ const [active, setActive] = React.useState(-1);
+ const [open, setOpen] = React.useState(-1);
+
+ const [valueDerived, setValueState] = useControlled({
+ controlled: valueProp,
+ default: defaultValue,
+ name: 'Slider',
+ });
+
+ const range = Array.isArray(valueDerived);
+ let values = range ? valueDerived.slice().sort(asc) : [valueDerived];
+ values = values.map((value) => clamp(value, min, max));
+ const marks =
+ marksProp === true && step !== null
+ ? [...Array(Math.floor((max - min) / step) + 1)].map((_, index) => ({
+ value: min + step * index,
+ }))
+ : marksProp || [];
+
+ const {
+ isFocusVisibleRef,
+ onBlur: handleBlurVisible,
+ onFocus: handleFocusVisible,
+ ref: focusVisibleRef,
+ } = useIsFocusVisible();
+ const [focusVisible, setFocusVisible] = React.useState(-1);
+
+ const sliderRef = React.useRef();
+ const handleFocusRef = useForkRef(focusVisibleRef, sliderRef);
+ const handleRef = useForkRef(ref, handleFocusRef);
+
+ const handleFocus = useEventCallback((event) => {
+ const index = Number(event.currentTarget.getAttribute('data-index'));
+ handleFocusVisible(event);
+ if (isFocusVisibleRef.current === true) {
+ setFocusVisible(index);
+ }
+ setOpen(index);
+ });
+ const handleBlur = useEventCallback((event) => {
+ handleBlurVisible(event);
+ if (isFocusVisibleRef.current === false) {
+ setFocusVisible(-1);
+ }
+ setOpen(-1);
+ });
+ const handleMouseOver = useEventCallback((event) => {
+ const index = Number(event.currentTarget.getAttribute('data-index'));
+ setOpen(index);
+ });
+ const handleMouseLeave = useEventCallback(() => {
+ setOpen(-1);
+ });
+
+ useEnhancedEffect(() => {
+ if (disabled && sliderRef.current.contains(document.activeElement)) {
+ // This is necessary because Firefox and Safari will keep focus
+ // on a disabled element:
+ // https://codesandbox.io/s/mui-pr-22247-forked-h151h?file=/src/App.js
+ document.activeElement.blur();
+ }
+ }, [disabled]);
+
+ if (disabled && active !== -1) {
+ setActive(-1);
+ }
+ if (disabled && focusVisible !== -1) {
+ setFocusVisible(-1);
+ }
+
+ const handleKeyDown = useEventCallback((event) => {
+ const index = Number(event.currentTarget.getAttribute('data-index'));
+ const value = values[index];
+ const tenPercents = (max - min) / 10;
+ const marksValues = marks.map((mark) => mark.value);
+ const marksIndex = marksValues.indexOf(value);
+ let newValue;
+ const increaseKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
+ const decreaseKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
+
+ switch (event.key) {
+ case 'Home':
+ newValue = min;
+ break;
+ case 'End':
+ newValue = max;
+ break;
+ case 'PageUp':
+ if (step) {
+ newValue = value + tenPercents;
+ }
+ break;
+ case 'PageDown':
+ if (step) {
+ newValue = value - tenPercents;
+ }
+ break;
+ case increaseKey:
+ case 'ArrowUp':
+ if (step) {
+ newValue = value + step;
+ } else {
+ newValue = marksValues[marksIndex + 1] || marksValues[marksValues.length - 1];
+ }
+ break;
+ case decreaseKey:
+ case 'ArrowDown':
+ if (step) {
+ newValue = value - step;
+ } else {
+ newValue = marksValues[marksIndex - 1] || marksValues[0];
+ }
+ break;
+ default:
+ return;
+ }
+
+ // Prevent scroll of the page
+ event.preventDefault();
+
+ if (step) {
+ newValue = roundValueToStep(newValue, step, min);
+ }
+
+ newValue = clamp(newValue, min, max);
+
+ if (range) {
+ const previousValue = newValue;
+ newValue = setValueIndex({
+ values,
+ source: valueDerived,
+ newValue,
+ index,
+ }).sort(asc);
+ focusThumb({ sliderRef, activeIndex: newValue.indexOf(previousValue) });
+ }
+
+ setValueState(newValue);
+ setFocusVisible(index);
+
+ if (onChange) {
+ onChange(event, newValue);
+ }
+ if (onChangeCommitted) {
+ onChangeCommitted(event, newValue);
+ }
+ });
+
+ const previousIndex = React.useRef();
+ let axis = orientation;
+ if (isRtl && orientation === 'horizontal') {
+ axis += '-reverse';
+ }
+
+ const getFingerNewValue = ({ finger, move = false, values: values2, source }) => {
+ const { current: slider } = sliderRef;
+ const { width, height, bottom, left } = slider.getBoundingClientRect();
+ let percent;
+
+ if (axis.indexOf('vertical') === 0) {
+ percent = (bottom - finger.y) / height;
+ } else {
+ percent = (finger.x - left) / width;
+ }
+
+ if (axis.indexOf('-reverse') !== -1) {
+ percent = 1 - percent;
+ }
+
+ let newValue;
+ newValue = percentToValue(percent, min, max);
+ if (step) {
+ newValue = roundValueToStep(newValue, step, min);
+ } else {
+ const marksValues = marks.map((mark) => mark.value);
+ const closestIndex = findClosest(marksValues, newValue);
+ newValue = marksValues[closestIndex];
+ }
+
+ newValue = clamp(newValue, min, max);
+ let activeIndex = 0;
+
+ if (range) {
+ if (!move) {
+ activeIndex = findClosest(values2, newValue);
+ } else {
+ activeIndex = previousIndex.current;
+ }
+
+ const previousValue = newValue;
+ newValue = setValueIndex({
+ values: values2,
+ source,
+ newValue,
+ index: activeIndex,
+ }).sort(asc);
+ activeIndex = newValue.indexOf(previousValue);
+ previousIndex.current = activeIndex;
+ }
+
+ return { newValue, activeIndex };
+ };
+
+ const handleTouchMove = useEventCallback((nativeEvent) => {
+ const finger = trackFinger(nativeEvent, touchId);
+
+ if (!finger) {
+ return;
+ }
+
+ // Cancel move in case some other element consumed a mouseup event and it was not fired.
+ if (nativeEvent.type === 'mousemove' && nativeEvent.buttons === 0) {
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ handleTouchEnd(nativeEvent);
+ return;
+ }
+
+ const { newValue, activeIndex } = getFingerNewValue({
+ finger,
+ move: true,
+ values,
+ source: valueDerived,
+ });
+
+ focusThumb({ sliderRef, activeIndex, setActive });
+ setValueState(newValue);
+
+ if (onChange) {
+ onChange(nativeEvent, newValue);
+ }
+ });
+
+ const handleTouchEnd = useEventCallback((nativeEvent) => {
+ const finger = trackFinger(nativeEvent, touchId);
+
+ if (!finger) {
+ return;
+ }
+
+ const { newValue } = getFingerNewValue({ finger, values, source: valueDerived });
+
+ setActive(-1);
+ if (nativeEvent.type === 'touchend') {
+ setOpen(-1);
+ }
+
+ if (onChangeCommitted) {
+ onChangeCommitted(nativeEvent, newValue);
+ }
+
+ touchId.current = undefined;
+
+ const doc = ownerDocument(sliderRef.current);
+ doc.removeEventListener('mousemove', handleTouchMove);
+ doc.removeEventListener('mouseup', handleTouchEnd);
+ doc.removeEventListener('touchmove', handleTouchMove);
+ doc.removeEventListener('touchend', handleTouchEnd);
+ });
+
+ const handleTouchStart = useEventCallback((event) => {
+ // If touch-action: none; is not supported we need to prevent the scroll manually.
+ if (!doesSupportTouchActionNone()) {
+ event.preventDefault();
+ }
+
+ const touch = event.changedTouches[0];
+ if (touch != null) {
+ // A number that uniquely identifies the current finger in the touch session.
+ touchId.current = touch.identifier;
+ }
+ const finger = trackFinger(event, touchId);
+ const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived });
+ focusThumb({ sliderRef, activeIndex, setActive });
+
+ setValueState(newValue);
+
+ if (onChange) {
+ onChange(event, newValue);
+ }
+
+ const doc = ownerDocument(sliderRef.current);
+ doc.addEventListener('touchmove', handleTouchMove);
+ doc.addEventListener('touchend', handleTouchEnd);
+ });
+
+ React.useEffect(() => {
+ const { current: slider } = sliderRef;
+ slider.addEventListener('touchstart', handleTouchStart, {
+ passive: doesSupportTouchActionNone(),
+ });
+
+ const doc = ownerDocument(slider);
+
+ return () => {
+ slider.removeEventListener('touchstart', handleTouchStart, {
+ passive: doesSupportTouchActionNone(),
+ });
+
+ doc.removeEventListener('mousemove', handleTouchMove);
+ doc.removeEventListener('mouseup', handleTouchEnd);
+ doc.removeEventListener('touchmove', handleTouchMove);
+ doc.removeEventListener('touchend', handleTouchEnd);
+ };
+ }, [handleTouchEnd, handleTouchMove, handleTouchStart]);
+
+ React.useEffect(() => {
+ if (disabled) {
+ const doc = ownerDocument(sliderRef.current);
+ doc.removeEventListener('mousemove', handleTouchMove);
+ doc.removeEventListener('mouseup', handleTouchEnd);
+ doc.removeEventListener('touchmove', handleTouchMove);
+ doc.removeEventListener('touchend', handleTouchEnd);
+ }
+ }, [disabled, handleTouchEnd, handleTouchMove]);
+
+ const handleMouseDown = useEventCallback((event) => {
+ if (onMouseDown) {
+ onMouseDown(event);
+ }
+
+ event.preventDefault();
+ const finger = trackFinger(event, touchId);
+ const { newValue, activeIndex } = getFingerNewValue({ finger, values, source: valueDerived });
+ focusThumb({ sliderRef, activeIndex, setActive });
+
+ setValueState(newValue);
+
+ if (onChange) {
+ onChange(event, newValue);
+ }
+
+ const doc = ownerDocument(sliderRef.current);
+ doc.addEventListener('mousemove', handleTouchMove);
+ doc.addEventListener('mouseup', handleTouchEnd);
+ });
+
+ const trackOffset = valueToPercent(range ? values[0] : min, min, max);
+ const trackLeap = valueToPercent(values[values.length - 1], min, max) - trackOffset;
+ const trackStyle = {
+ ...axisProps[axis].offset(trackOffset),
+ ...axisProps[axis].leap(trackLeap),
+ };
+
+ const Root = components.Root || 'span';
+ const rootProps = componentsProps.root || {};
+
+ const Rail = components.Rail || 'span';
+ const railProps = componentsProps.rail || {};
+
+ const Track = components.Track || 'span';
+ const trackProps = componentsProps.track || {};
+
+ const Thumb = components.Thumb || 'span';
+ const thumbProps = componentsProps.thumb || {};
+
+ const ValueLabel = components.ValueLabel || ValueLabelComponent;
+ const valueLabelProps = componentsProps.valueLabel || {};
+
+ const Mark = components.Mark || 'span';
+ const markProps = componentsProps.mark || {};
+
+ const MarkLabel = components.MarkLabel || 'span';
+ const markLabelProps = componentsProps.markLabel || {};
+
+ // all props with defaults
+ // consider extracting to hook an reusing the lint rule for the varints
+ const stateAndProps = {
+ ...props,
+ color,
+ disabled,
+ max,
+ min,
+ orientation,
+ scale,
+ step,
+ track,
+ valueLabelDisplay,
+ valueLabelFormat,
+ isRtl,
+ marked: marks.length > 0 && marks.some((mark) => mark.label),
+ };
+
+ const utilityClasses = useSliderClasses({
+ ...stateAndProps,
+ classes,
+ });
+
+ return (
+
+
+
+
+ {marks.map((mark, index) => {
+ const percent = valueToPercent(mark.value, min, max);
+ const style = axisProps[axis].offset(percent);
+
+ let markActive;
+ if (track === false) {
+ markActive = values.indexOf(mark.value) !== -1;
+ } else {
+ markActive =
+ (track === 'normal' &&
+ (range
+ ? mark.value >= values[0] && mark.value <= values[values.length - 1]
+ : mark.value <= values[0])) ||
+ (track === 'inverted' &&
+ (range
+ ? mark.value <= values[0] || mark.value >= values[values.length - 1]
+ : mark.value >= values[0]));
+ }
+
+ return (
+
+
+ {mark.label != null ? (
+
+ {mark.label}
+
+ ) : null}
+
+ );
+ })}
+ {values.map((value, index) => {
+ const percent = valueToPercent(value, min, max);
+ const style = axisProps[axis].offset(percent);
+
+ return (
+
+
+
+ );
+ })}
+
+ );
+});
+
+Slider.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the d.ts file and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The label of the slider.
+ */
+ 'aria-label': chainPropTypes(PropTypes.string, (props) => {
+ const range = Array.isArray(props.value || props.defaultValue);
+
+ if (range && props['aria-label'] != null) {
+ return new Error(
+ 'Material-UI: You need to use the `getAriaLabel` prop instead of `aria-label` when using a range slider.',
+ );
+ }
+
+ return null;
+ }),
+ /**
+ * The id of the element containing a label for the slider.
+ */
+ 'aria-labelledby': PropTypes.string,
+ /**
+ * A string value that provides a user-friendly name for the current value of the slider.
+ */
+ 'aria-valuetext': chainPropTypes(PropTypes.string, (props) => {
+ const range = Array.isArray(props.value || props.defaultValue);
+
+ if (range && props['aria-valuetext'] != null) {
+ return new Error(
+ 'Material-UI: You need to use the `getAriaValueText` prop instead of `aria-valuetext` when using a range slider.',
+ );
+ }
+
+ return null;
+ }),
+ /**
+ * @ignore
+ */
+ children: PropTypes.node,
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes: PropTypes.object,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The color of the component. It supports those theme colors that make sense for this component.
+ * @default 'primary'
+ */
+ color: PropTypes.oneOf(['primary', 'secondary']),
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * The default element value. Use when the component is not controlled.
+ */
+ defaultValue: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
+ /**
+ * If `true`, the slider will be disabled.
+ * @default false
+ */
+ disabled: PropTypes.bool,
+ /**
+ * Accepts a function which returns a string value that provides a user-friendly name for the thumb labels of the slider.
+ *
+ * @param {number} index The thumb label's index to format.
+ * @returns {string}
+ */
+ getAriaLabel: PropTypes.func,
+ /**
+ * Accepts a function which returns a string value that provides a user-friendly name for the current value of the slider.
+ *
+ * @param {number} value The thumb label's value to format.
+ * @param {number} index The thumb label's index to format.
+ * @returns {string}
+ */
+ getAriaValueText: PropTypes.func,
+ /**
+ * Indicates whether the theme context has rtl direction. It is set automatically.
+ */
+ isRtl: PropTypes.bool,
+ /**
+ * Marks indicate predetermined values to which the user can move the slider.
+ * If `true` the marks will be spaced according the value of the `step` prop.
+ * If an array, it should contain objects with `value` and an optional `label` keys.
+ * @default false
+ */
+ marks: PropTypes.oneOfType([
+ PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.node,
+ value: PropTypes.number.isRequired,
+ }),
+ ),
+ PropTypes.bool,
+ ]),
+ /**
+ * The maximum allowed value of the slider.
+ * Should not be equal to min.
+ * @default 100
+ */
+ max: PropTypes.number,
+ /**
+ * The minimum allowed value of the slider.
+ * Should not be equal to max.
+ * @default 0
+ */
+ min: PropTypes.number,
+ /**
+ * Name attribute of the hidden `input` element.
+ */
+ name: PropTypes.string,
+ /**
+ * Callback function that is fired when the slider's value changed.
+ *
+ * @param {object} event The event source of the callback. **Warning**: This is a generic event not a change event.
+ * @param {number | number[]} value The new value.
+ */
+ onChange: PropTypes.func,
+ /**
+ * Callback function that is fired when the `mouseup` is triggered.
+ *
+ * @param {object} event The event source of the callback. **Warning**: This is a generic event not a change event.
+ * @param {number | number[]} value The new value.
+ */
+ onChangeCommitted: PropTypes.func,
+ /**
+ * @ignore
+ */
+ onMouseDown: PropTypes.func,
+ /**
+ * The slider orientation.
+ * @default 'horizontal'
+ */
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
+ /**
+ * A transformation function, to change the scale of the slider.
+ * @default (x) => x
+ */
+ scale: PropTypes.func,
+ /**
+ * The granularity with which the slider can step through values. (A "discrete" slider.)
+ * The `min` prop serves as the origin for the valid values.
+ * We recommend (max - min) to be evenly divisible by the step.
+ *
+ * When step is `null`, the thumb can only be slid onto marks provided with the `marks` prop.
+ * @default 1
+ */
+ step: PropTypes.number,
+ /**
+ * The component used to display the value label.
+ * @default 'span'
+ */
+ ThumbComponent: PropTypes.elementType,
+ /**
+ * The track presentation:
+ *
+ * - `normal` the track will render a bar representing the slider value.
+ * - `inverted` the track will render a bar representing the remaining slider value.
+ * - `false` the track will render without a bar.
+ * @default 'normal'
+ */
+ track: PropTypes.oneOf(['inverted', 'normal', false]),
+ /**
+ * The value of the slider.
+ * For ranged sliders, provide an array with two values.
+ */
+ value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
+ /**
+ * The value label component.
+ * @default ValueLabel
+ */
+ ValueLabelComponent: PropTypes.elementType,
+ /**
+ * Controls when the value label is displayed:
+ *
+ * - `auto` the value label will display when the thumb is hovered or focused.
+ * - `on` will display persistently.
+ * - `off` will never display.
+ * @default 'off'
+ */
+ valueLabelDisplay: PropTypes.oneOf(['auto', 'off', 'on']),
+ /**
+ * The format function the value label's value.
+ *
+ * When a function is provided, it should have the following signature:
+ *
+ * - {number} value The value label's value to format
+ * - {number} index The value label's index to format
+ * @default (x) => x
+ */
+ valueLabelFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+};
+
+export default Slider;
diff --git a/packages/material-ui/src/Slider/ValueLabel.js b/packages/material-ui/src/Slider/ValueLabel.js
index 528c9161d1c4fc..1e070928732602 100644
--- a/packages/material-ui/src/Slider/ValueLabel.js
+++ b/packages/material-ui/src/Slider/ValueLabel.js
@@ -43,6 +43,7 @@ const styles = (theme) => ({
/**
* @ignore - internal component.
*/
+// TODO: convert to emotion
function ValueLabel(props) {
const { children, classes, className, open, value, valueLabelDisplay } = props;
diff --git a/packages/material-ui/src/Slider/index.d.ts b/packages/material-ui/src/Slider/index.d.ts
index 006f966fe2404f..e81742056288f6 100644
--- a/packages/material-ui/src/Slider/index.d.ts
+++ b/packages/material-ui/src/Slider/index.d.ts
@@ -1,2 +1,13 @@
export { default } from './Slider';
+export { default as SliderBase } from './SliderBase';
+export {
+ SliderRoot,
+ SliderMark,
+ SliderMarkLabel,
+ SliderRail,
+ SliderTrack,
+ SliderThumb,
+ SliderValueLabel,
+} from './Slider';
export * from './Slider';
+export * from './SliderBase';
diff --git a/packages/material-ui/src/Slider/index.js b/packages/material-ui/src/Slider/index.js
index 9898d6a85d1d01..f7405315520e87 100644
--- a/packages/material-ui/src/Slider/index.js
+++ b/packages/material-ui/src/Slider/index.js
@@ -1 +1,3 @@
export { default } from './Slider';
+export { default as SliderBase } from './SliderBase';
+export * from './Slider';
diff --git a/packages/material-ui/src/styles/index.d.ts b/packages/material-ui/src/styles/index.d.ts
index 812f80e6b4e30a..ad3c474e1a2188 100644
--- a/packages/material-ui/src/styles/index.d.ts
+++ b/packages/material-ui/src/styles/index.d.ts
@@ -23,6 +23,7 @@ export {
StyledComponentProps,
} from './withStyles';
export { default as withTheme, WithTheme } from './withTheme';
+export { default as muiStyled } from './muiStyled';
export { default as styled, ComponentCreator, StyledProps } from './styled';
export {
createGenerateClassName,
diff --git a/packages/material-ui/src/styles/index.js b/packages/material-ui/src/styles/index.js
index 2fb7099da2caaf..8d0f9ef14407bc 100644
--- a/packages/material-ui/src/styles/index.js
+++ b/packages/material-ui/src/styles/index.js
@@ -10,6 +10,7 @@ export * from './transitions';
export { default as useTheme } from './useTheme';
export { default as withStyles } from './withStyles';
export { default as withTheme } from './withTheme';
+export { default as muiStyled } from './muiStyled';
export {
createGenerateClassName,
jssPreset,
diff --git a/packages/material-ui/src/styles/muiStyled.d.ts b/packages/material-ui/src/styles/muiStyled.d.ts
new file mode 100644
index 00000000000000..b5a49f0cfb7f2b
--- /dev/null
+++ b/packages/material-ui/src/styles/muiStyled.d.ts
@@ -0,0 +1,8 @@
+/**
+ * Cutom styled functionality that support mui specific config.
+ *
+ * @param options Takes an incomplete theme object and adds the missing parts.
+ * @returns A complete, ready to use theme object.
+ */
+// TODO: fix typings
+export default function adaptV4Theme(component: any, config: any, muiConfig: any): React.Component;
diff --git a/packages/material-ui/src/styles/muiStyled.js b/packages/material-ui/src/styles/muiStyled.js
new file mode 100644
index 00000000000000..24249e1936b75f
--- /dev/null
+++ b/packages/material-ui/src/styles/muiStyled.js
@@ -0,0 +1,61 @@
+import styled from '@emotion/styled';
+import { propsToClassKey } from '@material-ui/styles';
+
+const getStyleOverrides = (name, theme) => {
+ let styleOverrides = {};
+
+ if (
+ theme &&
+ theme.components &&
+ theme.components[name] &&
+ theme.components[name].styleOverrides
+ ) {
+ styleOverrides = theme.components[name].styleOverrides;
+ }
+
+ return styleOverrides;
+};
+
+const getVariantStyles = (name, theme) => {
+ let variants = [];
+ if (theme && theme.components && theme.components[name] && theme.components[name].variants) {
+ variants = theme.components[name].variants;
+ }
+
+ const variantsStyles = {};
+
+ variants.forEach((definition) => {
+ const key = propsToClassKey(definition.props);
+ variantsStyles[key] = definition.style;
+ });
+
+ return variantsStyles;
+};
+
+const shouldForwardProp = (prop) => prop !== 'state' && prop !== 'theme';
+
+const muiStyled = (el, params, muiConfig) => {
+ const result = styled(el, { shouldForwardProp, ...params });
+ const muiFunc = (...params) => {
+ const name = muiConfig.muiName;
+
+ if (muiConfig.overridesResolver) {
+ params.push((props) => {
+ const theme = props.theme || defaultTheme;
+ return muiConfig.overridesResolver(props, getStyleOverrides(name, theme), name);
+ });
+ }
+
+ if (muiConfig.variantsResolver) {
+ params.push((props) => {
+ const theme = props.theme || defaultTheme;
+ return muiConfig.variantsResolver(props, getVariantStyles(name, theme), theme, name);
+ });
+ }
+
+ return result(params);
+ };
+ return muiFunc;
+};
+
+export default muiStyled;
diff --git a/packages/material-ui/src/styles/useThemeProps.d.ts b/packages/material-ui/src/styles/useThemeProps.d.ts
new file mode 100644
index 00000000000000..3326c8b117818d
--- /dev/null
+++ b/packages/material-ui/src/styles/useThemeProps.d.ts
@@ -0,0 +1,15 @@
+interface ThemeWithProps {
+ components?: { [K in keyof Components]: { defaultProps?: Partial } };
+}
+
+type ThemedProps = Theme extends {
+ components: Record;
+}
+ ? Props
+ : {};
+
+export default function useThemeProps<
+ Theme extends ThemeWithProps,
+ Props,
+ Name extends keyof any
+>(params: { props: Props; name: Name; }): Props & ThemedProps;
diff --git a/packages/material-ui/src/styles/useThemeProps.js b/packages/material-ui/src/styles/useThemeProps.js
new file mode 100644
index 00000000000000..3a204d062ee78b
--- /dev/null
+++ b/packages/material-ui/src/styles/useThemeProps.js
@@ -0,0 +1,20 @@
+import useTheme from './useTheme';
+import { getThemeProps } from '@material-ui/styles';
+import defaultTheme from './defaultTheme';
+
+export default function useThemeProps({ props: inputProps, name }) {
+ const props = Object.assign({}, inputProps);
+
+ const contextTheme = useTheme() || defaultTheme;
+
+ const more = getThemeProps({ theme: contextTheme, name, props });
+
+ const theme = more.theme || contextTheme;
+ const isRtl = theme.direction === 'rtl';
+
+ return {
+ theme,
+ isRtl,
+ ...more,
+ };
+};
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index aa404acdaf24be..8eb59cb079a61a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5791,6 +5791,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+cssjanus@^1.3.0:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/cssjanus/-/cssjanus-1.3.2.tgz#7c23d39be92f63e1557a75c015f61d95009bd6b3"
+ integrity sha512-5pM/C1MIfoqhXa7k9PqSnrjj1SSZDakfyB1DZhdYyJoDUH+evGbsUg6/bpQapTJeSnYaj0rdzPUMeM33CvB0vw==
+
cssnano-preset-default@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76"
@@ -15475,6 +15480,13 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
+stylis-plugin-rtl@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/stylis-plugin-rtl/-/stylis-plugin-rtl-1.1.0.tgz#028d72419ccc47eeaaec684f3e192534f2c57ece"
+ integrity sha512-FPoSxP+gbBLJRUXDRDFNBhqy/eToquDLn7ZrjIVBRfXaZ9bunwNnDtDm2qW1EoU0c93krm1Dy+8iVmJpjRGsKw==
+ dependencies:
+ cssjanus "^1.3.0"
+
stylis-rule-sheet@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"