diff --git a/packages/@react-aria/interactions/src/usePress.ts b/packages/@react-aria/interactions/src/usePress.ts
index 64bd621d1d5..aad8a37d33e 100644
--- a/packages/@react-aria/interactions/src/usePress.ts
+++ b/packages/@react-aria/interactions/src/usePress.ts
@@ -229,7 +229,7 @@ export function usePress(props: PressHookProps): PressResult {
let pressProps: DOMAttributes = {
onKeyDown(e) {
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target as Element)) {
- if (shouldPreventDefaultKeyboard(e.target as Element)) {
+ if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
e.preventDefault();
}
e.stopPropagation();
@@ -290,7 +290,7 @@ export function usePress(props: PressHookProps): PressResult {
let onKeyUp = (e: KeyboardEvent) => {
if (state.isPressed && isValidKeyboardEvent(e, state.target)) {
- if (shouldPreventDefaultKeyboard(e.target as Element)) {
+ if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
e.preventDefault();
}
e.stopPropagation();
@@ -674,15 +674,14 @@ function isHTMLAnchorLink(target: Element): boolean {
function isValidKeyboardEvent(event: KeyboardEvent, currentTarget: Element): boolean {
const {key, code} = event;
const element = currentTarget as HTMLElement;
- const {tagName, isContentEditable} = element;
const role = element.getAttribute('role');
// Accessibility for keyboards. Space and Enter only.
// "Spacebar" is for IE 11
return (
(key === 'Enter' || key === ' ' || key === 'Spacebar' || code === 'Space') &&
- (tagName !== 'INPUT' &&
- tagName !== 'TEXTAREA' &&
- isContentEditable !== true) &&
+ !((element instanceof HTMLInputElement && !isValidInputKey(element, key)) ||
+ element instanceof HTMLTextAreaElement ||
+ element.isContentEditable) &&
// A link with a valid href should be handled natively,
// unless it also has role='button' and was triggered using Space.
(!isHTMLAnchorLink(element) || (role === 'button' && key !== 'Enter')) &&
@@ -774,8 +773,35 @@ function shouldPreventDefault(target: Element) {
return !(target instanceof HTMLElement) || !target.draggable;
}
-function shouldPreventDefaultKeyboard(target: Element) {
- return !((target.tagName === 'INPUT' || target.tagName === 'BUTTON') && (target as HTMLButtonElement | HTMLInputElement).type === 'submit');
+function shouldPreventDefaultKeyboard(target: Element, key: string) {
+ if (target instanceof HTMLInputElement) {
+ return !isValidInputKey(target, key);
+ }
+
+ if (target instanceof HTMLButtonElement) {
+ return target.type !== 'submit';
+ }
+
+ return true;
+}
+
+const nonTextInputTypes = new Set([
+ 'checkbox',
+ 'radio',
+ 'range',
+ 'color',
+ 'file',
+ 'image',
+ 'button',
+ 'submit',
+ 'reset'
+]);
+
+function isValidInputKey(target: HTMLInputElement, key: string) {
+ // Only space should toggle checkboxes and radios, not enter.
+ return target.type === 'checkbox' || target.type === 'radio'
+ ? key === ' '
+ : nonTextInputTypes.has(target.type);
}
function isVirtualPointerEvent(event: PointerEvent) {
diff --git a/packages/@react-aria/interactions/test/usePress.test.js b/packages/@react-aria/interactions/test/usePress.test.js
index 4f11e07ce83..1ef45d4d8c5 100644
--- a/packages/@react-aria/interactions/test/usePress.test.js
+++ b/packages/@react-aria/interactions/test/usePress.test.js
@@ -22,7 +22,7 @@ import {usePress} from '../';
function Example(props) {
let {elementType: ElementType = 'div', style, draggable, ...otherProps} = props;
let {pressProps} = usePress(otherProps);
- return test;
+ return {ElementType !== 'input' ? 'test' : undefined};
}
function pointerEvent(type, opts) {
@@ -2182,6 +2182,95 @@ describe('usePress', function () {
expect(events).toEqual([]);
});
+
+ it('should fire press events on checkboxes but not prevent default', function () {
+ let events = [];
+ let addEvent = (e) => events.push(e);
+ let {getByRole} = render(
+ addEvent({type: 'presschange', pressed})}
+ onPress={addEvent}
+ onPressUp={addEvent} />
+ );
+
+ let el = getByRole('checkbox');
+ fireEvent.keyDown(el, {key: 'Enter'});
+ fireEvent.keyUp(el, {key: 'Enter'});
+
+ // Enter key handled should do nothing on a checkbox
+ expect(events).toEqual([]);
+
+ let allow = fireEvent.keyDown(el, {key: ' '});
+ expect(allow).toBeTruthy();
+ expect(events).toEqual([
+ {
+ type: 'pressstart',
+ target: el,
+ pointerType: 'keyboard',
+ ctrlKey: false,
+ metaKey: false,
+ shiftKey: false,
+ altKey: false
+ },
+ {
+ type: 'presschange',
+ pressed: true
+ }
+ ]);
+
+ allow = fireEvent.keyUp(el, {key: ' '});
+ expect(allow).toBeTruthy();
+ expect(events).toEqual([
+ {
+ type: 'pressstart',
+ target: el,
+ pointerType: 'keyboard',
+ ctrlKey: false,
+ metaKey: false,
+ shiftKey: false,
+ altKey: false
+ },
+ {
+ type: 'presschange',
+ pressed: true
+ },
+ {
+ type: 'pressup',
+ target: el,
+ pointerType: 'keyboard',
+ ctrlKey: false,
+ metaKey: false,
+ shiftKey: false,
+ altKey: false
+ },
+ {
+ type: 'pressend',
+ target: el,
+ pointerType: 'keyboard',
+ ctrlKey: false,
+ metaKey: false,
+ shiftKey: false,
+ altKey: false
+ },
+ {
+ type: 'presschange',
+ pressed: false
+ },
+ {
+ type: 'press',
+ target: el,
+ pointerType: 'keyboard',
+ ctrlKey: false,
+ metaKey: false,
+ shiftKey: false,
+ altKey: false
+ }
+ ]);
+ });
});
describe('virtual click events', function () {