mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Experimental event API: rework the propagation system for event components (#15462)
This commit is contained in:
+48
-64
@@ -18,6 +18,7 @@ import {
|
||||
HostComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import type {
|
||||
ReactEventResponder,
|
||||
ReactEventResponderEventType,
|
||||
ReactEventComponentInstance,
|
||||
ReactResponderContext,
|
||||
@@ -61,13 +62,10 @@ type ResponderTimeout = {|
|
||||
|
||||
type ResponderTimer = {|
|
||||
instance: ReactEventComponentInstance,
|
||||
func: () => boolean,
|
||||
func: () => void,
|
||||
id: Symbol,
|
||||
|};
|
||||
|
||||
const ROOT_PHASE = 0;
|
||||
const BUBBLE_PHASE = 1;
|
||||
const CAPTURE_PHASE = 2;
|
||||
const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
|
||||
const rootEventTypesToEventComponentInstances: Map<
|
||||
DOMTopLevelEventType | string,
|
||||
@@ -253,7 +251,7 @@ const eventResponderContext: ReactResponderContext = {
|
||||
triggerOwnershipListeners();
|
||||
return false;
|
||||
},
|
||||
setTimeout(func: () => boolean, delay): Symbol {
|
||||
setTimeout(func: () => void, delay): Symbol {
|
||||
validateResponderContext();
|
||||
if (currentTimers === null) {
|
||||
currentTimers = new Map();
|
||||
@@ -349,16 +347,13 @@ const eventResponderContext: ReactResponderContext = {
|
||||
|
||||
function processTimers(timers: Map<Symbol, ResponderTimer>): void {
|
||||
const timersArr = Array.from(timers.values());
|
||||
let shouldStopPropagation = false;
|
||||
currentEventQueue = createEventQueue();
|
||||
try {
|
||||
for (let i = 0; i < timersArr.length; i++) {
|
||||
const {instance, func, id} = timersArr[i];
|
||||
currentInstance = instance;
|
||||
try {
|
||||
if (!shouldStopPropagation) {
|
||||
shouldStopPropagation = func();
|
||||
}
|
||||
func();
|
||||
} finally {
|
||||
activeTimeouts.delete(id);
|
||||
}
|
||||
@@ -390,7 +385,6 @@ function createResponderEvent(
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: Element | Document,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
phase: 0 | 1 | 2,
|
||||
): ReactResponderEvent {
|
||||
const responderEvent = {
|
||||
nativeEvent: nativeEvent,
|
||||
@@ -398,7 +392,6 @@ function createResponderEvent(
|
||||
type: topLevelType,
|
||||
passive: (eventSystemFlags & IS_PASSIVE) !== 0,
|
||||
passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
|
||||
phase,
|
||||
};
|
||||
if (__DEV__) {
|
||||
Object.freeze(responderEvent);
|
||||
@@ -510,16 +503,7 @@ function getRootEventResponderInstances(
|
||||
return eventResponderInstances;
|
||||
}
|
||||
|
||||
function triggerEventResponderEventListener(
|
||||
responderEvent: ReactResponderEvent,
|
||||
eventComponentInstance: ReactEventComponentInstance,
|
||||
): boolean {
|
||||
const {responder, props, state} = eventComponentInstance;
|
||||
currentInstance = eventComponentInstance;
|
||||
return responder.onEvent(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
|
||||
function traverseAndTriggerEventResponderInstances(
|
||||
function traverseAndHandleEventResponderInstances(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
targetFiber: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
@@ -535,46 +519,54 @@ function traverseAndTriggerEventResponderInstances(
|
||||
topLevelType,
|
||||
targetFiber,
|
||||
);
|
||||
const responderEvent = createResponderEvent(
|
||||
((topLevelType: any): string),
|
||||
nativeEvent,
|
||||
((nativeEventTarget: any): Element | Document),
|
||||
eventSystemFlags,
|
||||
);
|
||||
const propagatedEventResponders: Set<ReactEventResponder> = new Set();
|
||||
let length = targetEventResponderInstances.length;
|
||||
let i;
|
||||
let shouldStopPropagation = false;
|
||||
let responderEvent;
|
||||
|
||||
// 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
|
||||
responderEvent = createResponderEvent(
|
||||
((topLevelType: any): string),
|
||||
nativeEvent,
|
||||
((nativeEventTarget: any): Element | Document),
|
||||
eventSystemFlags,
|
||||
CAPTURE_PHASE,
|
||||
);
|
||||
for (i = length; i-- > 0; ) {
|
||||
const targetEventResponderInstance = targetEventResponderInstances[i];
|
||||
shouldStopPropagation = triggerEventResponderEventListener(
|
||||
responderEvent,
|
||||
targetEventResponderInstance,
|
||||
);
|
||||
if (shouldStopPropagation) {
|
||||
return;
|
||||
const {responder, props, state} = targetEventResponderInstance;
|
||||
if (responder.stopLocalPropagation) {
|
||||
if (propagatedEventResponders.has(responder)) {
|
||||
continue;
|
||||
}
|
||||
propagatedEventResponders.add(responder);
|
||||
}
|
||||
const eventListener = responder.onEventCapture;
|
||||
if (eventListener !== undefined) {
|
||||
currentInstance = targetEventResponderInstance;
|
||||
eventListener(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
// We clean propagated event responders between phases.
|
||||
propagatedEventResponders.clear();
|
||||
// Bubble target phase
|
||||
responderEvent = createResponderEvent(
|
||||
((topLevelType: any): string),
|
||||
nativeEvent,
|
||||
((nativeEventTarget: any): Element | Document),
|
||||
eventSystemFlags,
|
||||
BUBBLE_PHASE,
|
||||
);
|
||||
for (i = 0; i < length; i++) {
|
||||
const targetEventResponderInstance = targetEventResponderInstances[i];
|
||||
shouldStopPropagation = triggerEventResponderEventListener(
|
||||
responderEvent,
|
||||
targetEventResponderInstance,
|
||||
);
|
||||
if (shouldStopPropagation) {
|
||||
return;
|
||||
const {responder, props, state} = targetEventResponderInstance;
|
||||
if (responder.stopLocalPropagation) {
|
||||
if (propagatedEventResponders.has(responder)) {
|
||||
continue;
|
||||
}
|
||||
propagatedEventResponders.add(responder);
|
||||
}
|
||||
const eventListener = responder.onEvent;
|
||||
if (eventListener !== undefined) {
|
||||
currentInstance = targetEventResponderInstance;
|
||||
eventListener(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,21 +576,13 @@ function traverseAndTriggerEventResponderInstances(
|
||||
);
|
||||
length = rootEventResponderInstances.length;
|
||||
if (length > 0) {
|
||||
responderEvent = createResponderEvent(
|
||||
((topLevelType: any): string),
|
||||
nativeEvent,
|
||||
((nativeEventTarget: any): Element | Document),
|
||||
eventSystemFlags,
|
||||
ROOT_PHASE,
|
||||
);
|
||||
for (i = 0; i < length; i++) {
|
||||
const targetEventResponderInstance = rootEventResponderInstances[i];
|
||||
shouldStopPropagation = triggerEventResponderEventListener(
|
||||
responderEvent,
|
||||
targetEventResponderInstance,
|
||||
);
|
||||
if (shouldStopPropagation) {
|
||||
return;
|
||||
const rootEventResponderInstance = rootEventResponderInstances[i];
|
||||
const {responder, props, state} = rootEventResponderInstance;
|
||||
const eventListener = responder.onRootEvent;
|
||||
if (eventListener !== undefined) {
|
||||
currentInstance = rootEventResponderInstance;
|
||||
eventListener(responderEvent, eventResponderContext, props, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,7 +656,7 @@ export function dispatchEventForResponderEventSystem(
|
||||
if (enableEventAPI) {
|
||||
currentEventQueue = createEventQueue();
|
||||
try {
|
||||
traverseAndTriggerEventResponderInstances(
|
||||
traverseAndHandleEventResponderInstances(
|
||||
topLevelType,
|
||||
targetFiber,
|
||||
nativeEvent,
|
||||
|
||||
+154
-166
@@ -18,15 +18,21 @@ function createReactEventComponent(
|
||||
targetEventTypes,
|
||||
createInitialState,
|
||||
onEvent,
|
||||
onEventCapture,
|
||||
onRootEvent,
|
||||
onUnmount,
|
||||
onOwnershipChange,
|
||||
stopLocalPropagation,
|
||||
) {
|
||||
const testEventResponder = {
|
||||
targetEventTypes,
|
||||
createInitialState,
|
||||
onEvent,
|
||||
onEventCapture,
|
||||
onRootEvent,
|
||||
onUnmount,
|
||||
onOwnershipChange,
|
||||
stopLocalPropagation: stopLocalPropagation || false,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -37,18 +43,6 @@ function createReactEventComponent(
|
||||
};
|
||||
}
|
||||
|
||||
const ROOT_PHASE = 0;
|
||||
const BUBBLE_PHASE = 1;
|
||||
const CAPTURE_PHASE = 2;
|
||||
|
||||
function phaseToString(phase) {
|
||||
return phase === ROOT_PHASE
|
||||
? 'root'
|
||||
: phase === BUBBLE_PHASE
|
||||
? 'bubble'
|
||||
: 'capture';
|
||||
}
|
||||
|
||||
function dispatchEvent(element, type) {
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent(type, true, true);
|
||||
@@ -88,7 +82,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('the event responder onEvent() function should fire on click event', () => {
|
||||
it('the event responder event listeners should fire on click event', () => {
|
||||
let eventResponderFiredCount = 0;
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
@@ -102,7 +96,16 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: event.phase,
|
||||
phase: 'bubble',
|
||||
});
|
||||
},
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: 'capture',
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -127,13 +130,13 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: CAPTURE_PHASE,
|
||||
phase: 'capture',
|
||||
},
|
||||
{
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: BUBBLE_PHASE,
|
||||
phase: 'bubble',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -149,7 +152,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
expect(eventResponderFiredCount).toBe(4);
|
||||
});
|
||||
|
||||
it('the event responder onEvent() function should fire on click event (passive events forced)', () => {
|
||||
it('the event responder event listeners should fire on click event (passive events forced)', () => {
|
||||
// JSDOM does not support passive events, so this manually overrides the value to be true
|
||||
const checkPassiveEvents = require('react-dom/src/events/checkPassiveEvents');
|
||||
checkPassiveEvents.passiveBrowserEventsSupported = true;
|
||||
@@ -165,7 +168,15 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: event.phase,
|
||||
phase: 'bubble',
|
||||
});
|
||||
},
|
||||
(event, context, props) => {
|
||||
eventLog.push({
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: 'capture',
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -187,18 +198,18 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: 'click',
|
||||
passive: true,
|
||||
passiveSupported: true,
|
||||
phase: CAPTURE_PHASE,
|
||||
phase: 'capture',
|
||||
},
|
||||
{
|
||||
name: 'click',
|
||||
passive: true,
|
||||
passiveSupported: true,
|
||||
phase: BUBBLE_PHASE,
|
||||
phase: 'bubble',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested event responders and their onEvent() function should fire multiple times', () => {
|
||||
it('nested event responders and their event listeners should fire multiple times', () => {
|
||||
let eventResponderFiredCount = 0;
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
@@ -212,7 +223,16 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: event.phase,
|
||||
phase: 'bubble',
|
||||
});
|
||||
},
|
||||
(event, context, props) => {
|
||||
eventResponderFiredCount++;
|
||||
eventLog.push({
|
||||
name: event.type,
|
||||
passive: event.passive,
|
||||
passiveSupported: event.passiveSupported,
|
||||
phase: 'capture',
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -238,30 +258,30 @@ describe('DOMEventResponderSystem', () => {
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: CAPTURE_PHASE,
|
||||
phase: 'capture',
|
||||
},
|
||||
{
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: CAPTURE_PHASE,
|
||||
phase: 'capture',
|
||||
},
|
||||
{
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: BUBBLE_PHASE,
|
||||
phase: 'bubble',
|
||||
},
|
||||
{
|
||||
name: 'click',
|
||||
passive: false,
|
||||
passiveSupported: false,
|
||||
phase: BUBBLE_PHASE,
|
||||
phase: 'bubble',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested event responders and their onEvent() should fire in the correct order', () => {
|
||||
it('nested event responders and their event listeners should fire in the correct order', () => {
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
@@ -269,7 +289,10 @@ describe('DOMEventResponderSystem', () => {
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`A [${phaseToString(event.phase)}]`);
|
||||
eventLog.push(`A [bubble]`);
|
||||
},
|
||||
(event, context, props) => {
|
||||
eventLog.push(`A [capture]`);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -277,7 +300,10 @@ describe('DOMEventResponderSystem', () => {
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
eventLog.push(`B [${phaseToString(event.phase)}]`);
|
||||
eventLog.push(`B [bubble]`);
|
||||
},
|
||||
(event, context, props) => {
|
||||
eventLog.push(`B [capture]`);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -303,140 +329,81 @@ describe('DOMEventResponderSystem', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested event responders and their onEvent() should fire in the correct order with stopPropagation', () => {
|
||||
let eventLog;
|
||||
let stopPropagationOnPhase;
|
||||
it('nested event responders should fire in the correct order without stopLocalPropagation', () => {
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
const ClickEventComponentA = createReactEventComponent(
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
context.addRootEventTypes(event.target.ownerDocument, [
|
||||
'click',
|
||||
'pointermove',
|
||||
]);
|
||||
eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
|
||||
eventLog.push(`${props.name} [bubble]`);
|
||||
},
|
||||
);
|
||||
|
||||
const ClickEventComponentB = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
context.addRootEventTypes(event.target.ownerDocument, [
|
||||
'click',
|
||||
'pointermove',
|
||||
]);
|
||||
eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
|
||||
if (event.phase === stopPropagationOnPhase) {
|
||||
return true;
|
||||
}
|
||||
eventLog.push(`${props.name} [capture]`);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
|
||||
const Test = () => (
|
||||
<ClickEventComponentA>
|
||||
<ClickEventComponentB>
|
||||
<ClickEventComponent name="A">
|
||||
<ClickEventComponent name="B">
|
||||
<button ref={buttonRef}>Click me!</button>
|
||||
</ClickEventComponentB>
|
||||
</ClickEventComponentA>
|
||||
</ClickEventComponent>
|
||||
</ClickEventComponent>
|
||||
);
|
||||
|
||||
function runTestWithPhase(phase) {
|
||||
eventLog = [];
|
||||
stopPropagationOnPhase = phase;
|
||||
ReactDOM.render(<Test />, container);
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
dispatchEvent(buttonElement, 'pointermove');
|
||||
}
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
|
||||
runTestWithPhase(BUBBLE_PHASE);
|
||||
// Root phase should not be skipped for different event type
|
||||
expect(eventLog).toEqual([
|
||||
'A [click, capture]',
|
||||
'B [click, capture]',
|
||||
'B [click, bubble]',
|
||||
'A [pointermove, root]',
|
||||
'B [pointermove, root]',
|
||||
]);
|
||||
runTestWithPhase(CAPTURE_PHASE);
|
||||
// Root phase should not be skipped for different event type
|
||||
expect(eventLog).toEqual([
|
||||
'A [click, capture]',
|
||||
'B [click, capture]',
|
||||
'A [pointermove, root]',
|
||||
'B [pointermove, root]',
|
||||
'A [capture]',
|
||||
'B [capture]',
|
||||
'B [bubble]',
|
||||
'A [bubble]',
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested event responders and their onEvent() should fire in the correct order with stopPropagation #2', () => {
|
||||
let eventLog;
|
||||
let stopPropagationOnPhase;
|
||||
it('nested event responders should fire in the correct order with stopLocalPropagation', () => {
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
const ClickEventComponentA = createReactEventComponent(
|
||||
const ClickEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
context.addRootEventTypes(event.target.ownerDocument, [
|
||||
'click',
|
||||
'pointermove',
|
||||
]);
|
||||
eventLog.push(`A [${event.type}, ${phaseToString(event.phase)}]`);
|
||||
if (event.phase === stopPropagationOnPhase) {
|
||||
return true;
|
||||
}
|
||||
eventLog.push(`${props.name} [bubble]`);
|
||||
},
|
||||
);
|
||||
|
||||
const ClickEventComponentB = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
context.addRootEventTypes(event.target.ownerDocument, [
|
||||
'click',
|
||||
'pointermove',
|
||||
]);
|
||||
eventLog.push(`B [${event.type}, ${phaseToString(event.phase)}]`);
|
||||
eventLog.push(`${props.name} [capture]`);
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const Test = () => (
|
||||
<ClickEventComponentA>
|
||||
<ClickEventComponentB>
|
||||
<ClickEventComponent name="A">
|
||||
<ClickEventComponent name="B">
|
||||
<button ref={buttonRef}>Click me!</button>
|
||||
</ClickEventComponentB>
|
||||
</ClickEventComponentA>
|
||||
</ClickEventComponent>
|
||||
</ClickEventComponent>
|
||||
);
|
||||
|
||||
function runTestWithPhase(phase) {
|
||||
eventLog = [];
|
||||
stopPropagationOnPhase = phase;
|
||||
ReactDOM.render(<Test />, container);
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
dispatchEvent(buttonElement, 'pointermove');
|
||||
}
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
runTestWithPhase(BUBBLE_PHASE);
|
||||
// Root phase should not be skipped for different event type
|
||||
expect(eventLog).toEqual([
|
||||
'A [click, capture]',
|
||||
'B [click, capture]',
|
||||
'B [click, bubble]',
|
||||
'A [click, bubble]',
|
||||
'A [pointermove, root]',
|
||||
'B [pointermove, root]',
|
||||
]);
|
||||
runTestWithPhase(CAPTURE_PHASE);
|
||||
// Root phase should not be skipped for different event type
|
||||
expect(eventLog).toEqual([
|
||||
'A [click, capture]',
|
||||
'A [pointermove, root]',
|
||||
'B [pointermove, root]',
|
||||
]);
|
||||
// Clicking the button should trigger the event responder onEvent()
|
||||
let buttonElement = buttonRef.current;
|
||||
dispatchClickEvent(buttonElement);
|
||||
|
||||
expect(eventLog).toEqual(['A [capture]', 'B [bubble]']);
|
||||
});
|
||||
|
||||
it('custom event dispatching for click -> magicClick works', () => {
|
||||
@@ -451,7 +418,19 @@ describe('DOMEventResponderSystem', () => {
|
||||
const syntheticEvent = {
|
||||
target: event.target,
|
||||
type: 'magicclick',
|
||||
phase: phaseToString(event.phase),
|
||||
phase: 'bubble',
|
||||
};
|
||||
context.dispatchEvent(syntheticEvent, props.onMagicClick, {
|
||||
discrete: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
(event, context, props) => {
|
||||
if (props.onMagicClick) {
|
||||
const syntheticEvent = {
|
||||
target: event.target,
|
||||
type: 'magicclick',
|
||||
phase: 'capture',
|
||||
};
|
||||
context.dispatchEvent(syntheticEvent, props.onMagicClick, {
|
||||
discrete: true,
|
||||
@@ -490,42 +469,47 @@ describe('DOMEventResponderSystem', () => {
|
||||
let eventLog = [];
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
function handleEvent(event, context, props, phase) {
|
||||
const pressEvent = {
|
||||
target: event.target,
|
||||
type: 'press',
|
||||
phase,
|
||||
};
|
||||
context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
|
||||
|
||||
context.setTimeout(() => {
|
||||
if (props.onLongPress) {
|
||||
const longPressEvent = {
|
||||
target: event.target,
|
||||
type: 'longpress',
|
||||
phase,
|
||||
};
|
||||
context.dispatchEvent(longPressEvent, props.onLongPress, {
|
||||
discrete: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.onLongPressChange) {
|
||||
const longPressChangeEvent = {
|
||||
target: event.target,
|
||||
type: 'longpresschange',
|
||||
phase,
|
||||
};
|
||||
context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, {
|
||||
discrete: true,
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const LongPressEventComponent = createReactEventComponent(
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props) => {
|
||||
const pressEvent = {
|
||||
target: event.target,
|
||||
type: 'press',
|
||||
phase: phaseToString(event.phase),
|
||||
};
|
||||
context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
|
||||
|
||||
context.setTimeout(() => {
|
||||
if (props.onLongPress) {
|
||||
const longPressEvent = {
|
||||
target: event.target,
|
||||
type: 'longpress',
|
||||
phase: phaseToString(event.phase),
|
||||
};
|
||||
context.dispatchEvent(longPressEvent, props.onLongPress, {
|
||||
discrete: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.onLongPressChange) {
|
||||
const longPressChangeEvent = {
|
||||
target: event.target,
|
||||
type: 'longpresschange',
|
||||
phase: phaseToString(event.phase),
|
||||
};
|
||||
context.dispatchEvent(
|
||||
longPressChangeEvent,
|
||||
props.onLongPressChange,
|
||||
{discrete: true},
|
||||
);
|
||||
}
|
||||
}, 500);
|
||||
handleEvent(event, context, props, 'bubble');
|
||||
},
|
||||
(event, context, props) => {
|
||||
handleEvent(event, context, props, 'capture');
|
||||
},
|
||||
);
|
||||
|
||||
@@ -565,6 +549,8 @@ describe('DOMEventResponderSystem', () => {
|
||||
const EventComponent = createReactEventComponent(
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
(event, context, props, state) => {},
|
||||
() => {
|
||||
onUnmountFired++;
|
||||
@@ -590,7 +576,9 @@ describe('DOMEventResponderSystem', () => {
|
||||
() => ({
|
||||
incrementAmount: 5,
|
||||
}),
|
||||
(event, context, props, state) => {},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
(context, props, state) => {
|
||||
counter += state.incrementAmount;
|
||||
},
|
||||
@@ -616,11 +604,11 @@ describe('DOMEventResponderSystem', () => {
|
||||
['click'],
|
||||
undefined,
|
||||
(event, context, props, state) => {
|
||||
if (event.phase === BUBBLE_PHASE) {
|
||||
ownershipGained = context.requestOwnership();
|
||||
}
|
||||
ownershipGained = context.requestOwnership();
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
() => {
|
||||
onOwnershipChangeFired++;
|
||||
},
|
||||
|
||||
Vendored
+23
-13
@@ -13,9 +13,12 @@ import type {
|
||||
} from 'shared/ReactTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const CAPTURE_PHASE = 2;
|
||||
const targetEventTypes = ['pointerdown', 'pointercancel'];
|
||||
const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
|
||||
const targetEventTypes = ['pointerdown'];
|
||||
const rootEventTypes = [
|
||||
'pointerup',
|
||||
'pointercancel',
|
||||
{name: 'pointermove', passive: false},
|
||||
];
|
||||
|
||||
type DragState = {
|
||||
dragTarget: null | Element | Document,
|
||||
@@ -30,8 +33,8 @@ type DragState = {
|
||||
// In the case we don't have PointerEvents (Safari), we listen to touch events
|
||||
// too
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
|
||||
rootEventTypes.push('mouseup', 'mousemove', {
|
||||
targetEventTypes.push('touchstart', 'mousedown');
|
||||
rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
|
||||
name: 'touchmove',
|
||||
passive: false,
|
||||
});
|
||||
@@ -88,18 +91,15 @@ const DragResponder = {
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
stopLocalPropagation: true,
|
||||
onEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: Object,
|
||||
state: DragState,
|
||||
): boolean {
|
||||
const {target, phase, type, nativeEvent} = event;
|
||||
): void {
|
||||
const {target, type, nativeEvent} = event;
|
||||
|
||||
// Drag doesn't handle capture target events at this point
|
||||
if (phase === CAPTURE_PHASE) {
|
||||
return false;
|
||||
}
|
||||
switch (type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
@@ -133,11 +133,22 @@ const DragResponder = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: Object,
|
||||
state: DragState,
|
||||
): void {
|
||||
const {type, nativeEvent} = event;
|
||||
|
||||
switch (type) {
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
case 'pointermove': {
|
||||
if (event.passive) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (state.isPointerDown) {
|
||||
const obj =
|
||||
@@ -230,7 +241,6 @@ const DragResponder = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Vendored
+3
-13
@@ -14,8 +14,6 @@ import type {
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
import {getEventCurrentTarget} from './utils.js';
|
||||
|
||||
const CAPTURE_PHASE = 2;
|
||||
|
||||
type FocusProps = {
|
||||
disabled: boolean,
|
||||
onBlur: (e: FocusEvent) => void,
|
||||
@@ -112,10 +110,8 @@ const FocusResponder = {
|
||||
context: ReactResponderContext,
|
||||
props: Object,
|
||||
state: FocusState,
|
||||
): boolean {
|
||||
const {type, phase, target} = event;
|
||||
const shouldStopPropagation =
|
||||
props.stopPropagation === undefined ? true : props.stopPropagation;
|
||||
): void {
|
||||
const {type, target} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
if (state.isFocused) {
|
||||
@@ -123,12 +119,7 @@ const FocusResponder = {
|
||||
state.isFocused = false;
|
||||
state.focusTarget = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Focus doesn't handle capture target events at this point
|
||||
if (phase === CAPTURE_PHASE) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
@@ -153,7 +144,6 @@ const FocusResponder = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
},
|
||||
onUnmount(
|
||||
context: ReactResponderContext,
|
||||
|
||||
Vendored
+9
-19
@@ -18,8 +18,6 @@ import {
|
||||
isEventPositionWithinTouchHitTarget,
|
||||
} from './utils';
|
||||
|
||||
const CAPTURE_PHASE = 2;
|
||||
|
||||
type HoverProps = {
|
||||
disabled: boolean,
|
||||
delayHoverEnd: number,
|
||||
@@ -141,7 +139,6 @@ function dispatchHoverStartEvents(
|
||||
state.hoverStartTimeout = context.setTimeout(() => {
|
||||
state.hoverStartTimeout = null;
|
||||
activate();
|
||||
return false;
|
||||
}, delayHoverStart);
|
||||
} else {
|
||||
activate();
|
||||
@@ -201,7 +198,6 @@ function dispatchHoverEndEvents(
|
||||
if (delayHoverEnd > 0) {
|
||||
state.hoverEndTimeout = context.setTimeout(() => {
|
||||
deactivate();
|
||||
return false;
|
||||
}, delayHoverEnd);
|
||||
} else {
|
||||
deactivate();
|
||||
@@ -245,12 +241,13 @@ const HoverResponder = {
|
||||
ignoreEmulatedMouseEvents: false,
|
||||
};
|
||||
},
|
||||
stopLocalPropagation: true,
|
||||
onEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: HoverProps,
|
||||
state: HoverState,
|
||||
): boolean {
|
||||
): void {
|
||||
const {type} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
@@ -261,14 +258,8 @@ const HoverResponder = {
|
||||
if (state.isTouched) {
|
||||
state.isTouched = false;
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Hover doesn't handle capture target events at this point
|
||||
if (event.phase === CAPTURE_PHASE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pointerType = getEventPointerType(event);
|
||||
|
||||
switch (type) {
|
||||
@@ -280,23 +271,23 @@ const HoverResponder = {
|
||||
// Prevent hover events for touch
|
||||
if (state.isTouched || pointerType === 'touch') {
|
||||
state.isTouched = true;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent hover events for emulated events
|
||||
if (isEmulatedMouseEvent(event, state)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEventPositionWithinTouchHitTarget(event, context)) {
|
||||
state.isOverTouchHitTarget = true;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
state.hoverTarget = getEventCurrentTarget(event, context);
|
||||
state.ignoreEmulatedMouseEvents = true;
|
||||
dispatchHoverStartEvents(event, context, props, state);
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// MOVE
|
||||
@@ -331,7 +322,7 @@ const HoverResponder = {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// END
|
||||
@@ -347,10 +338,9 @@ const HoverResponder = {
|
||||
if (state.isTouched) {
|
||||
state.isTouched = false;
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onUnmount(
|
||||
context: ReactResponderContext,
|
||||
|
||||
Vendored
+64
-59
@@ -19,8 +19,6 @@ import {
|
||||
isEventPositionWithinTouchHitTarget,
|
||||
} from './utils';
|
||||
|
||||
const CAPTURE_PHASE = 2;
|
||||
|
||||
type PressProps = {
|
||||
disabled: boolean,
|
||||
delayLongPress: number,
|
||||
@@ -103,10 +101,11 @@ const rootEventTypes = ['keyup', 'pointerup', 'pointermove', 'scroll'];
|
||||
|
||||
// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'touchend', 'touchcancel', 'mousedown');
|
||||
targetEventTypes.push('touchstart', 'touchcancel', 'mousedown');
|
||||
rootEventTypes.push(
|
||||
{name: 'mouseup', passive: false},
|
||||
'touchmove',
|
||||
'touchend',
|
||||
'mousemove',
|
||||
);
|
||||
}
|
||||
@@ -199,8 +198,6 @@ function dispatchPressStartEvents(
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const shouldStopPropagation =
|
||||
props.stopPropagation === undefined ? true : props.stopPropagation;
|
||||
state.isPressed = true;
|
||||
|
||||
if (state.pressEndTimeout !== null) {
|
||||
@@ -230,7 +227,6 @@ function dispatchPressStartEvents(
|
||||
if (props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
}, delayLongPress);
|
||||
}
|
||||
};
|
||||
@@ -245,7 +241,6 @@ function dispatchPressStartEvents(
|
||||
state.pressStartTimeout = context.setTimeout(() => {
|
||||
state.pressStartTimeout = null;
|
||||
dispatch();
|
||||
return shouldStopPropagation;
|
||||
}, delayPressStart);
|
||||
} else {
|
||||
dispatch();
|
||||
@@ -258,8 +253,6 @@ function dispatchPressEndEvents(
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const shouldStopPropagation =
|
||||
props.stopPropagation === undefined ? true : props.stopPropagation;
|
||||
const wasActivePressStart = state.isActivePressStart;
|
||||
let activationWasForced = false;
|
||||
|
||||
@@ -295,7 +288,6 @@ function dispatchPressEndEvents(
|
||||
state.pressEndTimeout = context.setTimeout(() => {
|
||||
state.pressEndTimeout = null;
|
||||
deactivate(context, props, state);
|
||||
return shouldStopPropagation;
|
||||
}, delayPressEnd);
|
||||
} else {
|
||||
deactivate(context, props, state);
|
||||
@@ -381,6 +373,24 @@ function unmountResponder(
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchCancel(
|
||||
type: string,
|
||||
nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
|
||||
context: ReactResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.isPressed) {
|
||||
if (type === 'contextmenu' && props.preventDefault !== false) {
|
||||
(nativeEvent: any).preventDefault();
|
||||
} else {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PressResponder = {
|
||||
targetEventTypes,
|
||||
createInitialState(): PressState {
|
||||
@@ -400,30 +410,23 @@ const PressResponder = {
|
||||
ignoreEmulatedMouseEvents: false,
|
||||
};
|
||||
},
|
||||
stopLocalPropagation: true,
|
||||
onEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): boolean {
|
||||
const {phase, target, type} = event;
|
||||
): void {
|
||||
const {target, type} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Press doesn't handle capture target events at this point
|
||||
if (phase === CAPTURE_PHASE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const pointerType = getEventPointerType(event);
|
||||
const shouldStopPropagation =
|
||||
props.stopPropagation === undefined ? true : props.stopPropagation;
|
||||
|
||||
switch (type) {
|
||||
// START
|
||||
@@ -440,7 +443,7 @@ const PressResponder = {
|
||||
// Ignore unrelated key events
|
||||
if (pointerType === 'keyboard') {
|
||||
if (!isValidKeyPress(nativeEvent.key)) {
|
||||
return shouldStopPropagation;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,13 +454,13 @@ const PressResponder = {
|
||||
state.ignoreEmulatedMouseEvents ||
|
||||
isEventPositionWithinTouchHitTarget(event, context)
|
||||
) {
|
||||
return shouldStopPropagation;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore any device buttons except left-mouse and touch/pen contact
|
||||
if (nativeEvent.button > 0) {
|
||||
return shouldStopPropagation;
|
||||
return;
|
||||
}
|
||||
|
||||
state.pointerType = pointerType;
|
||||
@@ -465,17 +468,46 @@ const PressResponder = {
|
||||
state.isPressWithinResponderRegion = true;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(target.ownerDocument, rootEventTypes);
|
||||
return shouldStopPropagation;
|
||||
} else {
|
||||
// Prevent spacebar press from scrolling the window
|
||||
if (isValidKeyPress(nativeEvent.key) && nativeEvent.key === ' ') {
|
||||
nativeEvent.preventDefault();
|
||||
return shouldStopPropagation;
|
||||
}
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
break;
|
||||
}
|
||||
|
||||
// CANCEL
|
||||
case 'contextmenu': {
|
||||
dispatchCancel(type, nativeEvent, context, props, state);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (isAnchorTagElement(target)) {
|
||||
const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
|
||||
// Check "open in new window/tab" and "open context menu" key modifiers
|
||||
const preventDefault = props.preventDefault;
|
||||
if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const {target, type} = event;
|
||||
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const pointerType = getEventPointerType(event);
|
||||
|
||||
switch (type) {
|
||||
// MOVE
|
||||
case 'pointermove':
|
||||
case 'mousemove':
|
||||
@@ -484,7 +516,7 @@ const PressResponder = {
|
||||
// Ignore emulated events (pointermove will dispatch touch and mouse events)
|
||||
// Ignore pointermove events during a keyboard press
|
||||
if (state.pointerType !== pointerType) {
|
||||
return shouldStopPropagation;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.responderRegion == null) {
|
||||
@@ -504,9 +536,8 @@ const PressResponder = {
|
||||
state.isPressWithinResponderRegion = false;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
// END
|
||||
@@ -518,7 +549,7 @@ const PressResponder = {
|
||||
// Ignore unrelated keyboard events
|
||||
if (pointerType === 'keyboard') {
|
||||
if (!isValidKeyPress(nativeEvent.key)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,45 +570,19 @@ const PressResponder = {
|
||||
}
|
||||
}
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
return shouldStopPropagation;
|
||||
} else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
// CANCEL
|
||||
case 'contextmenu':
|
||||
case 'pointercancel':
|
||||
case 'scroll':
|
||||
case 'touchcancel': {
|
||||
if (state.isPressed) {
|
||||
if (type === 'contextmenu' && props.preventDefault !== false) {
|
||||
nativeEvent.preventDefault();
|
||||
} else {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (isAnchorTagElement(target)) {
|
||||
const {ctrlKey, metaKey, shiftKey} = (nativeEvent: MouseEvent);
|
||||
// Check "open in new window/tab" and "open context menu" key modifiers
|
||||
const preventDefault = props.preventDefault;
|
||||
if (preventDefault !== false && !shiftKey && !metaKey && !ctrlKey) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
return shouldStopPropagation;
|
||||
}
|
||||
return false;
|
||||
dispatchCancel(type, nativeEvent, context, props, state);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onUnmount(
|
||||
context: ReactResponderContext,
|
||||
|
||||
Vendored
+25
-15
@@ -13,15 +13,18 @@ import type {
|
||||
} from 'shared/ReactTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const CAPTURE_PHASE = 2;
|
||||
const targetEventTypes = ['pointerdown', 'pointercancel'];
|
||||
const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
|
||||
const targetEventTypes = ['pointerdown'];
|
||||
const rootEventTypes = [
|
||||
'pointerup',
|
||||
'pointercancel',
|
||||
{name: 'pointermove', passive: false},
|
||||
];
|
||||
|
||||
// In the case we don't have PointerEvents (Safari), we listen to touch events
|
||||
// too
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
|
||||
rootEventTypes.push('mouseup', 'mousemove', {
|
||||
targetEventTypes.push('touchstart', 'mousedown');
|
||||
rootEventTypes.push('mouseup', 'mousemove', 'touchend', 'touchcancel', {
|
||||
name: 'touchmove',
|
||||
passive: false,
|
||||
});
|
||||
@@ -92,18 +95,15 @@ const SwipeResponder = {
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
stopLocalPropagation: true,
|
||||
onEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: Object,
|
||||
state: SwipeState,
|
||||
): boolean {
|
||||
const {target, phase, type, nativeEvent} = event;
|
||||
): void {
|
||||
const {target, type, nativeEvent} = event;
|
||||
|
||||
// Swipe doesn't handle capture target events at this point
|
||||
if (phase === CAPTURE_PHASE) {
|
||||
return false;
|
||||
}
|
||||
switch (type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
@@ -136,11 +136,22 @@ const SwipeResponder = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: Object,
|
||||
state: SwipeState,
|
||||
): void {
|
||||
const {type, nativeEvent} = event;
|
||||
|
||||
switch (type) {
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
case 'pointermove': {
|
||||
if (event.passive) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (state.isSwiping) {
|
||||
let obj = null;
|
||||
@@ -160,7 +171,7 @@ const SwipeResponder = {
|
||||
state.swipeTarget = null;
|
||||
state.touchId = null;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
const x = (obj: any).screenX;
|
||||
const y = (obj: any).screenY;
|
||||
@@ -196,7 +207,7 @@ const SwipeResponder = {
|
||||
case 'pointerup': {
|
||||
if (state.isSwiping) {
|
||||
if (state.x === state.startX && state.y === state.startY) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (props.onShouldClaimOwnership) {
|
||||
context.releaseOwnership();
|
||||
@@ -240,7 +251,6 @@ const SwipeResponder = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1127,15 +1127,10 @@ describe('Event responder: Press', () => {
|
||||
'pointerdown',
|
||||
'inner: onPressStart',
|
||||
'inner: onPressChange',
|
||||
'outer: onPressStart',
|
||||
'outer: onPressChange',
|
||||
'pointerup',
|
||||
'inner: onPressEnd',
|
||||
'inner: onPressChange',
|
||||
'inner: onPress',
|
||||
'outer: onPressEnd',
|
||||
'outer: onPressChange',
|
||||
'outer: onPress',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1214,82 +1209,6 @@ describe('Event responder: Press', () => {
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly bubble to other event responders when stopPropagation is set to false', () => {
|
||||
it('for onPress', () => {
|
||||
const ref = React.createRef();
|
||||
const fn = jest.fn();
|
||||
const element = (
|
||||
<Press onPress={fn}>
|
||||
<Press onPress={fn} stopPropagation={false}>
|
||||
<div ref={ref} />
|
||||
</Press>
|
||||
</Press>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('for onLongPress', () => {
|
||||
const ref = React.createRef();
|
||||
const fn = jest.fn();
|
||||
const element = (
|
||||
<Press onLongPress={fn}>
|
||||
<Press onLongPress={fn} stopPropagation={false}>
|
||||
<div ref={ref} />
|
||||
</Press>
|
||||
</Press>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('for onPressStart/onPressEnd', () => {
|
||||
const ref = React.createRef();
|
||||
const fn = jest.fn();
|
||||
const fn2 = jest.fn();
|
||||
const element = (
|
||||
<Press onPressStart={fn} onPressEnd={fn2}>
|
||||
<Press onPressStart={fn} onPressEnd={fn2} stopPropagation={false}>
|
||||
<div ref={ref} />
|
||||
</Press>
|
||||
</Press>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn2).toHaveBeenCalledTimes(0);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
expect(fn2).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('for onPressChange', () => {
|
||||
const ref = React.createRef();
|
||||
const fn = jest.fn();
|
||||
const element = (
|
||||
<Press onPressChange={fn}>
|
||||
<Press onPressChange={fn} stopPropagation={false}>
|
||||
<div ref={ref} />
|
||||
</Press>
|
||||
</Press>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
expect(fn).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('link components', () => {
|
||||
|
||||
@@ -88,12 +88,25 @@ export type ReactEventResponderEventType =
|
||||
export type ReactEventResponder = {
|
||||
targetEventTypes: Array<ReactEventResponderEventType>,
|
||||
createInitialState?: (props: null | Object) => Object,
|
||||
onEvent: (
|
||||
stopLocalPropagation: boolean,
|
||||
onEvent?: (
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: null | Object,
|
||||
state: null | Object,
|
||||
) => boolean,
|
||||
) => void,
|
||||
onEventCapture?: (
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: null | Object,
|
||||
state: null | Object,
|
||||
) => void,
|
||||
onRootEvent?: (
|
||||
event: ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: null | Object,
|
||||
state: null | Object,
|
||||
) => void,
|
||||
onUnmount: (
|
||||
context: ReactResponderContext,
|
||||
props: null | Object,
|
||||
@@ -135,7 +148,6 @@ export type ReactResponderEvent = {
|
||||
type: string,
|
||||
passive: boolean,
|
||||
passiveSupported: boolean,
|
||||
phase: 0 | 1 | 2,
|
||||
};
|
||||
|
||||
export type ReactResponderDispatchEventOptions = {
|
||||
@@ -168,7 +180,7 @@ export type ReactResponderContext = {
|
||||
hasOwnership: () => boolean,
|
||||
requestOwnership: () => boolean,
|
||||
releaseOwnership: () => boolean,
|
||||
setTimeout: (func: () => boolean, timeout: number) => Symbol,
|
||||
setTimeout: (func: () => void, timeout: number) => Symbol,
|
||||
clearTimeout: (timerId: Symbol) => void,
|
||||
getEventTargetsFromTarget: (
|
||||
target: Element | Document,
|
||||
|
||||
Reference in New Issue
Block a user