@zendeskgarden/container-slider
Version:
Containers relating to Slider in the Garden Design System
284 lines (278 loc) • 9.48 kB
JavaScript
/**
* Copyright Zendesk, Inc.
*
* Use of this source code is governed under the Apache License, Version 2.0
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/
;
var React = require('react');
var debounce = require('lodash.debounce');
var containerUtilities = require('@zendeskgarden/container-utilities');
var PropTypes = require('prop-types');
const SLIDER_MIN = 0;
const SLIDER_MAX = 100;
const SLIDER_STEP = 1;
function useSlider(_ref) {
let {
trackRef,
minThumbRef,
maxThumbRef,
min = SLIDER_MIN,
max = SLIDER_MAX,
defaultMinValue,
defaultMaxValue,
minValue,
maxValue,
onChange = () => undefined,
step = SLIDER_STEP,
jump = step,
disabled,
rtl,
environment
} = _ref;
const doc = environment || document;
const [trackRect, setTrackRect] = React.useState({
width: 0
});
const init = function (initMinValue, initMaxValue) {
if (initMinValue === void 0) {
initMinValue = min;
}
if (initMaxValue === void 0) {
initMaxValue = max;
}
const retVal = {
minValue: initMinValue < min ? min : initMinValue,
maxValue: initMaxValue > max ? max : initMaxValue
};
if (retVal.maxValue < min) {
retVal.maxValue = min;
}
if (retVal.minValue > retVal.maxValue) {
retVal.minValue = retVal.maxValue;
}
return retVal;
};
const [state, setState] = React.useState(init(defaultMinValue, defaultMaxValue));
const isControlled = minValue !== undefined && minValue !== null || maxValue !== undefined && maxValue !== null;
const position = isControlled ? init(minValue, maxValue) : state;
const setPosition = isControlled ? onChange : setState;
React.useEffect(() => {
const handleResize = debounce(() => {
if (trackRef.current) {
setTrackRect(trackRef.current.getBoundingClientRect());
}
}, 100);
handleResize();
const win = doc.defaultView || window;
win.addEventListener('resize', handleResize);
return () => {
win.removeEventListener('resize', handleResize);
handleResize.cancel();
};
}, [doc, trackRef]);
const getTrackPosition = React.useCallback(event => {
let retVal = undefined;
if (trackRect) {
const offset = rtl ? trackRect.left + trackRect.width : trackRect.left;
const mouseX = (event.pageX - offset) * (rtl ? -1 : 1);
const x = (max - min) * mouseX / trackRect.width;
const value = min + parseInt(x, 10);
retVal = Math.floor(value / step) * step;
}
return retVal;
}, [max, min, trackRect, rtl, step]);
const setThumbPosition = React.useCallback(thumb => value => {
if (!disabled) {
let newMinValue = position.minValue;
let newMaxValue = position.maxValue;
if (thumb === 'min') {
if (value < min) {
newMinValue = min;
} else if (value > position.maxValue) {
newMinValue = position.maxValue;
} else {
newMinValue = value;
}
} else if (thumb === 'max') {
if (value > max) {
newMaxValue = max;
} else if (value < position.minValue) {
newMaxValue = position.minValue;
} else {
newMaxValue = value;
}
}
if (!isNaN(newMinValue) && !isNaN(newMaxValue)) {
setPosition({
minValue: newMinValue,
maxValue: newMaxValue
});
}
}
}, [disabled, max, min, position.maxValue, position.minValue, setPosition]);
const getTrackProps = React.useCallback(function (_temp) {
let {
onMouseDown,
...other
} = _temp === void 0 ? {} : _temp;
const handleMouseDown = event => {
if (!disabled && event.button === 0) {
const value = getTrackPosition(event);
if (value !== undefined) {
const minOffset = Math.abs(position.minValue - value);
const maxOffset = Math.abs(position.maxValue - value);
if (minOffset <= maxOffset) {
minThumbRef.current && minThumbRef.current.focus();
setThumbPosition('min')(value);
} else {
maxThumbRef.current && maxThumbRef.current.focus();
setThumbPosition('max')(value);
}
}
}
};
return {
'data-garden-container-id': 'containers.slider.track',
'data-garden-container-version': '0.1.15',
'aria-disabled': disabled,
onMouseDown: containerUtilities.composeEventHandlers(onMouseDown, handleMouseDown),
...other
};
}, [disabled, getTrackPosition, maxThumbRef, minThumbRef, position.maxValue, position.minValue, setThumbPosition]);
const getThumbProps = React.useCallback(thumb => _ref2 => {
let {
onKeyDown,
onMouseDown,
onTouchStart,
...other
} = _ref2;
const handleKeyDown = event => {
if (!disabled) {
let value;
switch (event.key) {
case containerUtilities.KEYS.RIGHT:
value = (thumb === 'min' ? position.minValue : position.maxValue) + (rtl ? -step : step);
break;
case containerUtilities.KEYS.UP:
value = (thumb === 'min' ? position.minValue : position.maxValue) + step;
break;
case containerUtilities.KEYS.LEFT:
value = (thumb === 'min' ? position.minValue : position.maxValue) - (rtl ? -step : step);
break;
case containerUtilities.KEYS.DOWN:
value = (thumb === 'min' ? position.minValue : position.maxValue) - step;
break;
case containerUtilities.KEYS.PAGE_UP:
value = (thumb === 'min' ? position.minValue : position.maxValue) + jump;
break;
case containerUtilities.KEYS.PAGE_DOWN:
value = (thumb === 'min' ? position.minValue : position.maxValue) - jump;
break;
case containerUtilities.KEYS.HOME:
value = min;
break;
case containerUtilities.KEYS.END:
value = max;
break;
}
if (value !== undefined) {
event.preventDefault();
event.stopPropagation();
setThumbPosition(thumb)(value);
}
}
};
const handleMouseMove = event => {
const value = getTrackPosition(event);
if (value !== undefined) {
setThumbPosition(thumb)(value);
}
};
const handleTouchMove = event => {
const value = getTrackPosition(event.targetTouches[0]);
if (value !== undefined) {
setThumbPosition(thumb)(value);
}
};
const handleMouseUp = () => {
doc.removeEventListener('mousemove', handleMouseMove);
doc.removeEventListener('mouseup', handleMouseUp);
};
const handleTouchEnd = () => {
doc.removeEventListener('touchend', handleTouchEnd);
doc.removeEventListener('touchmove', handleTouchMove);
};
const handleMouseDown = event => {
if (!disabled && event.button === 0) {
event.stopPropagation();
doc.addEventListener('mousemove', handleMouseMove);
doc.addEventListener('mouseup', handleMouseUp);
event.target && event.target.focus();
}
};
const handleTouchStart = event => {
if (!disabled) {
event.stopPropagation();
doc.addEventListener('touchmove', handleTouchMove);
doc.addEventListener('touchend', handleTouchEnd);
event.target && event.target.focus();
}
};
return {
'data-garden-container-id': 'containers.slider.thumb',
'data-garden-container-version': '0.1.15',
role: 'slider',
tabIndex: disabled ? -1 : 0,
'aria-valuemin': thumb === 'min' ? min : position.minValue,
'aria-valuemax': thumb === 'min' ? position.maxValue : max,
'aria-valuenow': thumb === 'min' ? position.minValue : position.maxValue,
onKeyDown: containerUtilities.composeEventHandlers(onKeyDown, handleKeyDown),
onMouseDown: containerUtilities.composeEventHandlers(onMouseDown, handleMouseDown),
onTouchStart: containerUtilities.composeEventHandlers(onTouchStart, handleTouchStart),
...other
};
}, [doc, disabled, getTrackPosition, jump, max, min, position.maxValue, position.minValue, rtl, step, setThumbPosition]);
return React.useMemo(() => ({
getTrackProps,
getMinThumbProps: getThumbProps('min'),
getMaxThumbProps: getThumbProps('max'),
trackRect,
minValue: position.minValue,
maxValue: position.maxValue
}), [getTrackProps, getThumbProps, position.minValue, position.maxValue, trackRect]);
}
const SliderContainer = _ref => {
let {
children,
render = children,
...options
} = _ref;
return React.createElement(React.Fragment, null, render(useSlider(options)));
};
SliderContainer.propTypes = {
children: PropTypes.func,
render: PropTypes.func,
trackRef: PropTypes.any.isRequired,
minThumbRef: PropTypes.any.isRequired,
maxThumbRef: PropTypes.any.isRequired,
environment: PropTypes.any,
defaultMinValue: PropTypes.number,
defaultMaxValue: PropTypes.number,
minValue: PropTypes.number,
maxValue: PropTypes.number,
onChange: PropTypes.func,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
jump: PropTypes.number,
disabled: PropTypes.bool,
rtl: PropTypes.bool
};
SliderContainer.defaultProps = {
min: SLIDER_MIN,
max: SLIDER_MAX,
step: SLIDER_STEP
};
exports.SliderContainer = SliderContainer;
exports.useSlider = useSlider;