From f5b7d0be963035a122ffc5df9c2ee799f0f82d6a Mon Sep 17 00:00:00 2001 From: Joe Harvey <51208233+jdharvey-ibm@users.noreply.github.com> Date: Tue, 3 Mar 2020 16:35:04 -0500 Subject: [PATCH] fix(Slider): onRelease not always firing (#2695) (#5359) * fix(Slider): onRelease not always firing (#2695) * test(windows): fixing failing unit tests * Updating jest `testMatch` micromatch expressions (facebook/jest#7914) * Replacing `/` with `path.sep` in icon-build-helpers search.js * Replacing regex using `/` with `[\\/]` in test-utils scss.js * refactor(Slider): reworking event logic - Adding lodash.throttle as project dependency for use in Slider - Reworking Slider component's event handling - Including throttling in Slider event handling - Adjusting CSS for input element on Slider * test(Slider): updating/adding unit tests - Increasing (line) test coverage to 100% - Slight refactor of functions to be more testable - Correctly handling hidden text input - Correctly handling touchmove events - Gracefully handling edge case of zero-width bounding rect * fix(Slider): code review updates - Removing usage of functions that aren't available in all browsers - Adding unit test for display:none style on hidden text input * fix(Slider): code review updates - Removing throttling from keydown handling - Updating unit tests accordingly * refactor(Slider): use matches module Using `matches(...)` for arrow key event matching instead of custom impl Co-authored-by: Josh Black Co-authored-by: TJ Egan Co-authored-by: Abbey Hart --- package.json | 4 +- .../src/components/slider/_slider.scss | 1 - packages/icon-build-helpers/src/search.js | 2 +- packages/react/package.json | 1 + .../src/components/Slider/Slider-test.js | 221 +++++++- .../react/src/components/Slider/Slider.js | 473 ++++++++++-------- packages/test-utils/scss.js | 2 +- 7 files changed, 482 insertions(+), 222 deletions(-) diff --git a/package.json b/package.json index 1bd369a6abbf..36064e631511 100644 --- a/package.json +++ b/package.json @@ -175,8 +175,8 @@ ], "testMatch": [ "/**/__tests__/**/*.js?(x)", - "/**/?(*.)(spec|test).js?(x)", - "/**/?(*-)(spec|test).js?(x)" + "/**/*.(spec|test).js?(x)", + "/**/*-(spec|test).js?(x)" ], "transform": { "^.+\\.(js|jsx)$": "./tasks/jest/jsTransform.js", diff --git a/packages/components/src/components/slider/_slider.scss b/packages/components/src/components/slider/_slider.scss index b1bad6ed745c..f42f85af31b1 100644 --- a/packages/components/src/components/slider/_slider.scss +++ b/packages/components/src/components/slider/_slider.scss @@ -117,7 +117,6 @@ .#{$prefix}-slider-text-input { width: rem(64px); height: rem(40px); - padding: 0; text-align: center; -moz-appearance: textfield; diff --git a/packages/icon-build-helpers/src/search.js b/packages/icon-build-helpers/src/search.js index 07c71bfcb7dd..4412d0d40d5c 100644 --- a/packages/icon-build-helpers/src/search.js +++ b/packages/icon-build-helpers/src/search.js @@ -55,7 +55,7 @@ async function search(directory) { const dirname = path.dirname(filepath); const prefix = path .relative(directory, dirname) - .split('/') + .split(path.sep) .filter(Boolean); return { ...file, diff --git a/packages/react/package.json b/packages/react/package.json index 38937b930f7d..a47d929e8f17 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -47,6 +47,7 @@ "lodash.findlast": "^4.5.0", "lodash.isequal": "^4.5.0", "lodash.omit": "^4.5.0", + "lodash.throttle": "^4.1.1", "react-is": "^16.8.6", "warning": "^3.0.0", "window-or-global": "^1.0.1" diff --git a/packages/react/src/components/Slider/Slider-test.js b/packages/react/src/components/Slider/Slider-test.js index c3974ce790eb..e88def03f200 100644 --- a/packages/react/src/components/Slider/Slider-test.js +++ b/packages/react/src/components/Slider/Slider-test.js @@ -15,9 +15,10 @@ import { settings } from 'carbon-components'; const { prefix } = settings; describe('Slider', () => { describe('Renders as expected', () => { + const id = 'slider'; const wrapper = mount( { wrapper.setProps({ light: true }); expect(wrapper.props().light).toEqual(true); }); + + it('marks input field as hidden if hidden via props', () => { + wrapper.setProps({ hideTextInput: true }); + expect(wrapper.find(`#${id}-input-for-slider`).props().type).toEqual( + 'hidden' + ); + }); + + it('sets style to display:none on input field if hidden via props', () => { + wrapper.setProps({ hideTextInput: true }); + expect(wrapper.find(`#${id}-input-for-slider`).props().style).toEqual({ + display: 'none', + }); + }); }); describe('Supporting label', () => { @@ -102,7 +117,7 @@ describe('Slider', () => { }); }); - describe('updatePosition method', () => { + describe('key/mouse event processing', () => { const handleChange = jest.fn(); const handleRelease = jest.fn(); const wrapper = mount( @@ -113,6 +128,7 @@ describe('Slider', () => { min={0} max={100} step={1} + stepMultiplier={5} onChange={handleChange} onRelease={handleRelease} /> @@ -123,9 +139,9 @@ describe('Slider', () => { type: 'keydown', which: '38', }; - wrapper.instance().updatePosition(evt); - expect(handleChange).lastCalledWith({ value: 51 }); + wrapper.instance().onKeyDown(evt); expect(wrapper.state().value).toEqual(51); + expect(handleChange).lastCalledWith({ value: 51 }); }); it('sets correct state from event with a left/down keydown', () => { @@ -133,28 +149,68 @@ describe('Slider', () => { type: 'keydown', which: '40', }; - wrapper.instance().updatePosition(evt); - expect(handleChange).lastCalledWith({ value: 50 }); + wrapper.instance().onKeyDown(evt); expect(wrapper.state().value).toEqual(50); + expect(handleChange).lastCalledWith({ value: 50 }); }); - it('sets correct state from event with a clientX', () => { + it('correctly uses setMultiplier with a right/up keydown', () => { const evt = { - type: 'click', + type: 'keydown', + which: '38', + shiftKey: true, + }; + wrapper.instance().onKeyDown(evt); + expect(wrapper.state().value).toEqual(55); + expect(handleChange).lastCalledWith({ value: 55 }); + }); + + it('sets correct state from event with a clientX in a mousemove', () => { + const evt = { + type: 'mousemove', clientX: '1000', }; - wrapper.instance().updatePosition(evt); + wrapper.instance()._onDrag(evt); expect(handleChange).lastCalledWith({ value: 100 }); expect(wrapper.state().value).toEqual(100); }); + it('sets correct state from event with a clientX in a touchmove', () => { + const evt = { + type: 'touchmove', + touches: [{ clientX: '0' }], + }; + wrapper.instance()._onDrag(evt); + expect(handleChange).lastCalledWith({ value: 0 }); + expect(wrapper.state().value).toEqual(0); + }); + + it('throttles mousemove events', () => { + const evt1 = { + type: 'mousemove', + clientX: '1000', + }; + const evt2 = { + type: 'mousemove', + clientX: '0', + }; + wrapper.instance().onDrag(evt1); + wrapper.instance().onDrag(evt2); + expect(wrapper.state().value).toEqual(100); + expect(handleChange).lastCalledWith({ value: 100 }); + }); + describe('user is holding the handle', () => { it('does not call onRelease', () => { + const evt = { + type: 'mousemove', + clientX: '1000', + }; handleRelease.mockClear(); expect(handleRelease).not.toHaveBeenCalled(); - wrapper.instance().handleMouseStart(); - wrapper.instance().updatePosition(); + wrapper.instance().onDragStart(evt); + wrapper.instance().onDrag(evt); expect(handleRelease).not.toHaveBeenCalled(); }); }); @@ -164,12 +220,151 @@ describe('Slider', () => { handleRelease.mockClear(); expect(handleRelease).not.toHaveBeenCalled(); wrapper.setState({ - holding: false, + needsOnRelease: true, }); - wrapper.instance().updatePosition(); + wrapper.instance().onDragStop(); expect(handleRelease).toHaveBeenCalled(); }); }); + + it('sets correct state when typing in input field', () => { + const evt = { + target: { + value: '999', + }, + }; + wrapper.instance().onChange(evt); + expect(wrapper.state().value).toEqual(100); + expect(handleChange).lastCalledWith({ value: 100 }); + }); + }); + + describe('error handling', () => { + const handleChange = jest.fn(); + const handleRelease = jest.fn(); + const wrapper = mount( + + ); + + it('handles non-number typed into input field', () => { + const evt = { + target: { + value: '', + }, + }; + wrapper.instance().onChange(evt); + expect(wrapper.state().value).toEqual(''); + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('gracefully tolerates empty event passed to _onDrag', () => { + const evt = {}; + wrapper.instance()._onDrag(evt); + expect(wrapper.state().value).toEqual(''); // from last test + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('gracefully tolerates empty event passed to onChange', () => { + const evt = {}; + wrapper.instance().onChange(evt); + expect(wrapper.state().value).toEqual(''); // from last test + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('gracefully tolerates empty event passed to onKeyDown', () => { + const evt = {}; + wrapper.instance().onKeyDown(evt); + expect(wrapper.state().value).toEqual(''); // from last test + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('gracefully tolerates bad key code passed to onKeyDown', () => { + const evt = { + which: '123', + }; + wrapper.instance().onKeyDown(evt); + expect(wrapper.state().value).toEqual(''); // from last test + expect(handleChange).not.toHaveBeenCalled(); + }); + }); + + describe('slider is disabled', () => { + const handleChange = jest.fn(); + const handleRelease = jest.fn(); + const wrapper = mount( + + ); + + it('does nothing when trying to type in the input', () => { + const evt = { + target: { + value: '', + }, + }; + wrapper.instance().onChange(evt); + expect(wrapper.state().value).toEqual(50); + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('does nothing when trying to start a drag', () => { + const evt = { + type: 'mousedown', + clientX: '1001', + }; + wrapper.instance().onDragStart(evt); + expect(wrapper.state().value).toEqual(50); + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('does nothing when trying to drag', () => { + const evt = { + type: 'mousemove', + clientX: '1000', + }; + wrapper.instance()._onDrag(evt); + expect(wrapper.state().value).toEqual(50); + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('does nothing when trying to stop a drag', () => { + const evt = { + type: 'mouseup', + clientX: '1001', + }; + wrapper.instance().onDragStop(evt); + expect(wrapper.state().needsOnRelease).toEqual(false); + expect(handleChange).not.toHaveBeenCalled(); + expect(handleRelease).not.toHaveBeenCalled(); + }); + + it('does nothing when using arrow key', () => { + const evt = { + type: 'keydown', + which: '40', + }; + wrapper.instance().onKeyDown(evt); + expect(wrapper.state().value).toEqual(50); + expect(handleChange).not.toHaveBeenCalled(); + }); }); }); diff --git a/packages/react/src/components/Slider/Slider.js b/packages/react/src/components/Slider/Slider.js index 3d62b5aa8af4..c1c29fd1d573 100644 --- a/packages/react/src/components/Slider/Slider.js +++ b/packages/react/src/components/Slider/Slider.js @@ -9,6 +9,10 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { settings } from 'carbon-components'; +import throttle from 'lodash.throttle'; + +import * as keys from '../../internal/keyboard/keys'; +import { matches } from '../../internal/keyboard/match'; import deprecate from '../../prop-types/deprecate'; const { prefix } = settings; @@ -17,6 +21,21 @@ const defaultFormatLabel = (value, label) => { return typeof label === 'function' ? label(value) : `${value}${label}`; }; +/** + * Minimum time between processed "drag" events. + */ +const EVENT_THROTTLE = 16; // ms + +/** + * Event types that trigger "drags". + */ +const DRAG_EVENT_TYPES = new Set(['mousemove', 'touchmove']); + +/** + * Event types that trigger a "drag" to stop. + */ +const DRAG_STOP_EVENT_TYPES = new Set(['mouseup', 'touchend', 'touchcancel']); + export default class Slider extends PureComponent { static propTypes = { /** @@ -143,221 +162,273 @@ export default class Slider extends PureComponent { }; state = { - dragging: false, - holding: false, value: this.props.value, left: 0, + needsOnRelease: false, }; - static getDerivedStateFromProps({ value, min, max }, state) { - const { value: currentValue, prevValue, prevMin, prevMax } = state; - if (prevValue === value && prevMin === min && prevMax === max) { - return null; - } - const effectiveValue = Math.min( - Math.max(prevValue === value ? currentValue : value, min), - max - ); - return { - value: effectiveValue, - left: ((effectiveValue - min) / (max - min)) * 100, - prevValue: value, - prevMin: min, - prevMax: max, - }; + /** + * Sets up initial slider position and value in response to component mount. + */ + componentDidMount() { + const { value, left } = this.calcValue({}); + this.setState({ value, left }); } - updatePosition = evt => { - if (evt && this.props.disabled) { - return; + /** + * Handles firing of `onChange` and `onRelease` callbacks to parent in + * response to state changes. + * + * @param {*} _ Unused (prevProps) + * @param {*} prevState The previous Slider state, used to see if callbacks + * should be called. + */ + componentDidUpdate(_, prevState) { + // Fire onChange event handler if present, if there's a usable value, and + // if the value is different from the last one + if ( + this.state.value !== '' && + prevState.value !== this.state.value && + typeof this.props.onChange === 'function' + ) { + this.props.onChange({ value: this.state.value }); } - if (evt && evt.dispatchConfig) { - evt.persist(); + // Fire onRelease event handler if present and if needed + if ( + this.state.needsOnRelease && + typeof this.props.onRelease === 'function' + ) { + this.props.onRelease({ value: this.state.value }); + // Reset the flag + this.setState({ needsOnRelease: false }); } + } - if (this.state.dragging) { + /** + * Synonymous to ECMA2017+ `Math.clamp`. + * + * @param {number} val + * @param {number} min + * @param {number} max + * + * @returns `val` if `max>=val>=min`; `min` if `valmax`. + */ + clamp(val, min, max) { + return Math.max(min, Math.min(val, max)); + } + + /** + * Sets up "drag" event handlers and calls `this.onDrag` in case dragging + * started on somewhere other than the thumb without a corresponding "move" + * event. + * + * @param {Event} evt The event. + */ + onDragStart = evt => { + // Do nothing if component is disabled + if (this.props.disabled) { return; } - this.setState({ dragging: true }); - - this.handleDrag(); - - requestAnimationFrame(() => { - this.setState((prevState, props) => { - // Note: In FF, `evt.target` of `mousemove` event can be `HTMLDocument` which doesn't have `classList`. - // One example is dragging out of browser viewport. - const fromInput = - evt && - evt.target && - evt.target.classList && - evt.target.classList.contains('bx-slider-text-input'); - const { left, newValue: newSliderValue } = this.calcValue( - evt, - prevState, - props - ); - const newValue = fromInput ? Number(evt.target.value) : newSliderValue; - if (prevState.left === left && prevState.value === newValue) { - return { dragging: false }; - } - if (typeof props.onChange === 'function') { - props.onChange({ value: newValue }); - } - return { - dragging: false, - left, - value: newValue, - }; - }); + + // Register drag stop handlers + DRAG_STOP_EVENT_TYPES.forEach(element => { + this.element.ownerDocument.addEventListener(element, this.onDragStop); }); - }; - calcValue = (evt, prevState, props) => { - const { min, max, step, stepMuliplier, stepMultiplier } = props; + // Register drag handlers + DRAG_EVENT_TYPES.forEach(element => { + this.element.ownerDocument.addEventListener(element, this.onDrag); + }); - const { value } = prevState; + // Perform first recalculation since we probably didn't click exactly in the + // middle of the thumb + this.onDrag(evt); + }; - const range = max - min; - const valuePercentage = ((value - min) / range) * 100; + /** + * Unregisters "drag" and "drag stop" event handlers and calls sets the flag + * inidicating that the `onRelease` callback should be called. + */ + onDragStop = () => { + // Do nothing if component is disabled + if (this.props.disabled) { + return; + } - let left; - let newValue; - left = valuePercentage; - newValue = value; + // Remove drag stop handlers + DRAG_STOP_EVENT_TYPES.forEach(element => { + this.element.ownerDocument.removeEventListener(element, this.onDragStop); + }); - if (evt) { - const { type } = evt; + // Remove drag handlers + DRAG_EVENT_TYPES.forEach(element => { + this.element.ownerDocument.removeEventListener(element, this.onDrag); + }); - if (type === 'keydown') { - const direction = { - 40: -1, // decreasing - 37: -1, // decreasing - 38: 1, // increasing - 39: 1, // increasing - }[evt.which]; + // Set needsOnRelease flag so event fires on next update + this.setState({ needsOnRelease: true }); + }; - const multiplyStep = stepMuliplier || stepMultiplier; + /** + * Handles a "drag" event by recalculating the value/thumb and setting state + * accordingly. + * + * @param {Event} evt The event. + */ + _onDrag = evt => { + // Do nothing if component is disabled or we have no event + if (this.props.disabled || !evt) { + return; + } - if (direction !== undefined) { - const multiplier = - evt.shiftKey === true ? range / step / multiplyStep : 1; - const stepMultiplied = step * multiplier; - const stepSize = (stepMultiplied / range) * 100; - left = valuePercentage + stepSize * direction; - newValue = Number(value) + stepMultiplied * direction; - } - } - if (type === 'mousemove' || type === 'click' || type === 'touchmove') { - const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX; - const track = this.track.getBoundingClientRect(); - const ratio = (clientX - track.left) / track.width; - const rounded = min + Math.round((range * ratio) / step) * step; - left = ((rounded - min) / range) * 100; - newValue = rounded; - } + let clientX; + if ('clientX' in evt) { + clientX = evt.clientX; + } else if ( + 'touches' in evt && + 0 in evt.touches && + 'clientX' in evt.touches[0] + ) { + clientX = evt.touches[0].clientX; + } else { + // Do nothing if we have no valid clientX + return; } - if (newValue <= Number(min)) { - left = 0; - newValue = min; + const { value, left } = this.calcValue({ clientX }); + this.setState({ value, left }); + }; + + /** + * Throttles calls to `this._onDrag` by limiting events to being processed at + * most once every `EVENT_THROTTLE` milliseconds. + */ + onDrag = throttle(this._onDrag, EVENT_THROTTLE, { + leading: true, + trailing: false, + }); + + /** + * Handles a `keydown` event by recalculating the value/thumb and setting + * state accordingly. + * + * @param {Event} evt The event. + */ + onKeyDown = evt => { + // Do nothing if component is disabled or we don't have a valid event + if (this.props.disabled || !('which' in evt)) { + return; } - if (newValue >= Number(max)) { - left = 100; - newValue = max; + + const which = Number.parseInt(evt.which); + let delta = 0; + if (matches(which, [keys.ArrowDown, keys.ArrowLeft])) { + delta = -this.props.step; + } else if (matches(which, [keys.ArrowUp, keys.ArrowRight])) { + delta = this.props.step; + } else { + // Ignore keys we don't want to handle + return; } - return { left, newValue }; - }; + // If shift was held, account for the stepMultiplier + if (evt.shiftKey) { + const stepMultiplier = + this.props.stepMultiplier || this.props.stepMuliplier; + delta *= stepMultiplier; + } - handleMouseStart = () => { - this.setState({ - holding: true, + const { value, left } = this.calcValue({ + value: this.state.value + delta, }); - - this.element.ownerDocument.addEventListener( - 'mousemove', - this.updatePosition - ); - this.element.ownerDocument.addEventListener('mouseup', this.handleMouseEnd); + this.setState({ value, left }); }; - handleMouseEnd = () => { - this.setState( - { - holding: false, - }, - this.updatePosition - ); + /** + * Provides the two-way binding for the input field of the Slider. It also + * Handles a change to the input field by recalculating the value/thumb and + * setting state accordingly. + * + * @param {Event} evt The event. + */ + onChange = evt => { + // Do nothing if component is disabled + if (this.props.disabled) { + return; + } - this.element.ownerDocument.removeEventListener( - 'mousemove', - this.updatePosition - ); - this.element.ownerDocument.removeEventListener( - 'mouseup', - this.handleMouseEnd - ); - }; + // Do nothing if we have no valid event, target, or value + if (!evt || !('target' in evt) || typeof evt.target.value !== 'string') { + return; + } - handleTouchStart = () => { - this.setState({ - holding: true, - }); - this.element.ownerDocument.addEventListener( - 'touchmove', - this.updatePosition - ); - this.element.ownerDocument.addEventListener('touchup', this.handleTouchEnd); - this.element.ownerDocument.addEventListener( - 'touchend', - this.handleTouchEnd - ); - this.element.ownerDocument.addEventListener( - 'touchcancel', - this.handleTouchEnd - ); - }; + let targetValue = Number.parseFloat(evt.target.value); - handleTouchEnd = () => { - this.setState( - { - holding: false, - }, - this.updatePosition - ); + // Avoid calling calcValue for invaid numbers, but still update the state + if (isNaN(targetValue)) { + this.setState({ value: evt.target.value }); + } else { + targetValue = this.clamp(targetValue, this.props.min, this.props.max); - this.element.ownerDocument.removeEventListener( - 'touchmove', - this.updatePosition - ); - this.element.ownerDocument.removeEventListener( - 'touchup', - this.handleTouchEnd - ); - this.element.ownerDocument.removeEventListener( - 'touchend', - this.handleTouchEnd - ); - this.element.ownerDocument.removeEventListener( - 'touchcancel', - this.handleTouchEnd - ); + // Recalculate the state's value and update the Slider + const { value, left } = this.calcValue({ value: targetValue }); + this.setState({ value, left, needsOnRelease: true }); + } }; - handleChange = evt => { - this.setState({ value: evt.target.value }); - this.updatePosition(evt); - }; + /** + * Calculates a new Slider `value` and `left` (thumb offset) given a `clientX`, + * `value`, or neither of those. + * - If `clientX` is specified, it will be used in + * conjunction with the Slider's bounding rectangle to calculate the new + * values. + * - If `clientX` is not specified and `value` is, it will be used to + * calculate new values as though it were the current value of the Slider. + * - If neither `clientX` nor `value` are specified, `this.props.value` will + * be used to calculate the new values as though it were the current value + * of the Slider. + * + * @param {object} params + * @param {number} [params.clientX] Optional clientX value expected to be from + * an event fired by one of the Slider's `DRAG_EVENT_TYPES` events. + * @param {number} [params.value] Optional value use during calculations if + * clientX is not provided. + */ + calcValue = ({ clientX = null, value = null }) => { + const range = this.props.max - this.props.min; + const boundingRect = this.element.getBoundingClientRect(); + const totalSteps = range / this.props.step; + let width = boundingRect.right - boundingRect.left; + + // Enforce a minimum width of at least 1 for calculations + if (width <= 0) { + width = 1; + } - handleDrag = () => { - if ( - typeof this.props.onRelease === 'function' && - !this.props.disabled && - !this.state.holding - ) { - this.props.onRelease({ value: this.state.value }); + // 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; + leftPercent = leftOffset / width; + } else { + if (value == null) { + value = this.state.value; + } + leftPercent = value / (range - this.props.min); } + + let steppedValue = Math.round(leftPercent * totalSteps) * this.props.step; + let steppedPercent = this.clamp(steppedValue / range, 0, 1); + + steppedValue = this.clamp( + steppedValue + this.props.min, + this.props.min, + this.props.max + ); + + return { value: steppedValue, left: steppedPercent * 100 }; }; render() { @@ -416,6 +487,9 @@ export default class Slider extends PureComponent { const thumbStyle = { left: `${left}%`, }; + const hiddenInputStyle = { + display: 'none', + }; return (
@@ -431,8 +505,9 @@ export default class Slider extends PureComponent { ref={node => { this.element = node; }} - onClick={this.updatePosition} - onKeyPress={this.updatePosition} + onMouseDown={this.onDragStart} + onTouchStart={this.onDragStart} + onKeyDown={this.onKeyDown} role="presentation" tabIndex={-1} {...other}> @@ -445,9 +520,6 @@ export default class Slider extends PureComponent { aria-valuemin={min} aria-valuenow={value} style={thumbStyle} - onMouseDown={this.handleMouseStart} - onTouchStart={this.handleTouchStart} - onKeyDown={this.updatePosition} />
-
{formatLabel(max, maxLabel)} - {!hideTextInput && ( - - )} +
); diff --git a/packages/test-utils/scss.js b/packages/test-utils/scss.js index 878f3a4079d4..8866615f1fa7 100644 --- a/packages/test-utils/scss.js +++ b/packages/test-utils/scss.js @@ -51,7 +51,7 @@ function createImporter(cwd) { }, pathFilter(pkg, path, relativePath) { // Transforms `scss/filename` to `scss/_filename.scss` - return relativePath.replace(/^(scss\/)([a-z-]+)/, '$1_$2.scss'); + return relativePath.replace(/^(scss[\\/])([a-z-]+)/, '$1_$2.scss'); }, }); done({ file });