From 1b055c90e34fe5229f16c19e946712ddefc19977 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Thu, 20 Jul 2023 13:25:31 -0400 Subject: [PATCH 01/62] feat(slider): add prop for two handles to range slider --- .../src/components/Slider/Slider.stories.js | 13 ++++++++++ .../react/src/components/Slider/Slider.tsx | 25 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.stories.js b/packages/react/src/components/Slider/Slider.stories.js index 147e770ec8fd..11029015b0d2 100644 --- a/packages/react/src/components/Slider/Slider.stories.js +++ b/packages/react/src/components/Slider/Slider.stories.js @@ -94,6 +94,19 @@ export const ControlledSliderWithLayer = () => { ); }; +export const TwoThumbSlider = () => ( + +); + export const Skeleton = () => ; export const Playground = (args) => ( diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 3b07778da43b..c310dd765847 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -54,7 +54,7 @@ export interface SliderProps children?: ReactNodeLike; /** - * The CSS class name for the slider. + * The CSS class name for the slider, set on the wrapping div. */ className?: string; @@ -173,6 +173,11 @@ export interface SliderProps */ stepMultiplier?: number; + /** + * Turn the slider into a range slider. + */ + twoHandles?: boolean; + /** * The value. */ @@ -328,6 +333,8 @@ export default class Slider extends PureComponent { */ stepMultiplier: PropTypes.number, + twoHandles: PropTypes.bool, + /** * The value. */ @@ -353,6 +360,7 @@ export default class Slider extends PureComponent { maxLabel: '', inputType: 'number', readOnly: false, + twoHandles: false, }; static contextType = FeatureFlagContext; @@ -758,6 +766,7 @@ export default class Slider extends PureComponent { readOnly, warn, warnText, + twoHandles, ...other } = this.props; @@ -815,7 +824,7 @@ export default class Slider extends PureComponent { data-invalid={!isValid && !readOnly ? true : null} {...other}>
{ aria-labelledby={labelId} ref={this.thumbRef} /> + {twoHandles ? ( +
+ ) : null}
{ From 7a0677f6546114d70350f3548cbcc89fb639ecff Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Thu, 20 Jul 2023 14:12:18 -0400 Subject: [PATCH 02/62] feat(slider): first pass adding second input for two handles --- .../src/components/Slider/Slider.stories.js | 3 +- .../react/src/components/Slider/Slider.tsx | 81 ++++++++++++++++--- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.stories.js b/packages/react/src/components/Slider/Slider.stories.js index 11029015b0d2..951fa6ffca5a 100644 --- a/packages/react/src/components/Slider/Slider.stories.js +++ b/packages/react/src/components/Slider/Slider.stories.js @@ -97,7 +97,8 @@ export const ControlledSliderWithLayer = () => { export const TwoThumbSlider = () => ( { */ name: PropTypes.string, + nameLower: PropTypes.string, + + nameUpper: PropTypes.string, + /** * Provide an optional function to be called when the input element * loses focus @@ -340,6 +352,10 @@ export default class Slider extends PureComponent { */ value: PropTypes.number.isRequired, + valueLower: PropTypes.number, + + valueUpper: PropTypes.number, + /** * `Specify whether the Slider is in a warn state */ @@ -367,6 +383,8 @@ export default class Slider extends PureComponent { state = { value: this.props.value, + valueLower: this.props.valueLower, + valueUpper: this.props.valueUpper, left: 0, needsOnRelease: false, isValid: true, @@ -762,6 +780,8 @@ export default class Slider extends PureComponent { required, disabled, name, + nameLower, + nameUpper, light, readOnly, warn, @@ -773,7 +793,7 @@ export default class Slider extends PureComponent { delete other.onRelease; delete other.invalid; - const { value, isValid } = this.state; + const { value, valueLower, valueUpper, isValid } = this.state; return ( @@ -789,17 +809,30 @@ export default class Slider extends PureComponent { { [`${prefix}--slider--readonly`]: readOnly } ); - const inputClasses = classNames( + const fixedInputClasses = [ `${prefix}--text-input`, `${prefix}--slider-text-input`, - { - [`${prefix}--text-input--light`]: light, - [`${prefix}--text-input--invalid`]: - !readOnly && isValid === false, - [`${prefix}--slider-text-input--hidden`]: hideTextInput, - [`${prefix}--slider-text-input--warn`]: !readOnly && warn, - } + ]; + const conditionalClasses = { + [`${prefix}--text-input--light`]: light, + [`${prefix}--text-input--invalid`]: !readOnly && isValid === false, + [`${prefix}--slider-text-input--hidden`]: hideTextInput, + [`${prefix}--slider-text-input--warn`]: !readOnly && warn, + }; + const inputClasses = classNames( + fixedInputClasses, + conditionalClasses ); + const lowerInputClasses = classNames([ + ...fixedInputClasses, + `${prefix}--slider-text-input--lower`, + conditionalClasses, + ]); + const upperInputClasses = classNames([ + ...fixedInputClasses, + `${prefix}--slider-text-input--upper`, + conditionalClasses, + ]); return (
@@ -807,6 +840,28 @@ export default class Slider extends PureComponent { {labelText}
+ {twoHandles ? ( + + ) : null} {formatLabel(min, minLabel)} @@ -862,10 +917,10 @@ export default class Slider extends PureComponent { Date: Thu, 20 Jul 2023 14:13:47 -0400 Subject: [PATCH 03/62] feat(slider): fix spacing to accomodate two handles --- packages/styles/scss/components/slider/_slider.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/styles/scss/components/slider/_slider.scss b/packages/styles/scss/components/slider/_slider.scss index 1181f02206bf..d6bf9c574dba 100644 --- a/packages/styles/scss/components/slider/_slider.scss +++ b/packages/styles/scss/components/slider/_slider.scss @@ -30,6 +30,7 @@ position: relative; display: flex; align-items: center; + gap: 1rem; user-select: none; } @@ -39,7 +40,6 @@ min-width: rem(200px); max-width: rem(640px); padding: $spacing-05 0; - margin: 0 $spacing-05; cursor: pointer; } @@ -48,10 +48,6 @@ color: $text-primary; white-space: nowrap; - - &:last-of-type { - margin-right: $spacing-05; - } } .#{$prefix}--slider__track { From dbd985159f09f21f87d56779cd01e3bf7e2c9623 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Thu, 20 Jul 2023 16:46:08 -0400 Subject: [PATCH 04/62] feat(slider): use initial props to set state and initial render --- .../react/src/components/Slider/Slider.tsx | 173 +++++++++++++----- 1 file changed, 127 insertions(+), 46 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index f85dcd2a5152..99dc589708f4 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -37,6 +37,10 @@ const DRAG_EVENT_TYPES = new Set(['mousemove', 'touchmove']); */ const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']); +const LOWER = 'lower'; + +const UPPER = 'upper'; + type ExcludedAttributes = 'onChange' | 'onBlur'; export interface SliderProps extends Omit< @@ -208,6 +212,12 @@ interface CalcValueProps { useRawValue?: boolean; } +interface CalcLeftPercentProps { + clientX?: number; + value?: number; + range?: number; +} + export default class Slider extends PureComponent { static propTypes = { /** @@ -386,11 +396,16 @@ export default class Slider extends PureComponent { valueLower: this.props.valueLower, valueUpper: this.props.valueUpper, left: 0, + leftLower: 0, + leftUpper: 0, needsOnRelease: false, isValid: true, + activeHandle: null, }; thumbRef: React.RefObject; + thumbRefLower: React.RefObject; + thumbRefUpper: React.RefObject; filledTrackRef: React.RefObject; element: HTMLDivElement | null = null; inputId = ''; @@ -399,6 +414,8 @@ export default class Slider extends PureComponent { constructor(props) { super(props); this.thumbRef = React.createRef(); + this.thumbRefLower = React.createRef(); + this.thumbRefUpper = React.createRef(); this.filledTrackRef = React.createRef(); } @@ -406,11 +423,25 @@ export default class Slider extends PureComponent { * Sets up initial slider position and value in response to component mount. */ componentDidMount() { + const { twoHandles } = this.props; if (this.element) { - const { value, left } = this.calcValue({ - useRawValue: true, - }); - this.setState({ value, left }); + if (twoHandles) { + const { value: valueLower, left: leftLower } = this.calcValue({ + value: this.state.valueLower, + useRawValue: true, + }); + const { value: valueUpper, left: leftUpper } = this.calcValue({ + value: this.state.valueUpper, + useRawValue: true, + }); + this.setState({ valueLower, leftLower, valueUpper, leftUpper }); + } else { + const { value, left } = this.calcValue({ + value: this.state.value, + useRawValue: true, + }); + this.setState({ value, left }); + } } } @@ -426,13 +457,29 @@ export default class Slider extends PureComponent { // Fire onChange event handler if present, if there's a usable value, and // if the value is different from the last one - if (this.thumbRef.current) { - this.thumbRef.current.style.left = `${this.state.left}%`; - } - if (this.filledTrackRef.current) { - this.filledTrackRef.current.style.transform = `translate(0%, -50%) scaleX(${ - this.state.left / 100 - })`; + if (this.props.twoHandles) { + if (this.thumbRefLower.current) { + this.thumbRefLower.current.style.left = `${this.state.leftLower}%`; + } + if (this.thumbRefUpper.current) { + this.thumbRefUpper.current.style.left = `${this.state.leftUpper}%`; + } + if (this.filledTrackRef.current) { + this.filledTrackRef.current.style.transform = `translate(${ + this.state.leftLower + }%, -50%) scaleX(${ + (this.state.leftUpper - this.state.leftLower) / 100 + })`; + } + } else { + if (this.thumbRef.current) { + this.thumbRef.current.style.left = `${this.state.left}%`; + } + if (this.filledTrackRef.current) { + this.filledTrackRef.current.style.transform = `translate(0%, -50%) scaleX(${ + this.state.left / 100 + })`; + } } if ( prevState.value !== this.state.value && @@ -455,6 +502,8 @@ export default class Slider extends PureComponent { // Otherwise, do prop -> state sync without "value capping". if ( prevProps.value === this.props.value && + prevProps.valueLower === this.props.valueLower && + prevProps.valueUpper === this.props.valueUpper && prevProps.max === this.props.max && prevProps.min === this.props.min ) { @@ -504,9 +553,24 @@ export default class Slider extends PureComponent { this.element?.ownerDocument.addEventListener(element, this.onDrag); }); + // If we're set to two handles, negotiate which drag handle is closest to + // the users interaction, set the state and pass it along. + let activeHandle; + if (this.props.twoHandles) { + // @todo calculate distance to each handle, implement as a method. + const distanceToLower = 0; + const distanceToUpper = 0; + if (distanceToLower <= distanceToUpper) { + activeHandle = LOWER; + } else { + activeHandle = UPPER; + } + this.setState({ activeHandle }); + } + // Perform first recalculation since we probably didn't click exactly in the // middle of the thumb - this.onDrag(evt); + this.onDrag(evt, activeHandle); }; /** @@ -539,7 +603,7 @@ export default class Slider extends PureComponent { * * @param {Event} evt The event. */ - _onDrag = (evt) => { + _onDrag = (evt, activeHandle) => { // Do nothing if component is disabled or we have no event if (this.props.disabled || this.props.readOnly || !evt) { return; @@ -673,6 +737,43 @@ export default class Slider extends PureComponent { this.props.onBlur?.({ value }); }; + calcLeftPercent = ({ clientX, value, range }: CalcLeftPercentProps) => { + const boundingRect = this.element?.getBoundingClientRect?.(); + let width = boundingRect ? boundingRect.right - boundingRect.left : 0; + + // Enforce a minimum width of at least 1 for calculations + if (width <= 0) { + width = 1; + } + + // If a clientX is specified, use it to calculate the leftPercent. If not, + // use the provided value to calculate it instead. + if (clientX != null) { + const leftOffset = clientX - (boundingRect?.left ?? 0); + return leftOffset / width; + } else { + // Prevent NaN calculation if the range is 0. + return range === 0 ? 0 : (value - this.props.min) / range; + } + }; + + calcSteppedValuePercent = ({ leftPercent, range }) => { + const totalSteps = range / (this.props.step ?? Slider.defaultProps.step); + + let steppedValue = + Math.round(leftPercent * totalSteps) * + (this.props.step ?? Slider.defaultProps.step); + const steppedPercent = this.clamp(steppedValue / range, 0, 1); + + steppedValue = this.clamp( + steppedValue + this.props.min, + this.props.min, + this.props.max + ); + + return [steppedValue, steppedPercent]; + }; + /** * Calculates a new Slider `value` and `left` (thumb offset) given a `clientX`, * `value`, or neither of those. @@ -692,31 +793,14 @@ export default class Slider extends PureComponent { * clientX is not provided. * @param {boolean} [params.useRawValue=false] `true` to use the given value as-is. */ - calcValue = ({ clientX, value, useRawValue = false }: CalcValueProps) => { const range = this.props.max - this.props.min; - const boundingRect = this.element?.getBoundingClientRect?.(); - const totalSteps = range / (this.props.step ?? Slider.defaultProps.step); - let width = boundingRect ? boundingRect.right - boundingRect.left : 0; - - // Enforce a minimum width of at least 1 for calculations - if (width <= 0) { - width = 1; - } - // If a clientX is specified, use it to calculate the leftPercent. If not, - // use the provided value or state's value to calculate it instead. - let leftPercent; - if (clientX != null) { - const leftOffset = clientX - (boundingRect?.left ?? 0); - leftPercent = leftOffset / width; - } else { - if (value == null) { - value = this.state.value; - } - // prevent NaN calculation if the range is 0 - leftPercent = range === 0 ? 0 : (value - this.props.min) / range; - } + const leftPercent = this.calcLeftPercent({ + clientX, + value, + range, + }); if (useRawValue) { // Adjusts only for min/max of thumb position @@ -726,16 +810,10 @@ export default class Slider extends PureComponent { }; } - let steppedValue = - Math.round(leftPercent * totalSteps) * - (this.props.step ?? Slider.defaultProps.step); - const steppedPercent = this.clamp(steppedValue / range, 0, 1); - - steppedValue = this.clamp( - steppedValue + this.props.min, - this.props.min, - this.props.max - ); + const [steppedValue, steppedPercent] = this.calcSteppedValuePercent({ + leftPercent, + range, + }); return { value: steppedValue, left: steppedPercent * 100 }; }; @@ -887,7 +965,8 @@ export default class Slider extends PureComponent { aria-valuemin={min} aria-valuenow={value} aria-labelledby={labelId} - ref={this.thumbRef} + ref={twoHandles ? this.thumbRefLower : this.thumbRef} + onFocus={() => this.setState({ activeHandle: LOWER })} /> {twoHandles ? (
{ aria-valuemin={min} aria-valuenow={value} aria-labelledby={labelId} + ref={this.thumbRefUpper} + onFocus={() => this.setState({ activeHandle: UPPER })} /> ) : null}
Date: Thu, 20 Jul 2023 17:12:05 -0400 Subject: [PATCH 05/62] feat(slider): implement drag handle logic for two handles --- .../src/components/Slider/Slider.stories.js | 2 +- .../react/src/components/Slider/Slider.tsx | 46 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.stories.js b/packages/react/src/components/Slider/Slider.stories.js index 951fa6ffca5a..f51353ad3d3a 100644 --- a/packages/react/src/components/Slider/Slider.stories.js +++ b/packages/react/src/components/Slider/Slider.stories.js @@ -94,7 +94,7 @@ export const ControlledSliderWithLayer = () => { ); }; -export const TwoThumbSlider = () => ( +export const TwoHandleSlider = () => ( { this.element?.ownerDocument.addEventListener(element, this.onDrag); }); - // If we're set to two handles, negotiate which drag handle is closest to - // the users interaction, set the state and pass it along. - let activeHandle; - if (this.props.twoHandles) { - // @todo calculate distance to each handle, implement as a method. - const distanceToLower = 0; - const distanceToUpper = 0; - if (distanceToLower <= distanceToUpper) { - activeHandle = LOWER; - } else { - activeHandle = UPPER; - } - this.setState({ activeHandle }); - } - // Perform first recalculation since we probably didn't click exactly in the // middle of the thumb - this.onDrag(evt, activeHandle); + this.onDrag(evt); }; /** @@ -603,7 +588,7 @@ export default class Slider extends PureComponent { * * @param {Event} evt The event. */ - _onDrag = (evt, activeHandle) => { + _onDrag = (evt) => { // Do nothing if component is disabled or we have no event if (this.props.disabled || this.props.readOnly || !evt) { return; @@ -623,8 +608,23 @@ export default class Slider extends PureComponent { return; } - const { value, left } = this.calcValue({ clientX }); - this.setState({ value, left, isValid: true }); + const { value, left } = this.calcValue({ + clientX, + value: this.state.value, + }); + // If we're set to two handles, negotiate which drag handle is closest to + // the users' interaction. + if (this.props.twoHandles) { + const distanceToLower = this.calcDistanceToHandle(LOWER, clientX); + const distanceToUpper = this.calcDistanceToHandle(UPPER, clientX); + if (distanceToLower <= distanceToUpper) { + this.setState({ valueLower: value, leftLower: left, isValid: true }); + } else { + this.setState({ valueUpper: value, leftUpper: left, isValid: true }); + } + } else { + this.setState({ value, left, isValid: true }); + } }; /** @@ -818,6 +818,14 @@ export default class Slider extends PureComponent { return { value: steppedValue, left: steppedPercent * 100 }; }; + calcDistanceToHandle(handle, clientX) { + // left is a whole value between 0 and 100. + const left = handle === LOWER ? this.state.leftLower : this.state.leftUpper; + const boundingRect = this.element?.getBoundingClientRect(); + const handleX = boundingRect.left + (left / 100) * boundingRect.width; + return Math.abs(handleX - clientX); + } + // syncs invalid state and prop static getDerivedStateFromProps(props, state) { const { isValid } = state; From 2b1457ea0af39a2ec6303f716d2ded171a66c6b9 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Mon, 24 Jul 2023 13:19:09 -0400 Subject: [PATCH 06/62] feat(slider): collision detection for the drag handles --- .../react/src/components/Slider/Slider.tsx | 85 ++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 026de6e42011..9652c03c8343 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -37,9 +37,10 @@ const DRAG_EVENT_TYPES = new Set(['mousemove', 'touchmove']); */ const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']); -const LOWER = 'lower'; - -const UPPER = 'upper'; +enum HandlePosition { + LOWER = 'lower', + UPPER = 'upper', +} type ExcludedAttributes = 'onChange' | 'onBlur'; export interface SliderProps @@ -553,9 +554,27 @@ export default class Slider extends PureComponent { this.element?.ownerDocument.addEventListener(element, this.onDrag); }); + let activeHandle; + if (this.props.twoHandles) { + const distanceToLower = this.calcDistanceToHandle( + HandlePosition.LOWER, + evt.clientX + ); + const distanceToUpper = this.calcDistanceToHandle( + HandlePosition.UPPER, + evt.clientX + ); + if (distanceToLower <= distanceToUpper) { + activeHandle = HandlePosition.LOWER; + } else { + activeHandle = HandlePosition.UPPER; + } + } + this.setState({ activeHandle }); + // Perform first recalculation since we probably didn't click exactly in the - // middle of the thumb - this.onDrag(evt); + // middle of the thumb. + this.onDrag(evt, activeHandle); }; /** @@ -578,8 +597,9 @@ export default class Slider extends PureComponent { this.element?.ownerDocument.removeEventListener(element, this.onDrag); }); - // Set needsOnRelease flag so event fires on next update - this.setState({ needsOnRelease: true, isValid: true }); + // Set needsOnRelease flag so event fires on next update. Also unset the + // activeHandle. + this.setState({ needsOnRelease: true, isValid: true, activeHandle: null }); }; /** @@ -587,9 +607,13 @@ export default class Slider extends PureComponent { * accordingly. * * @param {Event} evt The event. + * @param activeHandle + * The first drag event call, we may have an explicit activeHandle value, + * which is to be used before state is used. */ - _onDrag = (evt) => { - // Do nothing if component is disabled or we have no event + _onDrag = (evt, activeHandle: HandlePosition | null = null) => { + activeHandle = activeHandle ?? this.state.activeHandle; + // Do nothing if component is disabled, or we have no event. if (this.props.disabled || this.props.readOnly || !evt) { return; } @@ -614,14 +638,8 @@ export default class Slider extends PureComponent { }); // If we're set to two handles, negotiate which drag handle is closest to // the users' interaction. - if (this.props.twoHandles) { - const distanceToLower = this.calcDistanceToHandle(LOWER, clientX); - const distanceToUpper = this.calcDistanceToHandle(UPPER, clientX); - if (distanceToLower <= distanceToUpper) { - this.setState({ valueLower: value, leftLower: left, isValid: true }); - } else { - this.setState({ valueUpper: value, leftUpper: left, isValid: true }); - } + if (this.props.twoHandles && activeHandle) { + this.setStateForHandle(activeHandle, { value, left }); } else { this.setState({ value, left, isValid: true }); } @@ -818,14 +836,35 @@ export default class Slider extends PureComponent { return { value: steppedValue, left: steppedPercent * 100 }; }; - calcDistanceToHandle(handle, clientX) { + calcDistanceToHandle(handle: HandlePosition, clientX) { // left is a whole value between 0 and 100. - const left = handle === LOWER ? this.state.leftLower : this.state.leftUpper; + const left = + handle === HandlePosition.LOWER + ? this.state.leftLower + : this.state.leftUpper; const boundingRect = this.element?.getBoundingClientRect(); const handleX = boundingRect.left + (left / 100) * boundingRect.width; return Math.abs(handleX - clientX); } + setStateForHandle(handle: HandlePosition, { value, left }) { + const { valueLower, valueUpper, leftLower, leftUpper } = this.state; + if (handle === HandlePosition.LOWER) { + // Don't allow higher than the upper handle. + this.setState({ + valueLower: valueUpper && value > valueUpper ? valueUpper : value, + leftLower: valueUpper && value > valueUpper ? leftUpper : left, + isValid: true, + }); + } else { + this.setState({ + valueUpper: valueLower && value < valueLower ? valueLower : value, + leftUpper: valueLower && value < valueLower ? leftLower : left, + isValid: true, + }); + } + } + // syncs invalid state and prop static getDerivedStateFromProps(props, state) { const { isValid } = state; @@ -974,7 +1013,9 @@ export default class Slider extends PureComponent { aria-valuenow={value} aria-labelledby={labelId} ref={twoHandles ? this.thumbRefLower : this.thumbRef} - onFocus={() => this.setState({ activeHandle: LOWER })} + onFocus={() => + this.setState({ activeHandle: HandlePosition.LOWER }) + } /> {twoHandles ? (
{ aria-valuenow={value} aria-labelledby={labelId} ref={this.thumbRefUpper} - onFocus={() => this.setState({ activeHandle: UPPER })} + onFocus={() => + this.setState({ activeHandle: HandlePosition.UPPER }) + } /> ) : null}
Date: Mon, 24 Jul 2023 13:39:02 -0400 Subject: [PATCH 07/62] feat(slider): remove unused expression --- packages/react/src/components/Slider/Slider.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 9652c03c8343..4458794d5723 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -682,9 +682,6 @@ export default class Slider extends PureComponent { delta *= stepMultiplier ?? Slider.defaultProps.stepMultiplier; } - Math.floor( - this.state.value / (this.props.step ?? Slider.defaultProps.step) - ) * (this.props.step ?? Slider.defaultProps.step); const { value, left } = this.calcValue({ // Ensures custom value from `` won't cause skipping next stepping point with right arrow key, // e.g. Typing 51 in ``, moving focus onto the thumb and the hitting right arrow key should yield 52 instead of 54 From d4a48e1608ee6429b75fa3b98c5a1b55194a3fb0 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Mon, 24 Jul 2023 14:08:56 -0400 Subject: [PATCH 08/62] feat(slider): collision detection for keyboard events --- .../react/src/components/Slider/Slider.tsx | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 4458794d5723..050b821efa5a 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -682,18 +682,28 @@ export default class Slider extends PureComponent { delta *= stepMultiplier ?? Slider.defaultProps.stepMultiplier; } - const { value, left } = this.calcValue({ - // Ensures custom value from `` won't cause skipping next stepping point with right arrow key, - // e.g. Typing 51 in ``, moving focus onto the thumb and the hitting right arrow key should yield 52 instead of 54 - value: - (delta > 0 - ? Math.floor( - this.state.value / (this.props.step ?? Slider.defaultProps.step) - ) * (this.props.step ?? Slider.defaultProps.step) - : this.state.value) + delta, - }); - - this.setState({ value, left, isValid: true }); + if (this.props.twoHandles && this.state.activeHandle) { + const currentValue = + this.state.activeHandle === HandlePosition.LOWER + ? this.state.valueLower + : this.state.valueUpper; + const { value, left } = this.calcValue({ + value: this.calcValueForDelta(currentValue, delta, this.props.step), + }); + this.setStateForHandle(this.state.activeHandle, { + value, + left, + }); + } else { + const { value, left } = this.calcValue({ + // Ensures custom value from `` won't cause skipping next stepping + // point with right arrow key, e.g. Typing 51 in ``, moving focus + // onto the thumb and the hitting right arrow key should yield 52 instead + // of 54. + value: this.calcValueForDelta(this.state.value, delta, this.props.step), + }); + this.setState({ value, left, isValid: true }); + } }; /** @@ -844,6 +854,13 @@ export default class Slider extends PureComponent { return Math.abs(handleX - clientX); } + calcValueForDelta(currentValue, delta, step = Slider.defaultProps.step) { + return ( + (delta > 0 ? Math.floor(currentValue / step) * step : currentValue) + + delta + ); + } + setStateForHandle(handle: HandlePosition, { value, left }) { const { valueLower, valueUpper, leftLower, leftUpper } = this.state; if (handle === HandlePosition.LOWER) { From 4d9e0a330aca771b814fa5dab27a72f96a2e0e11 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Mon, 24 Jul 2023 14:57:45 -0400 Subject: [PATCH 09/62] feat(slider): tighten up some type checks --- packages/react/src/components/Slider/Slider.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 050b821efa5a..6cb1418b4861 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -773,13 +773,16 @@ export default class Slider extends PureComponent { // If a clientX is specified, use it to calculate the leftPercent. If not, // use the provided value to calculate it instead. - if (clientX != null) { + if (clientX) { const leftOffset = clientX - (boundingRect?.left ?? 0); return leftOffset / width; - } else { + } else if (value && range) { // Prevent NaN calculation if the range is 0. return range === 0 ? 0 : (value - this.props.min) / range; } + // We should never end up in this scenario, but in case we do, and to + // re-assure Typescript, return 0. + return 0; }; calcSteppedValuePercent = ({ leftPercent, range }) => { @@ -849,7 +852,7 @@ export default class Slider extends PureComponent { handle === HandlePosition.LOWER ? this.state.leftLower : this.state.leftUpper; - const boundingRect = this.element?.getBoundingClientRect(); + const boundingRect = this.getSliderBoundingRect(); const handleX = boundingRect.left + (left / 100) * boundingRect.width; return Math.abs(handleX - clientX); } @@ -879,6 +882,11 @@ export default class Slider extends PureComponent { } } + getSliderBoundingRect() { + const boundingRect = this.element?.getBoundingClientRect(); + return boundingRect ?? new DOMRect(); + } + // syncs invalid state and prop static getDerivedStateFromProps(props, state) { const { isValid } = state; From 19039cbaaa13236ec34616151fc01c7621fb175d Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Mon, 24 Jul 2023 15:04:22 -0400 Subject: [PATCH 10/62] feat(slider): handle the empty case for the text inputs --- .../react/src/components/Slider/Slider.tsx | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 6cb1418b4861..5fa0f69cca45 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -727,18 +727,33 @@ export default class Slider extends PureComponent { const targetValue = Number.parseFloat(evt.target.value); - // Avoid calling calcValue for invalid numbers, but still update the state + // Avoid calling calcValue for invalid numbers, but still update the state. + const activeHandle = + evt.target.dataset.handlePosition ?? HandlePosition.LOWER; if (isNaN(targetValue)) { - this.setState({ value: evt.target.value }); + if (this.props.twoHandles && activeHandle === HandlePosition.LOWER) { + this.setState({ valueLower: evt.target.value }); + } else if ( + this.props.twoHandles && + activeHandle === HandlePosition.UPPER + ) { + this.setState({ valueUpper: evt.target.value }); + } else { + this.setState({ value: evt.target.value }); + } } else { const { value, left } = this.calcValue({ value: targetValue, useRawValue: true, }); - this.setState({ - value, - left, - }); + if (this.props.twoHandles) { + this.setStateForHandle(activeHandle, { value, left }); + } else { + this.setState({ + value, + left, + }); + } } }; @@ -1005,6 +1020,9 @@ export default class Slider extends PureComponent { onBlur={this.onBlur} onKeyUp={this.props.onInputKeyUp} data-invalid={!isValid && !readOnly ? true : null} + data-handle-position={ + twoHandles ? HandlePosition.LOWER : null + } aria-invalid={!isValid && !readOnly ? true : undefined} readOnly={readOnly} /> @@ -1086,6 +1104,9 @@ export default class Slider extends PureComponent { onBlur={this.onBlur} onKeyUp={this.props.onInputKeyUp} data-invalid={!isValid && !readOnly ? true : null} + data-handle-position={ + twoHandles ? HandlePosition.UPPER : null + } aria-invalid={!isValid && !readOnly ? true : undefined} readOnly={readOnly} /> From fc7a33da19472d9f5ccc8b93301aa473de798348 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Mon, 24 Jul 2023 17:26:51 -0400 Subject: [PATCH 11/62] feat(slider): adjust aria attributes for thumbs and inputs --- .../react/src/components/Slider/Slider.tsx | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 5fa0f69cca45..6f154a2cfdf1 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -53,6 +53,16 @@ export interface SliderProps */ ariaLabelInput?: string; + /** + * The `ariaLabel` for the lower bound `` when twoHandles is set. + */ + ariaLabelInputLower?: string; + + /** + * The `ariaLabel` for the upper bound `` when twoHandles is set. + */ + ariaLabelInputUpper?: string; + /** * The child nodes. */ @@ -226,6 +236,16 @@ export default class Slider extends PureComponent { */ ariaLabelInput: PropTypes.string, + /** + * The `ariaLabel` for the lower bound `` when twoHandles is set. + */ + ariaLabelInputLower: PropTypes.string, + + /** + * The `ariaLabel` for the upper bound `` when twoHandles is set. + */ + ariaLabelInputUpper: PropTypes.string, + /** * The child nodes. */ @@ -924,6 +944,8 @@ export default class Slider extends PureComponent { render() { const { ariaLabelInput, + ariaLabelInputLower, + ariaLabelInputUpper, className, hideTextInput, id = (this.inputId = @@ -1009,8 +1031,10 @@ export default class Slider extends PureComponent { name={nameLower} className={lowerInputClasses} value={valueLower} - aria-labelledby={!ariaLabelInput ? labelId : undefined} - aria-label={ariaLabelInput ? ariaLabelInput : undefined} + aria-labelledby={!ariaLabelInputLower ? labelId : undefined} + aria-label={ + ariaLabelInputLower ? ariaLabelInputLower : undefined + } disabled={disabled} required={required} min={min} @@ -1048,9 +1072,9 @@ export default class Slider extends PureComponent { role="slider" id={id} tabIndex={!readOnly ? 0 : -1} - aria-valuemax={max} + aria-valuemax={twoHandles ? valueUpper : max} aria-valuemin={min} - aria-valuenow={value} + aria-valuenow={twoHandles ? valueLower : value} aria-labelledby={labelId} ref={twoHandles ? this.thumbRefLower : this.thumbRef} onFocus={() => @@ -1064,8 +1088,8 @@ export default class Slider extends PureComponent { id={id} tabIndex={!readOnly ? 0 : -1} aria-valuemax={max} - aria-valuemin={min} - aria-valuenow={value} + aria-valuemin={valueLower} + aria-valuenow={valueUpper} aria-labelledby={labelId} ref={this.thumbRefUpper} onFocus={() => @@ -1093,8 +1117,19 @@ export default class Slider extends PureComponent { name={twoHandles ? nameUpper : name} className={twoHandles ? upperInputClasses : inputClasses} value={twoHandles ? valueUpper : value} - aria-labelledby={!ariaLabelInput ? labelId : undefined} - aria-label={ariaLabelInput ? ariaLabelInput : undefined} + aria-labelledby={ + (twoHandles && !ariaLabelInputUpper) || + (!twoHandles && !ariaLabelInput) + ? labelId + : undefined + } + aria-label={ + twoHandles && ariaLabelInputUpper + ? ariaLabelInputUpper + : ariaLabelInput + ? ariaLabelInput + : undefined + } disabled={disabled} required={required} min={min} From c95c165e613370f801dfef4bfac548dbb3294d0f Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 12:06:33 -0400 Subject: [PATCH 12/62] feat(slider): code consistency and some docs --- .../react/src/components/Slider/Slider.tsx | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 6f154a2cfdf1..69dd53a62395 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -37,6 +37,9 @@ const DRAG_EVENT_TYPES = new Set(['mousemove', 'touchmove']); */ const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']); +/** + * When twoHandles prop is set, we're in range slider mode with two handles. + */ enum HandlePosition { LOWER = 'lower', UPPER = 'upper', @@ -144,8 +147,14 @@ export interface SliderProps */ name?: string; + /** + * The `name` attribute of the lower bound `` when twoHandles is set. + */ nameLower?: string; + /** + * The `name` attribute of the upper bound `` when twoHandles is set. + */ nameUpper?: string; /** @@ -202,8 +211,14 @@ export interface SliderProps */ value: number; + /** + * The lower bound value when twoHandles in set. + */ valueLower?: number; + /** + * The upper bound value when twoHandles in set. + */ valueUpper?: number; /** @@ -330,8 +345,14 @@ export default class Slider extends PureComponent { */ name: PropTypes.string, + /** + * The `name` attribute of the lower bound `` when twoHandles is set. + */ nameLower: PropTypes.string, + /** + * The `name` attribute of the upper bound `` when twoHandles is set. + */ nameUpper: PropTypes.string, /** @@ -376,6 +397,9 @@ export default class Slider extends PureComponent { */ stepMultiplier: PropTypes.number, + /** + * Turn the slider into a range slider. + */ twoHandles: PropTypes.bool, /** @@ -383,8 +407,14 @@ export default class Slider extends PureComponent { */ value: PropTypes.number.isRequired, + /** + * The lower bound value when twoHandles in set. + */ valueLower: PropTypes.number, + /** + * The upper bound value when twoHandles in set. + */ valueUpper: PropTypes.number, /** @@ -881,7 +911,7 @@ export default class Slider extends PureComponent { return { value: steppedValue, left: steppedPercent * 100 }; }; - calcDistanceToHandle(handle: HandlePosition, clientX) { + calcDistanceToHandle = (handle: HandlePosition, clientX) => { // left is a whole value between 0 and 100. const left = handle === HandlePosition.LOWER @@ -890,16 +920,36 @@ export default class Slider extends PureComponent { const boundingRect = this.getSliderBoundingRect(); const handleX = boundingRect.left + (left / 100) * boundingRect.width; return Math.abs(handleX - clientX); - } + }; - calcValueForDelta(currentValue, delta, step = Slider.defaultProps.step) { + /** + * Given the current value, delta and step, calculate the new value. + * + * @param {number} currentValue + * Current value user is moving from. + * @param {number} delta + * Movement from the current value. Can be positive or negative. + * @param {number} step + * A value determining how much the value should increase/decrease by moving + * the thumb by mouse. + */ + calcValueForDelta = ( + currentValue, + delta, + step = Slider.defaultProps.step + ) => { return ( (delta > 0 ? Math.floor(currentValue / step) * step : currentValue) + delta ); - } + }; - setStateForHandle(handle: HandlePosition, { value, left }) { + /** + * Sets state relevant to the given handle position. + * + * Guards against setting either lower or upper values beyond its counterpart. + */ + setStateForHandle = (handle: HandlePosition, { value, left }) => { const { valueLower, valueUpper, leftLower, leftUpper } = this.state; if (handle === HandlePosition.LOWER) { // Don't allow higher than the upper handle. @@ -915,12 +965,17 @@ export default class Slider extends PureComponent { isValid: true, }); } - } + }; - getSliderBoundingRect() { + /** + * Get the bounding rect for the slider DOM element. + * + * If the bounding rect is not available, a new, empty DOMRect is returned. + */ + getSliderBoundingRect = (): DOMRect => { const boundingRect = this.element?.getBoundingClientRect(); return boundingRect ?? new DOMRect(); - } + }; // syncs invalid state and prop static getDerivedStateFromProps(props, state) { From 6b635c7623d121607ac22d73830b03b8c4826a38 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 12:28:59 -0400 Subject: [PATCH 13/62] feat(slider): expand the onChange callback for valueLower and valueUpper --- .../react/src/components/Slider/Slider.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 69dd53a62395..7a613bbdfee1 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -168,7 +168,11 @@ export interface SliderProps * `({ value}) => void` // * @param {{ value }} */ - onChange?: (data: { value: SliderProps['value'] }) => void; + onChange?: (data: { + value: SliderProps['value']; + valueLower: SliderProps['valueLower']; + valueUpper: SliderProps['valueUpper']; + }) => void; /** * Provide an optional function to be called when a key is pressed in the number input @@ -207,9 +211,9 @@ export interface SliderProps twoHandles?: boolean; /** - * The value. + * The single value when twoHandles is not set. */ - value: number; + value?: number; /** * The lower bound value when twoHandles in set. @@ -533,10 +537,16 @@ export default class Slider extends PureComponent { } } if ( - prevState.value !== this.state.value && + (prevState.value !== this.state.value || + prevState.valueLower !== this.state.valueLower || + prevState.valueUpper !== this.state.valueUpper) && typeof this.props.onChange === 'function' ) { - this.props.onChange({ value: this.state.value }); + this.props.onChange({ + value: this.state.value, + valueLower: this.state.valueLower, + valueUpper: this.state.valueUpper, + }); } // Fire onRelease event handler if present and if needed From ccba687306a032459b349ce8811400c2292ae294 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 12:45:28 -0400 Subject: [PATCH 14/62] feat(slider): expand the onBlur callback for valueLower and valueUpper --- packages/react/src/components/Slider/Slider.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 7a613bbdfee1..8e1c1c5f560e 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -161,7 +161,10 @@ export interface SliderProps * Provide an optional function to be called when the input element * loses focus */ - onBlur?: (data: { value: string }) => void; + onBlur?: (data: { + value: string; + handlePosition: HandlePosition | undefined; + }) => void; /** * The callback to get notified of change in value. @@ -834,7 +837,12 @@ export default class Slider extends PureComponent { const { value } = evt.target; this.setState({ isValid: validity }); - this.props.onBlur?.({ value }); + this.props.onBlur?.({ + value, + handlePosition: evt.target?.dataset?.handlePosition as + | HandlePosition + | undefined, + }); }; calcLeftPercent = ({ clientX, value, range }: CalcLeftPercentProps) => { From 4e22da674bc0ad47258391f3e41319de0a8fdb6c Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 13:46:11 -0400 Subject: [PATCH 15/62] feat(slider): expand the onInputKeyUp and onRelease callbacks --- packages/react/src/components/Slider/Slider.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.tsx b/packages/react/src/components/Slider/Slider.tsx index 8e1c1c5f560e..f91fb3dcda48 100644 --- a/packages/react/src/components/Slider/Slider.tsx +++ b/packages/react/src/components/Slider/Slider.tsx @@ -178,14 +178,18 @@ export interface SliderProps }) => void; /** - * Provide an optional function to be called when a key is pressed in the number input + * Provide an optional function to be called when a key is pressed in the number input. When twoHandles is set, you can obtain the relevant handle position by using `event.target.dataset.handlePosition`. */ onInputKeyUp?: KeyboardEventHandler; /** * The callback to get notified of value on handle release. */ - onRelease?: (data: { value: SliderProps['value'] }) => void; + onRelease?: (data: { + value: SliderProps['value']; + valueLower: SliderProps['valueLower']; + valueUpper: SliderProps['valueUpper']; + }) => void; /** * Whether the slider should be read-only @@ -557,7 +561,11 @@ export default class Slider extends PureComponent { this.state.needsOnRelease && typeof this.props.onRelease === 'function' ) { - this.props.onRelease({ value: this.state.value }); + this.props.onRelease({ + value: this.state.value, + valueLower: this.state.valueLower, + valueUpper: this.state.valueUpper, + }); // Reset the flag this.setState({ needsOnRelease: false }); } From f3746ec744e132874d29a2a52830dcdcf9d7c616 Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 15:22:44 -0400 Subject: [PATCH 16/62] feat(slider): tidy up slider stories a bit --- .../src/components/Slider/Slider.stories.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/react/src/components/Slider/Slider.stories.js b/packages/react/src/components/Slider/Slider.stories.js index f51353ad3d3a..fb13e24e8a9f 100644 --- a/packages/react/src/components/Slider/Slider.stories.js +++ b/packages/react/src/components/Slider/Slider.stories.js @@ -118,6 +118,15 @@ export const Playground = (args) => ( ); Playground.argTypes = { + ariaLabelInput: { + control: { type: 'text' }, + }, + ariaLabelInputLower: { + control: { type: 'text' }, + }, + ariaLabelInputUpper: { + control: { type: 'text' }, + }, light: { table: { disable: true, @@ -171,6 +180,15 @@ Playground.argTypes = { max: { control: { type: 'number' }, }, + name: { + control: { type: 'text' }, + }, + nameLower: { + control: { type: 'text' }, + }, + nameUpper: { + control: { type: 'text' }, + }, readOnly: { control: { type: 'boolean', @@ -190,6 +208,12 @@ Playground.argTypes = { value: { control: { type: 'number' }, }, + valueLower: { + control: { type: 'number' }, + }, + valueUpper: { + control: { type: 'number' }, + }, onBlur: { table: { disable: true, @@ -233,7 +257,10 @@ Playground.args = { required: false, step: 5, stepMultiplier: 5, + twoHandles: false, value: 50, + valueLower: 10, + valueUpper: 90, warn: false, warnText: 'Warning message goes here', }; From 8e38e5c87d143c920966aa251fe31987717a343f Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Tue, 25 Jul 2023 16:00:15 -0400 Subject: [PATCH 17/62] feat(slider): fix the aria labeling for range slider --- .../src/components/Slider/Slider.stories.js | 8 +++- .../react/src/components/Slider/Slider.tsx | 48 +++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/react/src/components/Slider/Slider.stories.js b/packages/react/src/components/Slider/Slider.stories.js index fb13e24e8a9f..28eb8ede17e6 100644 --- a/packages/react/src/components/Slider/Slider.stories.js +++ b/packages/react/src/components/Slider/Slider.stories.js @@ -96,6 +96,8 @@ export const ControlledSliderWithLayer = () => { export const TwoHandleSlider = () => ( ` when twoHandles is set. + * The `ariaLabel` for the lower bound `` and handle when twoHandles is set. */ - ariaLabelInputLower?: string; + ariaLabelLower?: string; /** - * The `ariaLabel` for the upper bound `` when twoHandles is set. + * The `ariaLabel` for the upper bound `` and handle when twoHandles is set. */ - ariaLabelInputUpper?: string; + ariaLabelUpper?: string; /** * The child nodes. @@ -263,14 +263,15 @@ export default class Slider extends PureComponent { ariaLabelInput: PropTypes.string, /** - * The `ariaLabel` for the lower bound `` when twoHandles is set. + * The `ariaLabel` for the lower bound `` and handle when twoHandles + * is set. */ - ariaLabelInputLower: PropTypes.string, + ariaLabelLower: PropTypes.string, /** - * The `ariaLabel` for the upper bound `` when twoHandles is set. + * The `ariaLabel` for the upper bound `` and when twoHandles is set. */ - ariaLabelInputUpper: PropTypes.string, + ariaLabelUpper: PropTypes.string, /** * The child nodes. @@ -1025,8 +1026,8 @@ export default class Slider extends PureComponent { render() { const { ariaLabelInput, - ariaLabelInputLower, - ariaLabelInputUpper, + ariaLabelLower, + ariaLabelUpper, className, hideTextInput, id = (this.inputId = @@ -1101,7 +1102,10 @@ export default class Slider extends PureComponent { return (
-