Add additional event API responder surfaces (#15248)

* Add rest of event modules + small fixes
This commit is contained in:
Dominic Gannaway 2019-03-29 10:31:18 -07:00 committed by GitHub
parent 700f17be67
commit a41b217708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 659 additions and 14 deletions

View File

@ -9,6 +9,7 @@
import SyntheticEvent from 'events/SyntheticEvent';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import type {ReactEventResponderEventType} from 'shared/ReactTypes';
export type EventResponderContext = {
event: AnyNativeEvent,
@ -30,8 +31,12 @@ export type EventResponderContext = {
isTargetOwned: EventTarget => boolean,
isTargetWithinEventComponent: EventTarget => boolean,
isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
addRootEventTypes: (rootEventTypes: Array<string>) => void,
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
addRootEventTypes: (
rootEventTypes: Array<ReactEventResponderEventType>,
) => void,
removeRootEventTypes: (
rootEventTypes: Array<ReactEventResponderEventType>,
) => void,
requestOwnership: (target: EventTarget | null) => boolean,
releaseOwnership: (target: EventTarget | null) => boolean,
};

View File

@ -44,6 +44,7 @@ const targetEventTypeCached: Map<
Array<ReactEventResponderEventType>,
Set<DOMTopLevelEventType>,
> = new Map();
const targetOwnership: Map<EventTarget, Fiber> = new Map();
type EventListener = (event: SyntheticEvent) => void;
@ -204,16 +205,37 @@ DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
// TODO
};
DOMEventResponderContext.prototype.isTargetOwned = function() {
// TODO
DOMEventResponderContext.prototype.isTargetOwned = function(
targetElement: Element | Node,
): boolean {
const targetDoc = targetElement.ownerDocument;
return targetOwnership.has(targetDoc);
};
DOMEventResponderContext.prototype.requestOwnership = function() {
// TODO
DOMEventResponderContext.prototype.requestOwnership = function(
targetElement: Element | Node,
): boolean {
const targetDoc = targetElement.ownerDocument;
if (targetOwnership.has(targetDoc)) {
return false;
}
targetOwnership.set(targetDoc, this._fiber);
return true;
};
DOMEventResponderContext.prototype.releaseOwnership = function() {
// TODO
DOMEventResponderContext.prototype.releaseOwnership = function(
targetElement: Element | Node,
): boolean {
const targetDoc = targetElement.ownerDocument;
if (!targetOwnership.has(targetDoc)) {
return false;
}
const owner = targetOwnership.get(targetDoc);
if (owner === this._fiber || owner === this._fiber.alternate) {
targetOwnership.delete(targetDoc);
return true;
}
return false;
};
function getTargetEventTypes(

14
packages/react-events/drag.js vendored Normal file
View File

@ -0,0 +1,14 @@
/**
* 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.
*
* @flow
*/
'use strict';
const Drag = require('./src/Drag');
module.exports = Drag.default || Drag;

14
packages/react-events/focus.js vendored Normal file
View File

@ -0,0 +1,14 @@
/**
* 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.
*
* @flow
*/
'use strict';
const Focus = require('./src/Focus');
module.exports = Focus.default || Focus;

7
packages/react-events/npm/drag.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-drag.production.min.js');
} else {
module.exports = require('./cjs/react-events-drag.development.js');
}

7
packages/react-events/npm/focus.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-focus.production.min.js');
} else {
module.exports = require('./cjs/react-events-focus.development.js');
}

7
packages/react-events/npm/swipe.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-events-swipe.production.min.js');
} else {
module.exports = require('./cjs/react-events-swipe.development.js');
}

View File

@ -12,6 +12,11 @@
"LICENSE",
"README.md",
"press.js",
"hover.js",
"focus.js",
"swipe.js",
"drag.js",
"index.js",
"build-info.json",
"cjs/",
"umd/"

187
packages/react-events/src/Drag.js vendored Normal file
View File

@ -0,0 +1,187 @@
/**
* 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.
*
* @flow
*/
import type {EventResponderContext} from 'events/EventTypes';
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
const targetEventTypes = ['pointerdown', 'pointercancel'];
const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
type DragState = {
dragTarget: null | EventTarget,
isPointerDown: boolean,
isDragging: boolean,
startX: number,
startY: number,
x: number,
y: number,
};
// In the case we don't have PointerEvents (Safari), we listen to touch events
// too
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
rootEventTypes.push('mouseup', 'mousemove', {
name: 'touchmove',
passive: false,
});
}
function dispatchDragEvent(
context: EventResponderContext,
name: string,
listener: (e: Object) => void,
state: DragState,
discrete: boolean,
eventData?: {
diffX: number,
diffY: number,
},
): void {
context.dispatchEvent(name, listener, state.dragTarget, discrete, eventData);
}
const DragResponder = {
targetEventTypes,
createInitialState(): DragState {
return {
dragTarget: null,
isPointerDown: false,
isDragging: false,
startX: 0,
startY: 0,
x: 0,
y: 0,
};
},
handleEvent(
context: EventResponderContext,
props: Object,
state: DragState,
): void {
const {eventTarget, eventType, event} = context;
switch (eventType) {
case 'touchstart':
case 'mousedown':
case 'pointerdown': {
if (!state.isDragging) {
const obj =
eventType === 'touchstart' ? (event: any).changedTouches[0] : event;
const x = (state.startX = (obj: any).screenX);
const y = (state.startY = (obj: any).screenY);
state.x = x;
state.y = y;
state.dragTarget = eventTarget;
state.isPointerDown = true;
context.addRootEventTypes(rootEventTypes);
}
break;
}
case 'touchmove':
case 'mousemove':
case 'pointermove': {
if (context.isPassive()) {
return;
}
if (state.isPointerDown) {
const obj =
eventType === 'touchmove' ? (event: any).changedTouches[0] : event;
const x = (obj: any).screenX;
const y = (obj: any).screenY;
state.x = x;
state.y = y;
if (!state.isDragging && x !== state.startX && y !== state.startY) {
let shouldEnableDragging = true;
if (
props.onShouldClaimOwnership &&
props.onShouldClaimOwnership()
) {
shouldEnableDragging = context.requestOwnership(state.dragTarget);
}
if (shouldEnableDragging) {
state.isDragging = true;
if (props.onDragChange) {
const dragChangeEventListener = () => {
props.onDragChange(true);
};
context.dispatchEvent(
'dragchange',
dragChangeEventListener,
state.dragTarget,
true,
);
}
} else {
state.dragTarget = null;
state.isPointerDown = false;
context.removeRootEventTypes(rootEventTypes);
}
} else {
if (props.onDragMove) {
const eventData = {
diffX: x - state.startX,
diffY: y - state.startY,
};
dispatchDragEvent(
context,
'dragmove',
props.onDragMove,
state,
false,
eventData,
);
}
(event: any).preventDefault();
}
}
break;
}
case 'pointercancel':
case 'touchcancel':
case 'touchend':
case 'mouseup':
case 'pointerup': {
if (state.isDragging) {
if (props.onShouldClaimOwnership) {
context.releaseOwnership(state.dragTarget);
}
if (props.onDragEnd) {
dispatchDragEvent(context, 'dragend', props.onDragEnd, state, true);
}
if (props.onDragChange) {
const dragChangeEventListener = () => {
props.onDragChange(false);
};
context.dispatchEvent(
'dragchange',
dragChangeEventListener,
state.dragTarget,
true,
);
}
state.isDragging = false;
}
if (state.isPointerDown) {
state.dragTarget = null;
state.isPointerDown = false;
context.removeRootEventTypes(rootEventTypes);
}
break;
}
}
},
};
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
props: null,
responder: DragResponder,
};

101
packages/react-events/src/Focus.js vendored Normal file
View File

@ -0,0 +1,101 @@
/**
* 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.
*
* @flow
*/
import type {EventResponderContext} from 'events/EventTypes';
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
const targetEventTypes = [
{name: 'focus', passive: true, capture: true},
{name: 'blur', passive: true, capture: true},
];
type FocusState = {
isFocused: boolean,
};
function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
const {event, eventTarget} = context;
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
return;
}
if (props.onFocus) {
context.dispatchEvent('focus', props.onFocus, eventTarget, true);
}
if (props.onFocusChange) {
const focusChangeEventListener = () => {
props.onFocusChange(true);
};
context.dispatchEvent(
'focuschange',
focusChangeEventListener,
eventTarget,
true,
);
}
}
function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
const {event, eventTarget} = context;
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
return;
}
if (props.onBlur) {
context.dispatchEvent('blur', props.onBlur, eventTarget, true);
}
if (props.onFocusChange) {
const focusChangeEventListener = () => {
props.onFocusChange(false);
};
context.dispatchEvent(
'focuschange',
focusChangeEventListener,
eventTarget,
true,
);
}
}
const FocusResponder = {
targetEventTypes,
createInitialState(): FocusState {
return {
isFocused: false,
};
},
handleEvent(
context: EventResponderContext,
props: Object,
state: FocusState,
): void {
const {eventTarget, eventType} = context;
switch (eventType) {
case 'focus': {
if (!state.isFocused && !context.isTargetOwned(eventTarget)) {
dispatchFocusInEvents(context, props);
state.isFocused = true;
}
break;
}
case 'blur': {
if (state.isFocused) {
dispatchFocusOutEvents(context, props);
state.isFocused = false;
}
break;
}
}
},
};
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
props: null,
responder: FocusResponder,
};

View File

@ -17,13 +17,13 @@ const targetEventTypes = [
'pointercancel',
'contextmenu',
];
const rootEventTypes = ['pointerup', 'scroll'];
const rootEventTypes = [{name: 'pointerup', passive: false}, 'scroll'];
// In the case we don't have PointerEvents (Safari), we listen to touch events
// too
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
rootEventTypes.push('mouseup');
rootEventTypes.push({name: 'mouseup', passive: false});
}
type PressState = {
@ -169,11 +169,11 @@ const PressResponder = {
// Android can show previous of anchor tags that requires working
// with click rather than touch events (and mouse down/up).
if (!isAnchorTagElement(eventTarget)) {
keyPressEventListener = (e, key) => {
keyPressEventListener = e => {
if (!e.isDefaultPrevented() && !e.nativeEvent.defaultPrevented) {
e.preventDefault();
state.defaultPrevented = true;
props.onPress(e, key);
props.onPress(e);
}
};
}
@ -279,8 +279,8 @@ const PressResponder = {
props.onPress &&
!(state.isLongPressed && props.longPressCancelsPress)
) {
const pressEventListener = (e, key) => {
props.onPress(e, key);
const pressEventListener = e => {
props.onPress(e);
if (e.nativeEvent.defaultPrevented) {
state.defaultPrevented = true;
}

217
packages/react-events/src/Swipe.js vendored Normal file
View File

@ -0,0 +1,217 @@
/**
* 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.
*
* @flow
*/
import type {EventResponderContext} from 'events/EventTypes';
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
const targetEventTypes = ['pointerdown', 'pointercancel'];
const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
// In the case we don't have PointerEvents (Safari), we listen to touch events
// too
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
rootEventTypes.push('mouseup', 'mousemove', {
name: 'touchmove',
passive: false,
});
}
function dispatchSwipeEvent(
context: EventResponderContext,
name: string,
listener: (e: Object) => void,
state: SwipeState,
discrete: boolean,
eventData?: {
diffX: number,
diffY: number,
},
) {
context.dispatchEvent(name, listener, state.swipeTarget, discrete, eventData);
}
type SwipeState = {
direction: number,
isSwiping: boolean,
lastDirection: number,
startX: number,
startY: number,
touchId: null | number,
swipeTarget: null | EventTarget,
x: number,
y: number,
};
const SwipeResponder = {
targetEventTypes,
createInitialState(): SwipeState {
return {
direction: 0,
isSwiping: false,
lastDirection: 0,
startX: 0,
startY: 0,
touchId: null,
swipeTarget: null,
x: 0,
y: 0,
};
},
handleEvent(
context: EventResponderContext,
props: Object,
state: SwipeState,
): void {
const {eventTarget, eventType, event} = context;
switch (eventType) {
case 'touchstart':
case 'mousedown':
case 'pointerdown': {
if (!state.isSwiping && !context.isTargetOwned(eventTarget)) {
let obj = event;
if (eventType === 'touchstart') {
obj = (event: any).targetTouches[0];
state.touchId = obj.identifier;
}
const x = (obj: any).screenX;
const y = (obj: any).screenY;
let shouldEnableSwiping = true;
if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) {
shouldEnableSwiping = context.requestOwnership(eventTarget);
}
if (shouldEnableSwiping) {
state.isSwiping = true;
state.startX = x;
state.startY = y;
state.x = x;
state.y = y;
state.swipeTarget = eventTarget;
context.addRootEventTypes(rootEventTypes);
} else {
state.touchId = null;
}
}
break;
}
case 'touchmove':
case 'mousemove':
case 'pointermove': {
if (context.isPassive()) {
return;
}
if (state.isSwiping) {
let obj = null;
if (eventType === 'touchmove') {
const targetTouches = (event: any).targetTouches;
for (let i = 0; i < targetTouches.length; i++) {
if (state.touchId === targetTouches[i].identifier) {
obj = targetTouches[i];
break;
}
}
} else {
obj = event;
}
if (obj === null) {
state.isSwiping = false;
state.swipeTarget = null;
state.touchId = null;
context.removeRootEventTypes(rootEventTypes);
return;
}
const x = (obj: any).screenX;
const y = (obj: any).screenY;
if (x < state.x && props.onSwipeLeft) {
state.direction = 3;
} else if (x > state.x && props.onSwipeRight) {
state.direction = 1;
}
state.x = x;
state.y = y;
if (props.onSwipeMove) {
const eventData = {
diffX: x - state.startX,
diffY: y - state.startY,
};
dispatchSwipeEvent(
context,
'swipemove',
props.onSwipeMove,
state,
false,
eventData,
);
(event: any).preventDefault();
}
}
break;
}
case 'pointercancel':
case 'touchcancel':
case 'touchend':
case 'mouseup':
case 'pointerup': {
if (state.isSwiping) {
if (state.x === state.startX && state.y === state.startY) {
return;
}
if (props.onShouldClaimOwnership) {
context.releaseOwnership(state.swipeTarget);
}
const direction = state.direction;
const lastDirection = state.lastDirection;
if (direction !== lastDirection) {
if (props.onSwipeLeft && direction === 3) {
dispatchSwipeEvent(
context,
'swipeleft',
props.onSwipeLeft,
state,
true,
);
} else if (props.onSwipeRight && direction === 1) {
dispatchSwipeEvent(
context,
'swiperight',
props.onSwipeRight,
state,
true,
);
}
}
if (props.onSwipeEnd) {
dispatchSwipeEvent(
context,
'swipeend',
props.onSwipeEnd,
state,
true,
);
}
state.lastDirection = direction;
state.isSwiping = false;
state.swipeTarget = null;
state.touchId = null;
context.removeRootEventTypes(rootEventTypes);
}
break;
}
}
},
};
export default {
$$typeof: REACT_EVENT_COMPONENT_TYPE,
props: null,
responder: SwipeResponder,
};

14
packages/react-events/swipe.js vendored Normal file
View File

@ -0,0 +1,14 @@
/**
* 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.
*
* @flow
*/
'use strict';
const Swipe = require('./src/Swipe');
module.exports = Swipe.default || Swipe;

View File

@ -507,6 +507,51 @@ const bundles = [
global: 'ReactEventsHover',
externals: [],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-events/focus',
global: 'ReactEventsFocus',
externals: [],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-events/swipe',
global: 'ReactEventsSwipe',
externals: [],
},
{
bundleTypes: [
UMD_DEV,
UMD_PROD,
NODE_DEV,
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
],
moduleType: NON_FIBER_RENDERER,
entry: 'react-events/drag',
global: 'ReactEventsDrag',
externals: [],
},
];
// Based on deep-freeze by substack (public domain)