Skip to content

Commit

Permalink
feat(forms): allow multi-thumb range to allow track mouse interaction (
Browse files Browse the repository at this point in the history
  • Loading branch information
Austin Green committed Apr 27, 2020
1 parent 4b17136 commit c21ee15
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 20 deletions.
16 changes: 8 additions & 8 deletions packages/forms/.size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"dist/index.cjs.js": {
"bundled": 102434,
"minified": 68435,
"gzipped": 13063
"bundled": 103605,
"minified": 68827,
"gzipped": 13165
},
"dist/index.esm.js": {
"bundled": 98758,
"minified": 64827,
"gzipped": 12916,
"bundled": 99898,
"minified": 65188,
"gzipped": 13019,
"treeshaked": {
"rollup": {
"code": 51603,
"code": 51925,
"import_statements": 614
},
"webpack": {
"code": 57765
"code": 58156
}
}
}
Expand Down
34 changes: 33 additions & 1 deletion packages/forms/src/elements/MultiThumbRange.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React from 'react';
import { KEY_CODES } from '@zendeskgarden/container-utilities';
import { render, renderRtl, fireEvent } from 'garden-test-utils';
import { render, renderRtl, fireEvent, createEvent } from 'garden-test-utils';
import MultiThumbRange from './MultiThumbRange';

jest.mock('lodash.debounce');
Expand Down Expand Up @@ -79,6 +79,38 @@ describe('MultiThumbRange', () => {
});
});

describe('Slider selection', () => {
it('updates min value if slider is clicked near min thumb', () => {
const onChangeSpy = jest.fn();
const { getByTestId } = render(
<MultiThumbRange minValue={15} maxValue={75} step={5} onChange={onChangeSpy} />
);

const slider = getByTestId('slider');
const mouseEvent = createEvent.mouseDown(slider);

(mouseEvent as any).pageX = 45;
fireEvent(slider, mouseEvent);

expect(onChangeSpy).toHaveBeenCalledWith({ minValue: 25, maxValue: 75 });
});

it('updates max value if slider is clicked near max thumb', () => {
const onChangeSpy = jest.fn();
const { getByTestId } = render(
<MultiThumbRange minValue={15} maxValue={75} step={5} onChange={onChangeSpy} />
);

const slider = getByTestId('slider');
const mouseEvent = createEvent.mouseDown(slider);

(mouseEvent as any).pageX = 100;
fireEvent(slider, mouseEvent);

expect(onChangeSpy).toHaveBeenCalledWith({ minValue: 15, maxValue: 80 });
});
});

describe('Min Thumb', () => {
it('applies correct accessibility values', () => {
const { getAllByTestId } = render(<MultiThumbRange minValue={15} maxValue={75} />);
Expand Down
72 changes: 61 additions & 11 deletions packages/forms/src/elements/MultiThumbRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { KEY_CODES } from '@zendeskgarden/container-utilities';
import { KEY_CODES, composeEventHandlers } from '@zendeskgarden/container-utilities';
import { withTheme, useDocument, DEFAULT_THEME } from '@zendeskgarden/react-theming';
import {
StyledSlider,
Expand Down Expand Up @@ -55,6 +55,7 @@ const MultiThumbRange: React.FC<IMultiThumbRangeProps & ThemeProps<DefaultTheme>
step,
onChange,
theme,
onMouseDown,
...props
}) => {
const themedDocument = useDocument(theme);
Expand Down Expand Up @@ -167,13 +168,10 @@ const MultiThumbRange: React.FC<IMultiThumbRangeProps & ThemeProps<DefaultTheme>
[max, minValue, onRangeValuesChange]
);

/**
* Calculates the update thumb position based on current mouse position
*/
const onDocumentMouseMove = useCallback(
e => {
const calculateUpdatedValue = useCallback(
(e: React.MouseEvent) => {
if (!trackRailRef.current) {
return;
return undefined;
}

const trackOffsetLeft = trackRailRef.current.getBoundingClientRect().left;
Expand All @@ -186,19 +184,29 @@ const MultiThumbRange: React.FC<IMultiThumbRangeProps & ThemeProps<DefaultTheme>
diffX *= -1;
}

let newValue =
const newValue =
min! + parseInt(((((max! - min!) * diffX) / trackWidth) as unknown) as string, 10);

// Reduce updated value to align with step size
newValue = Math.floor(newValue / step!) * step!;
return Math.floor(newValue / step!) * step!;
},
[max, min, step, theme.rtl]
);

/**
* Calculates the update thumb position based on current mouse position
*/
const onDocumentMouseMove = useCallback(
e => {
const newValue = calculateUpdatedValue(e);

if (isMinThumbFocused) {
updateMinThumbSlider(newValue);
} else {
updateMaxThumbSlider(newValue);
}
},
[isMinThumbFocused, max, min, theme, step, updateMaxThumbSlider, updateMinThumbSlider]
[calculateUpdatedValue, isMinThumbFocused, updateMinThumbSlider, updateMaxThumbSlider]
);

const removeDragEvents = useCallback(() => {
Expand All @@ -210,6 +218,41 @@ const MultiThumbRange: React.FC<IMultiThumbRangeProps & ThemeProps<DefaultTheme>
setIsMousedDown(false);
}, [onDocumentMouseMove, themedDocument]);

const onTrackMouseDown = useCallback(
(e: React.MouseEvent) => {
if (e.button !== 0 || disabled) {
return;
}

e.preventDefault();

const valueAtMouseDown = calculateUpdatedValue(e);

if (valueAtMouseDown === undefined || minValue === undefined || maxValue === undefined) {
return;
}

const distanceFromMinThumb = Math.abs(minValue - valueAtMouseDown);
const distanceFromMaxThumb = Math.abs(maxValue - valueAtMouseDown);

if (distanceFromMinThumb <= distanceFromMaxThumb) {
minThumbRef.current && minThumbRef.current.focus();
updateMinThumbSlider(valueAtMouseDown);
} else {
maxThumbRef.current && maxThumbRef.current.focus();
updateMaxThumbSlider(valueAtMouseDown);
}
},
[
calculateUpdatedValue,
disabled,
maxValue,
minValue,
updateMaxThumbSlider,
updateMinThumbSlider
]
);

useEffect(() => {
if (isMousedDown && themedDocument) {
themedDocument.addEventListener('mousemove', onDocumentMouseMove);
Expand Down Expand Up @@ -309,8 +352,15 @@ const MultiThumbRange: React.FC<IMultiThumbRangeProps & ThemeProps<DefaultTheme>
const maxPosition = calculateMaxPosition(MIN_DISTANCE);
const sliderBackgroundSize = Math.abs(maxPosition) - Math.abs(minPosition);

const onSliderMouseDown = composeEventHandlers(onMouseDown, onTrackMouseDown);

return (
<StyledSlider data-test-id="slider" isDisabled={disabled} {...props}>
<StyledSlider
data-test-id="slider"
isDisabled={disabled}
onMouseDown={onSliderMouseDown}
{...props}
>
<StyledSliderTrack
backgroundSize={sliderBackgroundSize}
backgroundPosition={theme.rtl ? railWidth - maxPosition : minPosition}
Expand Down

0 comments on commit c21ee15

Please sign in to comment.