mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-17 04:32:26 +00:00
Add dom-event-testing-libary as a private package
This is copied from facebook/react with various fixes applied (which I'll push upstream at a later date). Necessary for testing the Responder Event System rewrite.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# `dom-event-testing-library`
|
||||
|
||||
A library for unit testing high-level interactions via simple pointer events, e.g.,
|
||||
`pointerdown`, that produce realistic and complete DOM event sequences.
|
||||
|
||||
There are number of challenges involved in unit testing modules that work with
|
||||
DOM events.
|
||||
|
||||
1. Testing environments with and without support for the `PointerEvent` API.
|
||||
2. Testing various user interaction modes including mouse, touch, and pen use.
|
||||
3. Testing against the event sequences browsers actually produce (e.g., emulated
|
||||
touch and mouse events.)
|
||||
4. Testing against the event properties DOM events include (i.e., more complete
|
||||
mock data)
|
||||
4. Testing against "virtual" events produced by tools like screen-readers.
|
||||
|
||||
Writing unit tests to cover all these scenarios is tedious and error prone. This
|
||||
event testing library is designed to avoid these issues by allowing developers to
|
||||
more easily dispatch events in unit tests, and to more reliably test interactions
|
||||
while using an API based on `PointerEvent`.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import {
|
||||
describeWithPointerEvent,
|
||||
testWithPointerType,
|
||||
clearPointers,
|
||||
createEventTarget,
|
||||
setPointerEvent,
|
||||
} from 'dom-event-testing-library';
|
||||
|
||||
describeWithPointerEvent('useTap', hasPointerEvent => {
|
||||
beforeEach(() => {
|
||||
// basic PointerEvent mock
|
||||
setPointerEvent(hasPointerEvent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// clear active pointers between test runs
|
||||
clearPointers();
|
||||
});
|
||||
|
||||
// test all the pointer types supported by the environment
|
||||
testWithPointerType('pointer down', pointerType => {
|
||||
const ref = createRef(null);
|
||||
const onTapStart = jest.fn();
|
||||
render(() => {
|
||||
useTap(ref, { onTapStart });
|
||||
return <div ref={ref} />
|
||||
});
|
||||
|
||||
// create an event target
|
||||
const target = createEventTarget(ref.current);
|
||||
// dispatch high-level pointer event
|
||||
target.pointerdown({ pointerType });
|
||||
expect(onTapStart).toBeCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This tests the interaction in multiple scenarios. In each case, a realistic DOM
|
||||
event sequence–with complete mock events–is produced. When running in a mock
|
||||
environment without the `PointerEvent` API, the test runs for both `mouse` and
|
||||
`touch` pointer types. When `touch` is the pointer type it produces emulated mouse
|
||||
events. When running in a mock environment with the `PointerEvent` API, the test
|
||||
runs for `mouse`, `touch`, and `pen` pointer types.
|
||||
|
||||
It's important to cover all these scenarios because it's very easy to introduce
|
||||
bugs – e.g., double calling of callbacks – if not accounting for emulated mouse
|
||||
events, differences in target capturing between `touch` and `mouse` pointers, and
|
||||
the different semantics of `button` across event APIs.
|
||||
|
||||
Default values are provided for the expected native events properties. They can
|
||||
also be customized as needed in a test.
|
||||
|
||||
```js
|
||||
target.pointerdown({
|
||||
button: 0,
|
||||
buttons: 1,
|
||||
pageX: 10,
|
||||
pageY: 10,
|
||||
pointerType,
|
||||
// NOTE: use x,y instead of clientX,clientY
|
||||
x: 10,
|
||||
y: 10
|
||||
});
|
||||
```
|
||||
|
||||
Tests that dispatch multiple pointer events will dispatch multi-touch native events
|
||||
on the target.
|
||||
|
||||
```js
|
||||
// first pointer is active
|
||||
target.pointerdown({pointerId: 1, pointerType});
|
||||
// second pointer is active
|
||||
target.pointerdown({pointerId: 2, pointerType});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Target and events
|
||||
|
||||
To create a new event target pass the DOM node to `createEventTarget(node)`. This target can then be used to dispatch event sequences and customize the event payload. The following are currently supported:
|
||||
|
||||
* `blur`
|
||||
* `click`
|
||||
* `contextmenu`
|
||||
* `focus`
|
||||
* `keydown`
|
||||
* `keyup`
|
||||
* `pointercancel`
|
||||
* `pointerdown`
|
||||
* `pointerhover` (moves when pointer is not down)
|
||||
* `pointermove` (moves when pointer is down)
|
||||
* `pointerover`
|
||||
* `pointerout`
|
||||
* `scroll`
|
||||
* `select`
|
||||
* `selectionchange`
|
||||
* `tap` (equivalent to `pointerdown` followed by `pointerup`)
|
||||
* `virtualclick`
|
||||
|
||||
The target also has `node` property equal to the node that was used to create the target, and a `setBoundClientRect({x,y,width,height})` method that can be used to mock the return value of `getBoundingClientRect`.
|
||||
|
||||
### Jest helpers
|
||||
|
||||
#### `describeWithPointerEvent`
|
||||
|
||||
This is just like `describe` but it will run the entire test suite twice, once in an environment with `PointerEvent` mocked and once without.
|
||||
|
||||
```js
|
||||
describeWithPointerEvent('useTap', hasPointerEvent => {
|
||||
// test suite
|
||||
});
|
||||
```
|
||||
|
||||
#### `testWithPointerType`
|
||||
|
||||
The is just like `test` but it will run the test for every pointer type supported by the environment. When `PointerEvent` is mocked, the pointer types will be `mouse`, `touch`, and `pen`; otherwise the pointer types will be `mouse` and `touch`.
|
||||
|
||||
```js
|
||||
testWithPointerType('pointer down', pointerType => {
|
||||
// test unit
|
||||
});
|
||||
```
|
||||
|
||||
### jsdom environment helpers
|
||||
|
||||
#### platform
|
||||
|
||||
Interactions that account for Windows / macOS differences can change the platform by calling `platform.set(value)`, where `value` can be either `'mac'` or `'windows'`. To retreive the current platform call `platform.get()`, and the clear it call `platform.clear()`.
|
||||
|
||||
#### hasPointerEvent / setPointerEvent
|
||||
|
||||
Interactions implemented using `PointerEvent` can create a basic mock for jsdom by calling `setPointerEvent(true)` (disable with `setPointerEvent(false)`), and check whether `PointerEvent` is available by calling `hasPointerEvent()`.
|
||||
@@ -0,0 +1 @@
|
||||
export * from './src/index';
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "dom-event-testing-library",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"description": "Browser event sequences for unit tests",
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/necolas/react-native-web/tree/master/packages/dom-event-testing-library"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent provides boolean to tests 1`] = `false`;
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent testWithPointerType: mouse 1`] = `"mouse"`;
|
||||
|
||||
exports[`describeWithPointerEvent: MouseEvent/TouchEvent testWithPointerType: touch 1`] = `"touch"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent provides boolean to tests 1`] = `true`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: mouse 1`] = `"mouse"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: pen 1`] = `"pen"`;
|
||||
|
||||
exports[`describeWithPointerEvent: PointerEvent testWithPointerType: touch 1`] = `"touch"`;
|
||||
@@ -0,0 +1,365 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { defaultBrowserChromeSize } from '../constants';
|
||||
|
||||
import {
|
||||
clearPointers,
|
||||
createEventTarget,
|
||||
describeWithPointerEvent,
|
||||
testWithPointerType
|
||||
} from '../index';
|
||||
|
||||
/**
|
||||
* Unit test helpers
|
||||
*/
|
||||
describeWithPointerEvent('describeWithPointerEvent', pointerEvent => {
|
||||
test('provides boolean to tests', () => {
|
||||
expect(pointerEvent).toMatchSnapshot();
|
||||
});
|
||||
|
||||
testWithPointerType('testWithPointerType', pointerType => {
|
||||
expect(pointerType).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* createEventTarget
|
||||
*/
|
||||
describe('createEventTarget', () => {
|
||||
let node;
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
node = null;
|
||||
clearPointers();
|
||||
});
|
||||
|
||||
test('returns expected API', () => {
|
||||
const target = createEventTarget(node);
|
||||
expect(target.node).toEqual(node);
|
||||
expect(Object.keys(target)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"node",
|
||||
"blur",
|
||||
"click",
|
||||
"focus",
|
||||
"keydown",
|
||||
"keyup",
|
||||
"scroll",
|
||||
"select",
|
||||
"selectionchange",
|
||||
"virtualclick",
|
||||
"contextmenu",
|
||||
"pointercancel",
|
||||
"pointerdown",
|
||||
"pointerhover",
|
||||
"pointermove",
|
||||
"pointerover",
|
||||
"pointerout",
|
||||
"pointerup",
|
||||
"tap",
|
||||
"setBoundingClientRect",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Simple events
|
||||
*/
|
||||
|
||||
describe('.blur()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('blur', e => {
|
||||
expect(e.relatedTarget).toBeUndefined();
|
||||
});
|
||||
target.blur();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
const relatedTarget = document.createElement('div');
|
||||
node.addEventListener('blur', e => {
|
||||
expect(e.relatedTarget).toBe(relatedTarget);
|
||||
});
|
||||
target.blur({ relatedTarget });
|
||||
});
|
||||
});
|
||||
|
||||
describe('.click()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.button).toEqual(0);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(e.detail).toEqual(1);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(e.movementX).toEqual(0);
|
||||
expect(e.movementY).toEqual(0);
|
||||
expect(e.offsetX).toEqual(0);
|
||||
expect(e.offsetY).toEqual(0);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(defaultBrowserChromeSize);
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.click();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.button).toEqual(1);
|
||||
expect(e.buttons).toEqual(4);
|
||||
expect(e.clientX).toEqual(10);
|
||||
expect(e.clientY).toEqual(20);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.movementX).toEqual(1);
|
||||
expect(e.movementY).toEqual(2);
|
||||
expect(e.offsetX).toEqual(5);
|
||||
expect(e.offsetY).toEqual(5);
|
||||
expect(e.pageX).toEqual(50);
|
||||
expect(e.pageY).toEqual(50);
|
||||
expect(e.screenX).toEqual(10);
|
||||
expect(e.screenY).toEqual(20 + defaultBrowserChromeSize);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.click({
|
||||
altKey: true,
|
||||
button: 1,
|
||||
buttons: 4,
|
||||
x: 10,
|
||||
y: 20,
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
movementX: 1,
|
||||
movementY: 2,
|
||||
offsetX: 5,
|
||||
offsetY: 5,
|
||||
pageX: 50,
|
||||
pageY: 50,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.focus()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('focus', e => {
|
||||
expect(e.relatedTarget).toBeUndefined();
|
||||
});
|
||||
target.focus();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
const relatedTarget = document.createElement('div');
|
||||
node.addEventListener('focus', e => {
|
||||
expect(e.relatedTarget).toBe(relatedTarget);
|
||||
});
|
||||
target.focus({ relatedTarget });
|
||||
});
|
||||
});
|
||||
|
||||
describe('.keydown()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keydown', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.key).toEqual('');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.keydown();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keydown', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.isComposing).toEqual(true);
|
||||
expect(e.key).toEqual('Enter');
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.keydown({
|
||||
altKey: true,
|
||||
ctrlKey: true,
|
||||
isComposing: true,
|
||||
key: 'Enter',
|
||||
metaKey: true,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.keyup()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keyup', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.key).toEqual('');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.keydown();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('keyup', e => {
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.isComposing).toEqual(true);
|
||||
expect(e.key).toEqual('Enter');
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.keyup({
|
||||
altKey: true,
|
||||
ctrlKey: true,
|
||||
isComposing: true,
|
||||
key: 'Enter',
|
||||
metaKey: true,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.scroll()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('scroll', e => {
|
||||
expect(e.type).toEqual('scroll');
|
||||
});
|
||||
target.scroll();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.virtualclick()', () => {
|
||||
test('default', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
expect(e.altKey).toEqual(false);
|
||||
expect(e.button).toEqual(0);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(false);
|
||||
expect(e.detail).toEqual(0);
|
||||
expect(typeof e.getModifierState).toEqual('function');
|
||||
expect(e.metaKey).toEqual(false);
|
||||
expect(e.movementX).toEqual(0);
|
||||
expect(e.movementY).toEqual(0);
|
||||
expect(e.offsetX).toEqual(0);
|
||||
expect(e.offsetY).toEqual(0);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(typeof e.preventDefault).toEqual('function');
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(0);
|
||||
expect(e.shiftKey).toEqual(false);
|
||||
expect(typeof e.timeStamp).toEqual('number');
|
||||
});
|
||||
target.virtualclick();
|
||||
});
|
||||
|
||||
test('custom payload', () => {
|
||||
const target = createEventTarget(node);
|
||||
node.addEventListener('click', e => {
|
||||
// expect most of the custom payload to be ignored
|
||||
expect(e.altKey).toEqual(true);
|
||||
expect(e.button).toEqual(1);
|
||||
expect(e.buttons).toEqual(0);
|
||||
expect(e.clientX).toEqual(0);
|
||||
expect(e.clientY).toEqual(0);
|
||||
expect(e.ctrlKey).toEqual(true);
|
||||
expect(e.detail).toEqual(0);
|
||||
expect(e.metaKey).toEqual(true);
|
||||
expect(e.pageX).toEqual(0);
|
||||
expect(e.pageY).toEqual(0);
|
||||
expect(e.screenX).toEqual(0);
|
||||
expect(e.screenY).toEqual(0);
|
||||
expect(e.shiftKey).toEqual(true);
|
||||
});
|
||||
target.virtualclick({
|
||||
altKey: true,
|
||||
button: 1,
|
||||
buttons: 4,
|
||||
x: 10,
|
||||
y: 20,
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
pageX: 50,
|
||||
pageY: 50,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Complex event sequences
|
||||
*
|
||||
* ...coming soon
|
||||
*/
|
||||
|
||||
/**
|
||||
* Other APIs
|
||||
*/
|
||||
|
||||
test('.setBoundingClientRect()', () => {
|
||||
const target = createEventTarget(node);
|
||||
expect(node.getBoundingClientRect()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"height": 0,
|
||||
"left": 0,
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
}
|
||||
`);
|
||||
target.setBoundingClientRect({ x: 10, y: 20, width: 100, height: 200 });
|
||||
expect(node.getBoundingClientRect()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bottom": 220,
|
||||
"height": 200,
|
||||
"left": 10,
|
||||
"right": 110,
|
||||
"top": 20,
|
||||
"width": 100,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export const defaultPointerId = 1;
|
||||
export const defaultPointerSize = 23;
|
||||
export const defaultBrowserChromeSize = 50;
|
||||
|
||||
/**
|
||||
* Button property
|
||||
* This property only guarantees to indicate which buttons are pressed during events caused by pressing or
|
||||
* releasing one or multiple buttons. As such, it is not reliable for events such as 'mouseenter', 'mouseleave',
|
||||
* 'mouseover', 'mouseout' or 'mousemove'. Furthermore, the semantics differ for PointerEvent, where the value
|
||||
* for 'pointermove' will always be -1.
|
||||
*/
|
||||
|
||||
export const buttonType = {
|
||||
// no change since last event
|
||||
none: -1,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 0,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
auxiliary: 1,
|
||||
// back mouse
|
||||
back: 3,
|
||||
// forward mouse
|
||||
forward: 4,
|
||||
// pen eraser
|
||||
eraser: 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Buttons bitmask
|
||||
*/
|
||||
|
||||
export const buttonsType = {
|
||||
none: 0,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 1,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
auxiliary: 4,
|
||||
// back mouse
|
||||
back: 8,
|
||||
// forward mouse
|
||||
forward: 16,
|
||||
// pen eraser
|
||||
eraser: 32
|
||||
};
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const defaultConfig = {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
};
|
||||
|
||||
const eventConfigs = {
|
||||
// Focus Events
|
||||
blur: {
|
||||
constructorType: 'FocusEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
focus: {
|
||||
constructorType: 'FocusEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
focusin: {
|
||||
constructorType: 'FocusEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
focusout: {
|
||||
constructorType: 'FocusEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
// Keyboard Events
|
||||
keydown: {
|
||||
constructorType: 'KeyboardEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
keyup: {
|
||||
constructorType: 'KeyboardEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Mouse Events
|
||||
click: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
contextmenu: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
dblclick: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
drag: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
dragend: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
dragenter: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
dragexit: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
dragleave: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
dragover: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
dragstart: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
drop: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
mousedown: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
mouseenter: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
mouseleave: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
mousemove: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
mouseout: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
mouseover: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
mouseup: {
|
||||
constructorType: 'MouseEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Selection events
|
||||
select: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: true, cancelable: false }
|
||||
},
|
||||
// Touch events
|
||||
touchcancel: {
|
||||
constructorType: 'TouchEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
touchend: {
|
||||
constructorType: 'TouchEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
touchmove: {
|
||||
constructorType: 'TouchEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
touchstart: {
|
||||
constructorType: 'TouchEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Pointer events
|
||||
gotpointercapture: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
lostpointercapture: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false, composed: true }
|
||||
},
|
||||
pointercancel: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
pointerdown: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
pointerenter: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false }
|
||||
},
|
||||
pointerleave: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false }
|
||||
},
|
||||
pointermove: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
pointerout: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
pointerover: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
pointerup: {
|
||||
constructorType: 'PointerEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Image events
|
||||
error: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: false, cancelable: false }
|
||||
},
|
||||
load: {
|
||||
constructorType: 'UIEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false }
|
||||
},
|
||||
// Form Events
|
||||
change: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: true, cancelable: false }
|
||||
},
|
||||
input: {
|
||||
constructorType: 'InputEvent',
|
||||
defaultInit: { bubbles: true, cancelable: false, composed: true }
|
||||
},
|
||||
invalid: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: false, cancelable: true }
|
||||
},
|
||||
submit: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: true, cancelable: true }
|
||||
},
|
||||
reset: {
|
||||
constructorType: 'Event',
|
||||
defaultInit: { bubbles: true, cancelable: true }
|
||||
},
|
||||
// Clipboard Events
|
||||
copy: {
|
||||
constructorType: 'ClipboardEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
cut: {
|
||||
constructorType: 'ClipboardEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
paste: {
|
||||
constructorType: 'ClipboardEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Composition Events
|
||||
compositionend: {
|
||||
constructorType: 'CompositionEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
compositionstart: {
|
||||
constructorType: 'CompositionEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
compositionupdate: {
|
||||
constructorType: 'CompositionEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
},
|
||||
// Other events
|
||||
scroll: {
|
||||
constructorType: 'UIEvent',
|
||||
defaultInit: { bubbles: false, cancelable: false }
|
||||
},
|
||||
wheel: {
|
||||
constructorType: 'WheelEvent',
|
||||
defaultInit: { bubbles: true, cancelable: true, composed: true }
|
||||
}
|
||||
};
|
||||
|
||||
function getEventConfig(type) {
|
||||
return eventConfigs[type] || defaultConfig;
|
||||
}
|
||||
|
||||
export default function createEvent(type, init) {
|
||||
const config = getEventConfig(type);
|
||||
const { constructorType, defaultInit } = config;
|
||||
const eventInit = { ...init, ...defaultInit };
|
||||
|
||||
const event = document.createEvent(constructorType);
|
||||
const { bubbles, cancelable, ...data } = eventInit;
|
||||
event.initEvent(type, bubbles, cancelable);
|
||||
|
||||
if (data != null) {
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key];
|
||||
if (key === 'timeStamp' && !value) {
|
||||
return;
|
||||
}
|
||||
Object.defineProperty(event, key, { value });
|
||||
});
|
||||
}
|
||||
return event;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Change environment support for PointerEvent.
|
||||
*/
|
||||
|
||||
const emptyFunction = function() {};
|
||||
|
||||
export function hasPointerEvent() {
|
||||
return global != null && global.PointerEvent != null;
|
||||
}
|
||||
|
||||
export function setPointerEvent(bool) {
|
||||
const pointerCaptureFn = name => id => {
|
||||
if (typeof id !== 'number') {
|
||||
if (process.env.NODE_DEV !== 'production') {
|
||||
console.error('A pointerId must be passed to "%s"', name);
|
||||
}
|
||||
}
|
||||
};
|
||||
global.PointerEvent = bool ? emptyFunction : undefined;
|
||||
global.HTMLElement.prototype.setPointerCapture = bool
|
||||
? pointerCaptureFn('setPointerCapture')
|
||||
: undefined;
|
||||
global.HTMLElement.prototype.releasePointerCapture = bool
|
||||
? pointerCaptureFn('releasePointerCapture')
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change environment host platform.
|
||||
*/
|
||||
|
||||
const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
|
||||
|
||||
export const platform = {
|
||||
clear() {
|
||||
platformGetter.mockClear();
|
||||
},
|
||||
get() {
|
||||
return global.navigator.platform === 'MacIntel' ? 'mac' : 'windows';
|
||||
},
|
||||
set(name) {
|
||||
switch (name) {
|
||||
case 'mac': {
|
||||
platformGetter.mockReturnValue('MacIntel');
|
||||
break;
|
||||
}
|
||||
case 'windows': {
|
||||
platformGetter.mockReturnValue('Win32');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
buttonType,
|
||||
buttonsType,
|
||||
defaultPointerId,
|
||||
defaultPointerSize,
|
||||
defaultBrowserChromeSize
|
||||
} from './constants';
|
||||
import * as domEvents from './domEvents';
|
||||
import { hasPointerEvent, platform } from './domEnvironment';
|
||||
import * as touchStore from './touchStore';
|
||||
|
||||
/**
|
||||
* Converts a PointerEvent payload to a Touch
|
||||
*/
|
||||
function createTouch(target, payload) {
|
||||
const {
|
||||
height = defaultPointerSize,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId,
|
||||
pressure = 1,
|
||||
twist = 0,
|
||||
width = defaultPointerSize,
|
||||
x = 0,
|
||||
y = 0
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
force: pressure,
|
||||
identifier: pointerId,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
radiusX: width / 2,
|
||||
radiusY: height / 2,
|
||||
rotationAngle: twist,
|
||||
target,
|
||||
screenX: x,
|
||||
screenY: y + defaultBrowserChromeSize
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PointerEvent to a TouchEvent
|
||||
*/
|
||||
function createTouchEventPayload(target, touch, payload) {
|
||||
const {
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
metaKey = false,
|
||||
preventDefault,
|
||||
shiftKey = false,
|
||||
timeStamp
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
altKey,
|
||||
changedTouches: [touch],
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
targetTouches: touchStore.getTargetTouches(target),
|
||||
timeStamp,
|
||||
touches: touchStore.getTouches()
|
||||
};
|
||||
}
|
||||
|
||||
function getPointerType(payload) {
|
||||
let pointerType = 'mouse';
|
||||
if (payload != null && payload.pointerType != null) {
|
||||
pointerType = payload.pointerType;
|
||||
}
|
||||
return pointerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointer events sequences.
|
||||
*
|
||||
* Creates representative browser event sequences for high-level gestures based on pointers.
|
||||
* This allows unit tests to be written in terms of simple pointer interactions while testing
|
||||
* that the responses to those interactions account for the complex sequence of events that
|
||||
* browsers produce as a result.
|
||||
*
|
||||
* Every time a new pointer touches the surface a 'touchstart' event should be dispatched.
|
||||
* - 'changedTouches' contains the new touch.
|
||||
* - 'targetTouches' contains all the active pointers for the target.
|
||||
* - 'touches' contains all the active pointers on the surface.
|
||||
*
|
||||
* Every time an existing pointer moves a 'touchmove' event should be dispatched.
|
||||
* - 'changedTouches' contains the updated touch.
|
||||
*
|
||||
* Every time an existing pointer leaves the surface a 'touchend' event should be dispatched.
|
||||
* - 'changedTouches' contains the released touch.
|
||||
* - 'targetTouches' contains any of the remaining active pointers for the target.
|
||||
*/
|
||||
|
||||
export function contextmenu(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const {
|
||||
ctrlKey,
|
||||
// eslint-disable-next-line
|
||||
pointerType: _,
|
||||
...restPayload
|
||||
} = defaultPayload;
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...restPayload,
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pointerType
|
||||
};
|
||||
|
||||
const preventDefault = payload.preventDefault;
|
||||
|
||||
if (pointerType === 'touch') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.addTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchstart(touchEventPayload));
|
||||
dispatch(
|
||||
domEvents.mousemove({
|
||||
...payload,
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.none
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
domEvents.contextmenu({
|
||||
...payload,
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.none,
|
||||
preventDefault
|
||||
})
|
||||
);
|
||||
touchStore.removeTouch(touch);
|
||||
} else if (pointerType === 'mouse') {
|
||||
if (ctrlKey === true) {
|
||||
const { button, buttons } = payload;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({ ...payload, ctrlKey }));
|
||||
}
|
||||
dispatch(domEvents.mousedown({ ...payload, ctrlKey }));
|
||||
if (platform.get() === 'mac') {
|
||||
dispatch(domEvents.contextmenu({ button, buttons, ctrlKey, preventDefault }));
|
||||
}
|
||||
} else {
|
||||
const button = buttonType.secondary;
|
||||
const buttons = buttonsType.secondary;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({ ...payload, button, buttons }));
|
||||
}
|
||||
dispatch(domEvents.mousedown({ ...payload, button, buttons }));
|
||||
dispatch(domEvents.contextmenu({ ...payload, button, buttons, preventDefault }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function focus(target, defaultPayload = {}) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const { relatedTarget, ...payload } = defaultPayload;
|
||||
if (relatedTarget) {
|
||||
relatedTarget.dispatchEvent(domEvents.focusout({ ...payload, relatedTarget: target }));
|
||||
}
|
||||
dispatch(domEvents.focusin({ ...payload, relatedTarget }));
|
||||
if (relatedTarget) {
|
||||
relatedTarget.dispatchEvent(domEvents.blur({ ...payload, relatedTarget: target }));
|
||||
}
|
||||
dispatch(domEvents.focus({ ...payload, relatedTarget }));
|
||||
}
|
||||
|
||||
export function pointercancel(target, defaultPayload) {
|
||||
const dispatchEvent = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatchEvent(domEvents.pointercancel(payload));
|
||||
} else {
|
||||
if (pointerType === 'mouse') {
|
||||
dispatchEvent(domEvents.dragstart(payload));
|
||||
} else {
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.removeTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatchEvent(domEvents.touchcancel(touchEventPayload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerdown(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
focus(target);
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.addTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchstart(touchEventPayload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.gotpointercapture(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerover(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
}
|
||||
|
||||
export function pointerout(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseout(payload));
|
||||
dispatch(domEvents.mouseleave(payload));
|
||||
}
|
||||
|
||||
// pointer is not down while moving
|
||||
export function pointerhover(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointermove(payload));
|
||||
}
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
}
|
||||
|
||||
// pointer is down while moving
|
||||
export function pointermove(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointermove({ pressure: 0.5, button: -1, ...payload }));
|
||||
}
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(
|
||||
domEvents.pointermove({
|
||||
pressure: 1,
|
||||
button: -1,
|
||||
...payload
|
||||
})
|
||||
);
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
touchStore.updateTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchmove(touchEventPayload));
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerup(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
|
||||
const payload = {
|
||||
pointerId: defaultPointerId,
|
||||
pointerType,
|
||||
...defaultPayload
|
||||
};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
dispatch(domEvents.click(payload));
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
dispatch(domEvents.lostpointercapture(payload));
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
const touch = createTouch(target, payload);
|
||||
const isGesture = touchStore.removeTouch(touch);
|
||||
const touchEventPayload = createTouchEventPayload(target, touch, payload);
|
||||
dispatch(domEvents.touchend(touchEventPayload));
|
||||
// emulated mouse events don't occur for multi-touch or after 'touchmove'
|
||||
if (!isGesture) {
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
}
|
||||
focus(target);
|
||||
if (!isGesture) {
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
}
|
||||
dispatch(domEvents.click(payload));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be called after each test to ensure the touchStore is cleared
|
||||
* in cases where the mock pointers weren't released before the test completed
|
||||
* (e.g., a test failed or ran a partial gesture).
|
||||
*/
|
||||
export function clearPointers() {
|
||||
touchStore.clear();
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import createEvent from './createEvent';
|
||||
import { buttonType, buttonsType, defaultPointerSize, defaultBrowserChromeSize } from './constants';
|
||||
|
||||
/**
|
||||
* Native event object mocks for higher-level events.
|
||||
*
|
||||
* 1. Each event type defines the exact object that it accepts. This ensures
|
||||
* that no arbitrary properties can be assigned to events, and the properties
|
||||
* that don't exist on specific event types (e.g., 'pointerType') are not added
|
||||
* to the respective native event.
|
||||
*
|
||||
* 2. Properties that cannot be relied on due to inconsistent browser support (e.g., 'x' and 'y') are not
|
||||
* added to the native event. Others that shouldn't be arbitrarily customized (e.g., 'screenX')
|
||||
* are automatically inferred from associated values.
|
||||
*
|
||||
* 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
|
||||
*/
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function createGetModifierState(keyArg, data) {
|
||||
if (keyArg === 'Alt') {
|
||||
return data.altKey || false;
|
||||
}
|
||||
if (keyArg === 'Control') {
|
||||
return data.ctrlKey || false;
|
||||
}
|
||||
if (keyArg === 'Meta') {
|
||||
return data.metaKey || false;
|
||||
}
|
||||
if (keyArg === 'Shift') {
|
||||
return data.shiftKey || false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KeyboardEvent
|
||||
*/
|
||||
|
||||
function createKeyboardEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
isComposing = false,
|
||||
key = '',
|
||||
metaKey = false,
|
||||
preventDefault = emptyFunction,
|
||||
shiftKey = false
|
||||
} = {}
|
||||
) {
|
||||
const modifierState = { altKey, ctrlKey, metaKey, shiftKey };
|
||||
|
||||
const eventPayload = {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
getModifierState(keyArg) {
|
||||
return createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
isComposing,
|
||||
key,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey
|
||||
};
|
||||
|
||||
if (isComposing) {
|
||||
eventPayload.keyCode = 229;
|
||||
}
|
||||
|
||||
return createEvent(type, eventPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* MouseEvent
|
||||
*/
|
||||
|
||||
function createMouseEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = buttonType.none,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
detail = 1,
|
||||
metaKey = false,
|
||||
movementX = 0,
|
||||
movementY = 0,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
pageX,
|
||||
pageY,
|
||||
preventDefault = emptyFunction,
|
||||
screenX,
|
||||
screenY,
|
||||
shiftKey = false,
|
||||
timeStamp,
|
||||
x = 0,
|
||||
y = 0
|
||||
} = {}
|
||||
) {
|
||||
const modifierState = { altKey, ctrlKey, metaKey, shiftKey };
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
ctrlKey,
|
||||
detail,
|
||||
getModifierState(keyArg) {
|
||||
return createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
metaKey,
|
||||
movementX,
|
||||
movementY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
preventDefault,
|
||||
screenX: screenX === 0 ? screenX : x,
|
||||
screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
|
||||
shiftKey,
|
||||
timeStamp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PointerEvent
|
||||
*/
|
||||
|
||||
function createPointerEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = buttonType.none,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
detail = 1,
|
||||
height,
|
||||
metaKey = false,
|
||||
movementX = 0,
|
||||
movementY = 0,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId,
|
||||
pressure = 0,
|
||||
preventDefault = emptyFunction,
|
||||
pointerType = 'mouse',
|
||||
screenX,
|
||||
screenY,
|
||||
shiftKey = false,
|
||||
tangentialPressure = 0,
|
||||
tiltX = 0,
|
||||
tiltY = 0,
|
||||
timeStamp,
|
||||
twist = 0,
|
||||
width,
|
||||
x = 0,
|
||||
y = 0
|
||||
} = {}
|
||||
) {
|
||||
const modifierState = { altKey, ctrlKey, metaKey, shiftKey };
|
||||
const isMouse = pointerType === 'mouse';
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
ctrlKey,
|
||||
detail,
|
||||
getModifierState(keyArg) {
|
||||
return createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
height: isMouse ? 1 : height != null ? height : defaultPointerSize,
|
||||
metaKey,
|
||||
movementX,
|
||||
movementY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
pointerId,
|
||||
pointerType,
|
||||
pressure,
|
||||
preventDefault,
|
||||
releasePointerCapture: emptyFunction,
|
||||
screenX: screenX === 0 ? screenX : x,
|
||||
screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
|
||||
setPointerCapture: emptyFunction,
|
||||
shiftKey,
|
||||
tangentialPressure,
|
||||
tiltX,
|
||||
tiltY,
|
||||
timeStamp,
|
||||
twist,
|
||||
width: isMouse ? 1 : width != null ? width : defaultPointerSize
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TouchEvent
|
||||
*/
|
||||
|
||||
function createTouchEvent(type, payload) {
|
||||
return createEvent(type, {
|
||||
preventDefault: emptyFunction,
|
||||
...payload,
|
||||
detail: 0,
|
||||
sourceCapabilities: {
|
||||
firesTouchEvents: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM events
|
||||
*/
|
||||
|
||||
export function blur({ relatedTarget } = {}) {
|
||||
return createEvent('blur', { relatedTarget });
|
||||
}
|
||||
|
||||
export function click(payload) {
|
||||
return createMouseEvent('click', {
|
||||
button: buttonType.primary,
|
||||
...payload
|
||||
});
|
||||
}
|
||||
|
||||
export function contextmenu(payload) {
|
||||
return createMouseEvent('contextmenu', {
|
||||
...payload,
|
||||
detail: 0
|
||||
});
|
||||
}
|
||||
|
||||
export function dragstart(payload) {
|
||||
return createMouseEvent('dragstart', {
|
||||
...payload,
|
||||
detail: 0
|
||||
});
|
||||
}
|
||||
|
||||
export function focus({ relatedTarget } = {}) {
|
||||
return createEvent('focus', { relatedTarget });
|
||||
}
|
||||
|
||||
export function focusin({ relatedTarget } = {}) {
|
||||
return createEvent('focusin', { relatedTarget });
|
||||
}
|
||||
|
||||
export function focusout({ relatedTarget } = {}) {
|
||||
return createEvent('focusout', { relatedTarget });
|
||||
}
|
||||
|
||||
export function gotpointercapture(payload) {
|
||||
return createPointerEvent('gotpointercapture', payload);
|
||||
}
|
||||
|
||||
export function keydown(payload) {
|
||||
return createKeyboardEvent('keydown', payload);
|
||||
}
|
||||
|
||||
export function keyup(payload) {
|
||||
return createKeyboardEvent('keyup', payload);
|
||||
}
|
||||
|
||||
export function lostpointercapture(payload) {
|
||||
return createPointerEvent('lostpointercapture', payload);
|
||||
}
|
||||
|
||||
export function mousedown(payload) {
|
||||
// The value of 'button' and 'buttons' for 'mousedown' must not be none.
|
||||
const button =
|
||||
payload != null && payload.button !== buttonType.none ? payload.button : buttonType.primary;
|
||||
const buttons =
|
||||
payload != null && payload.buttons !== buttonsType.none ? payload.buttons : buttonsType.primary;
|
||||
|
||||
return createMouseEvent('mousedown', {
|
||||
...payload,
|
||||
button,
|
||||
buttons
|
||||
});
|
||||
}
|
||||
|
||||
export function mouseenter(payload) {
|
||||
return createMouseEvent('mouseenter', payload);
|
||||
}
|
||||
|
||||
export function mouseleave(payload) {
|
||||
return createMouseEvent('mouseleave', payload);
|
||||
}
|
||||
|
||||
export function mousemove(payload) {
|
||||
return createMouseEvent('mousemove', {
|
||||
// 0 is also the uninitialized value (i.e., don't assume it means primary button down)
|
||||
button: 0,
|
||||
buttons: 0,
|
||||
...payload
|
||||
});
|
||||
}
|
||||
|
||||
export function mouseout(payload) {
|
||||
return createMouseEvent('mouseout', payload);
|
||||
}
|
||||
|
||||
export function mouseover(payload) {
|
||||
return createMouseEvent('mouseover', payload);
|
||||
}
|
||||
|
||||
export function mouseup(payload) {
|
||||
return createMouseEvent('mouseup', {
|
||||
button: buttonType.primary,
|
||||
...payload,
|
||||
buttons: buttonsType.none
|
||||
});
|
||||
}
|
||||
export function pointercancel(payload) {
|
||||
return createPointerEvent('pointercancel', {
|
||||
...payload,
|
||||
buttons: 0,
|
||||
detail: 0,
|
||||
height: 1,
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
pressure: 0,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
width: 1,
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
}
|
||||
|
||||
export function pointerdown(payload) {
|
||||
const isTouch = payload != null && payload.pointerType === 'touch';
|
||||
return createPointerEvent('pointerdown', {
|
||||
button: buttonType.primary,
|
||||
buttons: buttonsType.primary,
|
||||
pressure: isTouch ? 1 : 0.5,
|
||||
...payload
|
||||
});
|
||||
}
|
||||
|
||||
export function pointerenter(payload) {
|
||||
return createPointerEvent('pointerenter', payload);
|
||||
}
|
||||
|
||||
export function pointerleave(payload) {
|
||||
return createPointerEvent('pointerleave', payload);
|
||||
}
|
||||
|
||||
export function pointermove(payload) {
|
||||
return createPointerEvent('pointermove', {
|
||||
...payload,
|
||||
button: buttonType.none,
|
||||
buttons: buttonsType.none
|
||||
});
|
||||
}
|
||||
|
||||
export function pointerout(payload) {
|
||||
return createPointerEvent('pointerout', payload);
|
||||
}
|
||||
|
||||
export function pointerover(payload) {
|
||||
return createPointerEvent('pointerover', payload);
|
||||
}
|
||||
|
||||
export function pointerup(payload) {
|
||||
return createPointerEvent('pointerup', {
|
||||
button: buttonType.primary,
|
||||
...payload,
|
||||
buttons: buttonsType.none,
|
||||
pressure: 0
|
||||
});
|
||||
}
|
||||
|
||||
export function scroll() {
|
||||
return createEvent('scroll', { bubbles: false });
|
||||
}
|
||||
|
||||
export function select() {
|
||||
return createEvent('select');
|
||||
}
|
||||
|
||||
export function selectionchange() {
|
||||
return createEvent('selectionchange');
|
||||
}
|
||||
|
||||
export function touchcancel(payload) {
|
||||
return createTouchEvent('touchcancel', payload);
|
||||
}
|
||||
|
||||
export function touchend(payload) {
|
||||
return createTouchEvent('touchend', payload);
|
||||
}
|
||||
|
||||
export function touchmove(payload) {
|
||||
return createTouchEvent('touchmove', payload);
|
||||
}
|
||||
|
||||
export function touchstart(payload) {
|
||||
return createTouchEvent('touchstart', payload);
|
||||
}
|
||||
|
||||
export function virtualclick(payload) {
|
||||
return createMouseEvent('click', {
|
||||
button: 0,
|
||||
...payload,
|
||||
buttons: 0,
|
||||
detail: 0,
|
||||
height: 1,
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
pressure: 0,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
width: 1,
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { buttonType, buttonsType } from './constants';
|
||||
import * as domEvents from './domEvents';
|
||||
import * as domEventSequences from './domEventSequences';
|
||||
import { hasPointerEvent, setPointerEvent, platform } from './domEnvironment';
|
||||
import { describeWithPointerEvent, testWithPointerType } from './testHelpers';
|
||||
|
||||
const createEventTarget = node => ({
|
||||
node,
|
||||
/**
|
||||
* Simple events abstraction.
|
||||
*/
|
||||
blur(payload) {
|
||||
node.dispatchEvent(domEvents.blur(payload));
|
||||
},
|
||||
click(payload) {
|
||||
node.dispatchEvent(domEvents.click(payload));
|
||||
},
|
||||
focus(payload) {
|
||||
domEventSequences.focus(node, payload);
|
||||
try {
|
||||
node.focus();
|
||||
} catch (e) {}
|
||||
},
|
||||
keydown(payload) {
|
||||
node.dispatchEvent(domEvents.keydown(payload));
|
||||
},
|
||||
keyup(payload) {
|
||||
node.dispatchEvent(domEvents.keyup(payload));
|
||||
},
|
||||
scroll(payload) {
|
||||
node.dispatchEvent(domEvents.scroll(payload));
|
||||
},
|
||||
select(payload) {
|
||||
node.dispatchEvent(domEvents.select(payload));
|
||||
},
|
||||
// selectionchange is only dispatched on 'document'
|
||||
selectionchange(payload) {
|
||||
document.dispatchEvent(domEvents.selectionchange(payload));
|
||||
},
|
||||
virtualclick(payload) {
|
||||
node.dispatchEvent(domEvents.virtualclick(payload));
|
||||
},
|
||||
/**
|
||||
* PointerEvent abstraction.
|
||||
* Dispatches the expected sequence of PointerEvents, MouseEvents, and
|
||||
* TouchEvents for a given environment.
|
||||
*/
|
||||
contextmenu(payload) {
|
||||
domEventSequences.contextmenu(node, payload);
|
||||
},
|
||||
// node no longer receives events for the pointer
|
||||
pointercancel(payload) {
|
||||
domEventSequences.pointercancel(node, payload);
|
||||
},
|
||||
// node dispatches down events
|
||||
pointerdown(payload) {
|
||||
domEventSequences.pointerdown(node, payload);
|
||||
},
|
||||
// node dispatches move events (pointer is not down)
|
||||
pointerhover(payload) {
|
||||
domEventSequences.pointerhover(node, payload);
|
||||
},
|
||||
// node dispatches move events (pointer is down)
|
||||
pointermove(payload) {
|
||||
domEventSequences.pointermove(node, payload);
|
||||
},
|
||||
// node dispatches enter & over events
|
||||
pointerover(payload) {
|
||||
domEventSequences.pointerover(node, payload);
|
||||
},
|
||||
// node dispatches exit & leave events
|
||||
pointerout(payload) {
|
||||
domEventSequences.pointerout(node, payload);
|
||||
},
|
||||
// node dispatches up events
|
||||
pointerup(payload) {
|
||||
domEventSequences.pointerup(node, payload);
|
||||
},
|
||||
/**
|
||||
* Gesture abstractions.
|
||||
* Helpers for event sequences expected in a gesture.
|
||||
* target.tap({ pointerType: 'touch' })
|
||||
*/
|
||||
tap(payload) {
|
||||
domEventSequences.pointerdown(payload);
|
||||
domEventSequences.pointerup(payload);
|
||||
},
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
setBoundingClientRect({ x, y, width, height }) {
|
||||
node.getBoundingClientRect = function() {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left: x,
|
||||
right: x + width,
|
||||
top: y,
|
||||
bottom: y + height
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const clearPointers = domEventSequences.clearPointers;
|
||||
|
||||
export {
|
||||
buttonType,
|
||||
buttonsType,
|
||||
clearPointers,
|
||||
createEventTarget,
|
||||
describeWithPointerEvent,
|
||||
platform,
|
||||
hasPointerEvent,
|
||||
setPointerEvent,
|
||||
testWithPointerType
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { hasPointerEvent, setPointerEvent } from './domEnvironment';
|
||||
|
||||
export function describeWithPointerEvent(message, describeFn) {
|
||||
const pointerEvent = 'PointerEvent';
|
||||
const fallback = 'MouseEvent/TouchEvent';
|
||||
describe.each`
|
||||
value | name
|
||||
${true} | ${pointerEvent}
|
||||
${false} | ${fallback}
|
||||
`(`${message}: $name`, entry => {
|
||||
const hasPointerEvents = entry.value;
|
||||
setPointerEvent(hasPointerEvents);
|
||||
describeFn(hasPointerEvents);
|
||||
});
|
||||
}
|
||||
|
||||
export function testWithPointerType(message, testFn) {
|
||||
const table = hasPointerEvent() ? ['mouse', 'touch', 'pen'] : ['mouse', 'touch'];
|
||||
test.each(table)(`${message}: %s`, pointerType => {
|
||||
testFn(pointerType);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Touch events state machine.
|
||||
*
|
||||
* Keeps track of the active pointers and allows them to be reflected in touch events.
|
||||
*/
|
||||
|
||||
let isGesture = false;
|
||||
const activeTouches = new Map();
|
||||
|
||||
export function addTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (!activeTouches.has(target)) {
|
||||
activeTouches.set(target, new Map());
|
||||
}
|
||||
if (activeTouches.get(target).get(identifier)) {
|
||||
// Do not allow existing touches to be overwritten
|
||||
console.error(
|
||||
'Touch with identifier %s already exists. Did not record touch start.',
|
||||
identifier
|
||||
);
|
||||
} else {
|
||||
activeTouches.get(target).set(identifier, touch);
|
||||
}
|
||||
isGesture = activeTouches.size > 1;
|
||||
}
|
||||
|
||||
export function updateTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (activeTouches.get(target) != null) {
|
||||
activeTouches.get(target).set(identifier, touch);
|
||||
isGesture = true;
|
||||
} else {
|
||||
console.error(
|
||||
'Touch with identifier %s does not exist. Cannot record touch move without a touch start.',
|
||||
identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTouch(touch) {
|
||||
const identifier = touch.identifier;
|
||||
const target = touch.target;
|
||||
if (activeTouches.get(target) != null) {
|
||||
if (activeTouches.get(target).has(identifier)) {
|
||||
activeTouches.get(target).delete(identifier);
|
||||
} else {
|
||||
console.error(
|
||||
'Touch with identifier %s does not exist. Cannot record touch end without a touch start.',
|
||||
identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
return isGesture;
|
||||
}
|
||||
|
||||
export function getTouches() {
|
||||
const touches = [];
|
||||
activeTouches.forEach((_, target) => {
|
||||
touches.push(...getTargetTouches(target));
|
||||
});
|
||||
return touches;
|
||||
}
|
||||
|
||||
export function getTargetTouches(target) {
|
||||
if (activeTouches.get(target) != null) {
|
||||
return Array.from(activeTouches.get(target).values());
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function clear() {
|
||||
activeTouches.clear();
|
||||
}
|
||||
Reference in New Issue
Block a user