Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions packages/@react-aria/interactions/src/usePress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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')) &&
Expand Down Expand Up @@ -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'
]);
Comment on lines +788 to +798
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this list generated? I assume by going through a list of input types and adding any that wasn't a text input?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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) {
Expand Down
91 changes: 90 additions & 1 deletion packages/@react-aria/interactions/test/usePress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {usePress} from '../';
function Example(props) {
let {elementType: ElementType = 'div', style, draggable, ...otherProps} = props;
let {pressProps} = usePress(otherProps);
return <ElementType {...pressProps} style={style} tabIndex="0" draggable={draggable}>test</ElementType>;
return <ElementType {...pressProps} style={style} tabIndex="0" draggable={draggable}>{ElementType !== 'input' ? 'test' : undefined}</ElementType>;
}

function pointerEvent(type, opts) {
Expand Down Expand Up @@ -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(
<Example
elementType="input"
type="checkbox"
onPressStart={addEvent}
onPressEnd={addEvent}
onPressChange={pressed => 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 () {
Expand Down