Skip to content

Commit ff5b0a6

Browse files
authored
Fix usePress with HTML input elements (#3446)
1 parent 13696a9 commit ff5b0a6

File tree

2 files changed

+124
-9
lines changed

2 files changed

+124
-9
lines changed

packages/@react-aria/interactions/src/usePress.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export function usePress(props: PressHookProps): PressResult {
229229
let pressProps: DOMAttributes = {
230230
onKeyDown(e) {
231231
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target as Element)) {
232-
if (shouldPreventDefaultKeyboard(e.target as Element)) {
232+
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
233233
e.preventDefault();
234234
}
235235
e.stopPropagation();
@@ -290,7 +290,7 @@ export function usePress(props: PressHookProps): PressResult {
290290

291291
let onKeyUp = (e: KeyboardEvent) => {
292292
if (state.isPressed && isValidKeyboardEvent(e, state.target)) {
293-
if (shouldPreventDefaultKeyboard(e.target as Element)) {
293+
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
294294
e.preventDefault();
295295
}
296296
e.stopPropagation();
@@ -674,15 +674,14 @@ function isHTMLAnchorLink(target: Element): boolean {
674674
function isValidKeyboardEvent(event: KeyboardEvent, currentTarget: Element): boolean {
675675
const {key, code} = event;
676676
const element = currentTarget as HTMLElement;
677-
const {tagName, isContentEditable} = element;
678677
const role = element.getAttribute('role');
679678
// Accessibility for keyboards. Space and Enter only.
680679
// "Spacebar" is for IE 11
681680
return (
682681
(key === 'Enter' || key === ' ' || key === 'Spacebar' || code === 'Space') &&
683-
(tagName !== 'INPUT' &&
684-
tagName !== 'TEXTAREA' &&
685-
isContentEditable !== true) &&
682+
!((element instanceof HTMLInputElement && !isValidInputKey(element, key)) ||
683+
element instanceof HTMLTextAreaElement ||
684+
element.isContentEditable) &&
686685
// A link with a valid href should be handled natively,
687686
// unless it also has role='button' and was triggered using Space.
688687
(!isHTMLAnchorLink(element) || (role === 'button' && key !== 'Enter')) &&
@@ -774,8 +773,35 @@ function shouldPreventDefault(target: Element) {
774773
return !(target instanceof HTMLElement) || !target.draggable;
775774
}
776775

777-
function shouldPreventDefaultKeyboard(target: Element) {
778-
return !((target.tagName === 'INPUT' || target.tagName === 'BUTTON') && (target as HTMLButtonElement | HTMLInputElement).type === 'submit');
776+
function shouldPreventDefaultKeyboard(target: Element, key: string) {
777+
if (target instanceof HTMLInputElement) {
778+
return !isValidInputKey(target, key);
779+
}
780+
781+
if (target instanceof HTMLButtonElement) {
782+
return target.type !== 'submit';
783+
}
784+
785+
return true;
786+
}
787+
788+
const nonTextInputTypes = new Set([
789+
'checkbox',
790+
'radio',
791+
'range',
792+
'color',
793+
'file',
794+
'image',
795+
'button',
796+
'submit',
797+
'reset'
798+
]);
799+
800+
function isValidInputKey(target: HTMLInputElement, key: string) {
801+
// Only space should toggle checkboxes and radios, not enter.
802+
return target.type === 'checkbox' || target.type === 'radio'
803+
? key === ' '
804+
: nonTextInputTypes.has(target.type);
779805
}
780806

781807
function isVirtualPointerEvent(event: PointerEvent) {

packages/@react-aria/interactions/test/usePress.test.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {usePress} from '../';
2222
function Example(props) {
2323
let {elementType: ElementType = 'div', style, draggable, ...otherProps} = props;
2424
let {pressProps} = usePress(otherProps);
25-
return <ElementType {...pressProps} style={style} tabIndex="0" draggable={draggable}>test</ElementType>;
25+
return <ElementType {...pressProps} style={style} tabIndex="0" draggable={draggable}>{ElementType !== 'input' ? 'test' : undefined}</ElementType>;
2626
}
2727

2828
function pointerEvent(type, opts) {
@@ -2182,6 +2182,95 @@ describe('usePress', function () {
21822182

21832183
expect(events).toEqual([]);
21842184
});
2185+
2186+
it('should fire press events on checkboxes but not prevent default', function () {
2187+
let events = [];
2188+
let addEvent = (e) => events.push(e);
2189+
let {getByRole} = render(
2190+
<Example
2191+
elementType="input"
2192+
type="checkbox"
2193+
onPressStart={addEvent}
2194+
onPressEnd={addEvent}
2195+
onPressChange={pressed => addEvent({type: 'presschange', pressed})}
2196+
onPress={addEvent}
2197+
onPressUp={addEvent} />
2198+
);
2199+
2200+
let el = getByRole('checkbox');
2201+
fireEvent.keyDown(el, {key: 'Enter'});
2202+
fireEvent.keyUp(el, {key: 'Enter'});
2203+
2204+
// Enter key handled should do nothing on a checkbox
2205+
expect(events).toEqual([]);
2206+
2207+
let allow = fireEvent.keyDown(el, {key: ' '});
2208+
expect(allow).toBeTruthy();
2209+
expect(events).toEqual([
2210+
{
2211+
type: 'pressstart',
2212+
target: el,
2213+
pointerType: 'keyboard',
2214+
ctrlKey: false,
2215+
metaKey: false,
2216+
shiftKey: false,
2217+
altKey: false
2218+
},
2219+
{
2220+
type: 'presschange',
2221+
pressed: true
2222+
}
2223+
]);
2224+
2225+
allow = fireEvent.keyUp(el, {key: ' '});
2226+
expect(allow).toBeTruthy();
2227+
expect(events).toEqual([
2228+
{
2229+
type: 'pressstart',
2230+
target: el,
2231+
pointerType: 'keyboard',
2232+
ctrlKey: false,
2233+
metaKey: false,
2234+
shiftKey: false,
2235+
altKey: false
2236+
},
2237+
{
2238+
type: 'presschange',
2239+
pressed: true
2240+
},
2241+
{
2242+
type: 'pressup',
2243+
target: el,
2244+
pointerType: 'keyboard',
2245+
ctrlKey: false,
2246+
metaKey: false,
2247+
shiftKey: false,
2248+
altKey: false
2249+
},
2250+
{
2251+
type: 'pressend',
2252+
target: el,
2253+
pointerType: 'keyboard',
2254+
ctrlKey: false,
2255+
metaKey: false,
2256+
shiftKey: false,
2257+
altKey: false
2258+
},
2259+
{
2260+
type: 'presschange',
2261+
pressed: false
2262+
},
2263+
{
2264+
type: 'press',
2265+
target: el,
2266+
pointerType: 'keyboard',
2267+
ctrlKey: false,
2268+
metaKey: false,
2269+
shiftKey: false,
2270+
altKey: false
2271+
}
2272+
]);
2273+
});
21852274
});
21862275

21872276
describe('virtual click events', function () {

0 commit comments

Comments
 (0)