[fix] Pressable keyboard onPress bug

Don't call onPress when keyup is called on a different element than the
target.

Fix #2605
Close #2632
This commit is contained in:
Bernhard Owen Josephus
2024-04-02 16:10:32 +08:00
committed by Nicolas Gallagher
parent a0cd8ffba4
commit 7d60ff83d3
3 changed files with 79 additions and 44 deletions
@@ -61,14 +61,14 @@ exports[`components/Pressable hover interaction 3`] = `
/>
`;
exports[`components/Pressable press interaction (keyboard) 1`] = `
exports[`components/Pressable press interaction (keyboard) trigger press when keyup is on the same element 1`] = `
<div
class="css-view-175oi2r r-cursor-1loqt21 r-touchAction-1otgn73"
tabindex="0"
/>
`;
exports[`components/Pressable press interaction (keyboard) 2`] = `
exports[`components/Pressable press interaction (keyboard) trigger press when keyup is on the same element 2`] = `
<div
class="css-view-175oi2r r-cursor-1loqt21 r-touchAction-1otgn73"
style="outline: press-ring;"
@@ -80,7 +80,7 @@ exports[`components/Pressable press interaction (keyboard) 2`] = `
</div>
`;
exports[`components/Pressable press interaction (keyboard) 3`] = `null`;
exports[`components/Pressable press interaction (keyboard) trigger press when keyup is on the same element 3`] = `null`;
exports[`components/Pressable press interaction (pointer) 1`] = `
<div
@@ -200,50 +200,80 @@ describe('components/Pressable', () => {
expect(onContextMenu).toBeCalled();
});
test('press interaction (keyboard)', () => {
let container;
const onPress = jest.fn();
const onPressIn = jest.fn();
const onPressOut = jest.fn();
const ref = React.createRef();
describe('press interaction (keyboard)', () => {
test('trigger press when keyup is on the same element', () => {
let container;
const onPress = jest.fn();
const onPressIn = jest.fn();
const onPressOut = jest.fn();
const ref = React.createRef();
function TestCase() {
const [shown, setShown] = React.useState(true);
return shown ? (
<Pressable
children={({ pressed }) =>
pressed ? <div data-testid="press-content" /> : null
}
onPress={(e) => {
onPress(e);
setShown(false);
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
ref={ref}
style={({ pressed }) => [pressed && { outline: 'press-ring' }]}
/>
) : null;
}
function TestCase() {
const [shown, setShown] = React.useState(true);
return shown ? (
<Pressable
children={({ pressed }) =>
pressed ? <div data-testid="press-content" /> : null
}
onPress={(e) => {
onPress(e);
setShown(false);
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
ref={ref}
style={({ pressed }) => [pressed && { outline: 'press-ring' }]}
/>
) : null;
}
act(() => {
({ container } = render(<TestCase />));
act(() => {
({ container } = render(<TestCase />));
});
const target = createEventTarget(ref.current);
expect(container.firstChild).toMatchSnapshot();
act(() => {
target.keydown({ key: 'Enter' });
jest.runAllTimers();
});
expect(onPressIn).toBeCalled();
expect(container.firstChild).toMatchSnapshot();
act(() => {
target.keyup({ key: 'Enter' });
jest.runAllTimers();
});
expect(onPressOut).toBeCalled();
expect(onPress).toBeCalled();
expect(container.firstChild).toMatchSnapshot();
});
const target = createEventTarget(ref.current);
expect(container.firstChild).toMatchSnapshot();
act(() => {
target.keydown({ key: 'Enter' });
jest.runAllTimers();
test('ignore press when keyup is on a different element', () => {
const onPress = jest.fn();
const firstRef = React.createRef();
function TestCase() {
return (
<Pressable
onPress={(e) => {
onPress(e);
}}
ref={firstRef}
/>
);
}
act(() => {
render(<TestCase />);
});
const target = createEventTarget(firstRef.current);
const body = createEventTarget(document.body);
act(() => {
target.keydown({ key: 'Enter' });
body.keyup({ key: 'Enter' });
jest.runAllTimers();
});
expect(onPress).not.toBeCalled();
});
expect(onPressIn).toBeCalled();
expect(container.firstChild).toMatchSnapshot();
act(() => {
target.keyup({ key: 'Enter' });
jest.runAllTimers();
});
expect(onPressOut).toBeCalled();
expect(onPress).toBeCalled();
expect(container.firstChild).toMatchSnapshot();
});
test('press interaction as button (keyboard)', () => {
@@ -233,6 +233,7 @@ export default class PressResponder {
pageY: number
|}>;
_touchState: TouchState = NOT_RESPONDER;
_responderElement: ?HTMLElement = null;
constructor(config: PressResponderConfig) {
this.configure(config);
@@ -320,10 +321,13 @@ export default class PressResponder {
elementType === 'input' ||
elementType === 'select' ||
elementType === 'textarea';
const isActiveElement = this._responderElement === target;
if (onPress != null && !isNativeInteractiveElement) {
if (onPress != null && !isNativeInteractiveElement && isActiveElement) {
onPress(event);
}
this._responderElement = null;
}
};
@@ -345,6 +349,7 @@ export default class PressResponder {
if (!disabled && isValidKeyPress(event)) {
if (this._touchState === NOT_RESPONDER) {
start(event, false);
this._responderElement = target;
// Listen to 'keyup' on document to account for situations where
// focus is moved to another element during 'keydown'.
document.addEventListener('keyup', keyupHandler);