react-with-gesture
Advanced tools
Comparing version 3.0.3 to 4.0.0
@@ -27,17 +27,2 @@ import React from 'react'; | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
function _assertThisInitialized(self) { | ||
@@ -61,14 +46,16 @@ if (self === void 0) { | ||
args: undefined, | ||
temp: undefined, | ||
target: undefined, | ||
x: 0, | ||
y: 0, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xInitial: 0, | ||
yInitial: 0, | ||
xPrev: 0, | ||
yPrev: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
down: false | ||
time: undefined, | ||
xy: [0, 0], | ||
delta: [0, 0], | ||
initial: [0, 0], | ||
previous: [0, 0], | ||
direction: [0, 0], | ||
local: [0, 0], | ||
lastLocal: [0, 0], | ||
velocity: 0, | ||
distance: 0, | ||
down: false, | ||
first: true | ||
}; | ||
@@ -85,8 +72,11 @@ | ||
var newProps = _extends({}, state, { | ||
target: undefined, | ||
down: false | ||
down: false, | ||
first: false | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
lastLocal: state.local, | ||
temp: temp || newProps.temp | ||
}); | ||
}); | ||
@@ -100,21 +90,30 @@ }; | ||
set(function (state) { | ||
var newProps = _extends({}, state, { | ||
var lastLocal = state.lastLocal || initialState.lastLocal; | ||
var newProps = _extends({}, initialState, { | ||
event: event, | ||
target: target, | ||
args: args, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
xInitial: pageX, | ||
yInitial: pageY, | ||
xPrev: pageX, | ||
yPrev: pageY, | ||
down: true | ||
lastLocal: lastLocal, | ||
local: lastLocal, | ||
xy: [pageX, pageY], | ||
initial: [pageX, pageY], | ||
previous: [pageX, pageY], | ||
down: true, | ||
time: Date.now(), | ||
cancel: function cancel() { | ||
window.removeEventListener('touchmove', handleTouchMove); | ||
window.removeEventListener('touchend', handleTouchEnd); | ||
window.removeEventListener('mousemove', handleMove); | ||
window.removeEventListener('mouseup', handleMouseUp); | ||
requestAnimationFrame(function () { | ||
return handleUp(); | ||
}); | ||
} | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
temp: temp | ||
}); | ||
}); | ||
@@ -125,20 +124,32 @@ }; | ||
var pageX = event.pageX, | ||
pageY = event.pageY, | ||
movementX = event.movementX, | ||
movementY = event.movementY; | ||
pageY = event.pageY; | ||
set(function (state) { | ||
var time = Date.now(); | ||
var x_dist = pageX - state.xy[0]; | ||
var y_dist = pageY - state.xy[1]; | ||
var delta_x = pageX - state.initial[0]; | ||
var delta_y = pageY - state.initial[1]; | ||
var dx = pageX - state.initial[0]; | ||
var dy = pageY - state.initial[1]; | ||
var distance = Math.sqrt(dx * dx + dy * dy); | ||
var len = Math.sqrt(x_dist * x_dist + y_dist * y_dist); | ||
var scalar = 1 / (len || 1); | ||
var newProps = _extends({}, state, { | ||
event: event, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: pageX - state.xInitial, | ||
yDelta: pageY - state.yInitial, | ||
xPrev: state.x, | ||
yPrev: state.y, | ||
xVelocity: movementX, | ||
yVelocity: movementY | ||
time: time, | ||
xy: [pageX, pageY], | ||
delta: [delta_x, delta_y], | ||
local: [state.lastLocal[0] + pageX - state.initial[0], state.lastLocal[1] + pageY - state.initial[1]], | ||
velocity: len / (time - state.time), | ||
distance: distance, | ||
direction: [x_dist * scalar, y_dist * scalar], | ||
previous: state.xy, | ||
first: false | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
temp: temp || newProps.temp | ||
}); | ||
}); | ||
@@ -183,73 +194,60 @@ }; // Touch handlers | ||
var withGesture = function withGesture(Wrapped) { | ||
var _class, _temp; | ||
var Gesture = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(Gesture, _React$Component); | ||
return _temp = _class = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(_class, _React$Component); | ||
function Gesture(props) { | ||
var _this; | ||
function _class(props) { | ||
var _this; | ||
_this = _React$Component.call(this, props) || this; | ||
_this.state = initialState; | ||
_this = _React$Component.call(this, props) || this; | ||
_this.state = initialState; | ||
var set = _this.setState.bind(_assertThisInitialized(_assertThisInitialized(_this))); | ||
var set = _this.setState.bind(_assertThisInitialized(_assertThisInitialized(_this))); | ||
if (props.onAction) { | ||
_this._state = initialState; | ||
if (props.transient) { | ||
_this._state = initialState; | ||
set = function set(cb) { | ||
return _this._state = cb(_this._state); | ||
}; | ||
} | ||
_this.handlers = handlers(set, props); | ||
return _this; | ||
set = function set(cb) { | ||
return _this._state = cb(_this._state); | ||
}; | ||
} | ||
var _proto = _class.prototype; | ||
_this.handlers = handlers(set, props); | ||
return _this; | ||
} | ||
_proto.render = function render() { | ||
var _this$props = this.props, | ||
style = _this$props.style, | ||
className = _this$props.className, | ||
props = _objectWithoutPropertiesLoose(_this$props, ["style", "className"]); | ||
var _proto = Gesture.prototype; | ||
return React.createElement("div", _extends({}, this.handlers, { | ||
style: _extends({ | ||
display: 'contents' | ||
}, style), | ||
className: className | ||
}), React.createElement(Wrapped, _extends({}, props, this.state))); | ||
}; | ||
_proto.render = function render() { | ||
var _this$props = this.props, | ||
style = _this$props.style, | ||
children = _this$props.children, | ||
className = _this$props.className; | ||
return React.createElement("div", _extends({}, this.handlers, { | ||
style: _extends({ | ||
display: 'contents' | ||
}, style), | ||
className: className | ||
}), children(this.state)); | ||
}; | ||
return _class; | ||
}(React.Component), _class.defaultProps = defaultProps, _temp; | ||
}; | ||
return Gesture; | ||
}(React.Component); | ||
var Gesture = withGesture( | ||
/*#__PURE__*/ | ||
function (_React$PureComponent) { | ||
_inheritsLoose(_class2, _React$PureComponent); | ||
Gesture.defaultProps = defaultProps; | ||
function _class2() { | ||
return _React$PureComponent.apply(this, arguments) || this; | ||
} | ||
var _proto2 = _class2.prototype; | ||
_proto2.render = function render() { | ||
return this.props.children(this.props); | ||
var withGesture = function withGesture(config) { | ||
return function (Wrapped) { | ||
return function (props) { | ||
return React.createElement(Gesture, _extends({}, config, { | ||
children: function children(gestureProps) { | ||
return React.createElement(Wrapped, _extends({}, props, gestureProps)); | ||
} | ||
})); | ||
}; | ||
}; | ||
}; | ||
return _class2; | ||
}(React.PureComponent)); | ||
function useGesture(props) { | ||
if (props === void 0) { | ||
props = defaultProps; | ||
} | ||
var _React$useState = React.useState(initialState), | ||
@@ -260,6 +258,6 @@ state = _React$useState[0], | ||
var transientState = React.useRef(initialState); | ||
if (typeof props === 'function') props = _extends({ | ||
transient: true, | ||
if (typeof props === 'function') props = { | ||
onAction: props | ||
}, defaultProps); | ||
}; | ||
props = _extends({}, defaultProps, props); | ||
@@ -272,3 +270,3 @@ var _React$useState2 = React.useState(function () { | ||
return handlers(props && props.transient ? function (cb) { | ||
return handlers(props.onAction ? function (cb) { | ||
return transientState.current = cb(transientState.current); | ||
@@ -280,5 +278,5 @@ } : set, props, args); | ||
return props && props.transient ? spread : [spread, state]; | ||
return props.onAction ? spread : [spread, state]; | ||
} | ||
export { withGesture, Gesture, useGesture }; |
@@ -33,17 +33,2 @@ 'use strict'; | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
function _assertThisInitialized(self) { | ||
@@ -67,14 +52,16 @@ if (self === void 0) { | ||
args: undefined, | ||
temp: undefined, | ||
target: undefined, | ||
x: 0, | ||
y: 0, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xInitial: 0, | ||
yInitial: 0, | ||
xPrev: 0, | ||
yPrev: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
down: false | ||
time: undefined, | ||
xy: [0, 0], | ||
delta: [0, 0], | ||
initial: [0, 0], | ||
previous: [0, 0], | ||
direction: [0, 0], | ||
local: [0, 0], | ||
lastLocal: [0, 0], | ||
velocity: 0, | ||
distance: 0, | ||
down: false, | ||
first: true | ||
}; | ||
@@ -91,8 +78,11 @@ | ||
var newProps = _extends({}, state, { | ||
target: undefined, | ||
down: false | ||
down: false, | ||
first: false | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
lastLocal: state.local, | ||
temp: temp || newProps.temp | ||
}); | ||
}); | ||
@@ -106,21 +96,30 @@ }; | ||
set(function (state) { | ||
var newProps = _extends({}, state, { | ||
var lastLocal = state.lastLocal || initialState.lastLocal; | ||
var newProps = _extends({}, initialState, { | ||
event: event, | ||
target: target, | ||
args: args, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
xInitial: pageX, | ||
yInitial: pageY, | ||
xPrev: pageX, | ||
yPrev: pageY, | ||
down: true | ||
lastLocal: lastLocal, | ||
local: lastLocal, | ||
xy: [pageX, pageY], | ||
initial: [pageX, pageY], | ||
previous: [pageX, pageY], | ||
down: true, | ||
time: Date.now(), | ||
cancel: function cancel() { | ||
window.removeEventListener('touchmove', handleTouchMove); | ||
window.removeEventListener('touchend', handleTouchEnd); | ||
window.removeEventListener('mousemove', handleMove); | ||
window.removeEventListener('mouseup', handleMouseUp); | ||
requestAnimationFrame(function () { | ||
return handleUp(); | ||
}); | ||
} | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
temp: temp | ||
}); | ||
}); | ||
@@ -131,20 +130,32 @@ }; | ||
var pageX = event.pageX, | ||
pageY = event.pageY, | ||
movementX = event.movementX, | ||
movementY = event.movementY; | ||
pageY = event.pageY; | ||
set(function (state) { | ||
var time = Date.now(); | ||
var x_dist = pageX - state.xy[0]; | ||
var y_dist = pageY - state.xy[1]; | ||
var delta_x = pageX - state.initial[0]; | ||
var delta_y = pageY - state.initial[1]; | ||
var dx = pageX - state.initial[0]; | ||
var dy = pageY - state.initial[1]; | ||
var distance = Math.sqrt(dx * dx + dy * dy); | ||
var len = Math.sqrt(x_dist * x_dist + y_dist * y_dist); | ||
var scalar = 1 / (len || 1); | ||
var newProps = _extends({}, state, { | ||
event: event, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: pageX - state.xInitial, | ||
yDelta: pageY - state.yInitial, | ||
xPrev: state.x, | ||
yPrev: state.y, | ||
xVelocity: movementX, | ||
yVelocity: movementY | ||
time: time, | ||
xy: [pageX, pageY], | ||
delta: [delta_x, delta_y], | ||
local: [state.lastLocal[0] + pageX - state.initial[0], state.lastLocal[1] + pageY - state.initial[1]], | ||
velocity: len / (time - state.time), | ||
distance: distance, | ||
direction: [x_dist * scalar, y_dist * scalar], | ||
previous: state.xy, | ||
first: false | ||
}); | ||
props.onAction && props.onAction(newProps); | ||
return newProps; | ||
var temp = props.onAction && props.onAction(newProps); | ||
return _extends({}, newProps, { | ||
temp: temp || newProps.temp | ||
}); | ||
}); | ||
@@ -189,73 +200,60 @@ }; // Touch handlers | ||
var withGesture = function withGesture(Wrapped) { | ||
var _class, _temp; | ||
var Gesture = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(Gesture, _React$Component); | ||
return _temp = _class = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(_class, _React$Component); | ||
function Gesture(props) { | ||
var _this; | ||
function _class(props) { | ||
var _this; | ||
_this = _React$Component.call(this, props) || this; | ||
_this.state = initialState; | ||
_this = _React$Component.call(this, props) || this; | ||
_this.state = initialState; | ||
var set = _this.setState.bind(_assertThisInitialized(_assertThisInitialized(_this))); | ||
var set = _this.setState.bind(_assertThisInitialized(_assertThisInitialized(_this))); | ||
if (props.onAction) { | ||
_this._state = initialState; | ||
if (props.transient) { | ||
_this._state = initialState; | ||
set = function set(cb) { | ||
return _this._state = cb(_this._state); | ||
}; | ||
} | ||
_this.handlers = handlers(set, props); | ||
return _this; | ||
set = function set(cb) { | ||
return _this._state = cb(_this._state); | ||
}; | ||
} | ||
var _proto = _class.prototype; | ||
_this.handlers = handlers(set, props); | ||
return _this; | ||
} | ||
_proto.render = function render() { | ||
var _this$props = this.props, | ||
style = _this$props.style, | ||
className = _this$props.className, | ||
props = _objectWithoutPropertiesLoose(_this$props, ["style", "className"]); | ||
var _proto = Gesture.prototype; | ||
return React.createElement("div", _extends({}, this.handlers, { | ||
style: _extends({ | ||
display: 'contents' | ||
}, style), | ||
className: className | ||
}), React.createElement(Wrapped, _extends({}, props, this.state))); | ||
}; | ||
_proto.render = function render() { | ||
var _this$props = this.props, | ||
style = _this$props.style, | ||
children = _this$props.children, | ||
className = _this$props.className; | ||
return React.createElement("div", _extends({}, this.handlers, { | ||
style: _extends({ | ||
display: 'contents' | ||
}, style), | ||
className: className | ||
}), children(this.state)); | ||
}; | ||
return _class; | ||
}(React.Component), _class.defaultProps = defaultProps, _temp; | ||
}; | ||
return Gesture; | ||
}(React.Component); | ||
var Gesture = withGesture( | ||
/*#__PURE__*/ | ||
function (_React$PureComponent) { | ||
_inheritsLoose(_class2, _React$PureComponent); | ||
Gesture.defaultProps = defaultProps; | ||
function _class2() { | ||
return _React$PureComponent.apply(this, arguments) || this; | ||
} | ||
var _proto2 = _class2.prototype; | ||
_proto2.render = function render() { | ||
return this.props.children(this.props); | ||
var withGesture = function withGesture(config) { | ||
return function (Wrapped) { | ||
return function (props) { | ||
return React.createElement(Gesture, _extends({}, config, { | ||
children: function children(gestureProps) { | ||
return React.createElement(Wrapped, _extends({}, props, gestureProps)); | ||
} | ||
})); | ||
}; | ||
}; | ||
}; | ||
return _class2; | ||
}(React.PureComponent)); | ||
function useGesture(props) { | ||
if (props === void 0) { | ||
props = defaultProps; | ||
} | ||
var _React$useState = React.useState(initialState), | ||
@@ -266,6 +264,6 @@ state = _React$useState[0], | ||
var transientState = React.useRef(initialState); | ||
if (typeof props === 'function') props = _extends({ | ||
transient: true, | ||
if (typeof props === 'function') props = { | ||
onAction: props | ||
}, defaultProps); | ||
}; | ||
props = _extends({}, defaultProps, props); | ||
@@ -278,3 +276,3 @@ var _React$useState2 = React.useState(function () { | ||
return handlers(props && props.transient ? function (cb) { | ||
return handlers(props.onAction ? function (cb) { | ||
return transientState.current = cb(transientState.current); | ||
@@ -286,3 +284,3 @@ } : set, props, args); | ||
return props && props.transient ? spread : [spread, state]; | ||
return props.onAction ? spread : [spread, state]; | ||
} | ||
@@ -289,0 +287,0 @@ |
{ | ||
"name": "react-with-gesture", | ||
"version": "3.0.3", | ||
"version": "4.0.0", | ||
"description": "hoc for receiving gestures", | ||
@@ -5,0 +5,0 @@ "main": "dist/react-with-gesture", |
176
README.md
@@ -0,96 +1,140 @@ | ||
<p align="middle"> | ||
<img src="https://i.imgur.com/tg1mN1F.gif" width="660"/> | ||
<img src="https://i.imgur.com/ifdCBvG.gif" width="195"/> | ||
</p> | ||
npm install react-with-gesture | ||
import { useGesture, withGesture, Gesture } from 'react-with-gesture' | ||
Wraps a component into a div that receives MouseDown and TouchStart events, then captures movement until release. | ||
React-with-gesture is a small utility that lets you bind enriched mouse and touch events to any component or view. It calculates initial position, deltas, velocity, direction, distance, etc. With this data it becomes trivial to set up even complex gesture controls with just a few lines of code. | ||
You can use it stand-alone, but to make the most of it you should combine it with a animation library, preferrably [react-spring](https://github.com/react-spring/react-spring). | ||
<p align="middle"> | ||
<img src="assets/button.gif" width="600"/> | ||
</p> | ||
### Demos | ||
Demo: https://codesandbox.io/embed/jzn14k0ppy | ||
Viewpager/carousel: https://codesandbox.io/embed/n9vo1my91p | ||
- `down`, true on mouse-down or finger-touch | ||
- `x/y`, screen coordinates | ||
- `xDelta/yDelta`, coordinates relative to initial coordinates, great for sliding/dragging gestures | ||
- `xInitial/yInitial`, coordinates of the first click/touch | ||
- `xVelocity/yVelocity`, flick velocity | ||
- `xPrev/yPrev`, coordinates of the previous frame | ||
- `target`, the dom element | ||
- `event`, the event itself, in case you need to prevent defaults, etc. | ||
Draggable list: https://codesandbox.io/embed/r5qmj8m6lq | ||
### Decorated higher order component | ||
Slider: https://codesandbox.io/embed/zrj66y8714 | ||
```jsx | ||
import { withGesture } from 'react-with-gesture' | ||
### Config | ||
@withGesture | ||
export class App extends React.Component { | ||
render() { | ||
const { down, x, y, xDelta, yDelta, xInitial, yInitial } = this.props | ||
return ( | ||
<div> | ||
Drag me! coordinates: {x}, {y} | ||
</div> | ||
) | ||
} | ||
``` | ||
{ | ||
touch: true, // accept mouse input | ||
mouse: true, // accept touch input | ||
passive: { passive: true }, // event handler 3rd argument input, passive by default | ||
onAction: undefined // if you define onAction: event => eventHandler react-with-gesture | ||
// will not render changes through React any longer | ||
} | ||
``` | ||
### Higher order component | ||
Alternatively you can supply a function, which becomes `onAction` with defaults. | ||
```jsx | ||
class App extends React.Component { | ||
### Api | ||
```js | ||
// Short cut with event handler | ||
const bind = useGesture(event => eventHandler) | ||
return <div {...bind(optionalArgs)} /> | ||
// Full config with event handler | ||
const bind = useGesture({ onAction: event => eventHandler, ...config }) | ||
return <div {...bind(optionalArgs)} /> | ||
// No event handler (will re-render the component on event changes) | ||
const [bind, props] = useGesture() | ||
return <div {...bind(optionalArgs)} /> | ||
// Render props | ||
<Gesture {...config}> | ||
{event => <div />} | ||
</Gesture> | ||
// HOC | ||
@withGesture(config) | ||
class extends React.Component { | ||
render() { | ||
const { down, x, y, xDelta, yDelta, xInitial, yInitial } = this.props | ||
return ( | ||
<div> | ||
Drag me! coordinates: {x}, {y} | ||
</div> | ||
) | ||
const event = this.props.event | ||
return <div /> | ||
} | ||
} | ||
``` | ||
export withGesture(App) | ||
### Event data | ||
``` | ||
{ | ||
event, // source event | ||
target, // dom node | ||
time, // time tag | ||
initial, // click coordinates (vec2) | ||
xy, // page coordinates (vec2) | ||
previous, // previous page coordinates (vec2) | ||
delta, // delta offset (xy - initial) (vec2) | ||
direction, // direction normal (vec2) | ||
local, // delta with book-keeping (vec2) | ||
velocity, // drag momentuum / speed | ||
distance, // delta distance | ||
down, // mouse / touch down | ||
first, // marks first event (mouse / touch down) | ||
args, // arguments optionally passed to bind(a,b,c,d,..) | ||
temp, // arguments optionally returns by the onAction | ||
} | ||
``` | ||
### Render props | ||
### Examples | ||
#### React hooks (basic drag/n/drop) | ||
<img src="https://i.imgur.com/ooNu3jz.gif" width="195"/> | ||
Demo: https://codesandbox.io/embed/l2wy87l28l | ||
```jsx | ||
import { Gesture } from 'react-with-gesture' | ||
class App extends React.Component { | ||
render() { | ||
return ( | ||
<Gesture> | ||
{({ down, x, y, xDelta, yDelta, xInitial, yInitial }) => ( | ||
<div> | ||
Drag me! coordinates: {x}, {y} | ||
</div> | ||
)} | ||
</Gesture> | ||
) | ||
} | ||
} | ||
const [bind, { local: [x, y] }] = useGesture() | ||
return <div {...bind()} style={{ transform: `translate3d(${x}px,${y}px,0)` }} /> | ||
``` | ||
### Hooks | ||
#### React hooks with onAction (and react-spring) (basic pull & release) | ||
<img src="https://i.imgur.com/KDeJBqp.gif" width="195"/> | ||
Demo: https://codesandbox.io/embed/r24mzvo3q | ||
```jsx | ||
import { useGesture } from 'react-with-gesture' | ||
function App() { | ||
const [bind, { args, down, x, y, xDelta, yDelta, xInitial, yInitial }] = useGesture() | ||
return ( | ||
<div {...bind(/*arguments you pass here will be available under args*/)}> | ||
Drag me! coordinates: {x}, {y} | ||
</div> | ||
) | ||
} | ||
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] })) | ||
const bind = useGesture(({ down, delta }) => set({ xy: down ? delta : [0, 0] })) | ||
return ( | ||
<animated.div | ||
{...bind()} | ||
style={{ transform: xy.interpolate((x, y) => `translate3d(${x}px,${y}px,0)`) }} | ||
/> | ||
) | ||
``` | ||
### Transient mode | ||
#### React hooks with onAction (and react-spring) (decay) | ||
This won't cause new render passes, instead you will be notified through the callback. This works the same for Hoc's, render-props and hooks. | ||
<img src="https://i.imgur.com/JyeQsEI.gif" width="195"/> | ||
Demo: https://codesandbox.io/embed/zq19y1xr9m | ||
```jsx | ||
const bind = useGesture(e => console.log(e)) | ||
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] })) | ||
const bind = useGesture(({ down, delta, velocity, direction, temp = xy.getValue() }) => { | ||
set({ | ||
xy: add(delta, temp), | ||
immediate: down, | ||
config: { velocity: scale(direction, velocity), decay: true } | ||
}) | ||
return temp | ||
}) | ||
return ( | ||
<animated.div | ||
{...bind()} | ||
style={{ transform: xy.interpolate((x, y) => `translate3d(${x}px,${y}px,0)`) }} | ||
/> | ||
) | ||
``` |
176
src/index.js
@@ -5,18 +5,19 @@ import React from 'react' | ||
const defaultProps = { touch: true, mouse: true, passive: { passive: true } } | ||
const initialState = { | ||
event: undefined, | ||
args: undefined, | ||
temp: undefined, | ||
target: undefined, | ||
x: 0, | ||
y: 0, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xInitial: 0, | ||
yInitial: 0, | ||
xPrev: 0, | ||
yPrev: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
time: undefined, | ||
xy: [0, 0], | ||
delta: [0, 0], | ||
initial: [0, 0], | ||
previous: [0, 0], | ||
direction: [0, 0], | ||
local: [0, 0], | ||
lastLocal: [0, 0], | ||
velocity: 0, | ||
distance: 0, | ||
down: false, | ||
first: true | ||
} | ||
@@ -28,5 +29,9 @@ | ||
set(state => { | ||
const newProps = { ...state, target: undefined, down: false } | ||
props.onAction && props.onAction(newProps) | ||
return newProps | ||
const newProps = { ...state, down: false, first: false } | ||
const temp = props.onAction && props.onAction(newProps) | ||
return { | ||
...newProps, | ||
lastLocal: state.local, | ||
temp: temp || newProps.temp | ||
} | ||
}) | ||
@@ -36,40 +41,55 @@ const handleDown = event => { | ||
set(state => { | ||
const lastLocal = state.lastLocal || initialState.lastLocal | ||
const newProps = { | ||
...state, | ||
...initialState, | ||
event, | ||
target, | ||
args, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: 0, | ||
yDelta: 0, | ||
xVelocity: 0, | ||
yVelocity: 0, | ||
xInitial: pageX, | ||
yInitial: pageY, | ||
xPrev: pageX, | ||
yPrev: pageY, | ||
lastLocal, | ||
local: lastLocal, | ||
xy: [pageX, pageY], | ||
initial: [pageX, pageY], | ||
previous: [pageX, pageY], | ||
down: true, | ||
time: Date.now(), | ||
cancel: () => { | ||
window.removeEventListener('touchmove', handleTouchMove) | ||
window.removeEventListener('touchend', handleTouchEnd) | ||
window.removeEventListener('mousemove', handleMove) | ||
window.removeEventListener('mouseup', handleMouseUp) | ||
requestAnimationFrame(() => handleUp()) | ||
} | ||
} | ||
props.onAction && props.onAction(newProps) | ||
return newProps | ||
const temp = props.onAction && props.onAction(newProps) | ||
return { ...newProps, temp } | ||
}) | ||
} | ||
const handleMove = event => { | ||
const { pageX, pageY, movementX, movementY } = event | ||
const { pageX, pageY } = event | ||
set(state => { | ||
const time = Date.now() | ||
const x_dist = pageX - state.xy[0] | ||
const y_dist = pageY - state.xy[1] | ||
const delta_x = pageX - state.initial[0] | ||
const delta_y = pageY - state.initial[1] | ||
const dx = pageX - state.initial[0] | ||
const dy = pageY - state.initial[1] | ||
const distance = Math.sqrt(dx * dx + dy * dy) | ||
const len = Math.sqrt(x_dist * x_dist + y_dist * y_dist) | ||
const scalar = 1 / (len || 1) | ||
const newProps = { | ||
...state, | ||
event, | ||
x: pageX, | ||
y: pageY, | ||
xDelta: pageX - state.xInitial, | ||
yDelta: pageY - state.yInitial, | ||
xPrev: state.x, | ||
yPrev: state.y, | ||
xVelocity: movementX, | ||
yVelocity: movementY, | ||
time, | ||
xy: [pageX, pageY], | ||
delta: [delta_x, delta_y], | ||
local: [state.lastLocal[0] + pageX - state.initial[0], state.lastLocal[1] + pageY - state.initial[1]], | ||
velocity: len / (time - state.time), | ||
distance, | ||
direction: [x_dist * scalar, y_dist * scalar], | ||
previous: state.xy, | ||
first: false | ||
} | ||
props.onAction && props.onAction(newProps) | ||
return newProps | ||
const temp = props.onAction && props.onAction(newProps) | ||
return { ...newProps, temp: temp || newProps.temp } | ||
}) | ||
@@ -104,58 +124,56 @@ } | ||
onMouseDown: props.mouse ? handleMouseDown : undefined, | ||
onTouchStart: props.touch ? handleTouchStart : undefined, | ||
onTouchStart: props.touch ? handleTouchStart : undefined | ||
} | ||
} | ||
const withGesture = Wrapped => | ||
class extends React.Component { | ||
static propTypes = { | ||
/** When this holds true it will manage its state outside of React, in this case it will never ever | ||
cause a new render, clients have to rely on callbacks to get notified (onUp/Down/Move). */ | ||
transient: PropTypes.bool, | ||
/** Optional. Calls back on mouse or touch down/up/move */ | ||
onAction: PropTypes.func, | ||
/** Optional. Event config */ | ||
passive: PropTypes.any, | ||
} | ||
static defaultProps = defaultProps | ||
class Gesture extends React.Component { | ||
static propTypes = { | ||
/** Optional. Accept mouse input, true by default */ | ||
mouse: PropTypes.bool, | ||
/** Optional. Accept touch input, true by default */ | ||
touch: PropTypes.bool, | ||
/** Optional. Calls back on mouse or touch down/up/move. When this is given it will manage state outside of React, | ||
* in this case it will never cause a new render, clients have to rely on callbacks to get notified. */ | ||
onAction: PropTypes.func, | ||
/** Optional. addEventListener 3rd arg config, { passive: true } by default, should be false if you plan to call event.preventDefault() or event.stopPropagation() */ | ||
passive: PropTypes.any | ||
} | ||
static defaultProps = defaultProps | ||
constructor(props) { | ||
super(props) | ||
this.state = initialState | ||
let set = this.setState.bind(this) | ||
if (props.transient) { | ||
this._state = initialState | ||
set = cb => (this._state = cb(this._state)) | ||
} | ||
this.handlers = handlers(set, props) | ||
constructor(props) { | ||
super(props) | ||
this.state = initialState | ||
let set = this.setState.bind(this) | ||
if (props.onAction) { | ||
this._state = initialState | ||
set = cb => (this._state = cb(this._state)) | ||
} | ||
this.handlers = handlers(set, props) | ||
} | ||
render() { | ||
const { style, className, ...props } = this.props | ||
return ( | ||
<div {...this.handlers} style={{ display: 'contents', ...style }} className={className}> | ||
<Wrapped {...props} {...this.state} /> | ||
</div> | ||
) | ||
} | ||
render() { | ||
const { style, children, className } = this.props | ||
return ( | ||
<div {...this.handlers} style={{ display: 'contents', ...style }} className={className}> | ||
{children(this.state)} | ||
</div> | ||
) | ||
} | ||
} | ||
const Gesture = withGesture( | ||
class extends React.PureComponent { | ||
render() { | ||
return this.props.children(this.props) | ||
} | ||
}, | ||
const withGesture = config => Wrapped => props => ( | ||
<Gesture {...config} children={gestureProps => <Wrapped {...props} {...gestureProps} />} /> | ||
) | ||
function useGesture(props = defaultProps) { | ||
function useGesture(props) { | ||
const [state, set] = React.useState(initialState) | ||
const transientState = React.useRef(initialState) | ||
if (typeof props === 'function') props = { transient: true, onAction: props, ...defaultProps } | ||
if (typeof props === 'function') props = { onAction: props } | ||
props = { ...defaultProps, ...props } | ||
const [spread] = React.useState(() => (...args) => | ||
handlers(props && props.transient ? cb => (transientState.current = cb(transientState.current)) : set, props, args), | ||
handlers(props.onAction ? cb => (transientState.current = cb(transientState.current)) : set, props, args) | ||
) | ||
return props && props.transient ? spread : [spread, state] | ||
return props.onAction ? spread : [spread, state] | ||
} | ||
export { withGesture, Gesture, useGesture } | ||
export { withGesture, Gesture, useGesture } |
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
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
96632
749
141