@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.
Files changed (75) hide show
  1. package/dist/PressResponder.main.js +1 -1
  2. package/dist/PressResponder.main.js.map +1 -1
  3. package/dist/PressResponder.mjs +1 -1
  4. package/dist/PressResponder.module.js +1 -1
  5. package/dist/PressResponder.module.js.map +1 -1
  6. package/dist/Pressable.main.js +1 -0
  7. package/dist/Pressable.main.js.map +1 -1
  8. package/dist/Pressable.mjs +1 -0
  9. package/dist/Pressable.module.js +1 -0
  10. package/dist/Pressable.module.js.map +1 -1
  11. package/dist/createEventHandler.main.js +1 -1
  12. package/dist/createEventHandler.main.js.map +1 -1
  13. package/dist/createEventHandler.mjs +1 -1
  14. package/dist/createEventHandler.module.js +1 -1
  15. package/dist/createEventHandler.module.js.map +1 -1
  16. package/dist/focusSafely.main.js.map +1 -1
  17. package/dist/focusSafely.module.js.map +1 -1
  18. package/dist/textSelection.main.js.map +1 -1
  19. package/dist/textSelection.module.js.map +1 -1
  20. package/dist/types.d.ts +2 -2
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/useFocusVisible.main.js +3 -3
  23. package/dist/useFocusVisible.main.js.map +1 -1
  24. package/dist/useFocusVisible.mjs +3 -3
  25. package/dist/useFocusVisible.module.js +3 -3
  26. package/dist/useFocusVisible.module.js.map +1 -1
  27. package/dist/useFocusWithin.main.js +4 -4
  28. package/dist/useFocusWithin.main.js.map +1 -1
  29. package/dist/useFocusWithin.mjs +5 -5
  30. package/dist/useFocusWithin.module.js +5 -5
  31. package/dist/useFocusWithin.module.js.map +1 -1
  32. package/dist/useFocusable.main.js +1 -0
  33. package/dist/useFocusable.main.js.map +1 -1
  34. package/dist/useFocusable.mjs +1 -0
  35. package/dist/useFocusable.module.js +1 -0
  36. package/dist/useFocusable.module.js.map +1 -1
  37. package/dist/useHover.main.js +3 -3
  38. package/dist/useHover.main.js.map +1 -1
  39. package/dist/useHover.mjs +3 -3
  40. package/dist/useHover.module.js +3 -3
  41. package/dist/useHover.module.js.map +1 -1
  42. package/dist/useInteractOutside.main.js +1 -1
  43. package/dist/useInteractOutside.main.js.map +1 -1
  44. package/dist/useInteractOutside.mjs +1 -1
  45. package/dist/useInteractOutside.module.js +1 -1
  46. package/dist/useInteractOutside.module.js.map +1 -1
  47. package/dist/useMove.main.js +1 -1
  48. package/dist/useMove.main.js.map +1 -1
  49. package/dist/useMove.mjs +1 -1
  50. package/dist/useMove.module.js +1 -1
  51. package/dist/useMove.module.js.map +1 -1
  52. package/dist/usePress.main.js +54 -8
  53. package/dist/usePress.main.js.map +1 -1
  54. package/dist/usePress.mjs +54 -8
  55. package/dist/usePress.module.js +54 -8
  56. package/dist/usePress.module.js.map +1 -1
  57. package/dist/utils.main.js +23 -32
  58. package/dist/utils.main.js.map +1 -1
  59. package/dist/utils.mjs +22 -32
  60. package/dist/utils.module.js +22 -32
  61. package/dist/utils.module.js.map +1 -1
  62. package/package.json +6 -6
  63. package/src/PressResponder.tsx +8 -6
  64. package/src/Pressable.tsx +4 -0
  65. package/src/createEventHandler.ts +1 -1
  66. package/src/focusSafely.ts +1 -1
  67. package/src/textSelection.ts +4 -4
  68. package/src/useFocusVisible.ts +4 -4
  69. package/src/useFocusWithin.ts +4 -4
  70. package/src/useFocusable.tsx +4 -0
  71. package/src/useHover.ts +3 -3
  72. package/src/useInteractOutside.ts +2 -2
  73. package/src/useMove.ts +1 -1
  74. package/src/usePress.ts +63 -11
  75. package/src/utils.ts +23 -54
@@ -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
- console.warn(
43
- 'A PressResponder was rendered without a pressable child. ' +
44
- 'Either call the usePress hook, or wrap your DOM node with <Pressable> component.'
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;
@@ -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
@@ -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 || '';
@@ -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
  }
@@ -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 event = new SyntheticFocusEvent('blur', new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: e.target}));
108
- event.target = currentTarget;
109
- event.currentTarget = currentTarget;
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});
@@ -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 {preventFocus} from './utils';
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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
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
- triggerPressEnd(createEvent(state.target, e), 'keyboard', nodeContains(state.target, getEventTarget(e)));
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 almost entirely used by unit tests.
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
- // Remove user-select: none in case component unmounts immediately after pressStart
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
- export class SyntheticFocusEvent<Target = Element> implements ReactFocusEvent<Target> {
18
- nativeEvent: FocusEvent;
19
- target: EventTarget & Target;
20
- currentTarget: EventTarget & Target;
21
- relatedTarget: Element;
22
- bubbles: boolean;
23
- cancelable: boolean;
24
- defaultPrevented: boolean;
25
- eventPhase: number;
26
- isTrusted: boolean;
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
- persist() {}
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: SyntheticFocusEvent<Target>) => {
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
- dispatchBlur(new SyntheticFocusEvent('blur', e as FocusEvent));
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();