From 9e1d8d6db4194d8bba92cb63e2560027dd57ac45 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Mon, 28 Sep 2020 17:39:14 -0700 Subject: [PATCH] Fix #918 Adding preventFocusOnPress to usePress (#1073) --- .../@react-aria/interactions/src/usePress.ts | 13 ++- .../interactions/test/usePress.test.js | 96 ++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/packages/@react-aria/interactions/src/usePress.ts b/packages/@react-aria/interactions/src/usePress.ts index ac07e4d896c..df30a22f704 100644 --- a/packages/@react-aria/interactions/src/usePress.ts +++ b/packages/@react-aria/interactions/src/usePress.ts @@ -25,7 +25,9 @@ export interface PressProps extends PressEvents { /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */ isPressed?: boolean, /** Whether the press events should be disabled. */ - isDisabled?: boolean + isDisabled?: boolean, + /** Whether the target should not receive focus on press. */ + preventFocusOnPress?: boolean } export interface PressHookProps extends PressProps { @@ -93,6 +95,7 @@ export function usePress(props: PressHookProps): PressResult { onPressUp, isDisabled, isPressed: isPressedProp, + preventFocusOnPress, // eslint-disable-next-line @typescript-eslint/no-unused-vars ref: _, // Removing `ref` from `domProps` because TypeScript is dumb ...domProps @@ -233,7 +236,7 @@ export function usePress(props: PressHookProps): PressResult { // trigger as if it were a keyboard click. if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && isVirtualClick(e.nativeEvent)) { // Ensure the element receives focus (VoiceOver on iOS does not do this) - if (!isDisabled) { + if (!isDisabled && !preventFocusOnPress) { focusWithoutScrolling(e.currentTarget); } @@ -307,7 +310,7 @@ export function usePress(props: PressHookProps): PressResult { state.activePointerId = e.pointerId; state.target = e.currentTarget; - if (!isDisabled) { + if (!isDisabled && !preventFocusOnPress) { focusWithoutScrolling(e.currentTarget); } @@ -410,7 +413,7 @@ export function usePress(props: PressHookProps): PressResult { state.isOverTarget = true; state.target = e.currentTarget; - if (!isDisabled) { + if (!isDisabled && !preventFocusOnPress) { focusWithoutScrolling(e.currentTarget); } @@ -479,7 +482,7 @@ export function usePress(props: PressHookProps): PressResult { // Due to browser inconsistencies, especially on mobile browsers, we prevent default // on the emulated mouse event and handle focusing the pressable element ourselves. - if (!isDisabled) { + if (!isDisabled && !preventFocusOnPress) { focusWithoutScrolling(e.currentTarget); } diff --git a/packages/@react-aria/interactions/test/usePress.test.js b/packages/@react-aria/interactions/test/usePress.test.js index e397a853417..e2bcc03647f 100644 --- a/packages/@react-aria/interactions/test/usePress.test.js +++ b/packages/@react-aria/interactions/test/usePress.test.js @@ -17,7 +17,7 @@ import {usePress} from '../'; function Example(props) { let {elementType: ElementType = 'div', ...otherProps} = props; let {pressProps} = usePress(otherProps); - return test; + return test; } function pointerEvent(type, opts) { @@ -347,6 +347,28 @@ describe('usePress', function () { fireEvent(el, pointerEvent('pointerup', {pointerId: 1, pointerType: 'mouse', button: 1, clientX: 0, clientY: 0})); expect(events).toEqual([]); }); + + it('should not focus the target on click if preventFocusOnPress is true', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, pointerType: 'mouse'})); + fireEvent(el, pointerEvent('pointerup', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0})); + expect(document.activeElement).not.toBe(el); + }); + + it('should focus the target on click by default', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, pointerType: 'mouse'})); + fireEvent(el, pointerEvent('pointerup', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0})); + expect(document.activeElement).toBe(el); + }); }); describe('mouse events', function () { @@ -611,6 +633,32 @@ describe('usePress', function () { expect(events).toEqual([]); }); + + it('should not focus the element on click if preventFocusOnPress is true', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent.mouseDown(el); + fireEvent.mouseUp(el); + fireEvent.click(el); + + expect(document.activeElement).not.toBe(el); + }); + + it('should focus the element on click by default', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent.mouseDown(el); + fireEvent.mouseUp(el); + fireEvent.click(el); + + expect(document.activeElement).toBe(el); + }); }); describe('touch events', function () { @@ -1039,6 +1087,30 @@ describe('usePress', function () { } ]); }); + + it('should not focus the target if preventFocusOnPress is true', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent.touchStart(el, {targetTouches: [{identifier: 1}]}); + fireEvent.touchEnd(el, {changedTouches: [{identifier: 1, clientX: 0, clientY: 0}]}); + + expect(document.activeElement).not.toBe(el); + }); + + it('should focus the target on touch by default', function () { + let res = render( + + ); + + let el = res.getByText('test'); + fireEvent.touchStart(el, {targetTouches: [{identifier: 1}]}); + fireEvent.touchEnd(el, {changedTouches: [{identifier: 1, clientX: 0, clientY: 0}]}); + + expect(document.activeElement).toBe(el); + }); }); describe('keyboard events', function () { @@ -1557,4 +1629,26 @@ describe('usePress', function () { ]); }); }); + + it('should not focus the target if preventFocusOnPress is true', function () { + let {getByText} = render( + + ); + + let el = getByText('test'); + fireEvent.click(el); + + expect(document.activeElement).not.toBe(el); + }); + + it('should focus the target on virtual click by default', function () { + let {getByText} = render( + + ); + + let el = getByText('test'); + fireEvent.click(el); + + expect(document.activeElement).toBe(el); + }); });