UNPKG

@zendeskgarden/container-slider

Version:

Containers relating to Slider in the Garden Design System

284 lines (278 loc) 9.48 kB
/** * 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. */ 'use strict'; 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;