@react-aria/interactions 3.24.0 → 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.
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 +1 -1
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/useFocusVisible.main.js +2 -2
  23. package/dist/useFocusVisible.main.js.map +1 -1
  24. package/dist/useFocusVisible.mjs +2 -2
  25. package/dist/useFocusVisible.module.js +2 -2
  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 +42 -7
  53. package/dist/usePress.main.js.map +1 -1
  54. package/dist/usePress.mjs +42 -7
  55. package/dist/usePress.module.js +42 -7
  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 +7 -5
  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 +3 -3
  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 +49 -10
  75. package/src/utils.ts +23 -54
@@ -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}): 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;
@@ -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 || '';
@@ -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). */
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
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
- triggerPressEnd(createEvent(state.target, e), 'keyboard', nodeContains(state.target, getEventTarget(e)));
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 almost entirely used by unit tests.
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
- // Remove user-select: none in case component unmounts immediately after pressStart
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
- 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();