@shopgate/engage
Version:
Shopgate's ENGAGE library.
274 lines (263 loc) • 9.23 kB
JavaScript
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;