mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Experimental Event API: add rootEventTypes support to event responders (#15475)
* Adds rootEventTypes
This commit is contained in:
+1
-1
@@ -1302,7 +1302,7 @@ export function listenToEventResponderEventTypes(
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
typeof targetEventType === 'object' && targetEventType !== null,
|
||||
'Event Responder: invalid entry in targetEventTypes array. ' +
|
||||
'Event Responder: invalid entry in event types array. ' +
|
||||
'Entry must be string or an object. Instead, got %s.',
|
||||
targetEventType,
|
||||
);
|
||||
|
||||
+13
-4
@@ -34,6 +34,7 @@ import {
|
||||
setEnabled as ReactBrowserEventEmitterSetEnabled,
|
||||
} from '../events/ReactBrowserEventEmitter';
|
||||
import {Namespaces, getChildNamespace} from '../shared/DOMNamespaces';
|
||||
import {addRootEventTypesForComponentInstance} from '../events/DOMEventResponderSystem';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
@@ -906,10 +907,18 @@ export function updateEventComponent(
|
||||
if (enableEventAPI) {
|
||||
const rootContainerInstance = ((eventComponentInstance.rootInstance: any): Container);
|
||||
const rootElement = rootContainerInstance.ownerDocument;
|
||||
listenToEventResponderEventTypes(
|
||||
eventComponentInstance.responder.targetEventTypes,
|
||||
rootElement,
|
||||
);
|
||||
const responder = eventComponentInstance.responder;
|
||||
const {rootEventTypes, targetEventTypes} = responder;
|
||||
if (targetEventTypes !== undefined) {
|
||||
listenToEventResponderEventTypes(targetEventTypes, rootElement);
|
||||
}
|
||||
if (rootEventTypes !== undefined) {
|
||||
addRootEventTypesForComponentInstance(
|
||||
eventComponentInstance,
|
||||
rootEventTypes,
|
||||
);
|
||||
listenToEventResponderEventTypes(rootEventTypes, rootElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+61
-2
@@ -206,9 +206,20 @@ const eventResponderContext: ReactResponderContext = {
|
||||
rootEventComponentInstances,
|
||||
);
|
||||
}
|
||||
rootEventComponentInstances.add(
|
||||
((currentInstance: any): ReactEventComponentInstance),
|
||||
const componentInstance = ((currentInstance: any): ReactEventComponentInstance);
|
||||
let rootEventTypesSet = componentInstance.rootEventTypes;
|
||||
if (rootEventTypesSet === null) {
|
||||
rootEventTypesSet = componentInstance.rootEventTypes = new Set();
|
||||
}
|
||||
invariant(
|
||||
!rootEventTypesSet.has(topLevelEventType),
|
||||
'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(topLevelEventType);
|
||||
rootEventComponentInstances.add(componentInstance);
|
||||
}
|
||||
},
|
||||
removeRootEventTypes(
|
||||
@@ -222,6 +233,11 @@ const eventResponderContext: ReactResponderContext = {
|
||||
let rootEventComponents = rootEventTypesToEventComponentInstances.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
let rootEventTypesSet = ((currentInstance: any): ReactEventComponentInstance)
|
||||
.rootEventTypes;
|
||||
if (rootEventTypesSet !== null) {
|
||||
rootEventTypesSet.delete(topLevelEventType);
|
||||
}
|
||||
if (rootEventComponents !== undefined) {
|
||||
rootEventComponents.delete(
|
||||
((currentInstance: any): ReactEventComponentInstance),
|
||||
@@ -636,6 +652,20 @@ export function unmountEventResponder(
|
||||
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 validateResponderContext(): void {
|
||||
@@ -671,3 +701,32 @@ export function dispatchEventForResponderEventSystem(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addRootEventTypesForComponentInstance(
|
||||
eventComponentInstance: ReactEventComponentInstance,
|
||||
rootEventTypes: Array<ReactEventResponderEventType>,
|
||||
): void {
|
||||
for (let i = 0; i < rootEventTypes.length; i++) {
|
||||
const rootEventType = rootEventTypes[i];
|
||||
const topLevelEventType =
|
||||
typeof rootEventType === 'string' ? rootEventType : rootEventType.name;
|
||||
let rootEventComponentInstances = rootEventTypesToEventComponentInstances.get(
|
||||
topLevelEventType,
|
||||
);
|
||||
if (rootEventComponentInstances === undefined) {
|
||||
rootEventComponentInstances = new Set();
|
||||
rootEventTypesToEventComponentInstances.set(
|
||||
topLevelEventType,
|
||||
rootEventComponentInstances,
|
||||
);
|
||||
}
|
||||
let rootEventTypesSet = eventComponentInstance.rootEventTypes;
|
||||
if (rootEventTypesSet === null) {
|
||||
rootEventTypesSet = eventComponentInstance.rootEventTypes = new Set();
|
||||
}
|
||||
rootEventTypesSet.add(topLevelEventType);
|
||||
rootEventComponentInstances.add(
|
||||
((eventComponentInstance: any): ReactEventComponentInstance),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ let ReactSymbols;
|
||||
|
||||
function createReactEventComponent(
|
||||
targetEventTypes,
|
||||
rootEventTypes,
|
||||
createInitialState,
|
||||
onEvent,
|
||||
onEventCapture,
|
||||
@@ -26,6 +27,7 @@ function createReactEventComponent(
|
||||
) {
|
||||
const testEventResponder = {
|
||||
targetEventTypes,
|
||||
rootEventTypes,
|
||||
createInitialState,
|
||||
onEvent,
|
||||
onEventCapture,
|
||||
@@ -90,6 +92,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
@@ -163,6 +166,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push({
|
||||
name: event.type,
|
||||
@@ -217,6 +221,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
@@ -288,6 +293,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponentA = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`A [bubble]`);
|
||||
},
|
||||
@@ -299,6 +305,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponentB = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`B [bubble]`);
|
||||
},
|
||||
@@ -336,6 +343,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`${props.name} [bubble]`);
|
||||
},
|
||||
@@ -377,6 +385,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`${props.name} [bubble]`);
|
||||
},
|
||||
@@ -413,6 +422,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
if (props.onMagicClick) {
|
||||
const syntheticEvent = {
|
||||
@@ -505,6 +515,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const LongPressEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
handleEvent(event, context, props, 'bubble');
|
||||
},
|
||||
@@ -551,6 +562,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {},
|
||||
() => {
|
||||
onUnmountFired++;
|
||||
@@ -573,6 +585,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
const EventComponent = createReactEventComponent(
|
||||
[],
|
||||
undefined,
|
||||
() => ({
|
||||
incrementAmount: 5,
|
||||
}),
|
||||
@@ -603,6 +616,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
ownershipGained = context.requestOwnership();
|
||||
},
|
||||
@@ -641,6 +655,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
queryResult = Array.from(
|
||||
context.getEventTargetsFromTarget(event.target),
|
||||
@@ -696,6 +711,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
queryResult = context.getEventTargetsFromTarget(
|
||||
event.target,
|
||||
@@ -743,6 +759,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
queryResult = context.getEventTargetsFromTarget(
|
||||
event.target,
|
||||
@@ -795,6 +812,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
queryResult = context.getEventTargetsFromTarget(
|
||||
event.target,
|
||||
@@ -855,4 +873,48 @@ describe('DOMEventResponderSystem', () => {
|
||||
]);
|
||||
expect(queryResult3).toEqual([]);
|
||||
});
|
||||
|
||||
it('the event responder root listeners should fire on a root click event', () => {
|
||||
let eventResponderFiredCount = 0;
|
||||
let eventLog = [];
|
||||
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
undefined,
|
||||
['click'],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
event => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: 'root',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const Test = () => (
|
||||
<ClickEventComponent>
|
||||
<button>Click me!</button>
|
||||
</ClickEventComponent>
|
||||
);
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
expect(container.innerHTML).toBe('<button>Click me!</button>');
|
||||
|
||||
// Clicking the button should trigger the event responder onEvent() twice
|
||||
dispatchClickEvent(document.body);
|
||||
expect(eventResponderFiredCount).toBe(1);
|
||||
expect(eventLog.length).toBe(1);
|
||||
expect(eventLog).toEqual([
|
||||
{
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: 'root',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -816,6 +816,7 @@ function completeWork(
|
||||
context: null,
|
||||
props: newProps,
|
||||
responder,
|
||||
rootEventTypes: null,
|
||||
rootInstance: rootContainerInstance,
|
||||
state: responderState,
|
||||
};
|
||||
|
||||
@@ -86,7 +86,8 @@ export type ReactEventResponderEventType =
|
||||
| {name: string, passive?: boolean, capture?: boolean};
|
||||
|
||||
export type ReactEventResponder = {
|
||||
targetEventTypes: Array<ReactEventResponderEventType>,
|
||||
targetEventTypes?: Array<ReactEventResponderEventType>,
|
||||
rootEventTypes?: Array<ReactEventResponderEventType>,
|
||||
createInitialState?: (props: null | Object) => Object,
|
||||
stopLocalPropagation: boolean,
|
||||
onEvent?: (
|
||||
@@ -123,6 +124,7 @@ export type ReactEventComponentInstance = {|
|
||||
context: null | Object,
|
||||
props: null | Object,
|
||||
responder: ReactEventResponder,
|
||||
rootEventTypes: null | Set<string>,
|
||||
rootInstance: mixed,
|
||||
state: null | Object,
|
||||
|};
|
||||
|
||||
Reference in New Issue
Block a user