UNPKG

@shopgate/engage

Version:
274 lines (263 loc) • 9.23 kB
import _createClass from "@babel/runtime/helpers/createClass"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import cxs from 'classnames'; import styles from "./style"; import Handle from "./components/Handle"; import { isTouchDevice } from "../../core"; import { generateLinearEasingCallback, generateExponentialEasingCallback, getRangeStyle, getTouchPositionX, getAbsoluteValue, getRelativeValue } from "./helper"; /** * The range slider component. */ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; let RangeSlider = /*#__PURE__*/function (_PureComponent) { /** * Constructor * @param {Object} props The component properties */ function RangeSlider(props) { var _this; _this = _PureComponent.call(this, props) || this; /** * Processes touch start events on handles. * @param {Object} event The touch event * @param {number} index The index of the touched handle. */ _this.handleTouchStart = (event, index) => { _this.addEventListeners(); _this.draggedHandle = index; // Calculate the relative offset to the handles center const handleDOMElement = event.target; // Get the handles bounding rectangle ... const handleRect = handleDOMElement.getBoundingClientRect(); // ... and calculate its absolute center. const handleCenterX = handleRect.left + handleDOMElement.offsetWidth / 2; // Store the signed distanced between the current touch offset and the handle center. _this.touchOffset = getTouchPositionX(event) - handleCenterX; }; /** * Processes move events on handles. * @param {Object} event The touch event */ _this.handleTouchMove = event => { if (_this.props.min === _this.props.max) { return; } if (_this.draggedHandle === null) { return; } const { width: offsetWidth, left: offsetLeft } = _this.domElement.current.getBoundingClientRect(); // Calculate the absolute offset where the element was touched... let deltaX = getTouchPositionX(event) - offsetLeft - _this.touchOffset; // ...and convert it into a relative value between [0...1]. deltaX = Math.max(0, Math.min(1, deltaX / offsetWidth)); const stateUpdate = {}; if (_this.draggedHandle === 1) { // Right handle dragged if (_this.state.rangeMin < deltaX) { stateUpdate.rangeMax = Math.min(1, deltaX); _this.draggedHandlePixelOffset = Math.abs(stateUpdate.rangeMax - _this.state.rangeMax); } else { // Not in valid range, swap handles _this.draggedHandle = 0; stateUpdate.rangeMax = _this.state.rangeMin; stateUpdate.rangeMin = deltaX; _this.draggedHandlePixelOffset = Math.abs(stateUpdate.rangeMin - _this.state.rangeMin); } } else if (_this.draggedHandle === 0) { // Left handle dragged if (_this.state.rangeMax > deltaX) { stateUpdate.rangeMin = Math.max(0, deltaX); _this.draggedHandlePixelOffset = Math.abs(stateUpdate.rangeMin - _this.state.rangeMin); } else { // Not in valid range, swap handles _this.draggedHandle = 1; stateUpdate.rangeMin = _this.state.rangeMax; stateUpdate.rangeMax = deltaX; _this.draggedHandlePixelOffset = Math.abs(stateUpdate.rangeMax - _this.state.rangeMax); } } _this.draggedHandlePixelOffset *= offsetWidth; _this.setState(stateUpdate, _this.triggerChangeCallback); }; /** * Processes global touch end events for handles. * @param {Object} e The touch event */ _this.handleTouchEnd = () => { _this.removeEventListeners(); _this.touchOffset = 0; _this.draggedHandle = null; }; /** * Processes outer range touch end events. * @param {Object} event The touch event */ _this.handleRangeTouch = event => { const { width: offsetWidth, left: offsetLeft } = _this.domElement.current.getBoundingClientRect(); const dx = (getTouchPositionX(event) - offsetLeft) / offsetWidth; const d0 = Math.abs(_this.state.rangeMin - dx); const d1 = Math.abs(_this.state.rangeMax - dx); if (d0 < d1) { _this.draggedHandle = 0; } else { _this.draggedHandle = 1; } _this.handleTouchMove(event); }; _this.draggedHandle = null; // 0 for left handle, 1 for right handle or null _this.domElement = /*#__PURE__*/React.createRef(); _this.touchOffset = 0; _this.draggedHandlePixelOffset = 0; // The absolute pixel delta of the last handle move event. _this.state = _this.getRange(props); _this.useMouseEvents = !isTouchDevice(); return _this; } /** * Updates the component properties. * @param {Object} newProps The new component properties. */ _inheritsLoose(RangeSlider, _PureComponent); var _proto = RangeSlider.prototype; _proto.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(newProps) { this.setState(this.getRange(newProps)); } /** * Get the easing function. */; /** * Adds event listeners */ _proto.addEventListeners = function addEventListeners() { if (this.useMouseEvents) { document.addEventListener('mouseup', this.handleTouchEnd); document.addEventListener('mousemove', this.handleTouchMove); } else { document.addEventListener('touchend', this.handleTouchEnd); document.addEventListener('touchmove', this.handleTouchMove); } } /** * Removes event listeners */; _proto.removeEventListeners = function removeEventListeners() { if (this.useMouseEvents) { document.removeEventListener('mouseup', this.handleTouchEnd); document.removeEventListener('mousemove', this.handleTouchMove); } else { document.removeEventListener('touchend', this.handleTouchEnd); document.removeEventListener('touchmove', this.handleTouchMove); } } /** * Get range min and max from props. * @param {Object} props The component props. * @returns {Object} The new state */; _proto.getRange = function getRange(props) { const { value, min, max } = props; return { rangeMin: this.invertedEase(getRelativeValue(value[0], min, max)), rangeMax: this.invertedEase(getRelativeValue(value[1], min, max)) }; }; /** * Calls the change callback in case of a state update. */ _proto.triggerChangeCallback = function triggerChangeCallback() { const { value, onChange, min, max } = this.props; if (!onChange) { return; } const newRange = [getAbsoluteValue(this.ease(this.state.rangeMin), min, max, true), getAbsoluteValue(this.ease(this.state.rangeMax), min, max, true)]; if (newRange !== value) { onChange(newRange); } } /** * Renders the component. * @returns {JSX} */; _proto.render = function render() { const { classNames, animationSpeed } = this.props; const speed = Math.round(1000 / animationSpeed * this.draggedHandlePixelOffset); const rangeStyle = getRangeStyle(this.state.rangeMin, this.state.rangeMax, speed > 10 ? speed : 0); return /*#__PURE__*/_jsx("div", { className: cxs(classNames.container, 'engage__range-slider'), onMouseDown: this.handleRangeTouch, "aria-hidden": true, children: /*#__PURE__*/_jsx("div", { className: cxs(classNames.outerRange, styles.outerRange), ref: this.domElement, children: /*#__PURE__*/_jsxs("div", { className: cxs(classNames.range, styles.range), style: rangeStyle, children: [/*#__PURE__*/_jsx(Handle, { index: 0, onTouchStart: this.handleTouchStart, active: this.draggedHandle === 0, classNames: classNames, useMouseEvents: this.useMouseEvents }), /*#__PURE__*/_jsx(Handle, { index: 1, onTouchStart: this.handleTouchStart, active: this.draggedHandle === 1, classNames: classNames, useMouseEvents: this.useMouseEvents })] }) }) }); }; return _createClass(RangeSlider, [{ key: "ease", get: function () { return { linear: generateLinearEasingCallback(this.props.resolution), exponential: generateExponentialEasingCallback(this.props.factor) }[this.props.easing]; } /** * Get the function to invert an eased value to it's original value. */ }, { key: "invertedEase", get: function () { return { linear: generateLinearEasingCallback(this.props.resolution), exponential: generateExponentialEasingCallback(1 / this.props.factor) }[this.props.easing]; } }]); }(PureComponent); RangeSlider.defaultProps = { animationSpeed: 500, classNames: {}, easing: 'linear', factor: 2, max: 100, min: 0, resolution: 1, value: [0, 100], onChange: null }; export default RangeSlider;