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
6 changes: 4 additions & 2 deletions packages/@react-aria/interactions/src/usePress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,9 @@ export function usePress(props: PressHookProps): PressResult {
// However, iOS and Android do not focus or fire onClick after a long press.
// We work around this by triggering a click ourselves after a timeout.
// This timeout is canceled during the click event in case the real one fires first.
// In testing, a 0ms delay is too short. 5ms seems long enough for the browser to fire the real events.
// The timeout must be at least 32ms, because Safari on iOS delays the click event on
// non-form elements without certain ARIA roles (for hover emulation).
// https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892
let clicked = false;
let timeout = setTimeout(() => {
if (state.isPressed && state.target instanceof HTMLElement) {
Expand All @@ -509,7 +511,7 @@ export function usePress(props: PressHookProps): PressResult {
state.target.click();
}
}
}, 5);
}, 40);
// Use a capturing listener to track if a click occurred.
// If stopPropagation is called it may never reach our handler.
addGlobalListener(e.currentTarget as Document, 'click', () => clicked = true, true);
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-aria/interactions/test/usePress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('usePress', function () {
expect(shouldFocus).toBe(true);

// Mouse events are not fired in this case, and the browser does not focus the element.
act(() => jest.advanceTimersByTime(10));
act(() => jest.advanceTimersByTime(50));
expect(document.activeElement).toBe(el);

expect(events).toEqual([
Expand Down Expand Up @@ -352,7 +352,7 @@ describe('usePress', function () {
expect(shouldClick).toBe(true);
fireEvent(el, pointerEvent('pointerout', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0}));

act(() => jest.advanceTimersByTime(10));
act(() => jest.advanceTimersByTime(50));

expect(events).toEqual([
{
Expand Down
10 changes: 4 additions & 6 deletions packages/@react-aria/radio/src/useRadio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,14 @@ export function useRadio(props: AriaRadioProps, state: RadioGroupState, ref: Ref
state.setSelectedValue(value);
};

// Handle press state for keyboard interactions and cases where labelProps is not used.
let {pressProps, isPressed} = usePress({
isDisabled
});

// iOS does not toggle radios if you drag off and back onto the label, so handle it ourselves.
// Handle press state on the label.
let {pressProps: labelProps, isPressed: isLabelPressed} = usePress({
isDisabled,
onPress() {
state.setSelectedValue(value);
}
isDisabled
});

let {focusableProps} = useFocusable(mergeProps(props, {
Expand All @@ -97,7 +95,7 @@ export function useRadio(props: AriaRadioProps, state: RadioGroupState, ref: Ref
useFormValidation({validationBehavior}, state, ref);

return {
labelProps: mergeProps(labelProps, {onClick: e => e.preventDefault()}),
labelProps,
inputProps: mergeProps(domProps, {
...interactions,
type: 'radio',
Expand Down
11 changes: 4 additions & 7 deletions packages/@react-aria/toggle/src/useToggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ export function useToggle(props: AriaToggleProps, state: ToggleState, ref: RefOb
console.warn('If you do not provide children, you must specify an aria-label for accessibility');
}

// This handles focusing the input on pointer down, which Safari does not do by default.
// Handle press state for keyboard interactions and cases where labelProps is not used.
let {pressProps, isPressed} = usePress({
isDisabled
});

// iOS does not toggle checkboxes if you drag off and back onto the label, so handle it ourselves.
// Handle press state on the label.
let {pressProps: labelProps, isPressed: isLabelPressed} = usePress({
isDisabled: isDisabled || isReadOnly,
onPress() {
state.toggle();
}
isDisabled: isDisabled || isReadOnly
});

let {focusableProps} = useFocusable(props, ref);
Expand All @@ -84,7 +81,7 @@ export function useToggle(props: AriaToggleProps, state: ToggleState, ref: RefOb
useFormReset(ref, state.isSelected, state.setSelected);

return {
labelProps: mergeProps(labelProps, {onClick: e => e.preventDefault()}),
labelProps,
inputProps: mergeProps(domProps, {
'aria-invalid': isInvalid || validationState === 'invalid' || undefined,
'aria-errormessage': props['aria-errormessage'],
Expand Down
Loading