/**
* Component represent swiping element.
* This component will work only on childrens, childrens of passed childrens will not have handlers.
* If you use it with tree of nodes it will not work
*
* @module SwipeItem
*/
import React, { Component, cloneElement } from 'react';
/**
* @typedef {object} props
* @property {Array|object} children
* @property {string} className
* @property {Function} closeModal
* @property {Function} onClickElement
* @property {boolean} doNotStyle
*/
export default class SwipeItem extends Component {
constructor(props) {
super(props);
/**
* @member {object}
* @property {number} right Number of px from right margin of phone
* @property {Array|object|null} children All components passed as children
* with handlers added for swipe
*/
this.state = {
right: parseInt(props.rightMargin),
children: null,
};
/**
* @member {number}
* @description Right margin on swipe start
*/
this.originalOffset = 0;
/**
* @member {number}
* @description Number of px added on one move event
*/
this.velocity = 0;
/**
* @member {number}
* @description Time when move start
*/
this.timeOfLastDragEvent = 0;
/**
* @member {number}
* @description Position of element on move start event
*/
this.touchStartX = 0;
/**
* @member {number}
* @description Position of element on move event
*/
this.prevTouchX = 0;
/**
* @member {boolean}
* @description Indicate move event
*/
this.beingTouched = false;
/**
* @member {number|null}
*/
this.intervalId = null;
/**
* @member {number}
* @description Width of swipe view in px
*/
this.elementWidth = 600;
/**
* @member {number}
* @description Contain all click and mouse events
*/
this.handlers = this.props.viewPlaceBet
? {
onTouchStart: this.handleTouchStart,
onTouchMove: this.handleTouchMove,
onTouchEnd: this.handleTouchEnd,
onMouseDown: this.handleMouseDown,
onMouseMove: this.handleMouseMove,
onMouseUp: this.handleMouseUp,
onMouseLeave: this.handleMouseLeave,
}
: {
onTouchEnd: this.handleTouchEnd,
onMouseUp: this.handleMouseUp,
};
}
/**
* Add handlers on childrens
*
* @returns {void}
*/
componentDidMount() {
let children = this.props.children;
if (!children.map) {
children = [children];
}
children = children.map((child, i) => cloneElement(child, {
...this.handlers,
key: i,
}));
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({
children,
});
this.selfCloseTimeoutId = setTimeout(this.handleRemoveSelf, 5000);
}
/**
* clear handlers
*
* @returns {void}
*/
componentWillUnmount() {
this.handlers = null;
clearTimeout(this.selfCloseTimeoutId);
clearTimeout(this.tid1);
window.clearInterval(this.intervalId);
}
/**
* Called in interval to remove slider or to move it to defauld coordinates
*
* @function
* @returns {void}
*/
animateSlidingToZero = () => {
let { right } = this.state;
if (!this.beingTouched && right < -20) {
this.velocity += 10 * 0.033;
right += this.velocity;
if (right <= -(this.elementWidth - 200)) {
window.clearInterval(this.intervalId);
this.handleRemoveSelf();
}
this.setState({ right });
} else if (!this.beingTouched) {
right = 5;
this.velocity = 0;
window.clearInterval(this.intervalId);
this.setState({ right });
this.originalOffset = 0;
this.intervalId = null;
}
}
/**
* Remove slider
*
* @function
* @returns {void}
*/
handleRemoveSelf = () => {
this.tid1 = window.setTimeout(() => this.props.closeModal(), 100);
}
/**
* Record start position
*
* @function
* @param {number} clientX
* @returns {void}
*/
handleStart = (clientX) => {
if (this.intervalId !== null) {
window.clearInterval(this.intervalId);
}
this.originalOffset = this.state.right;
this.velocity = 0;
this.timeOfLastDragEvent = Date.now();
this.touchStartX = clientX;
this.beingTouched = true;
this.intervalId = null;
}
/**
* Calculate moving of slider
*
* @function
* @param {number} clientX
* @returns {void}
*/
handleMove = (clientX) => {
if (this.beingTouched) {
const touchX = clientX;
const currTime = Date.now();
const elapsed = currTime - this.timeOfLastDragEvent;
const velocity = (20 * (touchX - this.prevTouchX)) / elapsed;
let deltaX = this.touchStartX - (touchX + this.originalOffset);
if (deltaX === -this.elementWidth + 10) {
this.handleRemoveSelf();
} else if (deltaX > 5) {
deltaX = 5;
}
this.setState({
right: deltaX,
});
this.velocity = velocity;
this.timeOfLastDragEvent = currTime;
this.prevTouchX = touchX;
}
}
/**
* Record end position and check is it click. Move slider to beggining position or remove it.
*
* @function
* @param {number} clientX
* @returns {void}
*/
handleEnd = (clientX) => {
if (this.touchStartX === clientX) {
this.props.onClickElement();
} else {
this.touchStartX = 0;
this.beingTouched = false;
this.intervalId = window.setInterval(this.animateSlidingToZero.bind(this), 33);
}
}
/**
* @function
* @param {Event} touchStartEvent
* @returns {void}
*/
handleTouchStart = (touchStartEvent) => {
touchStartEvent.stopPropagation();
this.handleStart(touchStartEvent.targetTouches[0].clientX);
}
/**
* @function
* @param {Event} touchMoveEvent
* @returns {void}
*/
handleTouchMove = (touchMoveEvent) => {
touchMoveEvent.stopPropagation();
this.handleMove(touchMoveEvent.targetTouches[0].clientX);
}
/**
* @function
* @param {Event} touchEndEvent
* @returns {void}
*/
handleTouchEnd = (touchEndEvent) => {
touchEndEvent.stopPropagation();
if (this.props.viewPlaceBet) {
this.handleEnd(touchEndEvent.changedTouches[0] && touchEndEvent.changedTouches[0].clientX);
} else {
this.handleRemoveSelf();
}
}
/**
* @function
* @param {Event} mouseDownEvent
* @returns {void}
*/
handleMouseDown = (mouseDownEvent) => {
mouseDownEvent.stopPropagation();
this.handleStart(mouseDownEvent.clientX);
}
/**
* @function
* @param {Event} mouseMoveEvent
* @returns {void}
*/
handleMouseMove = (mouseMoveEvent) => {
mouseMoveEvent.stopPropagation();
this.handleMove(mouseMoveEvent.clientX);
}
/**
* @function
* @param {Event} mouseUpEvent
* @returns {void}
*/
handleMouseUp = (mouseUpEvent) => {
mouseUpEvent.stopPropagation();
if (this.props.viewPlaceBet) {
this.handleEnd(mouseUpEvent.clientX);
} else {
this.handleRemoveSelf();
}
}
/**
* @function
* @param {Event} mouseUpEvent
* @returns {void}
*/
handleMouseLeave = (mouseUpEvent) => {
mouseUpEvent.stopPropagation();
this.handleMouseUp(mouseUpEvent);
}
/**
* Render
*
* @returns {view}
*/
render() {
return (
<div
className={this.props.className}
ref={(reff) => {
if (reff) {
this.elementWidth = reff.getBoundingClientRect().width;
if (this.elementWidth > 600) {
this.elementWidth = 600;
}
}
}}
style={{
right: `${this.state.right}px`,
}}
{...this.handlers}
>
{this.state.children}
</div>
);
}
}