@react-aria/interactions 3.24.1 → 3.25.0
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 +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useFocusVisible.main.js +2 -2
- package/dist/useFocusVisible.main.js.map +1 -1
- package/dist/useFocusVisible.mjs +2 -2
- package/dist/useFocusVisible.module.js +2 -2
- 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 +42 -7
- package/dist/usePress.main.js.map +1 -1
- package/dist/usePress.mjs +42 -7
- package/dist/usePress.module.js +42 -7
- 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 +7 -5
- 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 +3 -3
- 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 +49 -10
- package/src/utils.ts +23 -54
package/src/PressResponder.tsx
CHANGED
|
@@ -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}): ReactNode {
|
|
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
|
@@ -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). */
|
|
@@ -170,13 +170,13 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
170
170
|
onPressStart,
|
|
171
171
|
onPressEnd,
|
|
172
172
|
onPressUp,
|
|
173
|
+
onClick,
|
|
173
174
|
isDisabled,
|
|
174
175
|
isPressed: isPressedProp,
|
|
175
176
|
preventFocusOnPress,
|
|
176
177
|
shouldCancelOnPointerExit,
|
|
177
178
|
allowTextSelectionOnPress,
|
|
178
|
-
|
|
179
|
-
ref: _, // Removing `ref` from `domProps` because TypeScript is dumb
|
|
179
|
+
ref: domRef,
|
|
180
180
|
...domProps
|
|
181
181
|
} = usePressResponderContext(props);
|
|
182
182
|
|
|
@@ -295,6 +295,23 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
295
295
|
}
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
+
let triggerClick = useEffectEvent((e: RMouseEvent<FocusableElement>) => {
|
|
299
|
+
onClick?.(e);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
let triggerSyntheticClick = useEffectEvent((e: KeyboardEvent | TouchEvent, target: FocusableElement) => {
|
|
303
|
+
// Some third-party libraries pass in onClick instead of onPress.
|
|
304
|
+
// Create a fake mouse event and trigger onClick as well.
|
|
305
|
+
// This matches the browser's native activation behavior for certain elements (e.g. button).
|
|
306
|
+
// https://html.spec.whatwg.org/#activation
|
|
307
|
+
// https://html.spec.whatwg.org/#fire-a-synthetic-pointer-event
|
|
308
|
+
if (onClick) {
|
|
309
|
+
let event = new MouseEvent('click', e);
|
|
310
|
+
setEventTarget(event, target);
|
|
311
|
+
onClick(createSyntheticEvent(event));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
298
315
|
let pressProps = useMemo(() => {
|
|
299
316
|
let state = ref.current;
|
|
300
317
|
let pressProps: DOMAttributes = {
|
|
@@ -362,11 +379,13 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
362
379
|
let stopPressStart = triggerPressStart(e, 'virtual');
|
|
363
380
|
let stopPressUp = triggerPressUp(e, 'virtual');
|
|
364
381
|
let stopPressEnd = triggerPressEnd(e, 'virtual');
|
|
382
|
+
triggerClick(e);
|
|
365
383
|
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd;
|
|
366
384
|
} else if (state.isPressed && state.pointerType !== 'keyboard') {
|
|
367
385
|
let pointerType = state.pointerType || (e.nativeEvent as PointerEvent).pointerType as PointerType || 'virtual';
|
|
368
386
|
shouldStopPropagation = triggerPressEnd(createEvent(e.currentTarget, e), pointerType, true);
|
|
369
387
|
state.isOverTarget = false;
|
|
388
|
+
triggerClick(e);
|
|
370
389
|
cancel(e);
|
|
371
390
|
}
|
|
372
391
|
|
|
@@ -385,7 +404,11 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
385
404
|
}
|
|
386
405
|
|
|
387
406
|
let target = getEventTarget(e);
|
|
388
|
-
|
|
407
|
+
let wasPressed = nodeContains(state.target, getEventTarget(e));
|
|
408
|
+
triggerPressEnd(createEvent(state.target, e), 'keyboard', wasPressed);
|
|
409
|
+
if (wasPressed) {
|
|
410
|
+
triggerSyntheticClick(e, state.target);
|
|
411
|
+
}
|
|
389
412
|
removeAllGlobalListeners();
|
|
390
413
|
|
|
391
414
|
// If a link was triggered with a key other than Enter, open the URL ourselves.
|
|
@@ -552,8 +575,8 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
552
575
|
// Safari does not call onPointerCancel when a drag starts, whereas Chrome and Firefox do.
|
|
553
576
|
cancel(e);
|
|
554
577
|
};
|
|
555
|
-
} else {
|
|
556
|
-
// NOTE: this fallback branch is
|
|
578
|
+
} else if (process.env.NODE_ENV === 'test') {
|
|
579
|
+
// NOTE: this fallback branch is entirely used by unit tests.
|
|
557
580
|
// All browsers now support pointer events, but JSDOM still does not.
|
|
558
581
|
|
|
559
582
|
pressProps.onMouseDown = (e) => {
|
|
@@ -723,6 +746,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
723
746
|
if (touch && isOverTarget(touch, e.currentTarget) && state.pointerType != null) {
|
|
724
747
|
triggerPressUp(createTouchEvent(state.target!, e), state.pointerType);
|
|
725
748
|
shouldStopPropagation = triggerPressEnd(createTouchEvent(state.target!, e), state.pointerType);
|
|
749
|
+
triggerSyntheticClick(e.nativeEvent, state.target!);
|
|
726
750
|
} else if (state.isOverTarget && state.pointerType != null) {
|
|
727
751
|
shouldStopPropagation = triggerPressEnd(createTouchEvent(state.target!, e), state.pointerType, false);
|
|
728
752
|
}
|
|
@@ -784,16 +808,31 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
784
808
|
cancelOnPointerExit,
|
|
785
809
|
triggerPressEnd,
|
|
786
810
|
triggerPressStart,
|
|
787
|
-
triggerPressUp
|
|
811
|
+
triggerPressUp,
|
|
812
|
+
triggerClick,
|
|
813
|
+
triggerSyntheticClick
|
|
788
814
|
]);
|
|
789
815
|
|
|
790
|
-
//
|
|
816
|
+
// Avoid onClick delay for double tap to zoom by default.
|
|
817
|
+
useEffect(() => {
|
|
818
|
+
let element = domRef?.current;
|
|
819
|
+
if (element && (element instanceof getOwnerWindow(element).Element)) {
|
|
820
|
+
// Only apply touch-action if not already set by another CSS rule.
|
|
821
|
+
let style = getOwnerWindow(element).getComputedStyle(element);
|
|
822
|
+
if (style.touchAction === 'auto') {
|
|
823
|
+
// touchAction: 'manipulation' is supposed to be equivalent, but in
|
|
824
|
+
// Safari it causes onPointerCancel not to fire on scroll.
|
|
825
|
+
// https://bugs.webkit.org/show_bug.cgi?id=240917
|
|
826
|
+
(element as HTMLElement).style.touchAction = 'pan-x pan-y pinch-zoom';
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}, [domRef]);
|
|
791
830
|
|
|
831
|
+
// Remove user-select: none in case component unmounts immediately after pressStart
|
|
792
832
|
useEffect(() => {
|
|
793
833
|
let state = ref.current;
|
|
794
834
|
return () => {
|
|
795
835
|
if (!allowTextSelectionOnPress) {
|
|
796
|
-
|
|
797
836
|
restoreTextSelection(state.target ?? undefined);
|
|
798
837
|
}
|
|
799
838
|
for (let dispose of state.disposables) {
|
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();
|