mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-23 14:57:13 +00:00
[fix] event normalization
Work better with simulated events, and avoid crashing if 'nativeEvent.target' isn't an element node. Close #597
This commit is contained in:
@@ -4,6 +4,9 @@ exports[`modules/createElement it normalizes event.nativeEvent 1`] = `
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"changedTouches": Array [],
|
||||
"identifier": undefined,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": undefined,
|
||||
"pageY": undefined,
|
||||
"preventDefault": [Function],
|
||||
|
||||
@@ -19,11 +19,7 @@ describe('modules/createElement', () => {
|
||||
};
|
||||
const component = shallow(createElement('span', { onClick }));
|
||||
component.find('span').simulate('click', {
|
||||
nativeEvent: {
|
||||
preventDefault() {},
|
||||
stopImmediatePropagation() {},
|
||||
stopPropagation() {}
|
||||
}
|
||||
nativeEvent: {}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,11 +34,7 @@ describe('modules/createElement', () => {
|
||||
);
|
||||
component.find('span').simulate('keyPress', {
|
||||
isDefaultPrevented() {},
|
||||
nativeEvent: {
|
||||
preventDefault() {},
|
||||
stopImmediatePropagation() {},
|
||||
stopPropagation() {}
|
||||
},
|
||||
nativeEvent: {},
|
||||
preventDefault() {},
|
||||
which
|
||||
});
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`modules/normalizeNativeEvent mouse events simulated event 1`] = `
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"changedTouches": Array [
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"clientX": undefined,
|
||||
"clientY": undefined,
|
||||
"force": undefined,
|
||||
"identifier": 0,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": undefined,
|
||||
"pageY": undefined,
|
||||
"screenX": undefined,
|
||||
"screenY": undefined,
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
},
|
||||
],
|
||||
"identifier": 0,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": undefined,
|
||||
"pageY": undefined,
|
||||
"preventDefault": [Function],
|
||||
"stopImmediatePropagation": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
"touches": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`modules/normalizeNativeEvent mouse events synthetic event 1`] = `
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"changedTouches": Array [
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"clientX": 100,
|
||||
"clientY": 100,
|
||||
"force": false,
|
||||
"identifier": 0,
|
||||
"locationX": 100,
|
||||
"locationY": 100,
|
||||
"pageX": 300,
|
||||
"pageY": 300,
|
||||
"screenX": 400,
|
||||
"screenY": 400,
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
},
|
||||
],
|
||||
"identifier": 0,
|
||||
"locationX": 200,
|
||||
"locationY": 200,
|
||||
"pageX": 300,
|
||||
"pageY": 300,
|
||||
"preventDefault": [Function],
|
||||
"stopImmediatePropagation": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
"touches": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`modules/normalizeNativeEvent touch events simulated event 1`] = `
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"changedTouches": Array [],
|
||||
"identifier": undefined,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": undefined,
|
||||
"pageY": undefined,
|
||||
"preventDefault": [Function],
|
||||
"stopImmediatePropagation": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
"touches": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`modules/normalizeNativeEvent touch events synthetic event 1`] = `
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"changedTouches": Array [
|
||||
Object {
|
||||
"_normalized": true,
|
||||
"clientX": 100,
|
||||
"clientY": 100,
|
||||
"force": false,
|
||||
"identifier": undefined,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": 300,
|
||||
"pageY": 300,
|
||||
"radiusX": 10,
|
||||
"radiusY": 10,
|
||||
"rotationAngle": 45,
|
||||
"screenX": 400,
|
||||
"screenY": 400,
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
},
|
||||
],
|
||||
"identifier": undefined,
|
||||
"locationX": undefined,
|
||||
"locationY": undefined,
|
||||
"pageX": 300,
|
||||
"pageY": 300,
|
||||
"preventDefault": [Function],
|
||||
"stopImmediatePropagation": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": undefined,
|
||||
"timestamp": 1496876171255,
|
||||
"touches": Array [],
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,82 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import normalizeNativeEvent from '..';
|
||||
|
||||
const normalizeEvent = (nativeEvent) => {
|
||||
const result = normalizeNativeEvent(nativeEvent);
|
||||
result.timestamp = 1496876171255;
|
||||
if (result.changedTouches && result.changedTouches[0]) {
|
||||
result.changedTouches[0].timestamp = 1496876171255;
|
||||
}
|
||||
if (result.touches && result.touches[0]) {
|
||||
result.touches[0].timestamp = 1496876171255;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
describe('modules/normalizeNativeEvent', () => {
|
||||
describe('mouse events', () => {
|
||||
test('simulated event', () => {
|
||||
const nativeEvent = {
|
||||
type: 'mouseup'
|
||||
};
|
||||
|
||||
const result = normalizeEvent(nativeEvent);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('synthetic event', () => {
|
||||
const nativeEvent = {
|
||||
type: 'mouseup',
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
force: false,
|
||||
offsetX: 200,
|
||||
offsetY: 200,
|
||||
pageX: 300,
|
||||
pageY: 300,
|
||||
screenX: 400,
|
||||
screenY: 400
|
||||
};
|
||||
|
||||
const result = normalizeEvent(nativeEvent);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('touch events', () => {
|
||||
test('simulated event', () => {
|
||||
const nativeEvent = {
|
||||
type: 'touchstart'
|
||||
};
|
||||
|
||||
const result = normalizeEvent(nativeEvent);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('synthetic event', () => {
|
||||
const nativeEvent = {
|
||||
type: 'touchstart',
|
||||
changedTouches: [
|
||||
{
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
force: false,
|
||||
pageX: 300,
|
||||
pageY: 300,
|
||||
radiusX: 10,
|
||||
radiusY: 10,
|
||||
rotationAngle: 45,
|
||||
screenX: 400,
|
||||
screenY: 400
|
||||
}
|
||||
],
|
||||
pageX: 300,
|
||||
pageY: 300
|
||||
};
|
||||
|
||||
const result = normalizeEvent(nativeEvent);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,19 +5,31 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const emptyArray = [];
|
||||
const emptyFunction = () => {};
|
||||
|
||||
// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier
|
||||
const normalizeTouches = (touches = emptyArray) =>
|
||||
Array.prototype.slice.call(touches).map(touch => {
|
||||
const identifier = touch.identifier > 20 ? touch.identifier % 20 : touch.identifier;
|
||||
const normalizeTouches = touches => {
|
||||
if (!touches) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const rect = touch.target && touch.target.getBoundingClientRect();
|
||||
const locationX = touch.pageX - rect.left;
|
||||
const locationY = touch.pageY - rect.top;
|
||||
return Array.prototype.slice.call(touches).map(touch => {
|
||||
const identifier = touch.identifier > 20 ? touch.identifier % 20 : touch.identifier;
|
||||
let locationX, locationY;
|
||||
|
||||
const node = touch.target;
|
||||
if (node) {
|
||||
const isElement = node.nodeType === 1 /* Node.ELEMENT_NODE */;
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
const rect = node.getBoundingClientRect();
|
||||
locationX = touch.pageX - rect.left;
|
||||
locationY = touch.pageY - rect.top;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_normalized: true,
|
||||
@@ -40,19 +52,36 @@ const normalizeTouches = (touches = emptyArray) =>
|
||||
timestamp: Date.now()
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
function normalizeTouchEvent(nativeEvent) {
|
||||
const changedTouches = normalizeTouches(nativeEvent.changedTouches);
|
||||
const touches = normalizeTouches(nativeEvent.touches);
|
||||
|
||||
const preventDefault =
|
||||
typeof nativeEvent.preventDefault === 'function'
|
||||
? nativeEvent.preventDefault.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const stopImmediatePropagation =
|
||||
typeof nativeEvent.stopImmediatePropagation === 'function'
|
||||
? nativeEvent.stopImmediatePropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const stopPropagation =
|
||||
typeof nativeEvent.stopPropagation === 'function'
|
||||
? nativeEvent.stopPropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
|
||||
const event = {
|
||||
_normalized: true,
|
||||
changedTouches,
|
||||
identifier: undefined,
|
||||
locationX: undefined,
|
||||
locationY: undefined,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
preventDefault: nativeEvent.preventDefault.bind(nativeEvent),
|
||||
stopImmediatePropagation: nativeEvent.stopImmediatePropagation.bind(nativeEvent),
|
||||
stopPropagation: nativeEvent.stopPropagation.bind(nativeEvent),
|
||||
preventDefault,
|
||||
stopImmediatePropagation,
|
||||
stopPropagation,
|
||||
target: nativeEvent.target,
|
||||
// normalize the timestamp
|
||||
// https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events
|
||||
@@ -89,6 +118,20 @@ function normalizeMouseEvent(nativeEvent) {
|
||||
timestamp: Date.now()
|
||||
}
|
||||
];
|
||||
|
||||
const preventDefault =
|
||||
typeof nativeEvent.preventDefault === 'function'
|
||||
? nativeEvent.preventDefault.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const stopImmediatePropagation =
|
||||
typeof nativeEvent.stopImmediatePropagation === 'function'
|
||||
? nativeEvent.stopImmediatePropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const stopPropagation =
|
||||
typeof nativeEvent.stopPropagation === 'function'
|
||||
? nativeEvent.stopPropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
|
||||
return {
|
||||
_normalized: true,
|
||||
changedTouches: touches,
|
||||
@@ -97,22 +140,27 @@ function normalizeMouseEvent(nativeEvent) {
|
||||
locationY: nativeEvent.offsetY,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
preventDefault: nativeEvent.preventDefault.bind(nativeEvent),
|
||||
stopImmediatePropagation: nativeEvent.stopImmediatePropagation.bind(nativeEvent),
|
||||
stopPropagation: nativeEvent.stopPropagation.bind(nativeEvent),
|
||||
preventDefault,
|
||||
stopImmediatePropagation,
|
||||
stopPropagation,
|
||||
target: nativeEvent.target,
|
||||
timestamp: touches[0].timestamp,
|
||||
touches: nativeEvent.type === 'mouseup' ? emptyArray : touches
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeNativeEvent(nativeEvent) {
|
||||
if (nativeEvent._normalized) {
|
||||
// TODO: how to best handle keyboard events?
|
||||
function normalizeNativeEvent(nativeEvent: Object) {
|
||||
if (!nativeEvent || nativeEvent._normalized) {
|
||||
return nativeEvent;
|
||||
}
|
||||
const eventType = nativeEvent.type || '';
|
||||
const mouse = eventType.indexOf('mouse') >= 0;
|
||||
return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent);
|
||||
if (mouse) {
|
||||
return normalizeMouseEvent(nativeEvent);
|
||||
} else {
|
||||
return normalizeTouchEvent(nativeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
export default normalizeNativeEvent;
|
||||
|
||||
Reference in New Issue
Block a user