mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-31 09:44:21 +00:00
[fix] ResponderSystem negotiation logic
This fixes a bug in the negotiation logic that caused a cycle of terminate->grant events to be sent to the current responder during a pointer move. The root cause was using an incorrect event path in the calculation of the lowest common ancestor's index. The fix is to ensure that the event path stored with the current responder is pruned to begin with the node that is the current responder (rather than any child responders it may have contained).
This commit is contained in:
@@ -64,3 +64,9 @@ Called when the pointer is released, but not if cancelled (e.g. by a scroll that
|
|||||||
<Stories.feedbackEvents />
|
<Stories.feedbackEvents />
|
||||||
</Story>
|
</Story>
|
||||||
</Preview>
|
</Preview>
|
||||||
|
|
||||||
|
<Preview withSource='none'>
|
||||||
|
<Story name="panAndPress">
|
||||||
|
<Stories.panAndPress />
|
||||||
|
</Story>
|
||||||
|
</Preview>
|
||||||
|
|||||||
@@ -56,13 +56,12 @@ export default function FeedbackEvents() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="none"
|
accessibilityRole="button"
|
||||||
onLongPress={handlePress('longPress - inner')}
|
onLongPress={handlePress('longPress - inner')}
|
||||||
onPress={handlePress('press - inner')}
|
onPress={handlePress('press - inner')}
|
||||||
onPressIn={handlePress('pressIn - inner')}
|
onPressIn={handlePress('pressIn - inner')}
|
||||||
onPressOut={handlePress('pressOut - inner')}
|
onPressOut={handlePress('pressOut - inner')}
|
||||||
style={({ hovered, pressed, focused }) => {
|
style={({ hovered, pressed, focused }) => {
|
||||||
console.log(focused);
|
|
||||||
let backgroundColor = 'white';
|
let backgroundColor = 'white';
|
||||||
if (hovered) {
|
if (hovered) {
|
||||||
backgroundColor = 'lightgray';
|
backgroundColor = 'lightgray';
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { Animated, View, StyleSheet, PanResponder, Text, TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const pan = useRef(new Animated.ValueXY()).current;
|
||||||
|
const [x, setX] = useState(0);
|
||||||
|
|
||||||
|
const panResponder = useRef(null);
|
||||||
|
if (panResponder.current == null) {
|
||||||
|
panResponder.current = PanResponder.create({
|
||||||
|
onMoveShouldSetPanResponder: () => true,
|
||||||
|
onPanResponderGrant: e => {
|
||||||
|
console.log('pan grant');
|
||||||
|
pan.setOffset({
|
||||||
|
x: pan.x._value,
|
||||||
|
y: pan.y._value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }]),
|
||||||
|
onPanResponderRelease: () => {
|
||||||
|
console.log('pan release');
|
||||||
|
pan.flattenOffset();
|
||||||
|
},
|
||||||
|
onPanResponderTerminate() {
|
||||||
|
console.log('pan terminate');
|
||||||
|
pan.flattenOffset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.titleText}>Pressed: {x}</Text>
|
||||||
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
transform: [{ translateX: pan.x }, { translateY: pan.y }]
|
||||||
|
}}
|
||||||
|
{...panResponder.current.panHandlers}
|
||||||
|
>
|
||||||
|
<View style={styles.box}>
|
||||||
|
<TouchableOpacity onPress={() => setX(x + 1)} style={styles.outerTouchable}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
console.log('press inner');
|
||||||
|
}}
|
||||||
|
style={styles.innerTouchable}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity disabled style={styles.innerTouchable} />
|
||||||
|
<TouchableOpacity
|
||||||
|
accessibilityRole="button"
|
||||||
|
disabled
|
||||||
|
style={[styles.innerTouchable, styles.disabledButton]}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
userSelect: 'none'
|
||||||
|
},
|
||||||
|
titleText: {
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 24,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
box: {
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
backgroundColor: 'lightblue',
|
||||||
|
borderRadius: 5
|
||||||
|
},
|
||||||
|
outerTouchable: {
|
||||||
|
height: 150,
|
||||||
|
width: 100,
|
||||||
|
margin: 25,
|
||||||
|
backgroundColor: 'blue',
|
||||||
|
borderRadius: 5,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
innerTouchable: {
|
||||||
|
height: 20,
|
||||||
|
flex: 1,
|
||||||
|
marginVertical: 10,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
backgroundColor: 'green',
|
||||||
|
borderRadius: 5
|
||||||
|
},
|
||||||
|
disabledButton: {
|
||||||
|
backgroundColor: 'red'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export { default as delayEvents } from './DelayEvents';
|
export { default as delayEvents } from './DelayEvents';
|
||||||
export { default as disabled } from './Disabled';
|
export { default as disabled } from './Disabled';
|
||||||
export { default as feedbackEvents } from './FeedbackEvents';
|
export { default as feedbackEvents } from './FeedbackEvents';
|
||||||
|
export { default as panAndPress } from './PanAndPress';
|
||||||
|
|||||||
+32
-9
@@ -374,12 +374,14 @@ function eventListener(domEvent: any) {
|
|||||||
// Start
|
// Start
|
||||||
if (isStartEvent) {
|
if (isStartEvent) {
|
||||||
if (onResponderStart != null) {
|
if (onResponderStart != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderStart';
|
||||||
onResponderStart(responderEvent);
|
onResponderStart(responderEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Move
|
// Move
|
||||||
else if (isMoveEvent) {
|
else if (isMoveEvent) {
|
||||||
if (onResponderMove != null) {
|
if (onResponderMove != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderMove';
|
||||||
onResponderMove(responderEvent);
|
onResponderMove(responderEvent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -404,12 +406,14 @@ function eventListener(domEvent: any) {
|
|||||||
// End
|
// End
|
||||||
if (isEndEvent) {
|
if (isEndEvent) {
|
||||||
if (onResponderEnd != null) {
|
if (onResponderEnd != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderEnd';
|
||||||
onResponderEnd(responderEvent);
|
onResponderEnd(responderEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Release
|
// Release
|
||||||
if (isReleaseEvent) {
|
if (isReleaseEvent) {
|
||||||
if (onResponderRelease != null) {
|
if (onResponderRelease != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderRelease';
|
||||||
onResponderRelease(responderEvent);
|
onResponderRelease(responderEvent);
|
||||||
}
|
}
|
||||||
changeCurrentResponder(emptyResponder);
|
changeCurrentResponder(emptyResponder);
|
||||||
@@ -424,18 +428,20 @@ function eventListener(domEvent: any) {
|
|||||||
eventType === 'scroll' ||
|
eventType === 'scroll' ||
|
||||||
eventType === 'selectionchange'
|
eventType === 'selectionchange'
|
||||||
) {
|
) {
|
||||||
if (
|
// Only call this function is it wasn't already called during negotiation.
|
||||||
wasNegotiated ||
|
if (wasNegotiated) {
|
||||||
// Only call this function is it wasn't already called during negotiation.
|
|
||||||
(onResponderTerminationRequest != null &&
|
|
||||||
onResponderTerminationRequest(responderEvent) === false)
|
|
||||||
) {
|
|
||||||
shouldTerminate = false;
|
shouldTerminate = false;
|
||||||
|
} else if (onResponderTerminationRequest != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
|
||||||
|
if (onResponderTerminationRequest(responderEvent) === false) {
|
||||||
|
shouldTerminate = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldTerminate) {
|
if (shouldTerminate) {
|
||||||
if (onResponderTerminate != null) {
|
if (onResponderTerminate != null) {
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
|
||||||
onResponderTerminate(responderEvent);
|
onResponderTerminate(responderEvent);
|
||||||
}
|
}
|
||||||
changeCurrentResponder(emptyResponder);
|
changeCurrentResponder(emptyResponder);
|
||||||
@@ -466,8 +472,11 @@ function findWantsResponder(eventPaths, domEvent, responderEvent) {
|
|||||||
const config = getResponderConfig(id);
|
const config = getResponderConfig(id);
|
||||||
const shouldSetCallback = config[callbackName];
|
const shouldSetCallback = config[callbackName];
|
||||||
if (shouldSetCallback != null) {
|
if (shouldSetCallback != null) {
|
||||||
|
responderEvent.currentTarget = node;
|
||||||
if (shouldSetCallback(responderEvent) === true) {
|
if (shouldSetCallback(responderEvent) === true) {
|
||||||
return { id, node, idPath };
|
// Start the path from the potential responder
|
||||||
|
const prunedIdPath = idPath.slice(idPath.indexOf(id));
|
||||||
|
return { id, node, idPath: prunedIdPath };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -521,6 +530,7 @@ function attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveR
|
|||||||
responderEvent.bubbles = false;
|
responderEvent.bubbles = false;
|
||||||
responderEvent.cancelable = false;
|
responderEvent.cancelable = false;
|
||||||
responderEvent.currentTarget = node;
|
responderEvent.currentTarget = node;
|
||||||
|
|
||||||
// Set responder
|
// Set responder
|
||||||
if (currentId == null) {
|
if (currentId == null) {
|
||||||
if (onResponderGrant != null) {
|
if (onResponderGrant != null) {
|
||||||
@@ -533,22 +543,35 @@ function attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveR
|
|||||||
// Negotiate with current responder
|
// Negotiate with current responder
|
||||||
else {
|
else {
|
||||||
const { onResponderTerminate, onResponderTerminationRequest } = getResponderConfig(currentId);
|
const { onResponderTerminate, onResponderTerminationRequest } = getResponderConfig(currentId);
|
||||||
const allowTransfer =
|
|
||||||
onResponderTerminationRequest != null && onResponderTerminationRequest(responderEvent);
|
let allowTransfer = true;
|
||||||
|
if (onResponderTerminationRequest != null) {
|
||||||
|
responderEvent.currentTarget = currentNode;
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
|
||||||
|
if (onResponderTerminationRequest(responderEvent) === false) {
|
||||||
|
allowTransfer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (allowTransfer) {
|
if (allowTransfer) {
|
||||||
// Terminate existing responder
|
// Terminate existing responder
|
||||||
if (onResponderTerminate != null) {
|
if (onResponderTerminate != null) {
|
||||||
responderEvent.currentTarget = currentNode;
|
responderEvent.currentTarget = currentNode;
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
|
||||||
onResponderTerminate(responderEvent);
|
onResponderTerminate(responderEvent);
|
||||||
}
|
}
|
||||||
// Grant next responder
|
// Grant next responder
|
||||||
if (onResponderGrant != null) {
|
if (onResponderGrant != null) {
|
||||||
|
responderEvent.currentTarget = node;
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderGrant';
|
||||||
onResponderGrant(responderEvent);
|
onResponderGrant(responderEvent);
|
||||||
}
|
}
|
||||||
changeCurrentResponder(wantsResponder);
|
changeCurrentResponder(wantsResponder);
|
||||||
} else {
|
} else {
|
||||||
// Reject responder request
|
// Reject responder request
|
||||||
if (onResponderReject != null) {
|
if (onResponderReject != null) {
|
||||||
|
responderEvent.currentTarget = node;
|
||||||
|
responderEvent.dispatchConfig.registrationName = 'onResponderReject';
|
||||||
onResponderReject(responderEvent);
|
onResponderReject(responderEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+195
-10
@@ -205,10 +205,9 @@ describe('useResponderEvents', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWithPointerType('start grants responder to grandParent', pointerType => {
|
testWithPointerType('start grants responder to grandParent', pointerType => {
|
||||||
let grantCurrentTarget, shouldSetCurrentTarget;
|
let grantCurrentTarget;
|
||||||
const grandParentCallbacks = {
|
const grandParentCallbacks = {
|
||||||
onStartShouldSetResponderCapture: jest.fn(e => {
|
onStartShouldSetResponderCapture: jest.fn(e => {
|
||||||
shouldSetCurrentTarget = e.currentTarget;
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
onResponderGrant: jest.fn(e => {
|
onResponderGrant: jest.fn(e => {
|
||||||
@@ -246,7 +245,6 @@ describe('useResponderEvents', () => {
|
|||||||
});
|
});
|
||||||
// responder set (capture phase)
|
// responder set (capture phase)
|
||||||
expect(grandParentCallbacks.onStartShouldSetResponderCapture).toBeCalledTimes(1);
|
expect(grandParentCallbacks.onStartShouldSetResponderCapture).toBeCalledTimes(1);
|
||||||
expect(shouldSetCurrentTarget).toBe(null);
|
|
||||||
expect(parentCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
|
expect(parentCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
|
||||||
expect(targetCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
|
expect(targetCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
|
||||||
// responder grant
|
// responder grant
|
||||||
@@ -1637,11 +1635,185 @@ describe('useResponderEvents', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When there is an active responder, negotiation captures to and bubbles from
|
* When there is an active responder, negotiation of the active pointer captures to
|
||||||
* the ancestor registered with the system. The responder is transferred and
|
* and bubbles from the closest common ancestor registered with the system. The
|
||||||
* the relevant termination events are called.
|
* responder is transferred and maintained for subsequent events of the same type.
|
||||||
*/
|
*/
|
||||||
test('negotiates from first registered ancestor of responder and transfers', () => {
|
test('negotiates single-touch from first registered ancestor of responder and transfers', () => {
|
||||||
|
const pointerType = 'touch';
|
||||||
|
const eventLog = [];
|
||||||
|
const grandParentCallbacks = {
|
||||||
|
onStartShouldSetResponderCapture() {
|
||||||
|
eventLog.push('grandParent: onStartShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onStartShouldSetResponder() {
|
||||||
|
eventLog.push('grandParent: onStartShouldSetResponder');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponderCapture() {
|
||||||
|
eventLog.push('grandParent: onMoveShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponder() {
|
||||||
|
eventLog.push('grandParent: onMoveShouldSetResponder');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onResponderGrant() {
|
||||||
|
eventLog.push('grandParent: onResponderGrant');
|
||||||
|
},
|
||||||
|
onResponderStart() {
|
||||||
|
eventLog.push('grandParent: onResponderStart');
|
||||||
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('grandParent: onResponderMove');
|
||||||
|
},
|
||||||
|
onResponderEnd() {
|
||||||
|
eventLog.push('grandParent: onResponderEnd');
|
||||||
|
},
|
||||||
|
onResponderRelease() {
|
||||||
|
eventLog.push('grandParent: onResponderRelease');
|
||||||
|
},
|
||||||
|
onResponderTerminate() {
|
||||||
|
eventLog.push('grandParent: onResponderTerminate');
|
||||||
|
},
|
||||||
|
onResponderTerminationRequest() {
|
||||||
|
eventLog.push('grandParent: onResponderTerminationRequest');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const parentCallbacks = {
|
||||||
|
onStartShouldSetResponderCapture() {
|
||||||
|
eventLog.push('parent: onStartShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onStartShouldSetResponder() {
|
||||||
|
eventLog.push('parent: onStartShouldSetResponder');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponderCapture() {
|
||||||
|
eventLog.push('parent: onMoveShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponder() {
|
||||||
|
eventLog.push('parent: onMoveShouldSetResponder');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onResponderGrant() {
|
||||||
|
eventLog.push('parent: onResponderGrant');
|
||||||
|
},
|
||||||
|
onResponderStart() {
|
||||||
|
eventLog.push('parent: onResponderStart');
|
||||||
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('parent: onResponderMove');
|
||||||
|
},
|
||||||
|
onResponderEnd() {
|
||||||
|
eventLog.push('parent: onResponderEnd');
|
||||||
|
},
|
||||||
|
onResponderRelease() {
|
||||||
|
eventLog.push('parent: onResponderRelease');
|
||||||
|
},
|
||||||
|
onResponderTerminate() {
|
||||||
|
eventLog.push('parent: onResponderTerminate');
|
||||||
|
},
|
||||||
|
onResponderTerminationRequest() {
|
||||||
|
eventLog.push('parent: onResponderTerminationRequest');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const targetCallbacks = {
|
||||||
|
onStartShouldSetResponderCapture() {
|
||||||
|
eventLog.push('target: onStartShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onStartShouldSetResponder() {
|
||||||
|
eventLog.push('target: onStartShouldSetResponder');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponderCapture() {
|
||||||
|
eventLog.push('target: onMoveShouldSetResponderCapture');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onMoveShouldSetResponder() {
|
||||||
|
eventLog.push('target: onMoveShouldSetResponder');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onResponderGrant() {
|
||||||
|
eventLog.push('target: onResponderGrant');
|
||||||
|
},
|
||||||
|
onResponderStart() {
|
||||||
|
eventLog.push('target: onResponderStart');
|
||||||
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('target: onResponderMove');
|
||||||
|
},
|
||||||
|
onResponderEnd() {
|
||||||
|
eventLog.push('target: onResponderEnd');
|
||||||
|
},
|
||||||
|
onResponderRelease() {
|
||||||
|
eventLog.push('target: onResponderRelease');
|
||||||
|
},
|
||||||
|
onResponderTerminate() {
|
||||||
|
eventLog.push('target: onResponderTerminate');
|
||||||
|
},
|
||||||
|
onResponderTerminationRequest() {
|
||||||
|
eventLog.push('target: onResponderTerminationRequest');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = () => {
|
||||||
|
useResponderEvents(grandParentRef, grandParentCallbacks);
|
||||||
|
useResponderEvents(parentRef, parentCallbacks);
|
||||||
|
useResponderEvents(targetRef, targetCallbacks);
|
||||||
|
return (
|
||||||
|
<div ref={grandParentRef}>
|
||||||
|
<div ref={parentRef}>
|
||||||
|
<div ref={targetRef} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// render
|
||||||
|
act(() => {
|
||||||
|
render(<Component />);
|
||||||
|
});
|
||||||
|
const target = createEventTarget(targetRef.current);
|
||||||
|
|
||||||
|
// gesture start
|
||||||
|
act(() => {
|
||||||
|
target.pointerdown({ pointerType, pointerId: 1 });
|
||||||
|
target.pointermove({ pointerType, pointerId: 1 });
|
||||||
|
target.pointermove({ pointerType, pointerId: 1 });
|
||||||
|
});
|
||||||
|
expect(eventLog).toEqual([
|
||||||
|
'grandParent: onStartShouldSetResponderCapture',
|
||||||
|
'parent: onStartShouldSetResponderCapture',
|
||||||
|
'target: onStartShouldSetResponderCapture',
|
||||||
|
'target: onStartShouldSetResponder',
|
||||||
|
'target: onResponderGrant',
|
||||||
|
'target: onResponderStart',
|
||||||
|
'grandParent: onMoveShouldSetResponderCapture',
|
||||||
|
'parent: onMoveShouldSetResponderCapture',
|
||||||
|
'parent: onMoveShouldSetResponder',
|
||||||
|
'grandParent: onMoveShouldSetResponder',
|
||||||
|
'target: onResponderTerminationRequest',
|
||||||
|
'target: onResponderTerminate',
|
||||||
|
'grandParent: onResponderGrant',
|
||||||
|
'grandParent: onResponderMove',
|
||||||
|
// Continues calling 'move' rather than entering into negotiation again
|
||||||
|
'grandParent: onResponderMove'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When there is an active responder, negotiation of a second pointer captures to
|
||||||
|
* and bubbles from the closest common ancestor registered with the system. The
|
||||||
|
* responder is transferred andvthe relevant termination events are called.
|
||||||
|
*/
|
||||||
|
test('negotiates multi-touch from first registered ancestor of responder and transfers', () => {
|
||||||
const pointerType = 'touch';
|
const pointerType = 'touch';
|
||||||
let eventLog = [];
|
let eventLog = [];
|
||||||
const grandParentCallbacks = {
|
const grandParentCallbacks = {
|
||||||
@@ -1667,6 +1839,9 @@ describe('useResponderEvents', () => {
|
|||||||
onResponderStart() {
|
onResponderStart() {
|
||||||
eventLog.push('grandParent: onResponderStart');
|
eventLog.push('grandParent: onResponderStart');
|
||||||
},
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('grandParent: onResponderMove');
|
||||||
|
},
|
||||||
onResponderEnd() {
|
onResponderEnd() {
|
||||||
eventLog.push('grandParent: onResponderEnd');
|
eventLog.push('grandParent: onResponderEnd');
|
||||||
},
|
},
|
||||||
@@ -1704,6 +1879,9 @@ describe('useResponderEvents', () => {
|
|||||||
onResponderStart() {
|
onResponderStart() {
|
||||||
eventLog.push('parent: onResponderStart');
|
eventLog.push('parent: onResponderStart');
|
||||||
},
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('parent: onResponderMove');
|
||||||
|
},
|
||||||
onResponderEnd() {
|
onResponderEnd() {
|
||||||
eventLog.push('parent: onResponderEnd');
|
eventLog.push('parent: onResponderEnd');
|
||||||
},
|
},
|
||||||
@@ -1741,6 +1919,9 @@ describe('useResponderEvents', () => {
|
|||||||
onResponderStart() {
|
onResponderStart() {
|
||||||
eventLog.push('target: onResponderStart');
|
eventLog.push('target: onResponderStart');
|
||||||
},
|
},
|
||||||
|
onResponderMove() {
|
||||||
|
eventLog.push('target: onResponderMove');
|
||||||
|
},
|
||||||
onResponderEnd() {
|
onResponderEnd() {
|
||||||
eventLog.push('target: onResponderEnd');
|
eventLog.push('target: onResponderEnd');
|
||||||
},
|
},
|
||||||
@@ -1816,21 +1997,25 @@ describe('useResponderEvents', () => {
|
|||||||
'parent: onMoveShouldSetResponder',
|
'parent: onMoveShouldSetResponder',
|
||||||
'target: onResponderTerminationRequest',
|
'target: onResponderTerminationRequest',
|
||||||
'target: onResponderTerminate',
|
'target: onResponderTerminate',
|
||||||
'parent: onResponderGrant'
|
'parent: onResponderGrant',
|
||||||
|
'parent: onResponderMove'
|
||||||
]);
|
]);
|
||||||
eventLog = [];
|
eventLog = [];
|
||||||
// second move gesture
|
// second move gesture
|
||||||
act(() => {
|
act(() => {
|
||||||
target.pointermove({ pointerType, pointerId: 1 });
|
target.pointermove({ pointerType, pointerId: 1 });
|
||||||
|
target.pointermove({ pointerType, pointerId: 2 });
|
||||||
});
|
});
|
||||||
// parent becomes responder, parent terminates
|
// grand parent becomes responder, parent terminates
|
||||||
expect(getResponderNode()).toBe(grandParentRef.current);
|
expect(getResponderNode()).toBe(grandParentRef.current);
|
||||||
expect(eventLog).toEqual([
|
expect(eventLog).toEqual([
|
||||||
'grandParent: onMoveShouldSetResponderCapture',
|
'grandParent: onMoveShouldSetResponderCapture',
|
||||||
'grandParent: onMoveShouldSetResponder',
|
'grandParent: onMoveShouldSetResponder',
|
||||||
'parent: onResponderTerminationRequest',
|
'parent: onResponderTerminationRequest',
|
||||||
'parent: onResponderTerminate',
|
'parent: onResponderTerminate',
|
||||||
'grandParent: onResponderGrant'
|
'grandParent: onResponderGrant',
|
||||||
|
'grandParent: onResponderMove',
|
||||||
|
'grandParent: onResponderMove'
|
||||||
]);
|
]);
|
||||||
eventLog = [];
|
eventLog = [];
|
||||||
// end gestures
|
// end gestures
|
||||||
|
|||||||
Reference in New Issue
Block a user