Skip to content

Commit

Permalink
Merge pull request #16 from nickschot/pointer-events
Browse files Browse the repository at this point in the history
implement PointerEvents replacing TouchEvents
  • Loading branch information
nickschot authored Nov 19, 2020
2 parents eee08ee + d8bae72 commit c1c41f5
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 121 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Currently only a Pan modifier is provided. More gestures will be added in the fu
- **axis** _(default: 'horizontal')_ - axis for the pan event to be recognized ('horizontal' or 'vertical')
- **capture** _(default: false)_ - whether or not to use capture events instead of bubbling
- **preventScroll** _(default: true)_ - whether or not to prevent scroll during panning
- **pointerEvents** _(default: ['touch'])_ - the pointer types to support (one or more of 'touch', 'mouse', 'pen')

The hooks are passed a TouchData object which looks like:
```javascript
Expand Down Expand Up @@ -80,6 +81,19 @@ The hooks are passed a TouchData object which looks like:
}
```

Testing
------------------------------------------------------------------------------
A `pan` test helper is exposed by the addon.

```javascript
import { pan } from 'ember-gesture-modifiers/test-support';

...

// arg1: CSS selector on which the pan happens
// arg2: a direction in which the pan should happen. Either 'left' or 'right'.
await pan('.my-css-selector', 'right');
```

Contributing
------------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions addon-test-support/create-pointer-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Generates a PointerEvent for testing purposes
*
* @param {Element} target
* @param {string} eventType pointerdown, pointermove or pointerend
* @param {number} x
* @param {number} y
* @param {number} identifier
* @returns {PointerEvent}
*/
export default function createPointerEvent(target, eventType, x, y, identifier = 0) {
return new PointerEvent(eventType, {
identifier: identifier || 0,
target,
clientX: x,
clientY: y,
pointerType: ['touch']
});
}
1 change: 1 addition & 0 deletions addon-test-support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as pan } from './pan';
10 changes: 5 additions & 5 deletions tests/helpers/pan.js → addon-test-support/pan.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { settled, getRootElement } from '@ember/test-helpers';
import createTouchEvent from './create-touch-event';
import createPointerEvent from './create-pointer-event';

function timeout(duration) {
return new Promise((resolve) => {
Expand Down Expand Up @@ -30,7 +30,7 @@ function getElement(target) {
}

function sendEvent(element, type, x, y){
const event = createTouchEvent(element, type, x, y);
const event = createPointerEvent(element, type, x, y);
element.dispatchEvent(event);
}

Expand All @@ -56,15 +56,15 @@ async function _pan(element, options = {}){
const steps = Math.ceil(duration / resolution);
const middleY = top + height/2;

sendEvent(element, 'touchstart', startX, middleY);
sendEvent(element, 'pointerdown', startX, middleY);
for(let i = 1; i < steps; i++){
await timeout(resolution);
const x = isLeft
? startX - (startX - endX)/steps*i
: (endX - startX)/steps * i;
sendEvent(element, 'touchmove', x, middleY);
sendEvent(element, 'pointermove', x, middleY);
}
sendEvent(element, 'touchend', endX, middleY);
sendEvent(element, 'pointerup', endX, middleY);
}

export default async function pan(target, direction) {
Expand Down
44 changes: 25 additions & 19 deletions addon/modifiers/did-pan.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export default class DidPanModifier extends Modifier {
axis;
capture;
preventScroll;
pointerTypes;
currentTouches = new Map();
dragging = false;

addEventListeners() {
// By default, CSS rule `touch-action` is `auto`, enabling panning on both directions.
Expand All @@ -22,35 +24,36 @@ export default class DidPanModifier extends Modifier {
this.element.style.touchAction = 'pan-x';
}

this.element.addEventListener('touchstart', this.didTouchStart, { capture: this.useCapture, passive: true });
this.element.addEventListener('touchmove', this.didTouchMove, { capture: this.useCapture, passive: !this.preventScroll });
this.element.addEventListener('touchend', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.addEventListener('touchcancel', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.addEventListener('pointerdown', this.didTouchStart, { capture: this.useCapture, passive: true });
this.element.addEventListener('pointermove', this.didTouchMove, { capture: this.useCapture, passive: !this.preventScroll });
this.element.addEventListener('pointerup', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.addEventListener('pointercancel', this.didTouchEnd, { capture: this.useCapture, passive: true });
}

removeEventListeners() {
this.element.style.touchAction = null;

this.element.removeEventListener('touchstart', this.didTouchStart, { capture: this.useCapture, passive: true });
this.element.removeEventListener('touchmove', this.didTouchMove, { capture: this.useCapture, passive: !this.preventScroll });
this.element.removeEventListener('touchend', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.removeEventListener('touchcancel', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.removeEventListener('pointerdown', this.didTouchStart, { capture: this.useCapture, passive: true });
this.element.removeEventListener('pointermove', this.didTouchMove, { capture: this.useCapture, passive: !this.preventScroll });
this.element.removeEventListener('pointerup', this.didTouchEnd, { capture: this.useCapture, passive: true });
this.element.removeEventListener('pointercancel', this.didTouchEnd, { capture: this.useCapture, passive: true });
}

@action
didTouchStart(e){
for(const touch of e.changedTouches){
const touchData = parseInitialTouchData(touch, e);
if (!this.dragging && this.pointerTypes.includes(e.pointerType)) {
const touchData = parseInitialTouchData(e);
this.currentTouches.set(e.pointerId, touchData);

this.currentTouches.set(touch.identifier, touchData);
this.dragging = true;
}
}

@action
didTouchMove(e){
for(const touch of e.changedTouches){
const previousTouchData = this.currentTouches.get(touch.identifier);
const touchData = parseTouchData(previousTouchData, touch, e);
if (this.dragging && this.currentTouches.has(e.pointerId)) {
const previousTouchData = this.currentTouches.get(e.pointerId);
const touchData = parseTouchData(previousTouchData, e);

if(touchData.panStarted){
// prevent scroll if a pan is still busy
Expand Down Expand Up @@ -87,21 +90,23 @@ export default class DidPanModifier extends Modifier {
}
}

this.currentTouches.set(touch.identifier, touchData);
this.currentTouches.set(e.pointerId, touchData);
}
}

@action
didTouchEnd(e){
for(const touch of e.changedTouches){
const previousTouchData = this.currentTouches.get(touch.identifier);
const touchData = parseTouchData(previousTouchData, touch, e);
if (this.dragging && this.currentTouches.has(e.pointerId)) {
this.dragging = false;

const previousTouchData = this.currentTouches.get(e.pointerId);
const touchData = parseTouchData(previousTouchData, e);

if(touchData.panStarted){
this.didPanEnd(touchData.data);
}

this.currentTouches.delete(touch.identifier);
this.currentTouches.delete(e.pointerId);
}
}

Expand All @@ -110,6 +115,7 @@ export default class DidPanModifier extends Modifier {
this.axis = this.args.named.axis ?? 'horizontal';
this.capture = this.args.named.capture ?? false;
this.preventScroll = this.args.named.preventScroll ?? true;
this.pointerTypes = this.args.named.pointerTypes ?? ['touch'];

this.didPanStart = this.args.named.onPanStart ?? _fn;
this.didPan = this.args.named.onPan ?? _fn;
Expand Down
30 changes: 15 additions & 15 deletions addon/utils/parse-touch-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
* @return {Object} Returns a TouchData object
* @private
*/
export function parseInitialTouchData(touch, e){
export function parseInitialTouchData(e){
return {
data: {
initial: {
x: touch.clientX,
y: touch.clientY,
x: e.clientX,
y: e.clientY,
timeStamp: e.timeStamp
},
cache: {
Expand Down Expand Up @@ -40,25 +40,25 @@ export function parseInitialTouchData(touch, e){
* @return {Object} The new touch data
* @private
*/
export function parseTouchData(previousTouchData, touch, e) {
const touchData = {...previousTouchData};
export function parseTouchData(previousTouchData, e) {
const touchData = JSON.parse(JSON.stringify(previousTouchData));
const data = touchData.data;

if(data.current){
data.current.deltaX = touch.clientX - data.current.x;
data.current.deltaY = touch.clientY - data.current.y;
data.current.deltaX = e.clientX - data.current.x;
data.current.deltaY = e.clientY - data.current.y;
} else {
data.current = {};
data.current.deltaX = touch.clientX - data.initial.x;
data.current.deltaY = touch.clientY - data.initial.y;
data.current.deltaX = e.clientX - data.initial.x;
data.current.deltaY = e.clientY - data.initial.y;
}

data.current.x = touch.clientX;
data.current.y = touch.clientY;
data.current.distance = getPointDistance(data.initial.x, touch.clientX, data.initial.y, touch.clientY);
data.current.distanceX = touch.clientX - data.initial.x;
data.current.distanceY = touch.clientY - data.initial.y;
data.current.angle = getAngle(data.initial.x, data.initial.y, touch.clientX, touch.clientY);
data.current.x = e.clientX;
data.current.y = e.clientY;
data.current.distance = getPointDistance(data.initial.x, e.clientX, data.initial.y, e.clientY);
data.current.distanceX = e.clientX - data.initial.x;
data.current.distanceY = e.clientY - data.initial.y;
data.current.angle = getAngle(data.initial.x, data.initial.y, e.clientX, e.clientY);

// overallVelocity can be calculated continuously
const overallDeltaTime = e.timeStamp - data.initial.timeStamp;
Expand Down
2 changes: 1 addition & 1 deletion tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<h2 id="title">Welcome to Ember</h2>

{{outlet}}
{{outlet}}
18 changes: 18 additions & 0 deletions tests/helpers/create-mock-pointer-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Generates a mock PointerEvent like object for testing purposes
* @param {string} eventType pointerdown, pointermove or pointerup
* @param {number} x
* @param {number} y
* @param {number} timeStampDelta
*/
export default function createMockPointerEvent(eventType, x, y, timeStampDelta = 0) {
// we mock it using an object because we can't otherwise set the timeStamp
return {
type: eventType,
identifier: Date.now(),
target: document.body,
clientX: x,
clientY: y,
timeStamp: 234011.29999995464 + timeStampDelta
}
}
24 changes: 0 additions & 24 deletions tests/helpers/create-mock-touch-event.js

This file was deleted.

26 changes: 0 additions & 26 deletions tests/helpers/create-touch-event.js

This file was deleted.

2 changes: 1 addition & 1 deletion tests/integration/modifiers/did-pan-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import pan from '../../helpers/pan';
import { pan } from 'ember-gesture-modifiers/test-support';

module('Integration | Modifier | did-pan', function(hooks) {
setupRenderingTest(hooks);
Expand Down
Loading

0 comments on commit c1c41f5

Please sign in to comment.