Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-swipeable

Package Overview
Dependencies
Maintainers
2
Versions
75
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-swipeable - npm Package Compare versions

Comparing version 3.9.2 to 4.0.0

9

CHANGELOG.md

@@ -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 @@

301

lib/Swipeable.js

@@ -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;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc