mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[react-events] Various core tweaks for event responder system (#16654)
This commit is contained in:
+47
-18
@@ -12,7 +12,7 @@ import {
|
||||
PASSIVE_NOT_SUPPORTED,
|
||||
} from 'legacy-events/EventSystemFlags';
|
||||
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
|
||||
import {HostComponent} from 'shared/ReactWorkTags';
|
||||
import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ReactDOMEventResponder,
|
||||
@@ -66,6 +66,7 @@ type ResponderTimer = {|
|
||||
instance: ReactDOMEventResponderInstance,
|
||||
func: () => void,
|
||||
id: number,
|
||||
targetFiber: Fiber | null,
|
||||
timeStamp: number,
|
||||
|};
|
||||
|
||||
@@ -80,6 +81,7 @@ let currentTimers = new Map();
|
||||
let currentInstance: null | ReactDOMEventResponderInstance = null;
|
||||
let currentTimerIDCounter = 0;
|
||||
let currentDocument: null | Document = null;
|
||||
let currentTargetFiber: null | Fiber = null;
|
||||
|
||||
const eventResponderContext: ReactDOMResponderContext = {
|
||||
dispatchEvent(
|
||||
@@ -158,16 +160,20 @@ const eventResponderContext: ReactDOMResponderContext = {
|
||||
validateResponderContext();
|
||||
const childFiber = getClosestInstanceFromNode(childTarget);
|
||||
const parentFiber = getClosestInstanceFromNode(parentTarget);
|
||||
const parentAlternateFiber = parentFiber.alternate;
|
||||
|
||||
let node = childFiber;
|
||||
while (node !== null) {
|
||||
if (node === parentFiber || node === parentAlternateFiber) {
|
||||
return true;
|
||||
if (childFiber != null && parentFiber != null) {
|
||||
const parentAlternateFiber = parentFiber.alternate;
|
||||
let node = childFiber;
|
||||
while (node !== null) {
|
||||
if (node === parentFiber || node === parentAlternateFiber) {
|
||||
return true;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node = node.return;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// Fallback to DOM APIs
|
||||
return parentTarget.contains(childTarget);
|
||||
},
|
||||
addRootEventTypes(rootEventTypes: Array<string>): void {
|
||||
validateResponderContext();
|
||||
@@ -221,6 +227,7 @@ const eventResponderContext: ReactDOMResponderContext = {
|
||||
instance: ((currentInstance: any): ReactDOMEventResponderInstance),
|
||||
func,
|
||||
id: timerId,
|
||||
targetFiber: currentTargetFiber,
|
||||
timeStamp: currentTimeStamp,
|
||||
});
|
||||
activeTimeouts.set(timerId, timeout);
|
||||
@@ -260,6 +267,24 @@ const eventResponderContext: ReactDOMResponderContext = {
|
||||
return false;
|
||||
},
|
||||
enqueueStateRestore,
|
||||
getCurrentTarget(): Element | null {
|
||||
validateResponderContext();
|
||||
const responderFiber = ((currentInstance: any): ReactDOMEventResponderInstance)
|
||||
.fiber;
|
||||
let fiber = currentTargetFiber;
|
||||
let currentTarget = null;
|
||||
|
||||
while (fiber !== null) {
|
||||
if (fiber.tag === HostComponent) {
|
||||
currentTarget = fiber.stateNode;
|
||||
}
|
||||
if (fiber === responderFiber || fiber.alternate === responderFiber) {
|
||||
break;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
return currentTarget;
|
||||
},
|
||||
};
|
||||
|
||||
function validateEventValue(eventValue: any): void {
|
||||
@@ -317,7 +342,8 @@ function doesFiberHaveResponder(
|
||||
fiber: Fiber,
|
||||
responder: ReactDOMEventResponder,
|
||||
): boolean {
|
||||
if (fiber.tag === HostComponent) {
|
||||
const tag = fiber.tag;
|
||||
if (tag === HostComponent || tag === ScopeComponent) {
|
||||
const dependencies = fiber.dependencies;
|
||||
if (dependencies !== null) {
|
||||
const respondersMap = dependencies.responders;
|
||||
@@ -341,8 +367,9 @@ function processTimers(
|
||||
try {
|
||||
batchedEventUpdates(() => {
|
||||
for (let i = 0; i < timersArr.length; i++) {
|
||||
const {instance, func, id, timeStamp} = timersArr[i];
|
||||
const {instance, func, id, timeStamp, targetFiber} = timersArr[i];
|
||||
currentInstance = instance;
|
||||
currentTargetFiber = targetFiber;
|
||||
currentTimeStamp = timeStamp + delay;
|
||||
try {
|
||||
func();
|
||||
@@ -355,6 +382,7 @@ function processTimers(
|
||||
currentTimers = null;
|
||||
currentInstance = null;
|
||||
currentTimeStamp = 0;
|
||||
currentTargetFiber = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +414,6 @@ function createDOMResponderEvent(
|
||||
passiveSupported,
|
||||
pointerId,
|
||||
pointerType: eventPointerType,
|
||||
responderTarget: null,
|
||||
target: nativeEventTarget,
|
||||
type: topLevelType,
|
||||
};
|
||||
@@ -443,13 +470,16 @@ function traverseAndHandleEventResponderInstances(
|
||||
let node = targetFiber;
|
||||
while (node !== null) {
|
||||
const {dependencies, tag} = node;
|
||||
if (tag === HostComponent && dependencies !== null) {
|
||||
if (
|
||||
(tag === HostComponent || tag === ScopeComponent) &&
|
||||
dependencies !== null
|
||||
) {
|
||||
const respondersMap = dependencies.responders;
|
||||
if (respondersMap !== null) {
|
||||
const responderInstances = Array.from(respondersMap.values());
|
||||
for (let i = 0, length = responderInstances.length; i < length; i++) {
|
||||
const responderInstance = responderInstances[i];
|
||||
const {props, responder, state, target} = responderInstance;
|
||||
const {props, responder, state} = responderInstance;
|
||||
if (
|
||||
!visitedResponders.has(responder) &&
|
||||
validateResponderTargetEventTypes(eventType, responder)
|
||||
@@ -458,9 +488,6 @@ function traverseAndHandleEventResponderInstances(
|
||||
const onEvent = responder.onEvent;
|
||||
if (onEvent !== null) {
|
||||
currentInstance = responderInstance;
|
||||
responderEvent.responderTarget = ((target: any):
|
||||
| Element
|
||||
| Document);
|
||||
onEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
@@ -478,11 +505,10 @@ function traverseAndHandleEventResponderInstances(
|
||||
|
||||
for (let i = 0; i < responderInstances.length; i++) {
|
||||
const responderInstance = responderInstances[i];
|
||||
const {props, responder, state, target} = responderInstance;
|
||||
const {props, responder, state} = responderInstance;
|
||||
const onRootEvent = responder.onRootEvent;
|
||||
if (onRootEvent !== null) {
|
||||
currentInstance = responderInstance;
|
||||
responderEvent.responderTarget = ((target: any): Element | Document);
|
||||
onRootEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
@@ -562,7 +588,9 @@ export function dispatchEventForResponderEventSystem(
|
||||
const previousTimers = currentTimers;
|
||||
const previousTimeStamp = currentTimeStamp;
|
||||
const previousDocument = currentDocument;
|
||||
const previousTargetFiber = currentTargetFiber;
|
||||
currentTimers = null;
|
||||
currentTargetFiber = targetFiber;
|
||||
// nodeType 9 is DOCUMENT_NODE
|
||||
currentDocument =
|
||||
(nativeEventTarget: any).nodeType === 9
|
||||
@@ -585,6 +613,7 @@ export function dispatchEventForResponderEventSystem(
|
||||
currentInstance = previousInstance;
|
||||
currentTimeStamp = previousTimeStamp;
|
||||
currentDocument = previousDocument;
|
||||
currentTargetFiber = previousTargetFiber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1017,7 +1017,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const obj = {
|
||||
counter,
|
||||
timeStamp: context.getTimeStamp(),
|
||||
target: event.responderTarget,
|
||||
target: context.getCurrentTarget(),
|
||||
type: 'click-test',
|
||||
};
|
||||
context.dispatchEvent(obj, props.onClick, DiscreteEvent);
|
||||
|
||||
@@ -22,7 +22,6 @@ type ResponderEventType = string;
|
||||
|
||||
type ResponderEvent = {|
|
||||
nativeEvent: any,
|
||||
responderTarget: Element | Document,
|
||||
target: Element | Document,
|
||||
pointerType: string,
|
||||
type: string,
|
||||
|
||||
+2
-2
@@ -289,7 +289,7 @@ const focusResponderImpl = {
|
||||
|
||||
switch (type) {
|
||||
case 'focus': {
|
||||
state.focusTarget = event.responderTarget;
|
||||
state.focusTarget = context.getCurrentTarget();
|
||||
// Limit focus events to the direct child of the event component.
|
||||
// Browser focus is not expected to bubble.
|
||||
if (!state.isFocused && state.focusTarget === target) {
|
||||
@@ -427,7 +427,7 @@ const focusWithinResponderImpl = {
|
||||
|
||||
switch (type) {
|
||||
case 'focus': {
|
||||
state.focusTarget = event.responderTarget;
|
||||
state.focusTarget = context.getCurrentTarget();
|
||||
// Limit focus events to the direct child of the event component.
|
||||
// Browser focus is not expected to bubble.
|
||||
if (!state.isFocused) {
|
||||
|
||||
+2
-2
@@ -235,7 +235,7 @@ const hoverResponderImpl = {
|
||||
// START
|
||||
case 'pointerover': {
|
||||
if (!state.isHovered && pointerType !== 'touch') {
|
||||
state.hoverTarget = event.responderTarget;
|
||||
state.hoverTarget = context.getCurrentTarget();
|
||||
dispatchHoverStartEvents(event, context, props, state);
|
||||
}
|
||||
break;
|
||||
@@ -295,7 +295,7 @@ const hoverResponderFallbackImpl = {
|
||||
// START
|
||||
case 'mouseover': {
|
||||
if (!state.isHovered && !state.ignoreEmulatedMouseEvents) {
|
||||
state.hoverTarget = event.responderTarget;
|
||||
state.hoverTarget = context.getCurrentTarget();
|
||||
dispatchHoverStartEvents(event, context, props, state);
|
||||
}
|
||||
break;
|
||||
|
||||
+6
-5
@@ -179,30 +179,31 @@ const inputResponderImpl = {
|
||||
context: ReactDOMResponderContext,
|
||||
props: InputResponderProps,
|
||||
): void {
|
||||
const {responderTarget, type, target} = event;
|
||||
const {type, target} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
if (target !== responderTarget || responderTarget === null) {
|
||||
const currentTarget = context.getCurrentTarget();
|
||||
if (target !== currentTarget || currentTarget === null) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
default: {
|
||||
if (shouldUseChangeEvent(target) && type === 'change') {
|
||||
dispatchBothChangeEvents(event, context, props, responderTarget);
|
||||
dispatchBothChangeEvents(event, context, props, currentTarget);
|
||||
} else if (
|
||||
isTextInputElement(target) &&
|
||||
(type === 'input' || type === 'change') &&
|
||||
updateValueIfChanged(target)
|
||||
) {
|
||||
dispatchBothChangeEvents(event, context, props, responderTarget);
|
||||
dispatchBothChangeEvents(event, context, props, currentTarget);
|
||||
} else if (
|
||||
isCheckable(target) &&
|
||||
type === 'click' &&
|
||||
updateValueIfChanged(target)
|
||||
) {
|
||||
dispatchBothChangeEvents(event, context, props, responderTarget);
|
||||
dispatchBothChangeEvents(event, context, props, currentTarget);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+2
-6
@@ -131,7 +131,6 @@ function createKeyboardEvent(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
type: KeyboardEventType,
|
||||
target: Document | Element,
|
||||
defaultPrevented: boolean,
|
||||
): KeyboardEvent {
|
||||
const nativeEvent = (event: any).nativeEvent;
|
||||
@@ -144,6 +143,7 @@ function createKeyboardEvent(
|
||||
repeat,
|
||||
shiftKey,
|
||||
} = nativeEvent;
|
||||
const target = ((context.getCurrentTarget(): any): Element);
|
||||
|
||||
return {
|
||||
altKey,
|
||||
@@ -166,14 +166,12 @@ function dispatchKeyboardEvent(
|
||||
listener: KeyboardEvent => void,
|
||||
context: ReactDOMResponderContext,
|
||||
type: KeyboardEventType,
|
||||
target: Element | Document,
|
||||
defaultPrevented: boolean,
|
||||
): void {
|
||||
const syntheticEvent = createKeyboardEvent(
|
||||
event,
|
||||
context,
|
||||
type,
|
||||
target,
|
||||
defaultPrevented,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
|
||||
@@ -186,7 +184,7 @@ const keyboardResponderImpl = {
|
||||
context: ReactDOMResponderContext,
|
||||
props: KeyboardProps,
|
||||
): void {
|
||||
const {responderTarget, type} = event;
|
||||
const {type} = event;
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
|
||||
if (props.disabled) {
|
||||
@@ -227,7 +225,6 @@ const keyboardResponderImpl = {
|
||||
onKeyDown,
|
||||
context,
|
||||
'keydown',
|
||||
((responderTarget: any): Element | Document),
|
||||
defaultPrevented,
|
||||
);
|
||||
}
|
||||
@@ -239,7 +236,6 @@ const keyboardResponderImpl = {
|
||||
onKeyUp,
|
||||
context,
|
||||
'keyup',
|
||||
((responderTarget: any): Element | Document),
|
||||
defaultPrevented,
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -582,7 +582,7 @@ const pressResponderImpl = {
|
||||
// We set these here, before the button check so we have this
|
||||
// data around for handling of the context menu
|
||||
state.pointerType = pointerType;
|
||||
const pressTarget = (state.pressTarget = event.responderTarget);
|
||||
const pressTarget = (state.pressTarget = context.getCurrentTarget());
|
||||
if (isPointerEvent) {
|
||||
state.activePointerId = pointerId;
|
||||
} else if (isTouchEvent) {
|
||||
@@ -634,7 +634,7 @@ const pressResponderImpl = {
|
||||
|
||||
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
|
||||
state.pointerType = 'keyboard';
|
||||
state.pressTarget = event.responderTarget;
|
||||
state.pressTarget = context.getCurrentTarget();
|
||||
const preventDefault = props.preventDefault;
|
||||
|
||||
if (preventDefault !== false) {
|
||||
|
||||
+1
-1
@@ -412,7 +412,7 @@ const pressResponderImpl = {
|
||||
if (type === 'topTouchStart') {
|
||||
if (!state.isPressed) {
|
||||
state.pointerType = 'touch';
|
||||
const pressTarget = (state.pressTarget = event.responderTarget);
|
||||
const pressTarget = (state.pressTarget = context.getCurrentTarget());
|
||||
const touchEvent = getTouchFromPressEvent(nativeEvent);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {HostComponent} from 'shared/ReactWorkTags';
|
||||
import {HostComponent, ScopeComponent} from 'shared/ReactWorkTags';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import {
|
||||
batchedEventUpdates,
|
||||
@@ -52,6 +52,7 @@ type ResponderTimer = {|
|
||||
instance: ReactNativeEventResponderInstance,
|
||||
func: () => void,
|
||||
id: number,
|
||||
targetFiber: Fiber | null,
|
||||
timeStamp: number,
|
||||
|};
|
||||
|
||||
@@ -77,6 +78,7 @@ let currentTimeStamp = 0;
|
||||
let currentTimers = new Map();
|
||||
let currentInstance: null | ReactNativeEventResponderInstance = null;
|
||||
let currentTimerIDCounter = 0;
|
||||
let currentTargetFiber: Fiber | null = null;
|
||||
|
||||
const eventResponderContext: ReactNativeResponderContext = {
|
||||
dispatchEvent(
|
||||
@@ -196,6 +198,7 @@ const eventResponderContext: ReactNativeResponderContext = {
|
||||
instance: ((currentInstance: any): ReactNativeEventResponderInstance),
|
||||
func,
|
||||
id: timerId,
|
||||
targetFiber: currentTargetFiber,
|
||||
timeStamp: currentTimeStamp,
|
||||
});
|
||||
activeTimeouts.set(timerId, timeout);
|
||||
@@ -217,6 +220,24 @@ const eventResponderContext: ReactNativeResponderContext = {
|
||||
validateResponderContext();
|
||||
return currentTimeStamp;
|
||||
},
|
||||
getCurrentTarget(): ReactNativeEventTarget | null {
|
||||
validateResponderContext();
|
||||
const responderFiber = ((currentInstance: any): ReactNativeEventResponderInstance)
|
||||
.fiber;
|
||||
let fiber = currentTargetFiber;
|
||||
let currentTarget = null;
|
||||
|
||||
while (fiber !== null) {
|
||||
if (fiber.tag === HostComponent) {
|
||||
currentTarget = fiber.stateNode;
|
||||
}
|
||||
if (fiber === responderFiber || fiber.alternate === responderFiber) {
|
||||
break;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
return currentTarget;
|
||||
},
|
||||
};
|
||||
|
||||
function validateEventValue(eventValue: any): void {
|
||||
@@ -287,8 +308,9 @@ function processTimers(
|
||||
try {
|
||||
batchedEventUpdates(() => {
|
||||
for (let i = 0; i < timersArr.length; i++) {
|
||||
const {instance, func, id, timeStamp} = timersArr[i];
|
||||
const {instance, func, id, targetFiber, timeStamp} = timersArr[i];
|
||||
currentInstance = instance;
|
||||
currentTargetFiber = targetFiber;
|
||||
currentTimeStamp = timeStamp + delay;
|
||||
try {
|
||||
func();
|
||||
@@ -301,6 +323,7 @@ function processTimers(
|
||||
currentTimers = null;
|
||||
currentInstance = null;
|
||||
currentTimeStamp = 0;
|
||||
currentTargetFiber = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +334,6 @@ function createFabricResponderEvent(
|
||||
): ReactNativeResponderEvent {
|
||||
return {
|
||||
nativeEvent,
|
||||
responderTarget: target,
|
||||
target,
|
||||
type: topLevelType,
|
||||
};
|
||||
@@ -373,13 +395,16 @@ function traverseAndHandleEventResponderInstances(
|
||||
let node = targetFiber;
|
||||
while (node !== null) {
|
||||
const {dependencies, tag} = node;
|
||||
if (tag === HostComponent && dependencies !== null) {
|
||||
if (
|
||||
(tag === HostComponent || tag === ScopeComponent) &&
|
||||
dependencies !== null
|
||||
) {
|
||||
const respondersMap = dependencies.responders;
|
||||
if (respondersMap !== null) {
|
||||
const responderInstances = Array.from(respondersMap.values());
|
||||
for (let i = 0, length = responderInstances.length; i < length; i++) {
|
||||
const responderInstance = responderInstances[i];
|
||||
const {props, responder, state, target} = responderInstance;
|
||||
const {props, responder, state} = responderInstance;
|
||||
if (
|
||||
!visitedResponders.has(responder) &&
|
||||
validateResponderTargetEventTypes(eventType, responder)
|
||||
@@ -388,7 +413,6 @@ function traverseAndHandleEventResponderInstances(
|
||||
visitedResponders.add(responder);
|
||||
if (onEvent !== null) {
|
||||
currentInstance = responderInstance;
|
||||
responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
|
||||
onEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
@@ -406,11 +430,10 @@ function traverseAndHandleEventResponderInstances(
|
||||
|
||||
for (let i = 0; i < responderInstances.length; i++) {
|
||||
const responderInstance = responderInstances[i];
|
||||
const {props, responder, state, target} = responderInstance;
|
||||
const {props, responder, state} = responderInstance;
|
||||
const onRootEvent = responder.onRootEvent;
|
||||
if (onRootEvent !== null) {
|
||||
currentInstance = responderInstance;
|
||||
responderEvent.responderTarget = ((target: any): ReactNativeEventTarget);
|
||||
onRootEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
@@ -427,7 +450,9 @@ export function dispatchEventForResponderEventSystem(
|
||||
const previousInstance = currentInstance;
|
||||
const previousTimers = currentTimers;
|
||||
const previousTimeStamp = currentTimeStamp;
|
||||
const previousTargetFiber = currentTargetFiber;
|
||||
currentTimers = null;
|
||||
currentTargetFiber = targetFiber;
|
||||
// We might want to control timeStamp another way here
|
||||
currentTimeStamp = Date.now();
|
||||
try {
|
||||
@@ -442,6 +467,7 @@ export function dispatchEventForResponderEventSystem(
|
||||
currentTimers = previousTimers;
|
||||
currentInstance = previousInstance;
|
||||
currentTimeStamp = previousTimeStamp;
|
||||
currentTargetFiber = previousTargetFiber;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -191,7 +191,6 @@ export type ReactFaricEvent = {
|
||||
|
||||
export type ReactNativeResponderEvent = {
|
||||
nativeEvent: ReactFaricEvent,
|
||||
responderTarget: null | ReactNativeEventTarget,
|
||||
target: null | ReactNativeEventTarget,
|
||||
type: string,
|
||||
};
|
||||
@@ -220,6 +219,7 @@ export type ReactNativeResponderContext = {
|
||||
setTimeout: (func: () => void, timeout: number) => number,
|
||||
clearTimeout: (timerId: number) => void,
|
||||
getTimeStamp: () => number,
|
||||
getCurrentTarget(): ReactNativeEventTarget | null,
|
||||
};
|
||||
|
||||
export type PointerType =
|
||||
|
||||
+10
-1
@@ -1343,7 +1343,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
const prevListeners = oldProps.listeners;
|
||||
const nextListeners = newProps.listeners;
|
||||
if (prevListeners !== nextListeners) {
|
||||
updateEventListeners(nextListeners, instance, finishedWork);
|
||||
updateEventListeners(nextListeners, finishedWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1394,6 +1394,15 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
if (enableScopeAPI) {
|
||||
const scopeInstance = finishedWork.stateNode;
|
||||
scopeInstance.fiber = finishedWork;
|
||||
if (enableFlareAPI) {
|
||||
const newProps = finishedWork.memoizedProps;
|
||||
const oldProps = current !== null ? current.memoizedProps : newProps;
|
||||
const prevListeners = oldProps.listeners;
|
||||
const nextListeners = newProps.listeners;
|
||||
if (prevListeners !== nextListeners) {
|
||||
updateEventListeners(nextListeners, finishedWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
+25
-7
@@ -722,10 +722,9 @@ function completeWork(
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
if (enableFlareAPI) {
|
||||
const instance = workInProgress.stateNode;
|
||||
const listeners = newProps.listeners;
|
||||
if (listeners != null) {
|
||||
updateEventListeners(listeners, instance, workInProgress);
|
||||
updateEventListeners(listeners, workInProgress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -739,10 +738,13 @@ function completeWork(
|
||||
|
||||
appendAllChildren(instance, workInProgress, false, false);
|
||||
|
||||
// This needs to be set before we mount Flare event listeners
|
||||
workInProgress.stateNode = instance;
|
||||
|
||||
if (enableFlareAPI) {
|
||||
const listeners = newProps.listeners;
|
||||
if (listeners != null) {
|
||||
updateEventListeners(listeners, instance, workInProgress);
|
||||
updateEventListeners(listeners, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,7 +762,6 @@ function completeWork(
|
||||
) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
workInProgress.stateNode = instance;
|
||||
}
|
||||
|
||||
if (workInProgress.ref !== null) {
|
||||
@@ -1229,17 +1230,34 @@ function completeWork(
|
||||
};
|
||||
workInProgress.stateNode = scopeInstance;
|
||||
scopeInstance.methods = createScopeMethods(type, scopeInstance);
|
||||
if (enableFlareAPI) {
|
||||
const listeners = newProps.listeners;
|
||||
if (listeners != null) {
|
||||
updateEventListeners(listeners, workInProgress);
|
||||
}
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
markRef(workInProgress);
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (enableFlareAPI) {
|
||||
const prevListeners = current.memoizedProps.listeners;
|
||||
const nextListeners = newProps.listeners;
|
||||
if (
|
||||
prevListeners !== nextListeners ||
|
||||
workInProgress.ref !== null
|
||||
) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (workInProgress.ref !== null) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
}
|
||||
if (current.ref !== workInProgress.ref) {
|
||||
markRef(workInProgress);
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
+19
-32
@@ -25,6 +25,7 @@ import warning from 'shared/warning';
|
||||
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import {HostComponent, HostRoot} from 'shared/ReactWorkTags';
|
||||
|
||||
const emptyObject = {};
|
||||
const isArray = Array.isArray;
|
||||
@@ -33,7 +34,6 @@ export function createResponderInstance(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderProps: Object,
|
||||
responderState: Object,
|
||||
target: Instance,
|
||||
fiber: Fiber,
|
||||
): ReactEventResponderInstance<any, any> {
|
||||
return {
|
||||
@@ -42,14 +42,12 @@ export function createResponderInstance(
|
||||
responder,
|
||||
rootEventTypes: null,
|
||||
state: responderState,
|
||||
target,
|
||||
};
|
||||
}
|
||||
|
||||
function mountEventResponder(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderProps: Object,
|
||||
instance: Instance,
|
||||
fiber: Fiber,
|
||||
respondersMap: Map<
|
||||
ReactEventResponder<any, any>,
|
||||
@@ -65,15 +63,27 @@ function mountEventResponder(
|
||||
responder,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
fiber,
|
||||
);
|
||||
let instance = null;
|
||||
let node = fiber;
|
||||
while (node !== null) {
|
||||
const tag = node.tag;
|
||||
if (tag === HostComponent) {
|
||||
instance = node.stateNode;
|
||||
break;
|
||||
} else if (tag === HostRoot) {
|
||||
instance = node.stateNode.containerInfo;
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
mountResponderInstance(
|
||||
responder,
|
||||
responderInstance,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
((instance: any): Instance),
|
||||
);
|
||||
respondersMap.set(responder, responderInstance);
|
||||
}
|
||||
@@ -86,7 +96,6 @@ function updateEventListener(
|
||||
ReactEventResponder<any, any>,
|
||||
ReactEventResponderInstance<any, any>,
|
||||
>,
|
||||
instance: Instance,
|
||||
): void {
|
||||
let responder;
|
||||
let props;
|
||||
@@ -118,13 +127,7 @@ function updateEventListener(
|
||||
|
||||
if (responderInstance === undefined) {
|
||||
// Mount (happens in either complete or commit phase)
|
||||
mountEventResponder(
|
||||
responder,
|
||||
listenerProps,
|
||||
instance,
|
||||
fiber,
|
||||
respondersMap,
|
||||
);
|
||||
mountEventResponder(responder, listenerProps, fiber, respondersMap);
|
||||
} else {
|
||||
// Update (happens during commit phase only)
|
||||
responderInstance.props = listenerProps;
|
||||
@@ -132,11 +135,7 @@ function updateEventListener(
|
||||
}
|
||||
}
|
||||
|
||||
export function updateEventListeners(
|
||||
listeners: any,
|
||||
instance: Instance,
|
||||
fiber: Fiber,
|
||||
): void {
|
||||
export function updateEventListeners(listeners: any, fiber: Fiber): void {
|
||||
const visistedResponders = new Set();
|
||||
let dependencies = fiber.dependencies;
|
||||
if (listeners != null) {
|
||||
@@ -154,22 +153,10 @@ export function updateEventListeners(
|
||||
if (isArray(listeners)) {
|
||||
for (let i = 0, length = listeners.length; i < length; i++) {
|
||||
const listener = listeners[i];
|
||||
updateEventListener(
|
||||
listener,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
);
|
||||
updateEventListener(listener, fiber, visistedResponders, respondersMap);
|
||||
}
|
||||
} else {
|
||||
updateEventListener(
|
||||
listeners,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
);
|
||||
updateEventListener(listeners, fiber, visistedResponders, respondersMap);
|
||||
}
|
||||
}
|
||||
if (dependencies !== null) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {createEventTarget} from 'react-events/src/dom/testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
|
||||
@@ -17,6 +19,7 @@ describe('ReactScope', () => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableScopeAPI = true;
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
});
|
||||
|
||||
@@ -194,6 +197,31 @@ describe('ReactScope', () => {
|
||||
const nodes = scopeRef.current.getScopedNodes();
|
||||
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
|
||||
});
|
||||
|
||||
it('event responders can be attached to scopes', () => {
|
||||
const TestScope = React.unstable_createScope((type, props) => true);
|
||||
const onKeyDown = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const useKeyboard = require('react-events/keyboard').useKeyboard;
|
||||
const Component = () => {
|
||||
const listener = useKeyboard({
|
||||
onKeyDown,
|
||||
});
|
||||
return (
|
||||
<TestScope listeners={listener}>
|
||||
<div ref={ref} />
|
||||
</TestScope>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Q'});
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({key: 'Q', type: 'keydown'}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactTestRenderer', () => {
|
||||
|
||||
@@ -30,7 +30,6 @@ export type ReactDOMResponderEvent = {
|
||||
passiveSupported: boolean,
|
||||
pointerId: null | number,
|
||||
pointerType: PointerType,
|
||||
responderTarget: null | Element | Document,
|
||||
target: Element | Document,
|
||||
type: string,
|
||||
};
|
||||
@@ -75,4 +74,5 @@ export type ReactDOMResponderContext = {
|
||||
) => boolean,
|
||||
// Used for controller components
|
||||
enqueueStateRestore(Element | Document): void,
|
||||
getCurrentTarget(): Element | null,
|
||||
};
|
||||
|
||||
@@ -85,7 +85,6 @@ export type ReactEventResponderInstance<E, C> = {|
|
||||
responder: ReactEventResponder<E, C>,
|
||||
rootEventTypes: null | Set<string>,
|
||||
state: Object,
|
||||
target: mixed,
|
||||
|};
|
||||
|
||||
export type ReactEventResponderListener<E, C> = {|
|
||||
|
||||
Reference in New Issue
Block a user