Experimental Event API: add rootEventTypes support to event responders (#15475)

* Adds rootEventTypes
This commit is contained in:
Dominic Gannaway
2019-04-23 19:55:50 +01:00
committed by GitHub
parent 784ebd8fa9
commit 017d6f14b7
6 changed files with 141 additions and 8 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
+3 -1
View File
@@ -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,
|};