@react-aria/interactions 3.24.1 → 3.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PressResponder.main.js +1 -1
- package/dist/PressResponder.main.js.map +1 -1
- package/dist/PressResponder.mjs +1 -1
- package/dist/PressResponder.module.js +1 -1
- package/dist/PressResponder.module.js.map +1 -1
- package/dist/Pressable.main.js +1 -0
- package/dist/Pressable.main.js.map +1 -1
- package/dist/Pressable.mjs +1 -0
- package/dist/Pressable.module.js +1 -0
- package/dist/Pressable.module.js.map +1 -1
- package/dist/createEventHandler.main.js +1 -1
- package/dist/createEventHandler.main.js.map +1 -1
- package/dist/createEventHandler.mjs +1 -1
- package/dist/createEventHandler.module.js +1 -1
- package/dist/createEventHandler.module.js.map +1 -1
- package/dist/focusSafely.main.js.map +1 -1
- package/dist/focusSafely.module.js.map +1 -1
- package/dist/textSelection.main.js.map +1 -1
- package/dist/textSelection.module.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/useFocusVisible.main.js +3 -3
- package/dist/useFocusVisible.main.js.map +1 -1
- package/dist/useFocusVisible.mjs +3 -3
- package/dist/useFocusVisible.module.js +3 -3
- package/dist/useFocusVisible.module.js.map +1 -1
- package/dist/useFocusWithin.main.js +4 -4
- package/dist/useFocusWithin.main.js.map +1 -1
- package/dist/useFocusWithin.mjs +5 -5
- package/dist/useFocusWithin.module.js +5 -5
- package/dist/useFocusWithin.module.js.map +1 -1
- package/dist/useFocusable.main.js +1 -0
- package/dist/useFocusable.main.js.map +1 -1
- package/dist/useFocusable.mjs +1 -0
- package/dist/useFocusable.module.js +1 -0
- package/dist/useFocusable.module.js.map +1 -1
- package/dist/useHover.main.js +3 -3
- package/dist/useHover.main.js.map +1 -1
- package/dist/useHover.mjs +3 -3
- package/dist/useHover.module.js +3 -3
- package/dist/useHover.module.js.map +1 -1
- package/dist/useInteractOutside.main.js +1 -1
- package/dist/useInteractOutside.main.js.map +1 -1
- package/dist/useInteractOutside.mjs +1 -1
- package/dist/useInteractOutside.module.js +1 -1
- package/dist/useInteractOutside.module.js.map +1 -1
- package/dist/useMove.main.js +1 -1
- package/dist/useMove.main.js.map +1 -1
- package/dist/useMove.mjs +1 -1
- package/dist/useMove.module.js +1 -1
- package/dist/useMove.module.js.map +1 -1
- package/dist/usePress.main.js +54 -8
- package/dist/usePress.main.js.map +1 -1
- package/dist/usePress.mjs +54 -8
- package/dist/usePress.module.js +54 -8
- package/dist/usePress.module.js.map +1 -1
- package/dist/utils.main.js +23 -32
- package/dist/utils.main.js.map +1 -1
- package/dist/utils.mjs +22 -32
- package/dist/utils.module.js +22 -32
- package/dist/utils.module.js.map +1 -1
- package/package.json +6 -6
- package/src/PressResponder.tsx +8 -6
- package/src/Pressable.tsx +4 -0
- package/src/createEventHandler.ts +1 -1
- package/src/focusSafely.ts +1 -1
- package/src/textSelection.ts +4 -4
- package/src/useFocusVisible.ts +4 -4
- package/src/useFocusWithin.ts +4 -4
- package/src/useFocusable.tsx +4 -0
- package/src/useHover.ts +3 -3
- package/src/useInteractOutside.ts +2 -2
- package/src/useMove.ts +1 -1
- package/src/usePress.ts +63 -11
- package/src/utils.ts +23 -54
package/src/PressResponder.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import {FocusableElement} from '@react-types/shared';
|
|
|
14
14
|
import {mergeProps, useObjectRef, useSyncRef} from '@react-aria/utils';
|
|
15
15
|
import {PressProps} from './usePress';
|
|
16
16
|
import {PressResponderContext} from './context';
|
|
17
|
-
import React, {ForwardedRef, ReactNode, useContext, useEffect, useMemo, useRef} from 'react';
|
|
17
|
+
import React, {ForwardedRef, JSX, ReactNode, useContext, useEffect, useMemo, useRef} from 'react';
|
|
18
18
|
|
|
19
19
|
interface PressResponderProps extends PressProps {
|
|
20
20
|
children: ReactNode
|
|
@@ -39,10 +39,12 @@ export const PressResponder = React.forwardRef(({children, ...props}: PressRespo
|
|
|
39
39
|
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
if (!isRegistered.current) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
43
|
+
console.warn(
|
|
44
|
+
'A PressResponder was rendered without a pressable child. ' +
|
|
45
|
+
'Either call the usePress hook, or wrap your DOM node with <Pressable> component.'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
46
48
|
isRegistered.current = true; // only warn once in strict mode.
|
|
47
49
|
}
|
|
48
50
|
}, []);
|
|
@@ -54,7 +56,7 @@ export const PressResponder = React.forwardRef(({children, ...props}: PressRespo
|
|
|
54
56
|
);
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
export function ClearPressResponder({children}: {children: ReactNode}) {
|
|
59
|
+
export function ClearPressResponder({children}: {children: ReactNode}): JSX.Element {
|
|
58
60
|
let context = useMemo(() => ({register: () => {}}), []);
|
|
59
61
|
return (
|
|
60
62
|
<PressResponderContext.Provider value={context}>
|
package/src/Pressable.tsx
CHANGED
|
@@ -27,6 +27,10 @@ export const Pressable = React.forwardRef(({children, ...props}: PressableProps,
|
|
|
27
27
|
let child = React.Children.only(children);
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
|
+
if (process.env.NODE_ENV === 'production') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
let el = ref.current;
|
|
31
35
|
if (!el || !(el instanceof getOwnerWindow(el).Element)) {
|
|
32
36
|
console.error('<Pressable> child must forward its ref to a DOM element.');
|
|
@@ -32,7 +32,7 @@ export function createEventHandler<T extends SyntheticEvent>(handler?: (e: BaseE
|
|
|
32
32
|
return e.isDefaultPrevented();
|
|
33
33
|
},
|
|
34
34
|
stopPropagation() {
|
|
35
|
-
if (shouldStopPropagation) {
|
|
35
|
+
if (shouldStopPropagation && process.env.NODE_ENV !== 'production') {
|
|
36
36
|
console.error('stopPropagation is now the default behavior for events in React Spectrum. You can use continuePropagation() to revert this behavior.');
|
|
37
37
|
} else {
|
|
38
38
|
shouldStopPropagation = true;
|
package/src/focusSafely.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {getInteractionModality} from './useFocusVisible';
|
|
|
23
23
|
* A utility function that focuses an element while avoiding undesired side effects such
|
|
24
24
|
* as page scrolling and screen reader issues with CSS transitions.
|
|
25
25
|
*/
|
|
26
|
-
export function focusSafely(element: FocusableElement) {
|
|
26
|
+
export function focusSafely(element: FocusableElement): void {
|
|
27
27
|
// If the user is interacting with a virtual cursor, e.g. screen reader, then
|
|
28
28
|
// wait until after any animated transitions that are currently occurring on
|
|
29
29
|
// the page before shifting focus. This avoids issues with VoiceOver on iOS
|
package/src/textSelection.ts
CHANGED
|
@@ -33,10 +33,10 @@ let state: State = 'default';
|
|
|
33
33
|
let savedUserSelect = '';
|
|
34
34
|
let modifiedElementMap = new WeakMap<Element, string>();
|
|
35
35
|
|
|
36
|
-
export function disableTextSelection(target?: Element) {
|
|
36
|
+
export function disableTextSelection(target?: Element): void {
|
|
37
37
|
if (isIOS()) {
|
|
38
38
|
if (state === 'default') {
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
const documentObject = getOwnerDocument(target);
|
|
41
41
|
savedUserSelect = documentObject.documentElement.style.webkitUserSelect;
|
|
42
42
|
documentObject.documentElement.style.webkitUserSelect = 'none';
|
|
@@ -52,7 +52,7 @@ export function disableTextSelection(target?: Element) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function restoreTextSelection(target?: Element) {
|
|
55
|
+
export function restoreTextSelection(target?: Element): void {
|
|
56
56
|
if (isIOS()) {
|
|
57
57
|
// If the state is already default, there's nothing to do.
|
|
58
58
|
// If it is restoring, then there's no need to queue a second restore.
|
|
@@ -70,7 +70,7 @@ export function restoreTextSelection(target?: Element) {
|
|
|
70
70
|
runAfterTransition(() => {
|
|
71
71
|
// Avoid race conditions
|
|
72
72
|
if (state === 'restoring') {
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
const documentObject = getOwnerDocument(target);
|
|
75
75
|
if (documentObject.documentElement.style.webkitUserSelect === 'none') {
|
|
76
76
|
documentObject.documentElement.style.webkitUserSelect = savedUserSelect || '';
|
package/src/useFocusVisible.ts
CHANGED
|
@@ -123,7 +123,7 @@ function handleWindowBlur() {
|
|
|
123
123
|
* Setup global event listeners to control when keyboard focus style should be visible.
|
|
124
124
|
*/
|
|
125
125
|
function setupGlobalFocusEvents(element?: HTMLElement | null) {
|
|
126
|
-
if (typeof window === 'undefined' || hasSetupGlobalListeners.get(getOwnerWindow(element))) {
|
|
126
|
+
if (typeof window === 'undefined' || typeof document === 'undefined' || hasSetupGlobalListeners.get(getOwnerWindow(element))) {
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -153,7 +153,7 @@ function setupGlobalFocusEvents(element?: HTMLElement | null) {
|
|
|
153
153
|
documentObject.addEventListener('pointerdown', handlePointerEvent, true);
|
|
154
154
|
documentObject.addEventListener('pointermove', handlePointerEvent, true);
|
|
155
155
|
documentObject.addEventListener('pointerup', handlePointerEvent, true);
|
|
156
|
-
} else {
|
|
156
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
157
157
|
documentObject.addEventListener('mousedown', handlePointerEvent, true);
|
|
158
158
|
documentObject.addEventListener('mousemove', handlePointerEvent, true);
|
|
159
159
|
documentObject.addEventListener('mouseup', handlePointerEvent, true);
|
|
@@ -189,7 +189,7 @@ const tearDownWindowFocusTracking = (element, loadListener?: () => void) => {
|
|
|
189
189
|
documentObject.removeEventListener('pointerdown', handlePointerEvent, true);
|
|
190
190
|
documentObject.removeEventListener('pointermove', handlePointerEvent, true);
|
|
191
191
|
documentObject.removeEventListener('pointerup', handlePointerEvent, true);
|
|
192
|
-
} else {
|
|
192
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
193
193
|
documentObject.removeEventListener('mousedown', handlePointerEvent, true);
|
|
194
194
|
documentObject.removeEventListener('mousemove', handlePointerEvent, true);
|
|
195
195
|
documentObject.removeEventListener('mouseup', handlePointerEvent, true);
|
|
@@ -247,7 +247,7 @@ export function getInteractionModality(): Modality | null {
|
|
|
247
247
|
return currentModality;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
export function setInteractionModality(modality: Modality) {
|
|
250
|
+
export function setInteractionModality(modality: Modality): void {
|
|
251
251
|
currentModality = modality;
|
|
252
252
|
triggerChangeHandlers(modality, null);
|
|
253
253
|
}
|
package/src/useFocusWithin.ts
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
// NOTICE file in the root directory of this source tree.
|
|
16
16
|
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
|
|
17
17
|
|
|
18
|
+
import {createSyntheticEvent, setEventTarget, useSyntheticBlurEvent} from './utils';
|
|
18
19
|
import {DOMAttributes} from '@react-types/shared';
|
|
19
20
|
import {FocusEvent, useCallback, useRef} from 'react';
|
|
20
21
|
import {getActiveElement, getEventTarget, getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils';
|
|
21
|
-
import {SyntheticFocusEvent, useSyntheticBlurEvent} from './utils';
|
|
22
22
|
|
|
23
23
|
export interface FocusWithinProps {
|
|
24
24
|
/** Whether the focus within events should be disabled. */
|
|
@@ -104,9 +104,9 @@ export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
|
|
|
104
104
|
let currentTarget = e.currentTarget;
|
|
105
105
|
addGlobalListener(ownerDocument, 'focus', e => {
|
|
106
106
|
if (state.current.isFocusWithin && !nodeContains(currentTarget, e.target as Element)) {
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
event
|
|
107
|
+
let nativeEvent = new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: e.target});
|
|
108
|
+
setEventTarget(nativeEvent, currentTarget);
|
|
109
|
+
let event = createSyntheticEvent<FocusEvent>(nativeEvent);
|
|
110
110
|
onBlur(event);
|
|
111
111
|
}
|
|
112
112
|
}, {capture: true});
|
package/src/useFocusable.tsx
CHANGED
|
@@ -112,6 +112,10 @@ export const Focusable = forwardRef(({children, ...props}: FocusableComponentPro
|
|
|
112
112
|
let child = React.Children.only(children);
|
|
113
113
|
|
|
114
114
|
useEffect(() => {
|
|
115
|
+
if (process.env.NODE_ENV === 'production') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
115
119
|
let el = ref.current;
|
|
116
120
|
if (!el || !(el instanceof getOwnerWindow(el).Element)) {
|
|
117
121
|
console.error('<Focusable> child must forward its ref to a DOM element.');
|
package/src/useHover.ts
CHANGED
|
@@ -61,7 +61,7 @@ function setupGlobalTouchEvents() {
|
|
|
61
61
|
|
|
62
62
|
if (typeof PointerEvent !== 'undefined') {
|
|
63
63
|
document.addEventListener('pointerup', handleGlobalPointerEvent);
|
|
64
|
-
} else {
|
|
64
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
65
65
|
document.addEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -74,7 +74,7 @@ function setupGlobalTouchEvents() {
|
|
|
74
74
|
|
|
75
75
|
if (typeof PointerEvent !== 'undefined') {
|
|
76
76
|
document.removeEventListener('pointerup', handleGlobalPointerEvent);
|
|
77
|
-
} else {
|
|
77
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
78
78
|
document.removeEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
|
|
79
79
|
}
|
|
80
80
|
};
|
|
@@ -182,7 +182,7 @@ export function useHover(props: HoverProps): HoverResult {
|
|
|
182
182
|
triggerHoverEnd(e, e.pointerType);
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
|
-
} else {
|
|
185
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
186
186
|
hoverProps.onTouchStart = () => {
|
|
187
187
|
state.ignoreEmulatedMouseEvents = true;
|
|
188
188
|
};
|
|
@@ -31,7 +31,7 @@ export interface InteractOutsideProps {
|
|
|
31
31
|
* Example, used in components like Dialogs and Popovers so they can close
|
|
32
32
|
* when a user clicks outside them.
|
|
33
33
|
*/
|
|
34
|
-
export function useInteractOutside(props: InteractOutsideProps) {
|
|
34
|
+
export function useInteractOutside(props: InteractOutsideProps): void {
|
|
35
35
|
let {ref, onInteractOutside, isDisabled, onInteractOutsideStart} = props;
|
|
36
36
|
let stateRef = useRef({
|
|
37
37
|
isPointerDown: false,
|
|
@@ -79,7 +79,7 @@ export function useInteractOutside(props: InteractOutsideProps) {
|
|
|
79
79
|
documentObject.removeEventListener('pointerdown', onPointerDown, true);
|
|
80
80
|
documentObject.removeEventListener('pointerup', onPointerUp, true);
|
|
81
81
|
};
|
|
82
|
-
} else {
|
|
82
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
83
83
|
let onMouseUp = (e) => {
|
|
84
84
|
if (state.ignoreEmulatedMouseEvents) {
|
|
85
85
|
state.ignoreEmulatedMouseEvents = false;
|
package/src/useMove.ts
CHANGED
|
@@ -93,7 +93,7 @@ export function useMove(props: MoveEvents): MoveResult {
|
|
|
93
93
|
state.current.didMove = false;
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
if (typeof PointerEvent === 'undefined') {
|
|
96
|
+
if (typeof PointerEvent === 'undefined' && process.env.NODE_ENV === 'test') {
|
|
97
97
|
let onMouseMove = (e: MouseEvent) => {
|
|
98
98
|
if (e.button === 0) {
|
|
99
99
|
move(e, 'mouse', e.pageX - (state.current.lastPosition?.pageX ?? 0), e.pageY - (state.current.lastPosition?.pageY ?? 0));
|
package/src/usePress.ts
CHANGED
|
@@ -31,12 +31,12 @@ import {
|
|
|
31
31
|
useGlobalListeners,
|
|
32
32
|
useSyncRef
|
|
33
33
|
} from '@react-aria/utils';
|
|
34
|
+
import {createSyntheticEvent, preventFocus, setEventTarget} from './utils';
|
|
34
35
|
import {disableTextSelection, restoreTextSelection} from './textSelection';
|
|
35
36
|
import {DOMAttributes, FocusableElement, PressEvent as IPressEvent, PointerType, PressEvents, RefObject} from '@react-types/shared';
|
|
36
37
|
import {flushSync} from 'react-dom';
|
|
37
38
|
import {PressResponderContext} from './context';
|
|
38
|
-
import {
|
|
39
|
-
import {TouchEvent as RTouchEvent, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
39
|
+
import {MouseEvent as RMouseEvent, TouchEvent as RTouchEvent, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
40
40
|
|
|
41
41
|
export interface PressProps extends PressEvents {
|
|
42
42
|
/** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */
|
|
@@ -157,6 +157,8 @@ class PressEvent implements IPressEvent {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const LINK_CLICKED = Symbol('linkClicked');
|
|
160
|
+
const STYLE_ID = 'react-aria-pressable-style';
|
|
161
|
+
const PRESSABLE_ATTRIBUTE = 'data-react-aria-pressable';
|
|
160
162
|
|
|
161
163
|
/**
|
|
162
164
|
* Handles press interactions across mouse, touch, keyboard, and screen readers.
|
|
@@ -170,13 +172,13 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
170
172
|
onPressStart,
|
|
171
173
|
onPressEnd,
|
|
172
174
|
onPressUp,
|
|
175
|
+
onClick,
|
|
173
176
|
isDisabled,
|
|
174
177
|
isPressed: isPressedProp,
|
|
175
178
|
preventFocusOnPress,
|
|
176
179
|
shouldCancelOnPointerExit,
|
|
177
180
|
allowTextSelectionOnPress,
|
|
178
|
-
|
|
179
|
-
ref: _, // Removing `ref` from `domProps` because TypeScript is dumb
|
|
181
|
+
ref: domRef,
|
|
180
182
|
...domProps
|
|
181
183
|
} = usePressResponderContext(props);
|
|
182
184
|
|
|
@@ -295,6 +297,23 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
295
297
|
}
|
|
296
298
|
});
|
|
297
299
|
|
|
300
|
+
let triggerClick = useEffectEvent((e: RMouseEvent<FocusableElement>) => {
|
|
301
|
+
onClick?.(e);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
let triggerSyntheticClick = useEffectEvent((e: KeyboardEvent | TouchEvent, target: FocusableElement) => {
|
|
305
|
+
// Some third-party libraries pass in onClick instead of onPress.
|
|
306
|
+
// Create a fake mouse event and trigger onClick as well.
|
|
307
|
+
// This matches the browser's native activation behavior for certain elements (e.g. button).
|
|
308
|
+
// https://html.spec.whatwg.org/#activation
|
|
309
|
+
// https://html.spec.whatwg.org/#fire-a-synthetic-pointer-event
|
|
310
|
+
if (onClick) {
|
|
311
|
+
let event = new MouseEvent('click', e);
|
|
312
|
+
setEventTarget(event, target);
|
|
313
|
+
onClick(createSyntheticEvent(event));
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
298
317
|
let pressProps = useMemo(() => {
|
|
299
318
|
let state = ref.current;
|
|
300
319
|
let pressProps: DOMAttributes = {
|
|
@@ -362,11 +381,13 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
362
381
|
let stopPressStart = triggerPressStart(e, 'virtual');
|
|
363
382
|
let stopPressUp = triggerPressUp(e, 'virtual');
|
|
364
383
|
let stopPressEnd = triggerPressEnd(e, 'virtual');
|
|
384
|
+
triggerClick(e);
|
|
365
385
|
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd;
|
|
366
386
|
} else if (state.isPressed && state.pointerType !== 'keyboard') {
|
|
367
387
|
let pointerType = state.pointerType || (e.nativeEvent as PointerEvent).pointerType as PointerType || 'virtual';
|
|
368
388
|
shouldStopPropagation = triggerPressEnd(createEvent(e.currentTarget, e), pointerType, true);
|
|
369
389
|
state.isOverTarget = false;
|
|
390
|
+
triggerClick(e);
|
|
370
391
|
cancel(e);
|
|
371
392
|
}
|
|
372
393
|
|
|
@@ -385,7 +406,11 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
385
406
|
}
|
|
386
407
|
|
|
387
408
|
let target = getEventTarget(e);
|
|
388
|
-
|
|
409
|
+
let wasPressed = nodeContains(state.target, getEventTarget(e));
|
|
410
|
+
triggerPressEnd(createEvent(state.target, e), 'keyboard', wasPressed);
|
|
411
|
+
if (wasPressed) {
|
|
412
|
+
triggerSyntheticClick(e, state.target);
|
|
413
|
+
}
|
|
389
414
|
removeAllGlobalListeners();
|
|
390
415
|
|
|
391
416
|
// If a link was triggered with a key other than Enter, open the URL ourselves.
|
|
@@ -552,8 +577,8 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
552
577
|
// Safari does not call onPointerCancel when a drag starts, whereas Chrome and Firefox do.
|
|
553
578
|
cancel(e);
|
|
554
579
|
};
|
|
555
|
-
} else {
|
|
556
|
-
// NOTE: this fallback branch is
|
|
580
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
581
|
+
// NOTE: this fallback branch is entirely used by unit tests.
|
|
557
582
|
// All browsers now support pointer events, but JSDOM still does not.
|
|
558
583
|
|
|
559
584
|
pressProps.onMouseDown = (e) => {
|
|
@@ -723,6 +748,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
723
748
|
if (touch && isOverTarget(touch, e.currentTarget) && state.pointerType != null) {
|
|
724
749
|
triggerPressUp(createTouchEvent(state.target!, e), state.pointerType);
|
|
725
750
|
shouldStopPropagation = triggerPressEnd(createTouchEvent(state.target!, e), state.pointerType);
|
|
751
|
+
triggerSyntheticClick(e.nativeEvent, state.target!);
|
|
726
752
|
} else if (state.isOverTarget && state.pointerType != null) {
|
|
727
753
|
shouldStopPropagation = triggerPressEnd(createTouchEvent(state.target!, e), state.pointerType, false);
|
|
728
754
|
}
|
|
@@ -784,16 +810,42 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
784
810
|
cancelOnPointerExit,
|
|
785
811
|
triggerPressEnd,
|
|
786
812
|
triggerPressStart,
|
|
787
|
-
triggerPressUp
|
|
813
|
+
triggerPressUp,
|
|
814
|
+
triggerClick,
|
|
815
|
+
triggerSyntheticClick
|
|
788
816
|
]);
|
|
789
817
|
|
|
790
|
-
//
|
|
818
|
+
// Avoid onClick delay for double tap to zoom by default.
|
|
819
|
+
useEffect(() => {
|
|
820
|
+
if (!domRef || process.env.NODE_ENV === 'test') {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const ownerDocument = getOwnerDocument(domRef.current);
|
|
825
|
+
if (!ownerDocument || !ownerDocument.head || ownerDocument.getElementById(STYLE_ID)) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
791
828
|
|
|
829
|
+
const style = ownerDocument.createElement('style');
|
|
830
|
+
style.id = STYLE_ID;
|
|
831
|
+
// touchAction: 'manipulation' is supposed to be equivalent, but in
|
|
832
|
+
// Safari it causes onPointerCancel not to fire on scroll.
|
|
833
|
+
// https://bugs.webkit.org/show_bug.cgi?id=240917
|
|
834
|
+
style.textContent = `
|
|
835
|
+
@layer {
|
|
836
|
+
[${PRESSABLE_ATTRIBUTE}] {
|
|
837
|
+
touch-action: pan-x pan-y pinch-zoom;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
`.trim();
|
|
841
|
+
ownerDocument.head.prepend(style);
|
|
842
|
+
}, [domRef]);
|
|
843
|
+
|
|
844
|
+
// Remove user-select: none in case component unmounts immediately after pressStart
|
|
792
845
|
useEffect(() => {
|
|
793
846
|
let state = ref.current;
|
|
794
847
|
return () => {
|
|
795
848
|
if (!allowTextSelectionOnPress) {
|
|
796
|
-
|
|
797
849
|
restoreTextSelection(state.target ?? undefined);
|
|
798
850
|
}
|
|
799
851
|
for (let dispose of state.disposables) {
|
|
@@ -805,7 +857,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
805
857
|
|
|
806
858
|
return {
|
|
807
859
|
isPressed: isPressedProp || isPressed,
|
|
808
|
-
pressProps: mergeProps(domProps, pressProps)
|
|
860
|
+
pressProps: mergeProps(domProps, pressProps, {[PRESSABLE_ATTRIBUTE]: true})
|
|
809
861
|
};
|
|
810
862
|
}
|
|
811
863
|
|
package/src/utils.ts
CHANGED
|
@@ -12,64 +12,32 @@
|
|
|
12
12
|
|
|
13
13
|
import {FocusableElement} from '@react-types/shared';
|
|
14
14
|
import {focusWithoutScrolling, getOwnerWindow, isFocusable, useEffectEvent, useLayoutEffect} from '@react-aria/utils';
|
|
15
|
-
import {FocusEvent as ReactFocusEvent, useCallback, useRef} from 'react';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
timeStamp: number;
|
|
28
|
-
type: string;
|
|
29
|
-
|
|
30
|
-
constructor(type: string, nativeEvent: FocusEvent) {
|
|
31
|
-
this.nativeEvent = nativeEvent;
|
|
32
|
-
this.target = nativeEvent.target as EventTarget & Target;
|
|
33
|
-
this.currentTarget = nativeEvent.currentTarget as EventTarget & Target;
|
|
34
|
-
this.relatedTarget = nativeEvent.relatedTarget as Element;
|
|
35
|
-
this.bubbles = nativeEvent.bubbles;
|
|
36
|
-
this.cancelable = nativeEvent.cancelable;
|
|
37
|
-
this.defaultPrevented = nativeEvent.defaultPrevented;
|
|
38
|
-
this.eventPhase = nativeEvent.eventPhase;
|
|
39
|
-
this.isTrusted = nativeEvent.isTrusted;
|
|
40
|
-
this.timeStamp = nativeEvent.timeStamp;
|
|
41
|
-
this.type = type;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
isDefaultPrevented(): boolean {
|
|
45
|
-
return this.nativeEvent.defaultPrevented;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
preventDefault(): void {
|
|
49
|
-
this.defaultPrevented = true;
|
|
50
|
-
this.nativeEvent.preventDefault();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
stopPropagation(): void {
|
|
54
|
-
this.nativeEvent.stopPropagation();
|
|
55
|
-
this.isPropagationStopped = () => true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
isPropagationStopped(): boolean {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
15
|
+
import {FocusEvent as ReactFocusEvent, SyntheticEvent, useCallback, useRef} from 'react';
|
|
16
|
+
|
|
17
|
+
// Turn a native event into a React synthetic event.
|
|
18
|
+
export function createSyntheticEvent<E extends SyntheticEvent>(nativeEvent: Event): E {
|
|
19
|
+
let event = nativeEvent as any as E;
|
|
20
|
+
event.nativeEvent = nativeEvent;
|
|
21
|
+
event.isDefaultPrevented = () => event.defaultPrevented;
|
|
22
|
+
// cancelBubble is technically deprecated in the spec, but still supported in all browsers.
|
|
23
|
+
event.isPropagationStopped = () => (event as any).cancelBubble;
|
|
24
|
+
event.persist = () => {};
|
|
25
|
+
return event;
|
|
26
|
+
}
|
|
61
27
|
|
|
62
|
-
|
|
28
|
+
export function setEventTarget(event: Event, target: Element): void {
|
|
29
|
+
Object.defineProperty(event, 'target', {value: target});
|
|
30
|
+
Object.defineProperty(event, 'currentTarget', {value: target});
|
|
63
31
|
}
|
|
64
32
|
|
|
65
|
-
export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEvent<Target>) => void) {
|
|
33
|
+
export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur: (e: ReactFocusEvent<Target>) => void): (e: ReactFocusEvent<Target>) => void {
|
|
66
34
|
let stateRef = useRef({
|
|
67
35
|
isFocused: false,
|
|
68
36
|
observer: null as MutationObserver | null
|
|
69
37
|
});
|
|
70
38
|
|
|
71
39
|
// Clean up MutationObserver on unmount. See below.
|
|
72
|
-
|
|
40
|
+
|
|
73
41
|
useLayoutEffect(() => {
|
|
74
42
|
const state = stateRef.current;
|
|
75
43
|
return () => {
|
|
@@ -80,7 +48,7 @@ export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEv
|
|
|
80
48
|
};
|
|
81
49
|
}, []);
|
|
82
50
|
|
|
83
|
-
let dispatchBlur = useEffectEvent((e:
|
|
51
|
+
let dispatchBlur = useEffectEvent((e: ReactFocusEvent<Target>) => {
|
|
84
52
|
onBlur?.(e);
|
|
85
53
|
});
|
|
86
54
|
|
|
@@ -104,7 +72,8 @@ export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEv
|
|
|
104
72
|
|
|
105
73
|
if (target.disabled) {
|
|
106
74
|
// For backward compatibility, dispatch a (fake) React synthetic event.
|
|
107
|
-
|
|
75
|
+
let event = createSyntheticEvent<ReactFocusEvent<Target>>(e);
|
|
76
|
+
dispatchBlur(event);
|
|
108
77
|
}
|
|
109
78
|
|
|
110
79
|
// We no longer need the MutationObserver once the target is blurred.
|
|
@@ -137,7 +106,7 @@ export let ignoreFocusEvent = false;
|
|
|
137
106
|
* It works by waiting for the series of focus events to occur, and reverts focus back to where it was before.
|
|
138
107
|
* It also makes these events mostly non-observable by using a capturing listener on the window and stopping propagation.
|
|
139
108
|
*/
|
|
140
|
-
export function preventFocus(target: FocusableElement | null) {
|
|
109
|
+
export function preventFocus(target: FocusableElement | null): (() => void) | undefined {
|
|
141
110
|
// The browser will focus the nearest focusable ancestor of our target.
|
|
142
111
|
while (target && !isFocusable(target)) {
|
|
143
112
|
target = target.parentElement;
|
|
@@ -148,7 +117,7 @@ export function preventFocus(target: FocusableElement | null) {
|
|
|
148
117
|
if (!activeElement || activeElement === target) {
|
|
149
118
|
return;
|
|
150
119
|
}
|
|
151
|
-
|
|
120
|
+
|
|
152
121
|
ignoreFocusEvent = true;
|
|
153
122
|
let isRefocusing = false;
|
|
154
123
|
let onBlur = (e: FocusEvent) => {
|
|
@@ -170,7 +139,7 @@ export function preventFocus(target: FocusableElement | null) {
|
|
|
170
139
|
}
|
|
171
140
|
}
|
|
172
141
|
};
|
|
173
|
-
|
|
142
|
+
|
|
174
143
|
let onFocus = (e: FocusEvent) => {
|
|
175
144
|
if (e.target === target || isRefocusing) {
|
|
176
145
|
e.stopImmediatePropagation();
|