[Flare] Bring Flare support to React Native Fabric (#15887)

This commit is contained in:
Dominic Gannaway
2019-06-28 01:22:32 +01:00
committed by GitHub
parent 9b0bd43550
commit eb2ace1281
13 changed files with 1680 additions and 27 deletions
+3 -3
View File
@@ -428,19 +428,19 @@ export function unhideTextInstance(textInstance, text): void {
}
export function mountEventComponent(
eventComponentInstance: ReactEventComponentInstance,
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
) {
throw new Error('Not yet implemented.');
}
export function updateEventComponent(
eventComponentInstance: ReactEventComponentInstance,
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
) {
throw new Error('Not yet implemented.');
}
export function unmountEventComponent(
eventComponentInstance: ReactEventComponentInstance,
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
): void {
throw new Error('Not yet implemented.');
}
+1 -1
View File
@@ -32,7 +32,6 @@ import {
setEnabled as ReactBrowserEventEmitterSetEnabled,
} from '../events/ReactBrowserEventEmitter';
import {getChildNamespace} from '../shared/DOMNamespaces';
import {addRootEventTypesForComponentInstance} from '../events/DOMEventResponderSystem';
import {
ELEMENT_NODE,
TEXT_NODE,
@@ -48,6 +47,7 @@ import type {
ReactDOMEventComponentInstance,
} from 'shared/ReactDOMTypes';
import {
addRootEventTypesForComponentInstance,
mountEventResponder,
unmountEventResponder,
} from '../events/DOMEventResponderSystem';
+1 -1
View File
@@ -231,7 +231,7 @@ const eventResponderContext: ReactDOMResponderContext = {
}
return false;
},
isTargetWithinElement(
isTargetWithinNode(
childTarget: Element | Document,
parentTarget: Element | Document,
): boolean {
+1 -1
View File
@@ -332,7 +332,7 @@ export function dispatchEvent(
targetInst,
);
} else {
// Responder event system (experimental event API)
// React Flare event system
dispatchEventForResponderEventSystem(
topLevelType,
targetInst,
+1 -1
View File
@@ -121,7 +121,7 @@ Component instance.
Returns `true` if the instance has taken ownership of the responder.
### isTargetWithinElement(target: Element, element: Element): boolean
### isTargetWithinNode(target: Element, element: Element): boolean
Returns `true` if `target` is a child of `element`.
+3 -3
View File
@@ -823,7 +823,7 @@ const PressResponder: ReactDOMEventResponder = {
if (
state.pressTarget !== null &&
(pointerType !== 'mouse' ||
!context.isTargetWithinElement(target, state.pressTarget))
!context.isTargetWithinNode(target, state.pressTarget))
) {
// Calculate the responder region we use for deactivation, as the
// element dimensions may have changed since activation.
@@ -936,7 +936,7 @@ const PressResponder: ReactDOMEventResponder = {
!isKeyboardEvent &&
state.pressTarget !== null &&
(pointerType !== 'mouse' ||
!context.isTargetWithinElement(target, state.pressTarget))
!context.isTargetWithinNode(target, state.pressTarget))
) {
// If the event target isn't within the press target, check if we're still
// within the responder region. The region may have changed if the
@@ -993,7 +993,7 @@ const PressResponder: ReactDOMEventResponder = {
if (
pressTarget !== null &&
(scrollTarget === doc ||
context.isTargetWithinElement(pressTarget, scrollTarget))
context.isTargetWithinNode(pressTarget, scrollTarget))
) {
dispatchCancel(event, context, props, state);
}
+683
View File
@@ -0,0 +1,683 @@
/**
* 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 {
ReactNativeResponderEvent,
ReactNativeResponderContext,
ReactNativeEventResponderEventType,
ReactNativeEventTarget,
PointerType,
ReactFaricEventTouch,
EventPriority,
} from 'react-native-renderer/src/ReactNativeTypes';
import type {ReactEventResponder} from 'shared/ReactTypes';
import React from 'react';
import {
DiscreteEvent,
UserBlockingEvent,
} from 'react-native-renderer/src/ReactNativeTypes';
type ReactNativeEventResponder = ReactEventResponder<
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
>;
type PressProps = {
disabled: boolean,
delayLongPress: number,
delayPressEnd: number,
delayPressStart: number,
onContextMenu: (e: PressEvent) => void,
onLongPress: (e: PressEvent) => void,
onLongPressChange: boolean => void,
onLongPressShouldCancelPress: () => boolean,
onPress: (e: PressEvent) => void,
onPressChange: boolean => void,
onPressEnd: (e: PressEvent) => void,
onPressMove: (e: PressEvent) => void,
onPressStart: (e: PressEvent) => void,
pressRetentionOffset: {
top: number,
right: number,
bottom: number,
left: number,
},
};
type PressEvent = {|
target: ReactNativeEventTarget,
type: PressEventType,
pointerType: PointerType,
timeStamp: number,
locationX: null | number,
locationY: null | number,
pageX: null | number,
pageY: null | number,
screenX: null | number,
screenY: null | number,
force: null | number,
|};
type PressState = {
activationPosition: null | $ReadOnly<{|
x: number,
y: number,
|}>,
addedRootEvents: boolean,
isActivePressed: boolean,
isActivePressStart: boolean,
isLongPressed: boolean,
isPressed: boolean,
isPressWithinResponderRegion: boolean,
longPressTimeout: null | number,
pointerType: PointerType,
pressTarget: null | ReactNativeEventTarget,
pressEndTimeout: null | number,
pressStartTimeout: null | number,
responderRegionOnActivation: null | $ReadOnly<{|
bottom: number,
left: number,
right: number,
top: number,
|}>,
responderRegionOnDeactivation: null | $ReadOnly<{|
bottom: number,
left: number,
right: number,
top: number,
|}>,
activePointerId: null | number,
touchEvent: null | ReactFaricEventTouch,
};
type PressEventType =
| 'press'
| 'pressmove'
| 'pressstart'
| 'pressend'
| 'presschange'
| 'longpress'
| 'longpresschange';
const DEFAULT_PRESS_END_DELAY_MS = 0;
const DEFAULT_PRESS_START_DELAY_MS = 0;
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
const DEFAULT_PRESS_RETENTION_OFFSET = {
bottom: 20,
top: 20,
left: 20,
right: 20,
};
const targetEventTypes = ['topTouchStart'];
const rootEventTypes = ['topTouchMove', 'topTouchEnd', 'topTouchCancel'];
function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
const maybeNumber = delay == null ? null : delay;
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
}
function createPressEvent(
context: ReactNativeResponderContext,
type: PressEventType,
target: ReactNativeEventTarget,
pointerType: PointerType,
touchEvent: null | ReactFaricEventTouch,
): PressEvent {
let timestamp = context.getTimeStamp();
let locationX = null;
let locationY = null;
let pageX = null;
let pageY = null;
let screenX = null;
let screenY = null;
let force = null;
if (touchEvent) {
({
force,
pageX,
pageY,
screenX,
screenY,
timestamp,
locationX,
locationY,
} = touchEvent);
}
return {
target,
type,
pointerType,
timeStamp: timestamp,
pageX,
pageY,
screenX,
screenY,
locationX,
locationY,
force,
};
}
function dispatchEvent(
event: ?ReactNativeResponderEvent,
context: ReactNativeResponderContext,
state: PressState,
name: PressEventType,
listener: (e: Object) => void,
eventPriority: EventPriority,
): void {
const target = ((state.pressTarget: any): ReactNativeEventTarget);
const pointerType = state.pointerType;
const syntheticEvent = createPressEvent(
context,
name,
target,
pointerType,
state.touchEvent,
);
context.dispatchEvent(syntheticEvent, listener, eventPriority);
}
function dispatchCancel(event, context, props, state): void {
state.touchEvent = null;
if (state.isPressed) {
dispatchPressEndEvents(event, context, props, state);
}
removeRootEventTypes(context, state);
}
function addRootEventTypes(context, state): void {
if (!state.addedRootEvents) {
state.addedRootEvents = true;
context.addRootEventTypes(rootEventTypes);
}
}
function removeRootEventTypes(context, state): void {
if (state.addedRootEvents) {
state.addedRootEvents = false;
context.removeRootEventTypes(rootEventTypes);
}
}
function getTouchFromPressEvent(nativeEvent): ReactFaricEventTouch | null {
const targetTouches = nativeEvent.targetTouches;
if (targetTouches.length > 0) {
return targetTouches[0];
}
return null;
}
function getTouchById(nativeEvent, pointerId): null | ReactFaricEventTouch {
const changedTouches = nativeEvent.changedTouches;
for (let i = 0; i < changedTouches.length; i++) {
const touch = changedTouches[i];
if (touch.identifier === pointerId) {
return touch;
}
}
return null;
}
function calculateResponderRegion(
context: ReactNativeResponderContext,
target: ReactNativeEventTarget,
props: PressProps,
cb: ({|
bottom: number,
top: number,
left: number,
right: number,
|}) => void,
) {
const pressRetentionOffset = Object.assign(
{},
DEFAULT_PRESS_RETENTION_OFFSET,
props.pressRetentionOffset,
);
context.getTargetBoundingRect(target, rect => {
let {left, right, bottom, top} = rect;
if (pressRetentionOffset) {
if (pressRetentionOffset.bottom != null) {
bottom += pressRetentionOffset.bottom;
}
if (pressRetentionOffset.left != null) {
left -= pressRetentionOffset.left;
}
if (pressRetentionOffset.right != null) {
right += pressRetentionOffset.right;
}
if (pressRetentionOffset.top != null) {
top -= pressRetentionOffset.top;
}
}
cb({
bottom,
top,
left,
right,
});
});
}
function dispatchPressChangeEvent(event, context, props, state): void {
const bool = state.isActivePressed;
const listener = () => {
props.onPressChange(bool);
};
dispatchEvent(event, context, state, 'presschange', listener, DiscreteEvent);
}
function dispatchLongPressChangeEvent(event, context, props, state): void {
const bool = state.isLongPressed;
const listener = () => {
props.onLongPressChange(bool);
};
dispatchEvent(
event,
context,
state,
'longpresschange',
listener,
DiscreteEvent,
);
}
function activate(event: ReactNativeResponderEvent, context, props, state) {
const {pageX: x, pageY: y} = ((state.touchEvent: any): ReactFaricEventTouch);
const wasActivePressed = state.isActivePressed;
state.isActivePressed = true;
if (x !== null && y !== null) {
state.activationPosition = {x, y};
}
if (props.onPressStart) {
dispatchEvent(
event,
context,
state,
'pressstart',
props.onPressStart,
DiscreteEvent,
);
}
if (!wasActivePressed && props.onPressChange) {
dispatchPressChangeEvent(event, context, props, state);
}
}
function deactivate(event, context, props, state) {
const wasLongPressed = state.isLongPressed;
state.isActivePressed = false;
state.isLongPressed = false;
if (props.onPressEnd) {
dispatchEvent(
event,
context,
state,
'pressend',
props.onPressEnd,
DiscreteEvent,
);
}
if (props.onPressChange) {
dispatchPressChangeEvent(event, context, props, state);
}
if (wasLongPressed && props.onLongPressChange) {
dispatchLongPressChangeEvent(event, context, props, state);
}
}
function validateRegion(touchEvent, state) {
const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
let left, top, right, bottom;
if (responderRegionOnActivation != null) {
left = responderRegionOnActivation.left;
top = responderRegionOnActivation.top;
right = responderRegionOnActivation.right;
bottom = responderRegionOnActivation.bottom;
if (responderRegionOnDeactivation != null) {
left = Math.min(left, responderRegionOnDeactivation.left);
top = Math.min(top, responderRegionOnDeactivation.top);
right = Math.max(right, responderRegionOnDeactivation.right);
bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
}
}
const {pageX: x, pageY: y} = touchEvent;
state.isPressWithinResponderRegion =
left != null &&
right != null &&
top != null &&
bottom != null &&
x !== null &&
y !== null &&
(x >= left && x <= right && y >= top && y <= bottom);
}
function updateIsPressWithinResponderRegion(
target,
touchEvent,
context,
props,
state,
cb,
): void {
const pressTarget = state.pressTarget;
if (pressTarget != null) {
// Calculate the responder region we use for deactivation if not
// already done during move event.
if (state.responderRegionOnDeactivation == null) {
calculateResponderRegion(context, pressTarget, props, obj => {
state.responderRegionOnDeactivation = obj;
validateRegion(touchEvent, state);
cb();
});
return;
} else {
validateRegion(touchEvent, state);
}
} else {
state.isPressWithinResponderRegion = true;
}
cb();
}
function dispatchPressStartEvents(event, context, props, state) {
state.isPressed = true;
if (state.pressEndTimeout !== null) {
context.clearTimeout(state.pressEndTimeout);
state.pressEndTimeout = null;
}
const dispatch = () => {
state.isActivePressStart = true;
activate(event, context, props, state);
if (
(props.onLongPress || props.onLongPressChange) &&
!state.isLongPressed
) {
const delayLongPress = calculateDelayMS(
props.delayLongPress,
10,
DEFAULT_LONG_PRESS_DELAY_MS,
);
state.longPressTimeout = context.setTimeout(() => {
state.isLongPressed = true;
state.longPressTimeout = null;
if (props.onLongPress) {
dispatchEvent(
event,
context,
state,
'longpress',
props.onLongPress,
DiscreteEvent,
);
}
if (props.onLongPressChange) {
dispatchLongPressChangeEvent(event, context, props, state);
}
}, delayLongPress);
}
};
if (!state.isActivePressStart) {
const delayPressStart = calculateDelayMS(
props.delayPressStart,
0,
DEFAULT_PRESS_START_DELAY_MS,
);
if (delayPressStart > 0) {
state.pressStartTimeout = context.setTimeout(() => {
state.pressStartTimeout = null;
dispatch();
}, delayPressStart);
} else {
dispatch();
}
}
}
function dispatchPressEndEvents(event, context, props, state): void {
const wasActivePressStart = state.isActivePressStart;
let activationWasForced = false;
state.isActivePressStart = false;
state.isPressed = false;
if (state.longPressTimeout !== null) {
context.clearTimeout(state.longPressTimeout);
state.longPressTimeout = null;
}
if (!wasActivePressStart && state.pressStartTimeout !== null) {
context.clearTimeout(state.pressStartTimeout);
state.pressStartTimeout = null;
// don't activate if a press has moved beyond the responder region
if (state.isPressWithinResponderRegion && event != null) {
// if we haven't yet activated (due to delays), activate now
activate(event, context, props, state);
activationWasForced = true;
}
}
if (state.isActivePressed) {
const delayPressEnd = calculateDelayMS(
props.delayPressEnd,
// if activation and deactivation occur during the same event there's no
// time for visual user feedback therefore a small delay is added before
// deactivating.
activationWasForced ? 10 : 0,
DEFAULT_PRESS_END_DELAY_MS,
);
if (delayPressEnd > 0) {
state.pressEndTimeout = context.setTimeout(() => {
state.pressEndTimeout = null;
deactivate(event, context, props, state);
}, delayPressEnd);
} else {
deactivate(event, context, props, state);
}
}
state.responderRegionOnDeactivation = null;
}
const PressResponder: ReactNativeEventResponder = {
displayName: 'Press',
targetEventTypes,
allowEventHooks: true,
allowMultipleHostChildren: false,
createInitialState(): PressState {
return {
activationPosition: null,
activePointerId: null,
addedRootEvents: false,
isActivePressed: false,
isActivePressStart: false,
isLongPressed: false,
isPressed: false,
isPressWithinResponderRegion: false,
longPressTimeout: null,
pointerType: '',
pressEndTimeout: null,
pressStartTimeout: null,
pressTarget: null,
responderRegionOnActivation: null,
responderRegionOnDeactivation: null,
touchEvent: null,
};
},
onEvent(
event: ReactNativeResponderEvent,
context: ReactNativeResponderContext,
props: PressProps,
state: PressState,
): void {
const {nativeEvent, type} = event;
if (type === 'topTouchStart') {
if (!state.isPressed) {
state.pointerType = 'touch';
const pressTarget = (state.pressTarget = context.getEventCurrentTarget(
event,
));
const touchEvent = getTouchFromPressEvent(nativeEvent);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
state.activePointerId = touchEvent.identifier;
calculateResponderRegion(
context,
((pressTarget: any): ReactNativeEventTarget),
props,
obj => {
state.responderRegionOnActivation = obj;
},
);
state.responderRegionOnDeactivation = null;
state.isPressWithinResponderRegion = true;
dispatchPressStartEvents(event, context, props, state);
addRootEventTypes(context, state);
}
}
},
onRootEvent(
event: ReactNativeResponderEvent,
context: ReactNativeResponderContext,
props: PressProps,
state: PressState,
): void {
const {nativeEvent, target, type} = event;
const activePointerId = state.activePointerId;
switch (type) {
case 'topTouchMove': {
const touchEvent = getTouchById(nativeEvent, activePointerId);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
updateIsPressWithinResponderRegion(
target,
touchEvent,
context,
props,
state,
() => {
if (state.isPressWithinResponderRegion) {
if (state.isPressed) {
if (props.onPressMove) {
dispatchEvent(
event,
context,
state,
'pressmove',
props.onPressMove,
UserBlockingEvent,
);
}
if (
state.activationPosition != null &&
state.longPressTimeout != null
) {
const deltaX = state.activationPosition.x - touchEvent.pageX;
const deltaY = state.activationPosition.y - touchEvent.pageY;
if (
Math.hypot(deltaX, deltaY) > 10 &&
state.longPressTimeout != null
) {
context.clearTimeout(state.longPressTimeout);
}
}
} else {
dispatchPressStartEvents(event, context, props, state);
}
} else {
dispatchPressEndEvents(event, context, props, state);
}
},
);
break;
}
case 'topTouchEnd': {
if (state.isPressed) {
const touchEvent = getTouchById(nativeEvent, activePointerId);
if (touchEvent === null) {
return;
}
state.touchEvent = touchEvent;
const wasLongPressed = state.isLongPressed;
dispatchPressEndEvents(event, context, props, state);
if (state.pressTarget !== null && props.onPress) {
// If the event target isn't within the press target, check if we're still
// within the responder region. The region may have changed if the
// element's layout was modified after activation.
updateIsPressWithinResponderRegion(
target,
touchEvent,
context,
props,
state,
() => {
if (state.isPressWithinResponderRegion) {
if (
!(
wasLongPressed &&
props.onLongPressShouldCancelPress &&
props.onLongPressShouldCancelPress()
)
) {
dispatchEvent(
event,
context,
state,
'press',
props.onPress,
DiscreteEvent,
);
}
}
},
);
}
}
state.touchEvent = null;
removeRootEventTypes(context, state);
break;
}
case 'topTouchCancel': {
dispatchCancel(event, context, props, state);
}
}
},
};
export const Press = React.unstable_createEvent(PressResponder);
export function usePress(props: PressProps): void {
React.unstable_useEvent(Press, props);
}
@@ -17,7 +17,9 @@ import {registrationNameModules} from 'events/EventPluginRegistry';
import {batchedUpdates} from 'events/ReactGenericBatching';
import type {AnyNativeEvent} from 'events/PluginModuleType';
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
import type {TopLevelType} from 'events/TopLevelEventTypes';
import {dispatchEventForResponderEventSystem} from './ReactFabricEventResponderSystem';
export {getListener, registrationNameModules as registrationNames};
@@ -27,7 +29,16 @@ export function dispatchEvent(
nativeEvent: AnyNativeEvent,
) {
const targetFiber = (target: null | Fiber);
if (enableFlareAPI) {
// React Flare event system
dispatchEventForResponderEventSystem(
(topLevelType: any),
target,
(nativeEvent: any),
);
}
batchedUpdates(function() {
// Heritage plugin event system
runExtractedPluginEventsInBatch(
topLevelType,
targetFiber,
@@ -0,0 +1,835 @@
/**
* 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 {EventComponent, HostComponent} from 'shared/ReactWorkTags';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import {
batchedEventUpdates,
discreteUpdates,
flushDiscreteUpdatesIfNeeded,
} from 'events/ReactGenericBatching';
import type {
ReactEventResponder,
ReactEventComponentInstance,
} from 'shared/ReactTypes';
import type {
ReactNativeEventResponderEventType,
ReactNativeResponderContext,
ReactNativeResponderEvent,
EventPriority,
ReactNativeEventTarget,
ReactFaricEvent,
} from './ReactNativeTypes';
import {
ContinuousEvent,
UserBlockingEvent,
DiscreteEvent,
} from './ReactNativeTypes';
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
import {enableUserBlockingEvents} from 'shared/ReactFeatureFlags';
import warning from 'shared/warning';
import invariant from 'shared/invariant';
// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
import * as Scheduler from 'scheduler';
const {
unstable_UserBlockingPriority: UserBlockingPriority,
unstable_runWithPriority: runWithPriority,
} = Scheduler;
type EventObjectType = $Shape<PartialEventObject>;
type PartialEventObject = {
target: ReactNativeEventTarget,
type: string,
};
type ResponderTimeout = {|
id: TimeoutID,
timers: Map<number, ResponderTimer>,
|};
type ResponderTimer = {|
isHook: boolean,
instance: ReactNativeEventComponentInstance,
func: () => void,
id: number,
timeStamp: number,
|};
type EventQueue = {
events: Array<EventObjectType>,
eventPriority: EventPriority,
};
type ReactNativeEventResponder = ReactEventResponder<
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
>;
type ReactNativeEventComponentInstance = ReactEventComponentInstance<
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
>;
const {measureInWindow} = nativeFabricUIManager;
const activeTimeouts: Map<number, ResponderTimeout> = new Map();
const rootEventTypesToEventComponentInstances: Map<
ReactNativeEventResponderEventType | string,
Set<ReactNativeEventComponentInstance>,
> = new Map();
const targetEventTypeCached: Map<
Array<ReactNativeEventResponderEventType>,
Set<ReactNativeEventResponderEventType>,
> = new Map();
const ownershipChangeListeners: Set<
ReactNativeEventComponentInstance,
> = new Set();
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const eventListeners:
| WeakMap
| Map<
$Shape<PartialEventObject>,
($Shape<PartialEventObject>) => void,
> = new PossiblyWeakMap();
let globalOwner = null;
let continueLocalPropagation = false;
let currentTimeStamp = 0;
let currentTimers = new Map();
let currentInstance: null | ReactNativeEventComponentInstance = null;
let currentEventQueue: null | EventQueue = null;
let currentTimerIDCounter = 0;
let currentlyInHook = false;
const eventResponderContext: ReactNativeResponderContext = {
dispatchEvent(
possibleEventObject: Object,
listener: ($Shape<PartialEventObject>) => void,
eventPriority: EventPriority,
): void {
validateResponderContext();
const {target, type, timeStamp} = possibleEventObject;
if (target == null || type == null || timeStamp == null) {
throw new Error(
'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.',
);
}
const showWarning = name => {
if (__DEV__) {
warning(
false,
'%s is not available on event objects created from event responder modules (React Flare). ' +
'Try wrapping in a conditional, i.e. `if (event.type !== "press") { event.%s }`',
name,
name,
);
}
};
possibleEventObject.preventDefault = () => {
if (__DEV__) {
showWarning('preventDefault()');
}
};
possibleEventObject.stopPropagation = () => {
if (__DEV__) {
showWarning('stopPropagation()');
}
};
possibleEventObject.isDefaultPrevented = () => {
if (__DEV__) {
showWarning('isDefaultPrevented()');
}
};
possibleEventObject.isPropagationStopped = () => {
if (__DEV__) {
showWarning('isPropagationStopped()');
}
};
// $FlowFixMe: we don't need value, Flow thinks we do
Object.defineProperty(possibleEventObject, 'nativeEvent', {
get() {
if (__DEV__) {
showWarning('nativeEvent');
}
},
});
const eventObject = ((possibleEventObject: any): $Shape<
PartialEventObject,
>);
const eventQueue = ((currentEventQueue: any): EventQueue);
eventQueue.eventPriority = eventPriority;
eventListeners.set(eventObject, listener);
eventQueue.events.push(eventObject);
},
isTargetWithinNode(
childTarget: ReactNativeEventTarget,
parentTarget: ReactNativeEventTarget,
) {
validateResponderContext();
const childFiber = getFiberFromTarget(childTarget);
const parentFiber = getFiberFromTarget(parentTarget);
let node = childFiber;
while (node !== null) {
if (node === parentFiber) {
return true;
}
node = node.return;
}
return false;
},
getEventCurrentTarget(
event: ReactNativeResponderEvent,
): ReactNativeEventTarget {
validateResponderContext();
const target = event.target;
let fiber = getFiberFromTarget(target);
let hostComponent = target;
const currentResponder = ((currentInstance: any): ReactNativeEventComponentInstance)
.responder;
while (fiber !== null) {
const stateNode = fiber.stateNode;
if (
fiber.tag === EventComponent &&
(stateNode === null || stateNode.responder === currentResponder)
) {
break;
}
if (fiber.tag === HostComponent) {
hostComponent = fiber.stateNode;
}
fiber = fiber.return;
}
return ((hostComponent: any): ReactNativeEventTarget);
},
getTargetBoundingRect(
target: ReactNativeEventTarget,
callback: ({
left: number,
right: number,
top: number,
bottom: number,
}) => void,
) {
measureInWindow(target.node, (x, y, width, height) => {
callback({
left: x,
right: x + width,
top: y,
bottom: y + height,
});
});
},
addRootEventTypes(
rootEventTypes: Array<ReactNativeEventResponderEventType>,
): void {
validateResponderContext();
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
const eventComponentInstance = ((currentInstance: any): ReactNativeEventComponentInstance);
registerRootEventType(rootEventType, eventComponentInstance);
}
},
removeRootEventTypes(
rootEventTypes: Array<ReactNativeEventResponderEventType>,
): void {
validateResponderContext();
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
let rootEventComponents = rootEventTypesToEventComponentInstances.get(
rootEventType,
);
let rootEventTypesSet = ((currentInstance: any): ReactNativeEventComponentInstance)
.rootEventTypes;
if (rootEventTypesSet !== null) {
rootEventTypesSet.delete(rootEventType);
}
if (rootEventComponents !== undefined) {
rootEventComponents.delete(
((currentInstance: any): ReactNativeEventComponentInstance),
);
}
}
},
setTimeout(func: () => void, delay): number {
validateResponderContext();
if (currentTimers === null) {
currentTimers = new Map();
}
let timeout = currentTimers.get(delay);
const timerId = currentTimerIDCounter++;
if (timeout === undefined) {
const timers = new Map();
const id = setTimeout(() => {
processTimers(timers, delay);
}, delay);
timeout = {
id,
timers,
};
currentTimers.set(delay, timeout);
}
timeout.timers.set(timerId, {
isHook: currentlyInHook,
instance: ((currentInstance: any): ReactNativeEventComponentInstance),
func,
id: timerId,
timeStamp: currentTimeStamp,
});
activeTimeouts.set(timerId, timeout);
return timerId;
},
clearTimeout(timerId: number): void {
validateResponderContext();
const timeout = activeTimeouts.get(timerId);
if (timeout !== undefined) {
const timers = timeout.timers;
timers.delete(timerId);
if (timers.size === 0) {
clearTimeout(timeout.id);
}
}
},
getTimeStamp(): number {
validateResponderContext();
return currentTimeStamp;
},
};
function getFiberFromTarget(
target: null | ReactNativeEventTarget,
): Fiber | null {
if (target === null) {
return null;
}
return ((target.canonical._internalInstanceHandle: any): Fiber);
}
function processTimers(
timers: Map<number, ResponderTimer>,
delay: number,
): void {
const timersArr = Array.from(timers.values());
currentEventQueue = createEventQueue();
try {
for (let i = 0; i < timersArr.length; i++) {
const {isHook, instance, func, id, timeStamp} = timersArr[i];
currentInstance = instance;
currentTimeStamp = timeStamp + delay;
currentlyInHook = isHook;
try {
func();
} finally {
activeTimeouts.delete(id);
}
}
processEventQueue();
} finally {
currentTimers = null;
currentInstance = null;
currentEventQueue = null;
currentTimeStamp = 0;
}
}
function createFabricResponderEvent(
topLevelType: ReactNativeEventResponderEventType,
nativeEvent: ReactFaricEvent,
target: null | ReactNativeEventTarget,
): ReactNativeResponderEvent {
const responderEvent = {
nativeEvent,
target,
type: topLevelType,
};
if (__DEV__) {
Object.freeze(responderEvent);
}
return responderEvent;
}
function validateResponderContext(): void {
invariant(
currentEventQueue && currentInstance,
'An event responder context was used outside of an event cycle. ' +
'Use context.setTimeout() to use asynchronous responder context outside of event cycle .',
);
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function createEventQueue(): EventQueue {
return {
events: [],
eventPriority: ContinuousEvent,
};
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function processEventQueue(): void {
const {events, eventPriority} = ((currentEventQueue: any): EventQueue);
if (events.length === 0) {
return;
}
switch (eventPriority) {
case DiscreteEvent: {
flushDiscreteUpdatesIfNeeded(currentTimeStamp);
discreteUpdates(() => {
batchedEventUpdates(processEvents, events);
});
break;
}
case UserBlockingEvent: {
if (enableUserBlockingEvents) {
runWithPriority(
UserBlockingPriority,
batchedEventUpdates.bind(null, processEvents, events),
);
} else {
batchedEventUpdates(processEvents, events);
}
break;
}
case ContinuousEvent: {
batchedEventUpdates(processEvents, events);
break;
}
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function triggerOwnershipListeners(): void {
const listeningInstances = Array.from(ownershipChangeListeners);
const previousInstance = currentInstance;
const previouslyInHook = currentlyInHook;
currentEventQueue = createEventQueue();
try {
for (let i = 0; i < listeningInstances.length; i++) {
const instance = listeningInstances[i];
const {isHook, props, responder, state} = instance;
currentInstance = instance;
currentlyInHook = isHook;
const onOwnershipChange = ((responder: any): ReactNativeEventResponder)
.onOwnershipChange;
if (onOwnershipChange !== undefined) {
onOwnershipChange(eventResponderContext, props, state);
}
}
processEventQueue();
} finally {
currentInstance = previousInstance;
currentlyInHook = previouslyInHook;
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function releaseOwnershipForEventComponentInstance(
eventComponentInstance: ReactNativeEventComponentInstance,
): boolean {
if (globalOwner === eventComponentInstance) {
globalOwner = null;
triggerOwnershipListeners();
return true;
}
return false;
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function processEvent(event: $Shape<PartialEventObject>): void {
const type = event.type;
const listener = ((eventListeners.get(event): any): (
$Shape<PartialEventObject>,
) => void);
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function processEvents(events: Array<EventObjectType>): void {
for (let i = 0, length = events.length; i < length; i++) {
processEvent(events[i]);
}
}
function getFabricTargetEventTypesSet(
eventTypes: Array<ReactNativeEventResponderEventType>,
): Set<ReactNativeEventResponderEventType> {
let cachedSet = targetEventTypeCached.get(eventTypes);
if (cachedSet === undefined) {
cachedSet = new Set();
for (let i = 0; i < eventTypes.length; i++) {
cachedSet.add(eventTypes[i]);
}
targetEventTypeCached.set(eventTypes, cachedSet);
}
return cachedSet;
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function getTargetEventResponderInstances(
topLevelType: ReactNativeEventResponderEventType,
targetFiber: null | Fiber,
): Array<ReactNativeEventComponentInstance> {
const eventResponderInstances = [];
let node = targetFiber;
while (node !== null) {
// Traverse up the fiber tree till we find event component fibers.
if (node.tag === EventComponent) {
const eventComponentInstance = node.stateNode;
const responder = eventComponentInstance.responder;
const targetEventTypes = responder.targetEventTypes;
// Validate the target event type exists on the responder
if (targetEventTypes !== undefined) {
const targetEventTypesSet = getFabricTargetEventTypesSet(
targetEventTypes,
);
if (targetEventTypesSet.has(topLevelType)) {
eventResponderInstances.push(eventComponentInstance);
}
}
}
node = node.return;
}
return eventResponderInstances;
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function shouldSkipEventComponent(
eventResponderInstance: ReactNativeEventComponentInstance,
responder: ReactNativeEventResponder,
propagatedEventResponders: null | Set<ReactNativeEventResponder>,
isHook: boolean,
): boolean {
if (propagatedEventResponders !== null && !isHook) {
if (propagatedEventResponders.has(responder)) {
return true;
}
propagatedEventResponders.add(responder);
}
if (globalOwner && globalOwner !== eventResponderInstance) {
return true;
}
return false;
}
function checkForLocalPropagationContinuation(
responder: ReactNativeEventResponder,
propagatedEventResponders: Set<ReactNativeEventResponder>,
): void {
if (continueLocalPropagation === true) {
propagatedEventResponders.delete(responder);
continueLocalPropagation = false;
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function getRootEventResponderInstances(
topLevelType: string,
): Array<ReactNativeEventComponentInstance> {
const eventResponderInstances = [];
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
topLevelType,
);
if (rootEventInstances !== undefined) {
const rootEventComponentInstances = Array.from(rootEventInstances);
for (let i = 0; i < rootEventComponentInstances.length; i++) {
const rootEventComponentInstance = rootEventComponentInstances[i];
eventResponderInstances.push(rootEventComponentInstance);
}
}
return eventResponderInstances;
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
function traverseAndHandleEventResponderInstances(
topLevelType: ReactNativeEventResponderEventType,
targetFiber: null | Fiber,
nativeEvent: ReactFaricEvent,
): void {
// Trigger event responders in this order:
// - Capture target phase
// - Bubble target phase
// - Root phase
const targetEventResponderInstances = getTargetEventResponderInstances(
topLevelType,
targetFiber,
);
const responderEvent = createFabricResponderEvent(
topLevelType,
nativeEvent,
targetFiber !== null
? ((targetFiber.stateNode: any): ReactNativeEventTarget)
: null,
);
const propagatedEventResponders: Set<ReactNativeEventResponder> = new Set();
let length = targetEventResponderInstances.length;
let i;
// Captured and bubbled event phases have the notion of local propagation.
// This means that the propgation chain can be stopped part of the the way
// through processing event component instances. The major difference to other
// events systems is that the stopping of propgation is localized to a single
// phase, rather than both phases.
if (length > 0) {
// Capture target phase
for (i = length; i-- > 0; ) {
const targetEventResponderInstance = targetEventResponderInstances[i];
const {isHook, responder, props, state} = targetEventResponderInstance;
const eventListener = ((responder: any): ReactNativeEventResponder)
.onEventCapture;
if (eventListener !== undefined) {
if (
shouldSkipEventComponent(
targetEventResponderInstance,
((responder: any): ReactNativeEventResponder),
propagatedEventResponders,
isHook,
)
) {
continue;
}
currentInstance = targetEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
if (!isHook) {
checkForLocalPropagationContinuation(
((responder: any): ReactNativeEventResponder),
propagatedEventResponders,
);
}
}
}
// We clean propagated event responders between phases.
propagatedEventResponders.clear();
// Bubble target phase
for (i = 0; i < length; i++) {
const targetEventResponderInstance = targetEventResponderInstances[i];
const {isHook, responder, props, state} = targetEventResponderInstance;
const eventListener = ((responder: any): ReactNativeEventResponder)
.onEvent;
if (eventListener !== undefined) {
if (
shouldSkipEventComponent(
targetEventResponderInstance,
((responder: any): ReactNativeEventResponder),
propagatedEventResponders,
isHook,
)
) {
continue;
}
currentInstance = targetEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
if (!isHook) {
checkForLocalPropagationContinuation(
((responder: any): ReactNativeEventResponder),
propagatedEventResponders,
);
}
}
}
}
// Root phase
const rootEventResponderInstances = getRootEventResponderInstances(
topLevelType,
);
length = rootEventResponderInstances.length;
if (length > 0) {
for (i = 0; i < length; i++) {
const rootEventResponderInstance = rootEventResponderInstances[i];
const {isHook, props, responder, state} = rootEventResponderInstance;
const eventListener = responder.onRootEvent;
if (eventListener !== undefined) {
if (
shouldSkipEventComponent(
rootEventResponderInstance,
responder,
null,
isHook,
)
) {
continue;
}
currentInstance = rootEventResponderInstance;
currentlyInHook = isHook;
eventListener(responderEvent, eventResponderContext, props, state);
}
}
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
export function dispatchEventForResponderEventSystem(
topLevelType: ReactNativeEventResponderEventType,
targetFiber: null | Fiber,
nativeEvent: ReactFaricEvent,
): void {
const previousEventQueue = currentEventQueue;
const previousInstance = currentInstance;
const previousTimers = currentTimers;
const previousTimeStamp = currentTimeStamp;
const previouslyInHook = currentlyInHook;
currentTimers = null;
currentEventQueue = createEventQueue();
// We might want to control timeStamp another way here
currentTimeStamp = Date.now();
try {
traverseAndHandleEventResponderInstances(
topLevelType,
targetFiber,
nativeEvent,
);
processEventQueue();
} finally {
currentTimers = previousTimers;
currentInstance = previousInstance;
currentEventQueue = previousEventQueue;
currentTimeStamp = previousTimeStamp;
currentlyInHook = previouslyInHook;
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
export function mountEventResponder(
eventComponentInstance: ReactNativeEventComponentInstance,
) {
const responder = ((eventComponentInstance.responder: any): ReactNativeEventResponder);
if (responder.onOwnershipChange !== undefined) {
ownershipChangeListeners.add(eventComponentInstance);
}
const onMount = responder.onMount;
if (onMount !== undefined) {
let {isHook, props, state} = eventComponentInstance;
currentEventQueue = createEventQueue();
currentInstance = eventComponentInstance;
currentlyInHook = isHook;
try {
onMount(eventResponderContext, props, state);
processEventQueue();
} finally {
currentEventQueue = null;
currentInstance = null;
currentTimers = null;
}
}
}
// TODO this function is almost an exact copy of the DOM version, we should
// somehow share the logic
export function unmountEventResponder(
eventComponentInstance: ReactNativeEventComponentInstance,
): void {
const responder = ((eventComponentInstance.responder: any): ReactNativeEventResponder);
const onUnmount = responder.onUnmount;
if (onUnmount !== undefined) {
let {isHook, props, state} = eventComponentInstance;
currentEventQueue = createEventQueue();
currentInstance = eventComponentInstance;
currentlyInHook = isHook;
try {
onUnmount(eventResponderContext, props, state);
processEventQueue();
} finally {
currentEventQueue = null;
currentInstance = null;
currentTimers = null;
}
}
try {
currentEventQueue = createEventQueue();
releaseOwnershipForEventComponentInstance(eventComponentInstance);
processEventQueue();
} finally {
currentEventQueue = null;
}
if (responder.onOwnershipChange !== undefined) {
ownershipChangeListeners.delete(eventComponentInstance);
}
const rootEventTypesSet = eventComponentInstance.rootEventTypes;
if (rootEventTypesSet !== null) {
const rootEventTypes = Array.from(rootEventTypesSet);
for (let i = 0; i < rootEventTypes.length; i++) {
const topLevelEventType = rootEventTypes[i];
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
topLevelEventType,
);
if (rootEventComponentInstances !== undefined) {
rootEventComponentInstances.delete(eventComponentInstance);
}
}
}
}
function registerRootEventType(
rootEventType: ReactNativeEventResponderEventType,
eventComponentInstance: ReactNativeEventComponentInstance,
) {
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
rootEventType,
);
if (rootEventComponentInstances === undefined) {
rootEventComponentInstances = new Set();
rootEventTypesToEventComponentInstances.set(
rootEventType,
rootEventComponentInstances,
);
}
let rootEventTypesSet = eventComponentInstance.rootEventTypes;
if (rootEventTypesSet === null) {
rootEventTypesSet = eventComponentInstance.rootEventTypes = new Set();
}
invariant(
!rootEventTypesSet.has(rootEventType),
'addRootEventTypes() found a duplicate root event ' +
'type of "%s". This might be because the event type exists in the event responder "rootEventTypes" ' +
'array or because of a previous addRootEventTypes() using this root event type.',
rootEventType,
);
rootEventTypesSet.add(rootEventType);
rootEventComponentInstances.add(eventComponentInstance);
}
export function addRootEventTypesForComponentInstance(
eventComponentInstance: ReactNativeEventComponentInstance,
rootEventTypes: Array<ReactNativeEventResponderEventType>,
): void {
for (let i = 0; i < rootEventTypes.length; i++) {
const rootEventType = rootEventTypes[i];
registerRootEventType(rootEventType, eventComponentInstance);
}
}
+35 -8
View File
@@ -13,7 +13,9 @@ import type {
MeasureOnSuccessCallback,
NativeMethodsMixinType,
ReactNativeBaseComponentViewConfig,
ReactNativeEventResponder,
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
} from './ReactNativeTypes';
import type {ReactEventComponentInstance} from 'shared/ReactTypes';
@@ -24,6 +26,13 @@ import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
import {dispatchEvent} from './ReactFabricEventEmitter';
import {
addRootEventTypesForComponentInstance,
mountEventResponder,
unmountEventResponder,
} from './ReactFabricEventResponderSystem';
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
// Modules provided by RN:
import {
@@ -56,6 +65,12 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
// This means that they never overlap.
let nextReactTag = 2;
type ReactNativeEventComponentInstance = ReactEventComponentInstance<
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
>;
type Node = Object;
export type Type = string;
export type Props = Object;
@@ -77,7 +92,6 @@ export type UpdatePayload = Object;
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type EventResponder = ReactNativeEventResponder;
// TODO: Remove this conditional once all changes have propagated.
if (registerEventHandler) {
@@ -431,21 +445,34 @@ export function replaceContainerChildren(
): void {}
export function mountEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
) {
throw new Error('Not yet implemented.');
if (enableFlareAPI) {
const responder = eventComponentInstance.responder;
const {rootEventTypes} = responder;
if (rootEventTypes !== undefined) {
addRootEventTypesForComponentInstance(
eventComponentInstance,
rootEventTypes,
);
}
mountEventResponder(eventComponentInstance);
}
}
export function updateEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
) {
throw new Error('Not yet implemented.');
// NO-OP, why might use this in the future
}
export function unmountEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
): void {
throw new Error('Not yet implemented.');
if (enableFlareAPI) {
// TODO stop listening to targetEventTypes
unmountEventResponder(eventComponentInstance);
}
}
export function getEventTargetChildElement(
+12 -5
View File
@@ -9,7 +9,9 @@
import type {
ReactNativeBaseComponentViewConfig,
ReactNativeEventResponder,
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
} from './ReactNativeTypes';
import type {ReactEventComponentInstance} from 'shared/ReactTypes';
@@ -32,6 +34,12 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
type ReactNativeEventComponentInstance = ReactEventComponentInstance<
ReactNativeEventResponderEventType,
ReactNativeResponderEvent,
ReactNativeResponderContext,
>;
export type Type = string;
export type Props = Object;
export type Container = number;
@@ -51,7 +59,6 @@ export type ChildSet = void; // Unused
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type EventResponder = ReactNativeEventResponder;
const UPDATE_SIGNAL = {};
if (__DEV__) {
@@ -490,19 +497,19 @@ export function unhideTextInstance(
}
export function mountEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
) {
throw new Error('Not yet implemented.');
}
export function updateEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
) {
throw new Error('Not yet implemented.');
}
export function unmountEventComponent(
eventComponentInstance: ReactEventComponentInstance<any, any, any>,
eventComponentInstance: ReactNativeEventComponentInstance,
): void {
throw new Error('Not yet implemented.');
}
+93 -3
View File
@@ -154,9 +154,99 @@ export type ReactFabricType = {
callback: ?Function,
): any,
unmountComponentAtNode(containerTag: number): any,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsFabricType,
};
// TODO will be addressed with upcoming React Flare support
export type ReactNativeEventResponder = any;
export type ReactNativeEventResponderEventType =
| 'topMouseDown'
| 'topMouseMove'
| 'topMouseUp'
| 'topScroll'
| 'topSelectionChange'
| 'topTouchCancel'
| 'topTouchEnd'
| 'topTouchMove'
| 'topTouchStart';
export type ReactNativeEventTarget = {
node: Object,
canonical: {
_nativeTag: number,
viewConfig: ReactNativeBaseComponentViewConfig<>,
currentProps: Object,
_internalInstanceHandle: Object,
},
};
export type ReactFaricEventTouch = {
identifier: number,
locationX: number,
locationY: number,
pageX: number,
pageY: number,
screenX: number,
screenY: number,
target: number,
timestamp: number,
force: number,
};
export type ReactFaricEvent = {
touches: Array<ReactFaricEventTouch>,
changedTouches: Array<ReactFaricEventTouch>,
targetTouches: Array<ReactFaricEventTouch>,
target: number,
};
export type ReactNativeResponderEvent = {
nativeEvent: ReactFaricEvent,
target: null | ReactNativeEventTarget,
type: ReactNativeEventResponderEventType,
};
export type ReactNativeResponderContext = {
dispatchEvent: (
eventObject: Object,
listener: (Object) => void,
eventPriority: EventPriority,
) => void,
isTargetWithinNode: (
childTarget: ReactNativeEventTarget,
parentTarget: ReactNativeEventTarget,
) => boolean,
getTargetBoundingRect(
target: ReactNativeEventTarget,
cb: ({
left: number,
right: number,
top: number,
bottom: number,
}) => void,
): void,
addRootEventTypes: (
rootEventTypes: Array<ReactNativeEventResponderEventType>,
) => void,
removeRootEventTypes: (
rootEventTypes: Array<ReactNativeEventResponderEventType>,
) => void,
getEventCurrentTarget(
event: ReactNativeResponderEvent,
): ReactNativeEventTarget,
setTimeout: (func: () => void, timeout: number) => number,
clearTimeout: (timerId: number) => void,
getTimeStamp: () => number,
};
export type PointerType =
| ''
| 'mouse'
| 'keyboard'
| 'pen'
| 'touch'
| 'trackpad';
export opaque type EventPriority = 0 | 1 | 2;
export const DiscreteEvent: EventPriority = 0;
export const UserBlockingEvent: EventPriority = 1;
export const ContinuousEvent: EventPriority = 2;
+1 -1
View File
@@ -55,7 +55,7 @@ export type ReactDOMResponderContext = {
listener: (Object) => void,
eventPriority: EventPriority,
) => void,
isTargetWithinElement: (
isTargetWithinNode: (
childTarget: Element | Document,
parentTarget: Element | Document,
) => boolean,