@appinfini/react-cursor-position
Version:
A React component that decorates its children with touch and mouse cursor coordinates, plotted relative to itself.
539 lines (481 loc) • 19.5 kB
JavaScript
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
import React, { Children, cloneElement } from 'react';
import PropTypes from 'prop-types';
import objectAssign from 'object-assign';
import omit from 'object.omit';
import Core from './lib/ElementRelativeCursorPosition';
import addEventListener from './utils/addEventListener';
import { INTERACTIONS, MOUSE_EMULATION_GUARD_TIMER_NAME } from './constants';
import noop from './utils/noop';
import PressActivation from './lib/PressActivation';
import TouchActivation from './lib/TouchActivation';
import TapActivation from './lib/TapActivation';
import HoverActivation from './lib/HoverActivation';
import ClickActivation from './lib/ClickActivation';
export { INTERACTIONS };
var _class = function (_React$Component) {
_inherits(_class, _React$Component);
function _class(props) {
_classCallCheck(this, _class);
var _this = _possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).call(this, props));
_initialiseProps.call(_this);
_this.state = {
detectedEnvironment: {
isMouseDetected: false,
isTouchDetected: false
},
elementDimensions: {
width: 0,
height: 0
},
isActive: false,
isPositionOutside: true,
position: {
x: 0,
y: 0
}
};
_this.shouldGuardAgainstMouseEmulationByDevices = false;
_this.eventListeners = [];
_this.timers = [];
_this.elementOffset = {
x: 0,
y: 0
};
_this.onTouchStart = _this.onTouchStart.bind(_this);
_this.onTouchMove = _this.onTouchMove.bind(_this);
_this.onTouchEnd = _this.onTouchEnd.bind(_this);
_this.onTouchCancel = _this.onTouchCancel.bind(_this);
_this.onMouseEnter = _this.onMouseEnter.bind(_this);
_this.onMouseMove = _this.onMouseMove.bind(_this);
_this.onMouseLeave = _this.onMouseLeave.bind(_this);
_this.onClick = _this.onClick.bind(_this);
_this.onIsActiveChanged = _this.onIsActiveChanged.bind(_this);
_this.setTouchActivationStrategy(props.activationInteractionTouch);
_this.setMouseActivationStrategy(props.activationInteractionMouse);
return _this;
}
_createClass(_class, [{
key: 'onIsActiveChanged',
value: function onIsActiveChanged(_ref) {
var isActive = _ref.isActive;
if (isActive) {
this.activate();
} else {
this.deactivate();
}
}
}, {
key: 'onTouchStart',
value: function onTouchStart(e) {
this.init();
this.onTouchDetected();
this.setShouldGuardAgainstMouseEmulationByDevices();
var position = this.core.getCursorPosition(this.getTouchEvent(e));
this.setPositionState(position);
this.touchActivation.touchStarted({ e: e, position: position });
}
}, {
key: 'onTouchMove',
value: function onTouchMove(e) {
if (!this.isCoreReady) {
return;
}
var position = this.core.getCursorPosition(this.getTouchEvent(e));
this.touchActivation.touchMoved({ e: e, position: position });
if (!this.state.isActive) {
return;
}
this.setPositionState(position);
e.preventDefault();
if (this.props.shouldStopTouchMovePropagation) {
e.stopPropagation();
}
}
}, {
key: 'onTouchEnd',
value: function onTouchEnd() {
this.touchActivation.touchEnded();
this.unsetShouldGuardAgainstMouseEmulationByDevices();
}
}, {
key: 'onTouchCancel',
value: function onTouchCancel() {
this.touchActivation.touchCanceled();
this.unsetShouldGuardAgainstMouseEmulationByDevices();
}
}, {
key: 'onMouseEnter',
value: function onMouseEnter(e) {
if (this.shouldGuardAgainstMouseEmulationByDevices) {
return;
}
this.init();
this.onMouseDetected();
this.setPositionState(this.core.getCursorPosition(e));
this.mouseActivation.mouseEntered();
}
}, {
key: 'onMouseMove',
value: function onMouseMove(e) {
if (!this.isCoreReady) {
return;
}
var position = this.core.getCursorPosition(e);
this.setPositionState(position);
this.mouseActivation.mouseMoved(position);
}
}, {
key: 'onMouseLeave',
value: function onMouseLeave() {
this.mouseActivation.mouseLeft();
this.setState({ isPositionOutside: true });
}
}, {
key: 'onClick',
value: function onClick(e) {
this.setPositionState(this.core.getCursorPosition(e));
this.mouseActivation.mouseClicked();
this.onMouseDetected();
}
}, {
key: 'onTouchDetected',
value: function onTouchDetected() {
var environment = {
isTouchDetected: true,
isMouseDetected: false
};
this.setState({ detectedEnvironment: environment });
this.props.onDetectedEnvironmentChanged(environment);
}
}, {
key: 'onMouseDetected',
value: function onMouseDetected() {
var environment = {
isTouchDetected: false,
isMouseDetected: true
};
this.setState({ detectedEnvironment: environment });
this.props.onDetectedEnvironmentChanged(environment);
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
if (this.props.isEnabled) {
this.enable();
}
}
}, {
key: 'UNSAFE_componentWillReceiveProps',
value: function UNSAFE_componentWillReceiveProps(_ref2) {
var willBeEnabled = _ref2.isEnabled;
var isEnabled = this.props.isEnabled;
var isEnabledWillChange = isEnabled !== willBeEnabled;
if (!isEnabledWillChange) {
return;
}
if (willBeEnabled) {
this.enable();
} else {
this.disable();
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.disable();
}
}, {
key: 'enable',
value: function enable() {
this.addEventListeners();
}
}, {
key: 'disable',
value: function disable() {
this.removeEventListeners();
}
}, {
key: 'init',
value: function init() {
this.core = new Core(this.el);
this.setElementDimensionsState(this.getElementDimensions(this.el));
}
}, {
key: 'setTouchActivationStrategy',
value: function setTouchActivationStrategy(interaction) {
var _props = this.props,
pressDurationInMs = _props.pressDurationInMs,
pressMoveThreshold = _props.pressMoveThreshold,
tapDurationInMs = _props.tapDurationInMs,
tapMoveThreshold = _props.tapMoveThreshold;
var TOUCH = INTERACTIONS.TOUCH,
TAP = INTERACTIONS.TAP,
PRESS = INTERACTIONS.PRESS;
switch (interaction) {
case PRESS:
this.touchActivation = new PressActivation({
onIsActiveChanged: this.onIsActiveChanged,
pressDurationInMs: pressDurationInMs,
pressMoveThreshold: pressMoveThreshold
});
break;
case TAP:
this.touchActivation = new TapActivation({
onIsActiveChanged: this.onIsActiveChanged,
tapDurationInMs: tapDurationInMs,
tapMoveThreshold: tapMoveThreshold
});
break;
case TOUCH:
this.touchActivation = new TouchActivation({
onIsActiveChanged: this.onIsActiveChanged
});
break;
default:
throw new Error('Must implement a touch activation strategy');
}
}
}, {
key: 'setMouseActivationStrategy',
value: function setMouseActivationStrategy(interaction) {
var _props2 = this.props,
hoverDelayInMs = _props2.hoverDelayInMs,
hoverOffDelayInMs = _props2.hoverOffDelayInMs;
var HOVER = INTERACTIONS.HOVER,
CLICK = INTERACTIONS.CLICK;
switch (interaction) {
case HOVER:
this.mouseActivation = new HoverActivation({
onIsActiveChanged: this.onIsActiveChanged,
hoverDelayInMs: hoverDelayInMs,
hoverOffDelayInMs: hoverOffDelayInMs
});
break;
case CLICK:
this.mouseActivation = new ClickActivation({
onIsActiveChanged: this.onIsActiveChanged
});
break;
default:
throw new Error('Must implement a mouse activation strategy');
}
}
}, {
key: 'reset',
value: function reset() {
var _core = this.core;
_core = _core === undefined ? {} : _core;
var lastMouseEvent = _core.lastEvent;
this.init();
if (!lastMouseEvent) {
return;
}
this.setPositionState(this.core.getCursorPosition(lastMouseEvent));
}
}, {
key: 'activate',
value: function activate() {
this.setState({ isActive: true });
this.props.onActivationChanged({ isActive: true });
}
}, {
key: 'deactivate',
value: function deactivate() {
var _this2 = this;
this.setState({ isActive: false }, function () {
var _state = _this2.state,
isPositionOutside = _state.isPositionOutside,
position = _state.position;
_this2.props.onPositionChanged({
isPositionOutside: isPositionOutside,
position: position
});
_this2.props.onActivationChanged({ isActive: false });
});
}
}, {
key: 'setPositionState',
value: function setPositionState(position) {
var isPositionOutside = this.getIsPositionOutside(position);
this.setState({
isPositionOutside: isPositionOutside,
position: position
}, this.onPositionChanged);
}
}, {
key: 'setElementDimensionsState',
value: function setElementDimensionsState(dimensions) {
this.setState({
elementDimensions: dimensions
});
}
}, {
key: 'setShouldGuardAgainstMouseEmulationByDevices',
value: function setShouldGuardAgainstMouseEmulationByDevices() {
this.shouldGuardAgainstMouseEmulationByDevices = true;
}
}, {
key: 'unsetShouldGuardAgainstMouseEmulationByDevices',
value: function unsetShouldGuardAgainstMouseEmulationByDevices() {
var _this3 = this;
this.timers.push({
name: MOUSE_EMULATION_GUARD_TIMER_NAME,
id: setTimeout(function () {
_this3.shouldGuardAgainstMouseEmulationByDevices = false;
}, 0)
});
}
}, {
key: 'getElementDimensions',
value: function getElementDimensions(el) {
var _el$getBoundingClient = el.getBoundingClientRect(),
width = _el$getBoundingClient.width,
height = _el$getBoundingClient.height;
return {
width: width,
height: height
};
}
}, {
key: 'getIsPositionOutside',
value: function getIsPositionOutside(position) {
var x = position.x,
y = position.y;
var _state$elementDimensi = this.state.elementDimensions,
width = _state$elementDimensi.width,
height = _state$elementDimensi.height;
var isPositionOutside = x < 0 || y < 0 || x > width || y > height;
return isPositionOutside;
}
}, {
key: 'getTouchEvent',
value: function getTouchEvent(e) {
return e.touches[0];
}
}, {
key: 'getIsReactComponent',
value: function getIsReactComponent(reactElement) {
return typeof reactElement.type === 'function';
}
}, {
key: 'shouldDecorateChild',
value: function shouldDecorateChild(child) {
return !!child && this.getIsReactComponent(child) && this.props.shouldDecorateChildren;
}
}, {
key: 'decorateChild',
value: function decorateChild(child, props) {
return cloneElement(child, props);
}
}, {
key: 'decorateChildren',
value: function decorateChildren(children, props) {
var _this4 = this;
return Children.map(children, function (child) {
return _this4.shouldDecorateChild(child) ? _this4.decorateChild(child, props) : child;
});
}
}, {
key: 'addEventListeners',
value: function addEventListeners() {
this.eventListeners.push(addEventListener(this.el, 'touchstart', this.onTouchStart, { passive: false }), addEventListener(this.el, 'touchmove', this.onTouchMove, { passive: false }), addEventListener(this.el, 'touchend', this.onTouchEnd), addEventListener(this.el, 'touchcancel', this.onTouchCancel), addEventListener(this.el, 'mouseenter', this.onMouseEnter), addEventListener(this.el, 'mousemove', this.onMouseMove), addEventListener(this.el, 'mouseleave', this.onMouseLeave), addEventListener(this.el, 'click', this.onClick));
}
}, {
key: 'removeEventListeners',
value: function removeEventListeners() {
while (this.eventListeners.length) {
this.eventListeners.pop().removeEventListener();
}
}
}, {
key: 'getPassThroughProps',
value: function getPassThroughProps() {
// const ownPropNames = Object.keys(this.constructor.propTypes);
// return omit(this.props, ownPropNames);
var ownPropNames = Object.keys(this.constructor.propTypes || {});
if (ownPropNames.length) return omit(this.props, ownPropNames);
}
}, {
key: 'render',
value: function render() {
var _this5 = this;
var _props3 = this.props,
children = _props3.children,
className = _props3.className,
mapChildProps = _props3.mapChildProps,
style = _props3.style;
var props = objectAssign({}, mapChildProps(this.state), this.getPassThroughProps());
return React.createElement(
'div',
{
className: className,
ref: function ref(el) {
return _this5.el = el;
},
style: objectAssign({}, style, {
WebkitUserSelect: 'none'
})
},
this.decorateChildren(children, props)
);
}
}, {
key: 'isCoreReady',
get: function get() {
return !!this.core;
}
}]);
return _class;
}(React.Component);
_class.displayName = 'ReactCursorPosition';
_class.propTypes = {
activationInteractionMouse: PropTypes.oneOf([INTERACTIONS.CLICK, INTERACTIONS.HOVER]),
activationInteractionTouch: PropTypes.oneOf([INTERACTIONS.PRESS, INTERACTIONS.TAP, INTERACTIONS.TOUCH]),
children: PropTypes.any,
className: PropTypes.string,
hoverDelayInMs: PropTypes.number,
hoverOffDelayInMs: PropTypes.number,
isEnabled: PropTypes.bool,
mapChildProps: PropTypes.func,
onActivationChanged: PropTypes.func,
onDetectedEnvironmentChanged: PropTypes.func,
onPositionChanged: PropTypes.func,
pressDurationInMs: PropTypes.number,
pressMoveThreshold: PropTypes.number,
shouldDecorateChildren: PropTypes.bool,
shouldStopTouchMovePropagation: PropTypes.bool,
style: PropTypes.object,
tapDurationInMs: PropTypes.number,
tapMoveThreshold: PropTypes.number
};
_class.defaultProps = {
activationInteractionMouse: INTERACTIONS.HOVER,
activationInteractionTouch: INTERACTIONS.PRESS,
hoverDelayInMs: 0,
hoverOffDelayInMs: 0,
isEnabled: true,
mapChildProps: function mapChildProps(props) {
return props;
},
onActivationChanged: noop,
onDetectedEnvironmentChanged: noop,
onPositionChanged: noop,
pressDurationInMs: 500,
pressMoveThreshold: 5,
shouldDecorateChildren: true,
shouldStopTouchMovePropagation: false,
tapDurationInMs: 180,
tapMoveThreshold: 5
};
var _initialiseProps = function _initialiseProps() {
var _this6 = this;
this.onPositionChanged = function () {
var onPositionChanged = _this6.props.onPositionChanged;
onPositionChanged(_this6.state);
};
};
export default _class;