react-swipeable
Advanced tools
Comparing version 3.9.2 to 4.0.0
@@ -0,1 +1,10 @@ | ||
# 4.0.0 | ||
* **Major Change** `preventDefaultTouchmoveEvent` defaults to `false` now [#69](https://github.com/dogfessional/react-swipeable/issue/69) | ||
* This change is in part due to a [Chrome56+ change](https://github.com/dogfessional/react-swipeable#chrome-56-and-later-warning-with-preventdefault) | ||
* **Major Change** drop support for React 12 & 13, `peerDependencies` updated [#64](https://github.com/dogfessional/react-swipeable/pull/64) | ||
* **Major Change** `trackMouse` now 'tracks' the swipe outside of the swipeable component, thanks for example [@TanaseHagi](https://github.com/TanaseHagi) [#67](https://github.com/dogfessional/react-swipeable/pull/67) | ||
* `prop-types` added as `dependencies` [#64](https://github.com/dogfessional/react-swipeable/pull/64) | ||
* react 16 added to `peerDependencies` | ||
# 3.9.0 | ||
@@ -2,0 +11,0 @@ |
@@ -5,3 +5,10 @@ 'use strict'; | ||
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; } | ||
var React = require('react'); | ||
var PropTypes = require('prop-types'); | ||
@@ -17,102 +24,106 @@ function getInitialState() { | ||
var Swipeable = React.createClass({ | ||
displayName: 'Swipeable', | ||
function getMovingPosition(e) { | ||
return 'changedTouches' in e ? { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY } : { x: e.clientX, y: e.clientY }; | ||
} | ||
function getPosition(e) { | ||
return 'touches' in e ? { x: e.touches[0].clientX, y: e.touches[0].clientY } : { x: e.clientX, y: e.clientY }; | ||
} | ||
propTypes: { | ||
onSwiped: React.PropTypes.func, | ||
onSwiping: React.PropTypes.func, | ||
onSwipingUp: React.PropTypes.func, | ||
onSwipingRight: React.PropTypes.func, | ||
onSwipingDown: React.PropTypes.func, | ||
onSwipingLeft: React.PropTypes.func, | ||
onSwipedUp: React.PropTypes.func, | ||
onSwipedRight: React.PropTypes.func, | ||
onSwipedDown: React.PropTypes.func, | ||
onSwipedLeft: React.PropTypes.func, | ||
onTap: React.PropTypes.func, | ||
flickThreshold: React.PropTypes.number, | ||
delta: React.PropTypes.number, | ||
preventDefaultTouchmoveEvent: React.PropTypes.bool, | ||
stopPropagation: React.PropTypes.bool, | ||
nodeName: React.PropTypes.string, | ||
trackMouse: React.PropTypes.bool, | ||
children: React.PropTypes.node | ||
}, | ||
function calculatePos(e, state) { | ||
var _getMovingPosition = getMovingPosition(e), | ||
x = _getMovingPosition.x, | ||
y = _getMovingPosition.y; | ||
getDefaultProps: function getDefaultProps() { | ||
return { | ||
flickThreshold: 0.6, | ||
delta: 10, | ||
preventDefaultTouchmoveEvent: true, | ||
stopPropagation: false, | ||
nodeName: 'div' | ||
}; | ||
}, | ||
componentWillMount: function componentWillMount() { | ||
this.swipeable = getInitialState(); | ||
}, | ||
calculatePos: function calculatePos(e) { | ||
var x = void 0; | ||
var y = void 0; | ||
var deltaX = state.x - x; | ||
var deltaY = state.y - y; | ||
if (e.changedTouches) { | ||
x = e.changedTouches[0].clientX; | ||
y = e.changedTouches[0].clientY; | ||
} else { | ||
x = e.clientX; | ||
y = e.clientY; | ||
} | ||
var absX = Math.abs(deltaX); | ||
var absY = Math.abs(deltaY); | ||
var xd = this.swipeable.x - x; | ||
var yd = this.swipeable.y - y; | ||
var time = Date.now() - state.start; | ||
var velocity = Math.sqrt(absX * absX + absY * absY) / time; | ||
var axd = Math.abs(xd); | ||
var ayd = Math.abs(yd); | ||
return { deltaX: deltaX, deltaY: deltaY, absX: absX, absY: absY, velocity: velocity }; | ||
} | ||
var time = Date.now() - this.swipeable.start; | ||
var velocity = Math.sqrt(axd * axd + ayd * ayd) / time; | ||
var Swipeable = function (_React$Component) { | ||
_inherits(Swipeable, _React$Component); | ||
return { | ||
deltaX: xd, | ||
deltaY: yd, | ||
absX: axd, | ||
absY: ayd, | ||
velocity: velocity | ||
}; | ||
}, | ||
eventStart: function eventStart(e) { | ||
if (typeof this.props.onMouseDown === 'function') { | ||
this.props.onMouseDown(e); | ||
} | ||
function Swipeable(props, context) { | ||
_classCallCheck(this, Swipeable); | ||
if (e.type === 'mousedown' && !this.props.trackMouse) { | ||
return; | ||
var _this = _possibleConstructorReturn(this, _React$Component.call(this, props, context)); | ||
_this.eventStart = _this.eventStart.bind(_this); | ||
_this.eventMove = _this.eventMove.bind(_this); | ||
_this.eventEnd = _this.eventEnd.bind(_this); | ||
_this.mouseDown = _this.mouseDown.bind(_this); | ||
_this.mouseMove = _this.mouseMove.bind(_this); | ||
_this.mouseUp = _this.mouseUp.bind(_this); | ||
return _this; | ||
} | ||
Swipeable.prototype.componentWillMount = function componentWillMount() { | ||
this.swipeable = getInitialState(); | ||
}; | ||
Swipeable.prototype.componentWillUnmount = function componentWillUnmount() { | ||
if (this.props.trackMouse) { | ||
document.removeEventListener('mousemove', this.mouseMove); | ||
document.removeEventListener('mouseup', this.mouseUp); | ||
} | ||
}; | ||
if (e.touches && e.touches.length > 1) { | ||
Swipeable.prototype.mouseDown = function mouseDown(e) { | ||
if (!this.props.trackMouse || e.type !== 'mousedown') { | ||
return; | ||
} | ||
var touches = e.touches; | ||
if (!touches) { | ||
touches = [{ clientX: e.clientX, clientY: e.clientY }]; | ||
} | ||
if (typeof this.props.onMouseDown === 'function') this.props.onMouseDown(e); | ||
this.eventStart(e); | ||
document.addEventListener('mousemove', this.mouseMove); | ||
document.addEventListener('mouseup', this.mouseUp); | ||
}; | ||
Swipeable.prototype.mouseMove = function mouseMove(e) { | ||
this.eventMove(e); | ||
}; | ||
Swipeable.prototype.mouseUp = function mouseUp(e) { | ||
document.removeEventListener('mousemove', this.mouseMove); | ||
document.removeEventListener('mouseup', this.mouseUp); | ||
this.eventEnd(e); | ||
}; | ||
Swipeable.prototype.eventStart = function eventStart(e) { | ||
if (e.touches && e.touches.length > 1) return; | ||
var _getPosition = getPosition(e), | ||
x = _getPosition.x, | ||
y = _getPosition.y; | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
this.swipeable = { | ||
start: Date.now(), | ||
x: touches[0].clientX, | ||
y: touches[0].clientY, | ||
swiping: false | ||
}; | ||
}, | ||
eventMove: function eventMove(e) { | ||
if (typeof this.props.onMouseMove === 'function') { | ||
this.props.onMouseMove(e); | ||
} | ||
this.swipeable = { start: Date.now(), x: x, y: y, swiping: false }; | ||
}; | ||
if (e.type === 'mousemove' && !this.props.trackMouse) { | ||
return; | ||
} | ||
Swipeable.prototype.eventMove = function eventMove(e) { | ||
var _props = this.props, | ||
stopPropagation = _props.stopPropagation, | ||
delta = _props.delta, | ||
onSwiping = _props.onSwiping, | ||
onSwipingLeft = _props.onSwipingLeft, | ||
onSwipedLeft = _props.onSwipedLeft, | ||
onSwipingRight = _props.onSwipingRight, | ||
onSwipedRight = _props.onSwipedRight, | ||
onSwipingUp = _props.onSwipingUp, | ||
onSwipedUp = _props.onSwipedUp, | ||
onSwipingDown = _props.onSwipingDown, | ||
onSwipedDown = _props.onSwipedDown, | ||
preventDefaultTouchmoveEvent = _props.preventDefaultTouchmoveEvent; | ||
if (!this.swipeable.x || !this.swipeable.y || e.touches && e.touches.length > 1) { | ||
@@ -122,33 +133,31 @@ return; | ||
var cancelPageSwipe = false; | ||
var pos = this.calculatePos(e); | ||
var pos = calculatePos(e, this.swipeable); | ||
if (pos.absX < this.props.delta && pos.absY < this.props.delta) { | ||
return; | ||
} | ||
if (pos.absX < delta && pos.absY < delta) return; | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
if (stopPropagation) e.stopPropagation(); | ||
if (this.props.onSwiping) { | ||
this.props.onSwiping(e, pos.deltaX, pos.deltaY, pos.absX, pos.absY, pos.velocity); | ||
if (onSwiping) { | ||
onSwiping(e, pos.deltaX, pos.deltaY, pos.absX, pos.absY, pos.velocity); | ||
} | ||
var cancelablePageSwipe = false; | ||
if (pos.absX > pos.absY) { | ||
if (pos.deltaX > 0) { | ||
if (this.props.onSwipingLeft || this.props.onSwipedLeft) { | ||
this.props.onSwipingLeft && this.props.onSwipingLeft(e, pos.absX); | ||
cancelPageSwipe = true; | ||
if (onSwipingLeft || onSwipedLeft) { | ||
onSwipingLeft && onSwipingLeft(e, pos.absX); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (this.props.onSwipingRight || this.props.onSwipedRight) { | ||
this.props.onSwipingRight && this.props.onSwipingRight(e, pos.absX); | ||
cancelPageSwipe = true; | ||
} else if (onSwipingRight || onSwipedRight) { | ||
onSwipingRight && onSwipingRight(e, pos.absX); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (pos.deltaY > 0) { | ||
if (this.props.onSwipingUp || this.props.onSwipedUp) { | ||
this.props.onSwipingUp && this.props.onSwipingUp(e, pos.absY); | ||
cancelPageSwipe = true; | ||
if (onSwipingUp || onSwipedUp) { | ||
onSwipingUp && onSwipingUp(e, pos.absY); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (this.props.onSwipingDown || this.props.onSwipedDown) { | ||
this.props.onSwipingDown && this.props.onSwipingDown(e, pos.absY); | ||
cancelPageSwipe = true; | ||
} else if (onSwipingDown || onSwipedDown) { | ||
onSwipingDown && onSwipingDown(e, pos.absY); | ||
cancelablePageSwipe = true; | ||
} | ||
@@ -158,42 +167,45 @@ | ||
if (cancelPageSwipe && this.props.preventDefaultTouchmoveEvent) { | ||
e.preventDefault(); | ||
} | ||
}, | ||
eventEnd: function eventEnd(e) { | ||
if (typeof this.props.onMouseUp === 'function') { | ||
this.props.onMouseUp(e); | ||
} | ||
if (cancelablePageSwipe && preventDefaultTouchmoveEvent) e.preventDefault(); | ||
}; | ||
if (e.type === 'mouseup' && !this.props.trackMouse) { | ||
return; | ||
} | ||
Swipeable.prototype.eventEnd = function eventEnd(e) { | ||
var _props2 = this.props, | ||
stopPropagation = _props2.stopPropagation, | ||
flickThreshold = _props2.flickThreshold, | ||
onSwiped = _props2.onSwiped, | ||
onSwipedLeft = _props2.onSwipedLeft, | ||
onSwipedRight = _props2.onSwipedRight, | ||
onSwipedUp = _props2.onSwipedUp, | ||
onSwipedDown = _props2.onSwipedDown, | ||
onTap = _props2.onTap; | ||
if (this.swipeable.swiping) { | ||
var pos = this.calculatePos(e); | ||
var pos = calculatePos(e, this.swipeable); | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
if (stopPropagation) e.stopPropagation(); | ||
var isFlick = pos.velocity > this.props.flickThreshold; | ||
var isFlick = pos.velocity > flickThreshold; | ||
this.props.onSwiped && this.props.onSwiped(e, pos.deltaX, pos.deltaY, isFlick, pos.velocity); | ||
onSwiped && onSwiped(e, pos.deltaX, pos.deltaY, isFlick, pos.velocity); | ||
if (pos.absX > pos.absY) { | ||
if (pos.deltaX > 0) { | ||
this.props.onSwipedLeft && this.props.onSwipedLeft(e, pos.deltaX, isFlick); | ||
onSwipedLeft && onSwipedLeft(e, pos.deltaX, isFlick); | ||
} else { | ||
this.props.onSwipedRight && this.props.onSwipedRight(e, pos.deltaX, isFlick); | ||
onSwipedRight && onSwipedRight(e, pos.deltaX, isFlick); | ||
} | ||
} else if (pos.deltaY > 0) { | ||
this.props.onSwipedUp && this.props.onSwipedUp(e, pos.deltaY, isFlick); | ||
onSwipedUp && onSwipedUp(e, pos.deltaY, isFlick); | ||
} else { | ||
this.props.onSwipedDown && this.props.onSwipedDown(e, pos.deltaY, isFlick); | ||
onSwipedDown && onSwipedDown(e, pos.deltaY, isFlick); | ||
} | ||
} else { | ||
this.props.onTap && this.props.onTap(e); | ||
onTap && onTap(e); | ||
} | ||
this.swipeable = getInitialState(); | ||
}, | ||
render: function render() { | ||
}; | ||
Swipeable.prototype.render = function render() { | ||
var newProps = _extends({}, this.props, { | ||
@@ -203,5 +215,3 @@ onTouchStart: this.eventStart, | ||
onTouchEnd: this.eventEnd, | ||
onMouseDown: this.eventStart, | ||
onMouseMove: this.eventMove, | ||
onMouseUp: this.eventEnd | ||
onMouseDown: this.mouseDown | ||
}); | ||
@@ -229,5 +239,36 @@ | ||
return React.createElement(this.props.nodeName, newProps, this.props.children); | ||
} | ||
}); | ||
}; | ||
return Swipeable; | ||
}(React.Component); | ||
Swipeable.propTypes = { | ||
onSwiped: PropTypes.func, | ||
onSwiping: PropTypes.func, | ||
onSwipingUp: PropTypes.func, | ||
onSwipingRight: PropTypes.func, | ||
onSwipingDown: PropTypes.func, | ||
onSwipingLeft: PropTypes.func, | ||
onSwipedUp: PropTypes.func, | ||
onSwipedRight: PropTypes.func, | ||
onSwipedDown: PropTypes.func, | ||
onSwipedLeft: PropTypes.func, | ||
onTap: PropTypes.func, | ||
flickThreshold: PropTypes.number, | ||
delta: PropTypes.number, | ||
preventDefaultTouchmoveEvent: PropTypes.bool, | ||
stopPropagation: PropTypes.bool, | ||
nodeName: PropTypes.string, | ||
trackMouse: PropTypes.bool, | ||
children: PropTypes.node | ||
}; | ||
Swipeable.defaultProps = { | ||
flickThreshold: 0.6, | ||
delta: 10, | ||
preventDefaultTouchmoveEvent: false, | ||
stopPropagation: false, | ||
nodeName: 'div' | ||
}; | ||
module.exports = Swipeable; |
{ | ||
"name": "react-swipeable", | ||
"version": "3.9.2", | ||
"version": "4.0.0", | ||
"description": "Swipe bindings for react", | ||
"main": "lib/Swipeable.js", | ||
"scripts": { | ||
"test": "jest && npm run lint", | ||
"test": "npm run test:unit && npm run lint", | ||
"test:unit": "jest", | ||
"lint": "eslint .", | ||
@@ -53,9 +54,13 @@ "build:lib": "babel src --out-dir lib --ignore __tests__ --no-comments", | ||
"jest": "^18.1.0", | ||
"react": ">=0.12.x", | ||
"react-addons-test-utils": "^15.4.2", | ||
"react-dom": "^15.4.2" | ||
"react": "^15.5.0", | ||
"react-addons-test-utils": "^15.5.0", | ||
"react-dom": "^15.5.0", | ||
"react-test-renderer": "^15.5.0" | ||
}, | ||
"dependencies": { | ||
"prop-types": "^15.5.8" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=0.12.0 || ^15.0.0-0" | ||
"react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0" | ||
} | ||
} |
@@ -7,3 +7,2 @@ # Swipeable [![npm version](https://img.shields.io/npm/v/react-swipeable.svg?style=flat-square)](https://www.npmjs.com/package/react-swipeable) [![npm downloads](https://img.shields.io/npm/dm/react-swipeable.svg?style=flat-square)](https://www.npmjs.com/package/react-swipeable) | ||
### Install | ||
Using npm: | ||
```console | ||
@@ -15,22 +14,20 @@ $ npm install --save react-swipeable | ||
react-swipeable generates a React component (defaults to `<div>`) and binds touch events to it. | ||
```js | ||
var Swipeable = require('react-swipeable') | ||
import Swipeable from 'react-swipeable' | ||
var SampleComponent = React.createClass({ | ||
render: function () { | ||
class SwipeComponent extends React.Component { | ||
swiping(e, deltaX, deltaY, absX, absY, velocity) { | ||
console.log('Swiping...', e, deltaX, deltaY, absX, absY, velocity) | ||
} | ||
swiped(e, deltaX, deltaY, isFlick, velocity) { | ||
console.log('Swiped...', e, deltaX, deltaY, isFlick, velocity) | ||
} | ||
render() { | ||
return ( | ||
<Swipeable | ||
onSwiping={this.swiping} | ||
onSwipingUp={this.swipingUp} | ||
onSwipingRight={this.swipingRight} | ||
onSwipingDown={this.swipingDown} | ||
onSwipingLeft={this.swipingLeft} | ||
onSwiped={this.swiped} | ||
onSwipedUp={this.swipedUp} | ||
onSwipedRight={this.swipedRight} | ||
onSwipedDown={this.swipedDown} | ||
onSwipedLeft={this.swipedLeft} | ||
onTap={this.tapped} > | ||
onSwiped={this.swiped} > | ||
You can swipe here! | ||
@@ -40,4 +37,5 @@ </Swipeable> | ||
} | ||
}) | ||
} | ||
``` | ||
react-swipeable generates a React element(`<div>` by default) under the hood and binds touch events to it which in turn are used to fire the `swiped` and `swiping` props. | ||
@@ -58,3 +56,3 @@ ## Event Props | ||
#####Configuration Props | ||
#### Configuration Props | ||
@@ -65,3 +63,6 @@ **`flickThreshold`** is a number (float) which determines the max velocity of a swipe before it's considered a flick. The default value is `0.6`. | ||
**`preventDefaultTouchmoveEvent`** is whether to prevent the browser's [touchmove](https://developer.mozilla.org/en-US/docs/Web/Events/touchmove) event. Sometimes you would like the target to scroll natively. The default value is `true`. | ||
**`preventDefaultTouchmoveEvent`** is whether to prevent the browser's [touchmove](https://developer.mozilla.org/en-US/docs/Web/Events/touchmove) event. Sometimes you would like the target to scroll natively. The default value is `false`. [Chrome 56 and later, warning with preventDefault](#chrome-56-and-later-warning-with-preventdefault) | ||
* **Notes** `e.preventDefault()` is only called when `preventDefaultTouchmoveEvent` is `true` **and** the user is swiping in a direction that has an associated directional `onSwiping` or `onSwiped` prop. | ||
* Example: user is swiping right with `<Swipable onSwipedRight={this.userSwipedRight} preventDefaultTouchmoveEvent={true} >` then `e.preventDefault()` will be called, but if user was swiping left `e.preventDefault()` would **not** be called. | ||
* Please experiment with [example](http://dogfessional.github.io/react-swipeable/) to test `preventDefaultTouchmoveEvent`. | ||
@@ -78,21 +79,38 @@ **`stopPropagation`** automatically calls stopPropagation on all 'swipe' events. The default value is `false`. | ||
``` | ||
onSwiped: React.PropTypes.func, | ||
onSwiping: React.PropTypes.func, | ||
onSwipingUp: React.PropTypes.func, | ||
onSwipingRight: React.PropTypes.func, | ||
onSwipingDown: React.PropTypes.func, | ||
onSwipingLeft: React.PropTypes.func, | ||
onSwipedUp: React.PropTypes.func, | ||
onSwipedRight: React.PropTypes.func, | ||
onSwipedDown: React.PropTypes.func, | ||
onSwipedLeft: React.PropTypes.func, | ||
onTap: React.PropTypes.func, | ||
flickThreshold: React.PropTypes.number, | ||
delta: React.PropTypes.number, | ||
preventDefaultTouchmoveEvent: React.PropTypes.bool, | ||
stopPropagation: React.PropTypes.bool, | ||
nodeName: React.PropTypes.string | ||
trackMouse: React.PropTypes.bool, | ||
Event Props: | ||
onSwiped: PropTypes.func, | ||
onSwiping: PropTypes.func, | ||
onSwipingUp: PropTypes.func, | ||
onSwipingRight: PropTypes.func, | ||
onSwipingDown: PropTypes.func, | ||
onSwipingLeft: PropTypes.func, | ||
onSwipedUp: PropTypes.func, | ||
onSwipedRight: PropTypes.func, | ||
onSwipedDown: PropTypes.func, | ||
onSwipedLeft: PropTypes.func, | ||
onTap: PropTypes.func, | ||
Config Props: | ||
flickThreshold: PropTypes.number, // default: 0.6 | ||
delta: PropTypes.number, // default: 10 | ||
preventDefaultTouchmoveEvent: PropTypes.bool, // default: false | ||
stopPropagation: PropTypes.bool, // default: false | ||
nodeName: PropTypes.string // default: div | ||
trackMouse: PropTypes.bool, // default: false | ||
``` | ||
### Chrome 56 and later, warning with preventDefault | ||
When this library tries to call `e.preventDefault()` in Chrome 56+ a warning is logged: | ||
`Unable to preventDefault inside passive event listener due to target being treated as passive.` | ||
This warning is because this [change](https://developers.google.com/web/updates/2017/01/scrolling-intervention) to Chrome 56+ and the way the synthetic events are setup in reactjs. | ||
If you'd like to prevent all scrolling/zooming within a `<Swipeable />` component you can pass a `touchAction` style property equal to `'none'`, [example](https://github.com/dogfessional/react-swipeable/blob/master/examples/app/Main.js#L143). Chrome's recommendation for [reference](https://developers.google.com/web/updates/2017/01/scrolling-intervention). | ||
``` | ||
<Swipeable style={{touchAction: 'none'}} /> | ||
``` | ||
Follow reacts handling of this issue here: [facebook/react#8968](https://github.com/facebook/react/issues/8968) | ||
## Development | ||
@@ -99,0 +117,0 @@ |
@@ -1,2 +0,1 @@ | ||
function createClientXYObject(x, y) { | ||
@@ -6,11 +5,14 @@ return { clientX: x, clientY: y }; | ||
export function createStartTouchEventObject({ x = 0, y = 0 }) { | ||
export function createStartTouchEventObject({ x = 0, y = 0, preventDefault = () => {} }) { | ||
return { | ||
touches: [createClientXYObject(x, y)], | ||
preventDefault, | ||
}; | ||
} | ||
export function createMoveTouchEventObject({ x = 0, y = 0, includeTouches = true }) { | ||
export function createMoveTouchEventObject(props) { | ||
const { x = 0, y = 0, includeTouches = true, preventDefault = () => {} } = props; | ||
const moveTouchEvent = { | ||
changedTouches: [createClientXYObject(x, y)], | ||
preventDefault, | ||
}; | ||
@@ -21,6 +23,7 @@ if (includeTouches) moveTouchEvent.touches = [createClientXYObject(x, y)]; | ||
export function createMouseEventObject({ x = 0, y = 0 }) { | ||
export function createMouseEventObject({ x = 0, y = 0, preventDefault = () => {} }) { | ||
return { | ||
...createClientXYObject(x, y), | ||
preventDefault, | ||
}; | ||
} |
@@ -0,1 +1,2 @@ | ||
/* global document */ | ||
import React from 'react'; | ||
@@ -24,2 +25,19 @@ import { mount } from 'enzyme'; | ||
describe('Swipeable', () => { | ||
let origEventListener; | ||
let eventListenerMap; | ||
beforeAll(() => { | ||
origEventListener = document.eventListener; | ||
}); | ||
beforeEach(() => { | ||
// track eventListener adds to trigger later | ||
// idea from - https://github.com/airbnb/enzyme/issues/426#issuecomment-228601631 | ||
eventListenerMap = {}; | ||
document.addEventListener = jest.fn((event, cb) => { | ||
eventListenerMap[event] = cb; | ||
}); | ||
}); | ||
afterAll(() => { | ||
document.eventListener = origEventListener; | ||
}); | ||
it('renders children', () => { | ||
@@ -69,17 +87,16 @@ const wrapper = mount(( | ||
const swipeFuncs = getMockedSwipeFunctions(); | ||
const onMouseUp = jest.fn(); | ||
const onMouseDown = jest.fn(); | ||
const onMouseMove = jest.fn(); | ||
const onTap = jest.fn(); | ||
const wrapper = mount(( | ||
<Swipeable | ||
trackMouse={true} | ||
onMouseUp={onMouseUp} | ||
onMouseDown={onMouseDown} | ||
onMouseMove={onMouseMove} | ||
onTap={onTap} | ||
{...swipeFuncs} | ||
> | ||
<span>Touch Here</span> | ||
</Swipeable> | ||
<div> | ||
<Swipeable | ||
trackMouse={true} | ||
onMouseDown={onMouseDown} | ||
onTap={onTap} | ||
{...swipeFuncs} | ||
> | ||
<span>Touch Here</span> | ||
</Swipeable> | ||
<div id="outsideElement" /> | ||
</div> | ||
)); | ||
@@ -89,7 +106,8 @@ | ||
touchHere.simulate('mouseDown', createMouseEventObject({ x: 100, y: 100 })); | ||
touchHere.simulate('mouseMove', createMouseEventObject({ x: 125, y: 100 })); | ||
touchHere.simulate('mouseMove', createMouseEventObject({ x: 150, y: 100 })); | ||
touchHere.simulate('mouseMove', createMouseEventObject({ x: 175, y: 100 })); | ||
touchHere.simulate('mouseUp', createMouseEventObject({ x: 200, y: 100 })); | ||
eventListenerMap.mousemove(createMouseEventObject({ x: 125, y: 100 })); | ||
eventListenerMap.mousemove(createMouseEventObject({ x: 150, y: 100 })); | ||
eventListenerMap.mousemove(createMouseEventObject({ x: 175, y: 100 })); | ||
eventListenerMap.mouseup(createMouseEventObject({ x: 200, y: 100 })); | ||
expect(swipeFuncs.onSwipedRight).toHaveBeenCalled(); | ||
@@ -107,6 +125,4 @@ expect(swipeFuncs.onSwipingRight).toHaveBeenCalledTimes(3); | ||
// still calls passed through mouse event props | ||
expect(onMouseUp).toHaveBeenCalled(); | ||
// still calls passed through mouse event prop | ||
expect(onMouseDown).toHaveBeenCalled(); | ||
expect(onMouseMove).toHaveBeenCalledTimes(3); | ||
}); | ||
@@ -128,3 +144,3 @@ | ||
// simulate what is probably a light tap, | ||
// meaning the user "swiped" just a little, but less than the delta | ||
// meaning the user "swiped" just a little, but less than the delta | ||
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 })); | ||
@@ -147,2 +163,49 @@ touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 103, y: 100 })); | ||
}); | ||
it('calls preventDefault correctly when swiping in direction that has a callback', () => { | ||
const onSwipedDown = jest.fn(); | ||
const preventDefault = jest.fn(); | ||
const wrapper = mount(( | ||
<Swipeable | ||
onSwipedDown={onSwipedDown} | ||
preventDefaultTouchmoveEvent={true} | ||
> | ||
<span>Touch Here</span> | ||
</Swipeable> | ||
)); | ||
const touchHere = wrapper.find('span'); | ||
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 125, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 150, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 175, preventDefault })); | ||
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 200, preventDefault })); | ||
expect(onSwipedDown).toHaveBeenCalled(); | ||
expect(preventDefault).toHaveBeenCalledTimes(3); | ||
}); | ||
it('does not call preventDefault when false', () => { | ||
const onSwipedUp = jest.fn(); | ||
const preventDefault = jest.fn(); | ||
const wrapper = mount(( | ||
<Swipeable | ||
onSwipedUp={onSwipedUp} | ||
> | ||
<span>Touch Here</span> | ||
</Swipeable> | ||
)); | ||
const touchHere = wrapper.find('span'); | ||
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 75, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 50, preventDefault })); | ||
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 25, preventDefault })); | ||
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 5, preventDefault })); | ||
expect(onSwipedUp).toHaveBeenCalled(); | ||
expect(preventDefault).not.toHaveBeenCalled(); | ||
}); | ||
}); |
@@ -0,2 +1,4 @@ | ||
/* global document */ | ||
const React = require('react'); | ||
const PropTypes = require('prop-types'); | ||
@@ -12,104 +14,102 @@ function getInitialState() { | ||
const Swipeable = React.createClass({ | ||
propTypes: { | ||
onSwiped: React.PropTypes.func, | ||
onSwiping: React.PropTypes.func, | ||
onSwipingUp: React.PropTypes.func, | ||
onSwipingRight: React.PropTypes.func, | ||
onSwipingDown: React.PropTypes.func, | ||
onSwipingLeft: React.PropTypes.func, | ||
onSwipedUp: React.PropTypes.func, | ||
onSwipedRight: React.PropTypes.func, | ||
onSwipedDown: React.PropTypes.func, | ||
onSwipedLeft: React.PropTypes.func, | ||
onTap: React.PropTypes.func, | ||
flickThreshold: React.PropTypes.number, | ||
delta: React.PropTypes.number, | ||
preventDefaultTouchmoveEvent: React.PropTypes.bool, | ||
stopPropagation: React.PropTypes.bool, | ||
nodeName: React.PropTypes.string, | ||
trackMouse: React.PropTypes.bool, | ||
children: React.PropTypes.node, | ||
}, | ||
function getMovingPosition(e) { | ||
// If not a touch, determine point from mouse coordinates | ||
return 'changedTouches' in e | ||
? { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY } | ||
: { x: e.clientX, y: e.clientY }; | ||
} | ||
function getPosition(e) { | ||
// If not a touch, determine point from mouse coordinates | ||
return 'touches' in e | ||
? { x: e.touches[0].clientX, y: e.touches[0].clientY } | ||
: { x: e.clientX, y: e.clientY }; | ||
} | ||
getDefaultProps() { | ||
return { | ||
flickThreshold: 0.6, | ||
delta: 10, | ||
preventDefaultTouchmoveEvent: true, | ||
stopPropagation: false, | ||
nodeName: 'div', | ||
}; | ||
}, | ||
function calculatePos(e, state) { | ||
const { x, y } = getMovingPosition(e); | ||
const deltaX = state.x - x; | ||
const deltaY = state.y - y; | ||
const absX = Math.abs(deltaX); | ||
const absY = Math.abs(deltaY); | ||
const time = Date.now() - state.start; | ||
const velocity = Math.sqrt(absX * absX + absY * absY) / time; | ||
return { deltaX, deltaY, absX, absY, velocity }; | ||
} | ||
class Swipeable extends React.Component { | ||
constructor(props, context) { | ||
super(props, context); | ||
this.eventStart = this.eventStart.bind(this); | ||
this.eventMove = this.eventMove.bind(this); | ||
this.eventEnd = this.eventEnd.bind(this); | ||
this.mouseDown = this.mouseDown.bind(this); | ||
this.mouseMove = this.mouseMove.bind(this); | ||
this.mouseUp = this.mouseUp.bind(this); | ||
} | ||
componentWillMount() { | ||
// setup internal swipeable state | ||
this.swipeable = getInitialState(); | ||
}, | ||
} | ||
componentWillUnmount() { | ||
if (this.props.trackMouse) { | ||
// just to be safe attempt removal | ||
document.removeEventListener('mousemove', this.mouseMove); | ||
document.removeEventListener('mouseup', this.mouseUp); | ||
} | ||
} | ||
calculatePos(e) { | ||
let x; | ||
let y; | ||
// If not a touch, determine point from mouse coordinates | ||
if (e.changedTouches) { | ||
x = e.changedTouches[0].clientX; | ||
y = e.changedTouches[0].clientY; | ||
} else { | ||
x = e.clientX; | ||
y = e.clientY; | ||
mouseDown(e) { | ||
if (!this.props.trackMouse || e.type !== 'mousedown') { | ||
return; | ||
} | ||
// allow 'orig' onMouseDown's to fire also | ||
// eslint-disable-next-line react/prop-types | ||
if (typeof this.props.onMouseDown === 'function') this.props.onMouseDown(e); | ||
const xd = this.swipeable.x - x; | ||
const yd = this.swipeable.y - y; | ||
this.eventStart(e); | ||
const axd = Math.abs(xd); | ||
const ayd = Math.abs(yd); | ||
// setup document listeners to track mouse movement outside <Swipeable>'s area | ||
document.addEventListener('mousemove', this.mouseMove); | ||
document.addEventListener('mouseup', this.mouseUp); | ||
} | ||
const time = Date.now() - this.swipeable.start; | ||
const velocity = Math.sqrt(axd * axd + ayd * ayd) / time; | ||
mouseMove(e) { | ||
this.eventMove(e); | ||
} | ||
return { | ||
deltaX: xd, | ||
deltaY: yd, | ||
absX: axd, | ||
absY: ayd, | ||
velocity, | ||
}; | ||
}, | ||
mouseUp(e) { | ||
document.removeEventListener('mousemove', this.mouseMove); | ||
document.removeEventListener('mouseup', this.mouseUp); | ||
this.eventEnd(e); | ||
} | ||
eventStart(e) { | ||
if (typeof this.props.onMouseDown === 'function') { // eslint-disable-line react/prop-types | ||
this.props.onMouseDown(e); // eslint-disable-line react/prop-types | ||
} | ||
// if more than a single touch don't track, for now... | ||
if (e.touches && e.touches.length > 1) return; | ||
if (e.type === 'mousedown' && !this.props.trackMouse) { | ||
return; | ||
} | ||
const { x, y } = getPosition(e); | ||
if (e.touches && e.touches.length > 1) { | ||
return; | ||
} | ||
// If not a touch, determine point from mouse coordinates | ||
let touches = e.touches; | ||
if (!touches) { | ||
touches = [{ clientX: e.clientX, clientY: e.clientY }]; | ||
} | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
this.swipeable = { | ||
start: Date.now(), | ||
x: touches[0].clientX, | ||
y: touches[0].clientY, | ||
swiping: false, | ||
}; | ||
}, | ||
this.swipeable = { start: Date.now(), x, y, swiping: false }; | ||
} | ||
eventMove(e) { | ||
if (typeof this.props.onMouseMove === 'function') { // eslint-disable-line react/prop-types | ||
this.props.onMouseMove(e); // eslint-disable-line react/prop-types | ||
} | ||
const { | ||
stopPropagation, | ||
delta, | ||
onSwiping, | ||
onSwipingLeft, onSwipedLeft, | ||
onSwipingRight, onSwipedRight, | ||
onSwipingUp, onSwipedUp, | ||
onSwipingDown, onSwipedDown, | ||
preventDefaultTouchmoveEvent, | ||
} = this.props; | ||
if (e.type === 'mousemove' && !this.props.trackMouse) { | ||
return; | ||
} | ||
if (!this.swipeable.x || !this.swipeable.y || e.touches && e.touches.length > 1) { | ||
@@ -119,33 +119,31 @@ return; | ||
let cancelPageSwipe = false; | ||
const pos = this.calculatePos(e); | ||
const pos = calculatePos(e, this.swipeable); | ||
if (pos.absX < this.props.delta && pos.absY < this.props.delta) { | ||
return; | ||
} | ||
if (pos.absX < delta && pos.absY < delta) return; | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
if (stopPropagation) e.stopPropagation(); | ||
if (this.props.onSwiping) { | ||
this.props.onSwiping(e, pos.deltaX, pos.deltaY, pos.absX, pos.absY, pos.velocity); | ||
if (onSwiping) { | ||
onSwiping(e, pos.deltaX, pos.deltaY, pos.absX, pos.absY, pos.velocity); | ||
} | ||
let cancelablePageSwipe = false; | ||
if (pos.absX > pos.absY) { | ||
if (pos.deltaX > 0) { | ||
if (this.props.onSwipingLeft || this.props.onSwipedLeft) { | ||
this.props.onSwipingLeft && this.props.onSwipingLeft(e, pos.absX); | ||
cancelPageSwipe = true; | ||
if (onSwipingLeft || onSwipedLeft) { | ||
onSwipingLeft && onSwipingLeft(e, pos.absX); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (this.props.onSwipingRight || this.props.onSwipedRight) { | ||
this.props.onSwipingRight && this.props.onSwipingRight(e, pos.absX); | ||
cancelPageSwipe = true; | ||
} else if (onSwipingRight || onSwipedRight) { | ||
onSwipingRight && onSwipingRight(e, pos.absX); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (pos.deltaY > 0) { | ||
if (this.props.onSwipingUp || this.props.onSwipedUp) { | ||
this.props.onSwipingUp && this.props.onSwipingUp(e, pos.absY); | ||
cancelPageSwipe = true; | ||
if (onSwipingUp || onSwipedUp) { | ||
onSwipingUp && onSwipingUp(e, pos.absY); | ||
cancelablePageSwipe = true; | ||
} | ||
} else if (this.props.onSwipingDown || this.props.onSwipedDown) { | ||
this.props.onSwipingDown && this.props.onSwipingDown(e, pos.absY); | ||
cancelPageSwipe = true; | ||
} else if (onSwipingDown || onSwipedDown) { | ||
onSwipingDown && onSwipingDown(e, pos.absY); | ||
cancelablePageSwipe = true; | ||
} | ||
@@ -155,48 +153,44 @@ | ||
if (cancelPageSwipe && this.props.preventDefaultTouchmoveEvent) { | ||
e.preventDefault(); | ||
} | ||
}, | ||
if (cancelablePageSwipe && preventDefaultTouchmoveEvent) e.preventDefault(); | ||
} | ||
eventEnd(e) { | ||
if (typeof this.props.onMouseUp === 'function') { // eslint-disable-line react/prop-types | ||
this.props.onMouseUp(e); // eslint-disable-line react/prop-types | ||
} | ||
const { | ||
stopPropagation, | ||
flickThreshold, | ||
onSwiped, | ||
onSwipedLeft, | ||
onSwipedRight, | ||
onSwipedUp, | ||
onSwipedDown, | ||
onTap, | ||
} = this.props; | ||
if (e.type === 'mouseup' && !this.props.trackMouse) { | ||
return; | ||
} | ||
if (this.swipeable.swiping) { | ||
const pos = this.calculatePos(e); | ||
const pos = calculatePos(e, this.swipeable); | ||
if (this.props.stopPropagation) e.stopPropagation(); | ||
if (stopPropagation) e.stopPropagation(); | ||
const isFlick = pos.velocity > this.props.flickThreshold; | ||
const isFlick = pos.velocity > flickThreshold; | ||
this.props.onSwiped && this.props.onSwiped( | ||
e, | ||
pos.deltaX, | ||
pos.deltaY, | ||
isFlick, | ||
pos.velocity, | ||
); | ||
onSwiped && onSwiped(e, pos.deltaX, pos.deltaY, isFlick, pos.velocity); | ||
if (pos.absX > pos.absY) { | ||
if (pos.deltaX > 0) { | ||
this.props.onSwipedLeft && this.props.onSwipedLeft(e, pos.deltaX, isFlick); | ||
onSwipedLeft && onSwipedLeft(e, pos.deltaX, isFlick); | ||
} else { | ||
this.props.onSwipedRight && this.props.onSwipedRight(e, pos.deltaX, isFlick); | ||
onSwipedRight && onSwipedRight(e, pos.deltaX, isFlick); | ||
} | ||
} else if (pos.deltaY > 0) { | ||
this.props.onSwipedUp && this.props.onSwipedUp(e, pos.deltaY, isFlick); | ||
onSwipedUp && onSwipedUp(e, pos.deltaY, isFlick); | ||
} else { | ||
this.props.onSwipedDown && this.props.onSwipedDown(e, pos.deltaY, isFlick); | ||
onSwipedDown && onSwipedDown(e, pos.deltaY, isFlick); | ||
} | ||
} else { | ||
this.props.onTap && this.props.onTap(e); | ||
onTap && onTap(e); | ||
} | ||
// finished swipe tracking, reset swipeable state | ||
this.swipeable = getInitialState(); | ||
}, | ||
} | ||
@@ -209,7 +203,6 @@ render() { | ||
onTouchEnd: this.eventEnd, | ||
onMouseDown: this.eventStart, | ||
onMouseMove: this.eventMove, | ||
onMouseUp: this.eventEnd, | ||
onMouseDown: this.mouseDown, | ||
}; | ||
// clean up swipeable's props to avoid react warning | ||
delete newProps.onSwiped; | ||
@@ -239,5 +232,34 @@ delete newProps.onSwiping; | ||
); | ||
}, | ||
}); | ||
} | ||
} | ||
Swipeable.propTypes = { | ||
onSwiped: PropTypes.func, | ||
onSwiping: PropTypes.func, | ||
onSwipingUp: PropTypes.func, | ||
onSwipingRight: PropTypes.func, | ||
onSwipingDown: PropTypes.func, | ||
onSwipingLeft: PropTypes.func, | ||
onSwipedUp: PropTypes.func, | ||
onSwipedRight: PropTypes.func, | ||
onSwipedDown: PropTypes.func, | ||
onSwipedLeft: PropTypes.func, | ||
onTap: PropTypes.func, | ||
flickThreshold: PropTypes.number, | ||
delta: PropTypes.number, | ||
preventDefaultTouchmoveEvent: PropTypes.bool, | ||
stopPropagation: PropTypes.bool, | ||
nodeName: PropTypes.string, | ||
trackMouse: PropTypes.bool, | ||
children: PropTypes.node, | ||
}; | ||
Swipeable.defaultProps = { | ||
flickThreshold: 0.6, | ||
delta: 10, | ||
preventDefaultTouchmoveEvent: false, | ||
stopPropagation: false, | ||
nodeName: 'div', | ||
}; | ||
module.exports = Swipeable; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36697
638
129
2
16
1
+ Addedprop-types@^15.5.8
+ Addedjs-tokens@4.0.0(transitive)
+ Addedloose-envify@1.4.0(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedprop-types@15.8.1(transitive)
+ Addedreact@16.14.0(transitive)
+ Addedreact-is@16.13.1(transitive)
- Removedreact@19.0.0(transitive)