diff --git a/src/lib/Preview.js b/src/lib/Preview.js index 6cbfef2c1..a71b71da7 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -1876,7 +1876,7 @@ class Preview extends EventEmitter { // Ignore key events when we are inside certain fields if ( !target || - (KEYDOWN_EXCEPTIONS.indexOf(target.nodeName) > -1 && !target.getAttribute('data-allow-keydown')) || + KEYDOWN_EXCEPTIONS.indexOf(target.nodeName) > -1 || (target.nodeName === 'DIV' && !!target.getAttribute('contenteditable')) ) { return; diff --git a/src/lib/__tests__/Preview-test.js b/src/lib/__tests__/Preview-test.js index 6962447e8..2f27bd913 100644 --- a/src/lib/__tests__/Preview-test.js +++ b/src/lib/__tests__/Preview-test.js @@ -2762,15 +2762,6 @@ describe('lib/Preview', () => { expect(stubs.event.preventDefault).not.toBeCalled(); }); - test('should pass the event to the viewer if the target is an input but allows keydown handling', () => { - stubs.decodeKeydown.mockReturnValue('M'); - stubs.event.target.getAttribute = jest.fn(() => true); // data-allow-keydown=true - preview.viewer.onKeydown = jest.fn(() => true); - preview.keydownHandler(stubs.event); - - expect(preview.viewer.onKeydown).toBeCalledWith('M', stubs.event); - }); - test('should navigate left is key is ArrowLeft and the event has not been consumed', () => { preview.viewer.onKeydown = jest.fn(() => false); stubs.event.target.nodeName = 'ArrowLeft'; diff --git a/src/lib/viewers/controls/media/TimeControls.scss b/src/lib/viewers/controls/media/TimeControls.scss index 52d699dc9..585f795c6 100644 --- a/src/lib/viewers/controls/media/TimeControls.scss +++ b/src/lib/viewers/controls/media/TimeControls.scss @@ -1,35 +1,31 @@ -@import '../slider/styles'; - -$bp-TimeControls-size: 18px; // Divisible by 3px (default) and 6px (hovered) for better vertical centering -$bp-TimeControls-space: 10px; +@import '../styles'; .bp-TimeControls { - height: $bp-TimeControls-size; + position: relative; + margin-right: 10px; + margin-left: 10px; +} - .bp-SliderControl-input { - padding-right: $bp-TimeControls-space; - padding-left: $bp-TimeControls-space; +.bp-TimeControls-slider { + height: 18px; // Divisible by 3px (default) and 6px (hovered) for better vertical centering - @include bp-SliderThumb { - transform: scale(0); - transition: transform 100ms ease; - will-change: transform; // Prevent flickering in Safari - } + .bp-SliderControl-thumb { + transform: scale(0); + transition: transform 100ms ease; + will-change: transform; // Prevent flickering in Safari } .bp-SliderControl-track { - margin-right: $bp-TimeControls-space; - margin-left: $bp-TimeControls-space; backface-visibility: hidden; // Prevent jank in Firefox transition: transform 100ms ease; will-change: transform; // Prevent flickering in Safari } + &.bp-is-scrubbing, + &:focus, &:hover { - .bp-SliderControl-input { - @include bp-SliderThumb { - transform: scale(1); - } + .bp-SliderControl-thumb { + transform: scale(1); } .bp-SliderControl-track { diff --git a/src/lib/viewers/controls/media/TimeControls.tsx b/src/lib/viewers/controls/media/TimeControls.tsx index 91d7fdbfa..75d95ad30 100644 --- a/src/lib/viewers/controls/media/TimeControls.tsx +++ b/src/lib/viewers/controls/media/TimeControls.tsx @@ -25,22 +25,24 @@ export default function TimeControls({ durationTime = 0, onTimeChange, }: Props): JSX.Element { - const currentValue = isFinite(currentTime) && isFinite(durationTime) ? percent(currentTime, durationTime) : 0; + const currentValue = isFinite(currentTime) ? currentTime : 0; + const durationValue = isFinite(durationTime) ? durationTime : 0; + const currentPercentage = percent(currentValue, durationValue); const bufferedAmount = bufferedRange && bufferedRange.length ? bufferedRange.end(bufferedRange.length - 1) : 0; - const bufferedValue = percent(bufferedAmount, durationTime); - - const handleChange = (newValue: number): void => { - onTimeChange(round(durationTime * (newValue / 100))); - }; + const bufferedPercentage = percent(bufferedAmount, durationValue); return ( - +
+ +
); } diff --git a/src/lib/viewers/controls/media/VolumeControls.scss b/src/lib/viewers/controls/media/VolumeControls.scss index d469c06af..f9eeb3692 100644 --- a/src/lib/viewers/controls/media/VolumeControls.scss +++ b/src/lib/viewers/controls/media/VolumeControls.scss @@ -18,7 +18,10 @@ } .bp-VolumeControls-slider { - width: 100px; + width: 100px - $bp-SliderControl-thumb-size; + height: 100%; + margin-right: $bp-SliderControl-thumb-radius; + margin-left: $bp-SliderControl-thumb-radius; } .bp-VolumeControls-toggle { diff --git a/src/lib/viewers/controls/media/VolumeControls.tsx b/src/lib/viewers/controls/media/VolumeControls.tsx index 77029cf8d..161df176f 100644 --- a/src/lib/viewers/controls/media/VolumeControls.tsx +++ b/src/lib/viewers/controls/media/VolumeControls.tsx @@ -8,7 +8,6 @@ import IconVolumeMute24 from '../icons/IconVolumeMute24'; import MediaToggle from './MediaToggle'; import SliderControl from '../slider'; import useAttention from '../hooks/useAttention'; -import { decodeKeydown } from '../../../util'; import './VolumeControls.scss'; export type Props = { @@ -38,34 +37,30 @@ export default function VolumeControls({ onMuteChange, onVolumeChange, volume = const title = isMuted ? __('media_unmute') : __('media_mute'); const value = Math.round(volume * 100); - const handleKeydown = (event: React.KeyboardEvent): void => { - const key = decodeKeydown(event); - - // Allow the range input to handle its own left/right keydown events - if (key === 'ArrowLeft' || key === 'ArrowRight') { - event.stopPropagation(); // Prevents global key handling - } - }; - - const handleMute = (): void => { - onMuteChange(!isMuted); - }; - - const handleVolume = (newValue: number): void => { - onVolumeChange(newValue / 100); - }; + const handleVolume = React.useCallback( + (newValue: number): void => { + onVolumeChange(newValue / 100); + }, + [onVolumeChange], + ); return (
- + onMuteChange(!isMuted)} + title={title} + {...handlers} + >
{ const getWrapper = (props = {}): ShallowWrapper => shallow(); - describe('event handlers', () => { - test.each` - percentage | value - ${0} | ${0} - ${20} | ${1111} - ${33} | ${1833.15} - ${33.3333} | ${1851.6648} - ${100} | ${5555} - `( - 'should calculate the absolute value $value based on the relative slider percentage $percentage', - ({ percentage, value }) => { - const onChange = jest.fn(); - const wrapper = getWrapper({ durationTime: 5555, onTimeChange: onChange }); - const slider = wrapper.find(SliderControl); - - slider.simulate('change', percentage); - - expect(onChange).toBeCalledWith(value); - }, - ); - }); - describe('render', () => { test('should return a valid wrapper', () => { const wrapper = getWrapper(); expect(wrapper.hasClass('bp-TimeControls')).toBe(true); - expect(wrapper.prop('step')).toEqual(0.1); + expect(wrapper.find(SliderControl).props()).toMatchObject({ + max: 10000, + min: 0, + step: 5, + value: 0, + }); }); - test('should not calculate percentage for invalid currentTime value', () => { - const wrapper = getWrapper({ currentTime: NaN, durationTime: 100 }); + test('should not default to zero for invalid currentTime value', () => { + const wrapper = getWrapper({ currentTime: NaN }); expect(wrapper.find(SliderControl).prop('value')).toBe(0); }); - test('should not calculate percentage for invalid durationTime value', () => { - const wrapper = getWrapper({ currentTime: 100, durationTime: NaN }); - expect(wrapper.find(SliderControl).prop('value')).toBe(0); + test('should default to zero for invalid durationTime value', () => { + const wrapper = getWrapper({ durationTime: NaN }); + expect(wrapper.find(SliderControl).prop('max')).toBe(0); }); test.each` - currentTime | track | value - ${0} | ${'linear-gradient(to right, #0061d5 0%, #fff 0%, #fff 10%, #767676 10%, #767676 100%)'} | ${0} - ${50} | ${'linear-gradient(to right, #0061d5 0.5%, #fff 0.5%, #fff 10%, #767676 10%, #767676 100%)'} | ${0.5} - ${1000} | ${'linear-gradient(to right, #0061d5 10%, #fff 10%, #fff 10%, #767676 10%, #767676 100%)'} | ${10} - ${2500} | ${'linear-gradient(to right, #0061d5 25%, #fff 25%, #fff 10%, #767676 10%, #767676 100%)'} | ${25} - ${10000} | ${'linear-gradient(to right, #0061d5 100%, #fff 100%, #fff 10%, #767676 10%, #767676 100%)'} | ${100} - `('should render the correct track and value for currentTime $currentTime', ({ currentTime, track, value }) => { + currentTime | track + ${0} | ${'linear-gradient(to right, #0061d5 0%, #fff 0%, #fff 10%, #767676 10%, #767676 100%)'} + ${50} | ${'linear-gradient(to right, #0061d5 0.5%, #fff 0.5%, #fff 10%, #767676 10%, #767676 100%)'} + ${1000} | ${'linear-gradient(to right, #0061d5 10%, #fff 10%, #fff 10%, #767676 10%, #767676 100%)'} + ${2500} | ${'linear-gradient(to right, #0061d5 25%, #fff 25%, #fff 10%, #767676 10%, #767676 100%)'} + ${10000} | ${'linear-gradient(to right, #0061d5 100%, #fff 100%, #fff 10%, #767676 10%, #767676 100%)'} + `('should render the correct track for currentTime $currentTime', ({ currentTime, track }) => { const buffer = getBuffer(1000, 0); // 10% buffered const wrapper = getWrapper({ bufferedRange: buffer, currentTime }); - - expect(wrapper.find(SliderControl).props()).toMatchObject({ - track, - value, - }); + expect(wrapper.find(SliderControl).prop('track')).toEqual(track); }); }); }); diff --git a/src/lib/viewers/controls/media/__tests__/VolumeControls-test.tsx b/src/lib/viewers/controls/media/__tests__/VolumeControls-test.tsx index 65fdc1810..e932b8b2d 100644 --- a/src/lib/viewers/controls/media/__tests__/VolumeControls-test.tsx +++ b/src/lib/viewers/controls/media/__tests__/VolumeControls-test.tsx @@ -28,15 +28,6 @@ describe('VolumeControls', () => { toggle.simulate('click'); expect(onMuteChange).toBeCalledWith(isMuted); }); - - test.each(['ArrowLeft', 'ArrowRight'])('should defer to the inner input for the %s key', key => { - const event = { key, stopPropagation: jest.fn() }; - const wrapper = getWrapper(); - - wrapper.find(SliderControl).simulate('keydown', event); - - expect(event.stopPropagation).toBeCalled(); - }); }); describe('render', () => { diff --git a/src/lib/viewers/controls/media/_styles.scss b/src/lib/viewers/controls/media/_styles.scss index 2b4f12d23..aaf8861f6 100644 --- a/src/lib/viewers/controls/media/_styles.scss +++ b/src/lib/viewers/controls/media/_styles.scss @@ -1,3 +1,4 @@ +@import '../slider/variables'; @import '../styles'; $bp-MediaControl-height: 40px; diff --git a/src/lib/viewers/controls/slider/SliderControl.scss b/src/lib/viewers/controls/slider/SliderControl.scss index 7a44341ed..49d7fbc64 100644 --- a/src/lib/viewers/controls/slider/SliderControl.scss +++ b/src/lib/viewers/controls/slider/SliderControl.scss @@ -1,98 +1,32 @@ -@import './styles'; - -$bp-SliderControl-thumb-size: 12px; -$bp-SliderControl-track-size: 3px; -$bp-SliderControl-track-space: 3px; +@import '../styles'; +@import './variables'; .bp-SliderControl { + @include bp-Control--outline; + position: relative; - width: 100%; - height: 100%; + cursor: pointer; } -.bp-SliderControl-input { - @include bp-Control--outline; - +.bp-SliderControl-thumb { position: absolute; - width: 100%; - height: 100%; - padding: 0 $bp-SliderControl-track-space; - background: transparent; + top: calc(50% - #{$bp-SliderControl-thumb-radius}); + width: $bp-SliderControl-thumb-size; + height: $bp-SliderControl-thumb-size; + margin-left: -#{$bp-SliderControl-thumb-radius}; + background: $box-blue; + border: 0; + border-radius: $bp-SliderControl-thumb-size; cursor: pointer; - appearance: none; - - @include bp-SliderThumb { - width: $bp-SliderControl-thumb-size; - height: $bp-SliderControl-thumb-size; - background: $box-blue; - border: 0; - border-radius: $bp-SliderControl-thumb-size; - cursor: pointer; - } - - @include bp-SliderTrack { - width: 100%; - height: $bp-SliderControl-track-size; - background: transparent; - border: 0; - cursor: pointer; - } - - // Thumb Button - &::-ms-thumb { - margin-top: 0; - } - - &::-webkit-slider-thumb { - margin-top: -5px; - appearance: none; - } - - // Track - &::-ms-fill-lower { - background: transparent; - } - - &::-ms-fill-upper { - background: transparent; - } - - &::-ms-track { - color: transparent; - border-color: transparent; - border-width: 5px 0; - } - - // Tooltip - &::-ms-tooltip { - display: none; - } - - // Overrides - @supports (-ms-ime-align: auto) { - & { - margin: 0; // Edge starts the margin from the thumb, not the track - } - - &::-webkit-slider-thumb { - margin-top: 0; - } - } } .bp-SliderControl-track { position: absolute; top: 0; - right: $bp-SliderControl-thumb-size / 2; // Allow the thumb to fully dock to edge of the track + right: 0; bottom: 0; - left: $bp-SliderControl-thumb-size / 2; // Allow the thumb to fully dock to edge of the track + left: 0; height: $bp-SliderControl-track-size; - margin: auto $bp-SliderControl-track-space; // Center the track within the slider + margin: auto $bp-SliderControl-track-space; // Center and allow the thumb to fully dock to edge of the track background: transparent linear-gradient($white, $white) no-repeat center border-box; - - @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - & { - transition: background-image 100ms ease; // Match the transition IE applies to thumb movement - } - } } diff --git a/src/lib/viewers/controls/slider/SliderControl.tsx b/src/lib/viewers/controls/slider/SliderControl.tsx index a9d6c2cdb..fbed63606 100644 --- a/src/lib/viewers/controls/slider/SliderControl.tsx +++ b/src/lib/viewers/controls/slider/SliderControl.tsx @@ -1,42 +1,151 @@ import React from 'react'; import classNames from 'classnames'; import noop from 'lodash/noop'; +import { decodeKeydown } from '../../../util'; import './SliderControl.scss'; -export type Props = Omit, 'onChange'> & { +export type Props = React.HTMLAttributes & { className?: string; - onChange?: (value: number) => void; + max?: number; + min?: number; + onMove?: (value: number, position: number, width: number) => void; + onUpdate?: (value: number) => void; + step?: number; + title: string; track?: string; + value: number; }; -export type Ref = HTMLInputElement; +export type Ref = HTMLDivElement; -function SliderControl({ className, onChange = noop, track, ...rest }: Props, ref: React.Ref): JSX.Element { - const handleChange = ({ target }: React.ChangeEvent): void => { - onChange(parseFloat(target.value)); +export default function SliderControl({ + className, + max = 100, + min = 0, + onMove = noop, + onUpdate = noop, + step = 1, + title, + track, + value, + ...rest +}: Props): JSX.Element { + const [isScrubbing, setIsScrubbing] = React.useState(false); + const sliderElRef = React.useRef(null); + + const getPosition = React.useCallback((pageX: number) => { + const { current: sliderEl } = sliderElRef; + + if (!sliderEl) return 0; + + const { left: sliderLeft, width: sliderWidth } = sliderEl.getBoundingClientRect(); + return Math.max(0, Math.min(pageX - sliderLeft, sliderWidth)); + }, []); + + const getPositionValue = React.useCallback( + (pageX: number) => { + const { current: sliderEl } = sliderElRef; + + if (!sliderEl) return 0; + + const { width: sliderWidth } = sliderEl.getBoundingClientRect(); + const newValue = (getPosition(pageX) / sliderWidth) * max; + return Math.max(min, Math.min(newValue, max)); + }, + [getPosition, max, min], + ); + + const handleKeydown = (event: React.KeyboardEvent): void => { + const key = decodeKeydown(event); + + if (key === 'ArrowLeft') { + event.stopPropagation(); // Prevents global key handling + onUpdate(Math.max(min, Math.min(value - step, max))); + } + + if (key === 'ArrowRight') { + event.stopPropagation(); // Prevents global key handling + onUpdate(Math.max(min, Math.min(value + step, max))); + } + }; + + const handleMouseDown = ({ button, ctrlKey, metaKey, pageX }: React.MouseEvent): void => { + if (button > 1 || ctrlKey || metaKey) return; + + onUpdate(getPositionValue(pageX)); + setIsScrubbing(true); }; + const handleMouseMove = ({ pageX }: React.MouseEvent): void => { + const { current: sliderEl } = sliderElRef; + const { width: sliderWidth } = sliderEl ? sliderEl.getBoundingClientRect() : { width: 0 }; + + onMove(getPositionValue(pageX), getPosition(pageX), sliderWidth); + }; + + const handleTouchStart = ({ touches }: React.TouchEvent): void => { + onUpdate(getPositionValue(touches[0].pageX)); + setIsScrubbing(true); + }; + + React.useEffect(() => { + const handleDocumentMoveStop = (): void => setIsScrubbing(false); + const handleDocumentMouseMove = (event: MouseEvent): void => { + if (!isScrubbing || event.button > 1 || event.ctrlKey || event.metaKey) return; + + event.preventDefault(); + onUpdate(getPositionValue(event.pageX)); + }; + const handleDocumentTouchMove = (event: TouchEvent): void => { + if (!isScrubbing || !event.touches || !event.touches[0]) return; + + event.preventDefault(); + onUpdate(getPositionValue(event.touches[0].pageX)); + }; + + if (isScrubbing) { + document.addEventListener('mousemove', handleDocumentMouseMove); + document.addEventListener('mouseup', handleDocumentMoveStop); + document.addEventListener('touchend', handleDocumentMoveStop); + document.addEventListener('touchmove', handleDocumentTouchMove); + } + + return (): void => { + document.removeEventListener('mousemove', handleDocumentMouseMove); + document.removeEventListener('mouseup', handleDocumentMoveStop); + document.removeEventListener('touchend', handleDocumentMoveStop); + document.removeEventListener('touchmove', handleDocumentTouchMove); + }; + }, [isScrubbing, getPositionValue, onUpdate]); + return ( -
+
-
); } - -export default React.forwardRef(SliderControl); diff --git a/src/lib/viewers/controls/slider/__tests__/SliderControl-test.tsx b/src/lib/viewers/controls/slider/__tests__/SliderControl-test.tsx index a8ce5adcc..bc25d6834 100644 --- a/src/lib/viewers/controls/slider/__tests__/SliderControl-test.tsx +++ b/src/lib/viewers/controls/slider/__tests__/SliderControl-test.tsx @@ -1,20 +1,173 @@ import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { mount, ReactWrapper } from 'enzyme'; import SliderControl from '../SliderControl'; describe('SliderControl', () => { - const getWrapper = (props = {}): ShallowWrapper => - shallow(); + const getWrapper = (props = {}): ReactWrapper => + mount(); + + beforeEach(() => { + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation( + (): DOMRect => ({ + bottom: 0, + height: 50, + left: 0, // Values are reduced by the left offset of the slider + right: 0, + top: 0, + toJSON: jest.fn(), + width: 1000, // Values are calculated based on width of the slider + x: 0, + y: 0, + }), + ); + }); describe('event handlers', () => { - test('should parse and return the current value on change', () => { - const onChange = jest.fn(); - const wrapper = getWrapper({ step: 0.1, onChange }); - const input = wrapper.find('[data-testid="bp-SliderControl-input"]'); + test.each` + pageX | result + ${-50} | ${0} + ${0} | ${0} + ${50} | ${5} + ${999} | ${99.9} + ${1500} | ${100} + `('should handle mousedown and update the value for pageX value $pageX', ({ pageX, result }) => { + const event = { button: 1, pageX }; + const onUpdate = jest.fn(); + const wrapper = getWrapper({ onUpdate, value: 0 }); + + act(() => { + wrapper.simulate('mousedown', event); + }); + wrapper.update(); + + expect(onUpdate).toBeCalledWith(result); + }); + + test('should handle mousemove by calling onMove with the value, position, and width', () => { + const event = { pageX: 100 }; + const onMove = jest.fn(); + const wrapper = getWrapper({ onMove }); - input.simulate('change', { target: { value: '0.5' } }); + act(() => { + wrapper.simulate('mousemove', event); + }); + wrapper.update(); + + expect(onMove).toBeCalledWith(10, 100, 1000); // Value, position, width + }); + + test.each` + initial | result + ${0} | ${0} + ${10} | ${9} + ${100} | ${99} + `('should handle keydown and decrement the value $initial to $result ', ({ initial, result }) => { + const event = { key: 'ArrowLeft', stopPropagation: jest.fn() }; + const onUpdate = jest.fn(); + const wrapper = getWrapper({ onUpdate, value: initial }); + + act(() => { + wrapper.simulate('keydown', event); + }); + wrapper.update(); - expect(onChange).toBeCalledWith(0.5); + expect(event.stopPropagation).toBeCalled(); + expect(onUpdate).toBeCalledWith(result); + }); + + test.each` + initial | result + ${0} | ${1} + ${10} | ${11} + ${100} | ${100} + `('should handle keydown and increment the value $initial to $result ', ({ initial, result }) => { + const event = { key: 'ArrowRight', stopPropagation: jest.fn() }; + const onUpdate = jest.fn(); + const wrapper = getWrapper({ onUpdate, value: initial }); + + wrapper.simulate('keydown', event); + + expect(event.stopPropagation).toBeCalled(); + expect(onUpdate).toBeCalledWith(result); + }); + }); + + describe('effects', () => { + beforeEach(() => { + jest.spyOn(document, 'addEventListener'); + jest.spyOn(document, 'removeEventListener'); + }); + + test('should add document-level event handlers when scrubbing starts', () => { + const wrapper = getWrapper(); + + act(() => { + wrapper.simulate('mousedown', { button: 1 }); + }); + wrapper.update(); + + expect(document.addEventListener).toBeCalledWith('mousemove', expect.any(Function)); + expect(document.addEventListener).toBeCalledWith('mouseup', expect.any(Function)); + expect(document.addEventListener).toBeCalledWith('touchend', expect.any(Function)); + expect(document.addEventListener).toBeCalledWith('touchmove', expect.any(Function)); + }); + + test('should remove document-level event handlers when scrubbing stops', () => { + const wrapper = getWrapper(); + + act(() => { + wrapper.simulate('mousedown', { button: 1 }); + }); + wrapper.update(); + + act(() => { + document.dispatchEvent(new Event('mouseup')); + }); + wrapper.update(); + + expect(document.removeEventListener).toBeCalledWith('mousemove', expect.any(Function)); + expect(document.removeEventListener).toBeCalledWith('mouseup', expect.any(Function)); + expect(document.removeEventListener).toBeCalledWith('touchend', expect.any(Function)); + expect(document.removeEventListener).toBeCalledWith('touchmove', expect.any(Function)); + }); + + test('should handle document-level mousemove events and call onUpdate', () => { + const onUpdate = jest.fn(); + const wrapper = getWrapper({ onUpdate }); + + act(() => { + wrapper.simulate('mousedown', { button: 1 }); + }); + wrapper.update(); + + act(() => { + const event = new MouseEvent('mousemove'); + Object.assign(event, { pageX: 100 }); + document.dispatchEvent(event); + }); + wrapper.update(); + + expect(onUpdate).toBeCalledWith(10); + }); + + test('should handle document-level touchmove events and call onUpdate', () => { + const onUpdate = jest.fn(); + const wrapper = getWrapper({ onUpdate }); + + act(() => { + wrapper.simulate('touchstart', { touches: [{ pageX: 0 }] }); + }); + wrapper.update(); + + act(() => { + const event = new MouseEvent('touchmove'); + Object.assign(event, { touches: [{ pageX: 100 }] }); + document.dispatchEvent(event); + }); + wrapper.update(); + + expect(onUpdate).toBeCalledWith(10); }); }); @@ -22,11 +175,9 @@ describe('SliderControl', () => { test('should return a valid wrapper', () => { const wrapper = getWrapper(); - expect(wrapper.hasClass('bp-SliderControl')).toBe(true); - expect(wrapper.find('[data-testid="bp-SliderControl-input"]').props()).toMatchObject({ - max: 100, - min: 0, - step: 1, + expect(wrapper.childAt(0).hasClass('bp-SliderControl')).toBe(true); + expect(wrapper.find('[data-testid="bp-SliderControl-thumb"]').prop('style')).toEqual({ + left: '0%', }); }); @@ -34,7 +185,9 @@ describe('SliderControl', () => { const track = 'linear-gradient(#fff %20, #000 100%'; const wrapper = getWrapper({ track, value: 20 }); - expect(wrapper.find('[data-testid="bp-SliderControl-input"]').prop('value')).toEqual(20); + expect(wrapper.find('[data-testid="bp-SliderControl-thumb"]').prop('style')).toEqual({ + left: '20%', + }); expect(wrapper.find('[data-testid="bp-SliderControl-track"]').prop('style')).toEqual({ backgroundImage: track, }); diff --git a/src/lib/viewers/controls/slider/_styles.scss b/src/lib/viewers/controls/slider/_styles.scss deleted file mode 100644 index a2c9630be..000000000 --- a/src/lib/viewers/controls/slider/_styles.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import '../styles'; - -@mixin bp-SliderThumb { - @each $prefix in ('-moz-range-thumb', '-ms-thumb', '-webkit-slider-thumb') { - &::#{$prefix} { - @content; - } - } -} - -@mixin bp-SliderTrack { - @each $prefix in ('-moz-range-track', '-ms-track', '-webkit-slider-runnable-track') { - &::#{$prefix} { - @content; - } - } -} diff --git a/src/lib/viewers/controls/slider/variables.scss b/src/lib/viewers/controls/slider/variables.scss new file mode 100644 index 000000000..58ba0c537 --- /dev/null +++ b/src/lib/viewers/controls/slider/variables.scss @@ -0,0 +1,4 @@ +$bp-SliderControl-thumb-size: 12px; +$bp-SliderControl-thumb-radius: #{$bp-SliderControl-thumb-size / 2}; +$bp-SliderControl-track-size: 3px; +$bp-SliderControl-track-space: 3px;