@react-aria/interactions 3.26.0 → 3.27.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 (60) hide show
  1. package/dist/PressResponder.main.js +2 -3
  2. package/dist/PressResponder.main.js.map +1 -1
  3. package/dist/PressResponder.mjs +3 -4
  4. package/dist/PressResponder.module.js +3 -4
  5. package/dist/PressResponder.module.js.map +1 -1
  6. package/dist/focusSafely.main.js +1 -0
  7. package/dist/focusSafely.main.js.map +1 -1
  8. package/dist/focusSafely.mjs +1 -0
  9. package/dist/focusSafely.module.js +1 -0
  10. package/dist/focusSafely.module.js.map +1 -1
  11. package/dist/main.js.map +1 -1
  12. package/dist/module.js.map +1 -1
  13. package/dist/types.d.ts +2 -1
  14. package/dist/types.d.ts.map +1 -1
  15. package/dist/useFocus.main.js +4 -3
  16. package/dist/useFocus.main.js.map +1 -1
  17. package/dist/useFocus.mjs +5 -4
  18. package/dist/useFocus.module.js +5 -4
  19. package/dist/useFocus.module.js.map +1 -1
  20. package/dist/useFocusVisible.main.js +16 -13
  21. package/dist/useFocusVisible.main.js.map +1 -1
  22. package/dist/useFocusVisible.mjs +18 -15
  23. package/dist/useFocusVisible.module.js +18 -15
  24. package/dist/useFocusVisible.module.js.map +1 -1
  25. package/dist/useFocusWithin.main.js +9 -7
  26. package/dist/useFocusWithin.main.js.map +1 -1
  27. package/dist/useFocusWithin.mjs +10 -8
  28. package/dist/useFocusWithin.module.js +10 -8
  29. package/dist/useFocusWithin.module.js.map +1 -1
  30. package/dist/useHover.main.js +5 -5
  31. package/dist/useHover.main.js.map +1 -1
  32. package/dist/useHover.mjs +6 -6
  33. package/dist/useHover.module.js +6 -6
  34. package/dist/useHover.module.js.map +1 -1
  35. package/dist/useInteractOutside.main.js +5 -4
  36. package/dist/useInteractOutside.main.js.map +1 -1
  37. package/dist/useInteractOutside.mjs +6 -5
  38. package/dist/useInteractOutside.module.js +6 -5
  39. package/dist/useInteractOutside.module.js.map +1 -1
  40. package/dist/usePress.main.js +31 -23
  41. package/dist/usePress.main.js.map +1 -1
  42. package/dist/usePress.mjs +31 -23
  43. package/dist/usePress.module.js +31 -23
  44. package/dist/usePress.module.js.map +1 -1
  45. package/dist/utils.main.js +8 -7
  46. package/dist/utils.main.js.map +1 -1
  47. package/dist/utils.mjs +9 -8
  48. package/dist/utils.module.js +9 -8
  49. package/dist/utils.module.js.map +1 -1
  50. package/package.json +4 -4
  51. package/src/PressResponder.tsx +3 -4
  52. package/src/focusSafely.ts +4 -0
  53. package/src/index.ts +1 -1
  54. package/src/useFocus.ts +4 -3
  55. package/src/useFocusVisible.ts +18 -12
  56. package/src/useFocusWithin.ts +9 -7
  57. package/src/useHover.ts +6 -6
  58. package/src/useInteractOutside.ts +6 -5
  59. package/src/usePress.ts +46 -32
  60. package/src/utils.ts +12 -11
package/src/index.ts CHANGED
@@ -41,7 +41,7 @@ export type {HoverProps, HoverResult} from './useHover';
41
41
  export type {InteractOutsideProps} from './useInteractOutside';
42
42
  export type {KeyboardProps, KeyboardResult} from './useKeyboard';
43
43
  export type {PressProps, PressHookProps, PressResult} from './usePress';
44
- export type {PressEvent, PressEvents, MoveStartEvent, MoveMoveEvent, MoveEndEvent, MoveEvents, HoverEvent, HoverEvents, FocusEvents, KeyboardEvents} from '@react-types/shared';
44
+ export type {PressEvent, PressEvents, LongPressEvent, MoveStartEvent, MoveMoveEvent, MoveEndEvent, MoveEvent, MoveEvents, HoverEvent, HoverEvents, FocusEvents, KeyboardEvents} from '@react-types/shared';
45
45
  export type {MoveResult} from './useMove';
46
46
  export type {LongPressProps, LongPressResult} from './useLongPress';
47
47
  export type {ScrollWheelProps} from './useScrollWheel';
package/src/useFocus.ts CHANGED
@@ -43,7 +43,7 @@ export function useFocus<Target extends FocusableElement = FocusableElement>(pro
43
43
  } = props;
44
44
 
45
45
  const onBlur: FocusProps<Target>['onBlur'] = useCallback((e: FocusEvent<Target>) => {
46
- if (e.target === e.currentTarget) {
46
+ if (getEventTarget(e) === e.currentTarget) {
47
47
  if (onBlurProp) {
48
48
  onBlurProp(e);
49
49
  }
@@ -63,9 +63,10 @@ export function useFocus<Target extends FocusableElement = FocusableElement>(pro
63
63
  // Double check that document.activeElement actually matches e.target in case a previously chained
64
64
  // focus handler already moved focus somewhere else.
65
65
 
66
- const ownerDocument = getOwnerDocument(e.target);
66
+ let eventTarget = getEventTarget(e);
67
+ const ownerDocument = getOwnerDocument(eventTarget);
67
68
  const activeElement = ownerDocument ? getActiveElement(ownerDocument) : getActiveElement();
68
- if (e.target === e.currentTarget && activeElement === getEventTarget(e.nativeEvent)) {
69
+ if (eventTarget === e.currentTarget && eventTarget === activeElement) {
69
70
  if (onFocusProp) {
70
71
  onFocusProp(e);
71
72
  }
@@ -15,7 +15,7 @@
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 {getOwnerDocument, getOwnerWindow, isMac, isVirtualClick, openLink} from '@react-aria/utils';
18
+ import {getActiveElement, getEventTarget, getOwnerDocument, getOwnerWindow, isMac, isVirtualClick, openLink} from '@react-aria/utils';
19
19
  import {ignoreFocusEvent} from './utils';
20
20
  import {PointerType} from '@react-types/shared';
21
21
  import {useEffect, useState} from 'react';
@@ -39,7 +39,7 @@ export interface FocusVisibleResult {
39
39
 
40
40
  let currentModality: null | Modality = null;
41
41
  let currentPointerType: PointerType = 'keyboard';
42
- let changeHandlers = new Set<Handler>();
42
+ export const changeHandlers = new Set<Handler>();
43
43
  interface GlobalListenerData {
44
44
  focus: () => void
45
45
  }
@@ -98,7 +98,7 @@ function handleFocusEvent(e: FocusEvent) {
98
98
  // Firefox fires two extra focus events when the user first clicks into an iframe:
99
99
  // first on the window, then on the document. We ignore these events so they don't
100
100
  // cause keyboard focus rings to appear.
101
- if (e.target === window || e.target === document || ignoreFocusEvent || !e.isTrusted) {
101
+ if (getEventTarget(e) === window || getEventTarget(e) === document || ignoreFocusEvent || !e.isTrusted) {
102
102
  return;
103
103
  }
104
104
 
@@ -302,18 +302,20 @@ const nonTextInputTypes = new Set([
302
302
  * focus visible style can be properly set.
303
303
  */
304
304
  function isKeyboardFocusEvent(isTextInput: boolean, modality: Modality, e: HandlerEvent) {
305
- let document = getOwnerDocument(e?.target as Element);
306
- const IHTMLInputElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLInputElement : HTMLInputElement;
307
- const IHTMLTextAreaElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement;
308
- const IHTMLElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLElement : HTMLElement;
309
- const IKeyboardEvent = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).KeyboardEvent : KeyboardEvent;
305
+ let document = getOwnerDocument(e ? getEventTarget(e) as Element : undefined);
306
+ let eventTarget = e ? getEventTarget(e) as Element : undefined;
307
+ const IHTMLInputElement = typeof window !== 'undefined' ? getOwnerWindow(eventTarget).HTMLInputElement : HTMLInputElement;
308
+ const IHTMLTextAreaElement = typeof window !== 'undefined' ? getOwnerWindow(eventTarget).HTMLTextAreaElement : HTMLTextAreaElement;
309
+ const IHTMLElement = typeof window !== 'undefined' ? getOwnerWindow(eventTarget).HTMLElement : HTMLElement;
310
+ const IKeyboardEvent = typeof window !== 'undefined' ? getOwnerWindow(eventTarget).KeyboardEvent : KeyboardEvent;
310
311
 
311
312
  // For keyboard events that occur on a non-input element that will move focus into input element (aka ArrowLeft going from Datepicker button to the main input group)
312
313
  // we need to rely on the user passing isTextInput into here. This way we can skip toggling focus visiblity for said input element
314
+ let activeElement = getActiveElement(document);
313
315
  isTextInput = isTextInput ||
314
- (document.activeElement instanceof IHTMLInputElement && !nonTextInputTypes.has(document.activeElement.type)) ||
315
- document.activeElement instanceof IHTMLTextAreaElement ||
316
- (document.activeElement instanceof IHTMLElement && document.activeElement.isContentEditable);
316
+ (activeElement instanceof IHTMLInputElement && !nonTextInputTypes.has(activeElement.type)) ||
317
+ activeElement instanceof IHTMLTextAreaElement ||
318
+ (activeElement instanceof IHTMLElement && activeElement.isContentEditable);
317
319
  return !(isTextInput && modality === 'keyboard' && e instanceof IKeyboardEvent && !FOCUS_VISIBLE_INPUT_KEYS[e.key]);
318
320
  }
319
321
 
@@ -333,10 +335,13 @@ export function useFocusVisible(props: FocusVisibleProps = {}): FocusVisibleResu
333
335
  /**
334
336
  * Listens for trigger change and reports if focus is visible (i.e., modality is not pointer).
335
337
  */
336
- export function useFocusVisibleListener(fn: FocusVisibleHandler, deps: ReadonlyArray<any>, opts?: {isTextInput?: boolean}): void {
338
+ export function useFocusVisibleListener(fn: FocusVisibleHandler, deps: ReadonlyArray<any>, opts?: {enabled?: boolean, isTextInput?: boolean}): void {
337
339
  setupGlobalFocusEvents();
338
340
 
339
341
  useEffect(() => {
342
+ if (opts?.enabled === false) {
343
+ return;
344
+ }
340
345
  let handler = (modality: Modality, e: HandlerEvent) => {
341
346
  // We want to early return for any keyboard events that occur inside text inputs EXCEPT for Tab and Escape
342
347
  if (!isKeyboardFocusEvent(!!(opts?.isTextInput), modality, e)) {
@@ -351,3 +356,4 @@ export function useFocusVisibleListener(fn: FocusVisibleHandler, deps: ReadonlyA
351
356
  // eslint-disable-next-line react-hooks/exhaustive-deps
352
357
  }, deps);
353
358
  }
359
+
@@ -54,14 +54,14 @@ export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
54
54
 
55
55
  let onBlur = useCallback((e: FocusEvent) => {
56
56
  // Ignore events bubbling through portals.
57
- if (!e.currentTarget.contains(e.target)) {
57
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
58
58
  return;
59
59
  }
60
60
 
61
61
  // We don't want to trigger onBlurWithin and then immediately onFocusWithin again
62
62
  // when moving focus inside the element. Only trigger if the currentTarget doesn't
63
63
  // include the relatedTarget (where focus is moving).
64
- if (state.current.isFocusWithin && !(e.currentTarget as Element).contains(e.relatedTarget as Element)) {
64
+ if (state.current.isFocusWithin && !nodeContains(e.currentTarget as Element, e.relatedTarget as Element)) {
65
65
  state.current.isFocusWithin = false;
66
66
  removeAllGlobalListeners();
67
67
 
@@ -78,15 +78,16 @@ export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
78
78
  let onSyntheticFocus = useSyntheticBlurEvent(onBlur);
79
79
  let onFocus = useCallback((e: FocusEvent) => {
80
80
  // Ignore events bubbling through portals.
81
- if (!e.currentTarget.contains(e.target)) {
81
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
82
82
  return;
83
83
  }
84
84
 
85
85
  // Double check that document.activeElement actually matches e.target in case a previously chained
86
86
  // focus handler already moved focus somewhere else.
87
- const ownerDocument = getOwnerDocument(e.target);
87
+ let eventTarget = getEventTarget(e);
88
+ const ownerDocument = getOwnerDocument(eventTarget);
88
89
  const activeElement = getActiveElement(ownerDocument);
89
- if (!state.current.isFocusWithin && activeElement === getEventTarget(e.nativeEvent)) {
90
+ if (!state.current.isFocusWithin && activeElement === eventTarget) {
90
91
  if (onFocusWithin) {
91
92
  onFocusWithin(e);
92
93
  }
@@ -103,8 +104,9 @@ export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
103
104
  // can manually fire onBlur.
104
105
  let currentTarget = e.currentTarget;
105
106
  addGlobalListener(ownerDocument, 'focus', e => {
106
- if (state.current.isFocusWithin && !nodeContains(currentTarget, e.target as Element)) {
107
- let nativeEvent = new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: e.target});
107
+ let eventTarget = getEventTarget(e);
108
+ if (state.current.isFocusWithin && !nodeContains(currentTarget, eventTarget as Element)) {
109
+ let nativeEvent = new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: eventTarget});
108
110
  setEventTarget(nativeEvent, currentTarget);
109
111
  let event = createSyntheticEvent<FocusEvent>(nativeEvent);
110
112
  onBlur(event);
package/src/useHover.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  // See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
17
17
 
18
18
  import {DOMAttributes, HoverEvents} from '@react-types/shared';
19
- import {getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils';
19
+ import {getEventTarget, getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils';
20
20
  import {useEffect, useMemo, useRef, useState} from 'react';
21
21
 
22
22
  export interface HoverProps extends HoverEvents {
@@ -108,7 +108,7 @@ export function useHover(props: HoverProps): HoverResult {
108
108
  let {hoverProps, triggerHoverEnd} = useMemo(() => {
109
109
  let triggerHoverStart = (event, pointerType) => {
110
110
  state.pointerType = pointerType;
111
- if (isDisabled || pointerType === 'touch' || state.isHovered || !event.currentTarget.contains(event.target)) {
111
+ if (isDisabled || pointerType === 'touch' || state.isHovered || !nodeContains(event.currentTarget, getEventTarget(event) as Element)) {
112
112
  return;
113
113
  }
114
114
 
@@ -120,8 +120,8 @@ export function useHover(props: HoverProps): HoverResult {
120
120
  // even though the originally hovered target may have shrunk in size so it is no longer hovered.
121
121
  // However, a pointerover event will be fired on the new target the mouse is over.
122
122
  // In Chrome this happens immediately. In Safari and Firefox, it happens upon moving the mouse one pixel.
123
- addGlobalListener(getOwnerDocument(event.target), 'pointerover', e => {
124
- if (state.isHovered && state.target && !nodeContains(state.target, e.target as Element)) {
123
+ addGlobalListener(getOwnerDocument(getEventTarget(event) as Element), 'pointerover', e => {
124
+ if (state.isHovered && state.target && !nodeContains(state.target, getEventTarget(e) as Element)) {
125
125
  triggerHoverEnd(e, e.pointerType);
126
126
  }
127
127
  }, {capture: true});
@@ -180,7 +180,7 @@ export function useHover(props: HoverProps): HoverResult {
180
180
  };
181
181
 
182
182
  hoverProps.onPointerLeave = (e) => {
183
- if (!isDisabled && e.currentTarget.contains(e.target as Element)) {
183
+ if (!isDisabled && nodeContains(e.currentTarget, getEventTarget(e) as Element)) {
184
184
  triggerHoverEnd(e, e.pointerType);
185
185
  }
186
186
  };
@@ -198,7 +198,7 @@ export function useHover(props: HoverProps): HoverResult {
198
198
  };
199
199
 
200
200
  hoverProps.onMouseLeave = (e) => {
201
- if (!isDisabled && e.currentTarget.contains(e.target as Element)) {
201
+ if (!isDisabled && nodeContains(e.currentTarget, getEventTarget(e) as Element)) {
202
202
  triggerHoverEnd(e, 'mouse');
203
203
  }
204
204
  };
@@ -15,7 +15,7 @@
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 {getOwnerDocument, useEffectEvent} from '@react-aria/utils';
18
+ import {getEventTarget, getOwnerDocument, nodeContains, useEffectEvent} from '@react-aria/utils';
19
19
  import {RefObject} from '@react-types/shared';
20
20
  import {useEffect, useRef} from 'react';
21
21
 
@@ -118,14 +118,15 @@ function isValidEvent(event, ref) {
118
118
  if (event.button > 0) {
119
119
  return false;
120
120
  }
121
- if (event.target) {
121
+ let target = getEventTarget(event) as Element;
122
+ if (target) {
122
123
  // if the event target is no longer in the document, ignore
123
- const ownerDocument = event.target.ownerDocument;
124
- if (!ownerDocument || !ownerDocument.documentElement.contains(event.target)) {
124
+ const ownerDocument = target.ownerDocument;
125
+ if (!ownerDocument || !nodeContains(ownerDocument.documentElement, target)) {
125
126
  return false;
126
127
  }
127
128
  // If the target is within a top layer element (e.g. toasts), ignore.
128
- if (event.target.closest('[data-react-aria-top-layer]')) {
129
+ if (target.closest('[data-react-aria-top-layer]')) {
129
130
  return false;
130
131
  }
131
132
  }
package/src/usePress.ts CHANGED
@@ -72,7 +72,7 @@ interface PressState {
72
72
  isOverTarget: boolean,
73
73
  pointerType: PointerType | null,
74
74
  userSelect?: string,
75
- metaKeyEvents?: Map<string, KeyboardEvent>,
75
+ metaKeyEvents?: Map<string, globalThis.KeyboardEvent>,
76
76
  disposables: Array<() => void>
77
77
  }
78
78
 
@@ -84,7 +84,8 @@ interface EventBase {
84
84
  altKey: boolean,
85
85
  clientX?: number,
86
86
  clientY?: number,
87
- targetTouches?: Array<{clientX?: number, clientY?: number}>
87
+ targetTouches?: Array<{clientX?: number, clientY?: number}>,
88
+ key?: string
88
89
  }
89
90
 
90
91
  export interface PressResult {
@@ -98,7 +99,9 @@ function usePressResponderContext(props: PressHookProps): PressHookProps {
98
99
  // Consume context from <PressResponder> and merge with props.
99
100
  let context = useContext(PressResponderContext);
100
101
  if (context) {
101
- let {register, ...contextProps} = context;
102
+ // Prevent mergeProps from merging ref.
103
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
+ let {register, ref, ...contextProps} = context;
102
105
  props = mergeProps(contextProps, props) as PressHookProps;
103
106
  register();
104
107
  }
@@ -117,6 +120,7 @@ class PressEvent implements IPressEvent {
117
120
  altKey: boolean;
118
121
  x: number;
119
122
  y: number;
123
+ key: string | undefined;
120
124
  #shouldStopPropagation = true;
121
125
 
122
126
  constructor(type: IPressEvent['type'], pointerType: PointerType, originalEvent: EventBase, state?: PressState) {
@@ -146,6 +150,7 @@ class PressEvent implements IPressEvent {
146
150
  this.altKey = originalEvent.altKey;
147
151
  this.x = x;
148
152
  this.y = y;
153
+ this.key = originalEvent.key;
149
154
  }
150
155
 
151
156
  continuePropagation() {
@@ -334,12 +339,12 @@ export function usePress(props: PressHookProps): PressResult {
334
339
  if (isElemKeyPressed) {
335
340
  let onKeyUp = (e: KeyboardEvent) => {
336
341
  if (state.isPressed && state.target && isValidKeyboardEvent(e, state.target)) {
337
- if (shouldPreventDefaultKeyboard(getEventTarget(e), e.key)) {
342
+ if (shouldPreventDefaultKeyboard(getEventTarget(e) as Element, e.key)) {
338
343
  e.preventDefault();
339
344
  }
340
345
 
341
- let target = getEventTarget(e);
342
- let wasPressed = nodeContains(state.target, getEventTarget(e));
346
+ let target = getEventTarget(e) as Element;
347
+ let wasPressed = nodeContains(state.target, target);
343
348
  triggerPressEndEvent(createEvent(state.target, e), 'keyboard', wasPressed);
344
349
  if (wasPressed) {
345
350
  triggerSyntheticClickEvent(e, state.target);
@@ -374,8 +379,8 @@ export function usePress(props: PressHookProps): PressResult {
374
379
  // instead of the same element where the key down event occurred. Make it capturing so that it will trigger
375
380
  // before stopPropagation from useKeyboard on a child element may happen and thus we can still call triggerPress for the parent element.
376
381
  let originalTarget = state.target;
377
- let pressUp = (e) => {
378
- if (originalTarget && isValidKeyboardEvent(e, originalTarget) && !e.repeat && nodeContains(originalTarget, getEventTarget(e)) && state.target) {
382
+ let pressUp = (e: KeyboardEvent) => {
383
+ if (originalTarget && isValidKeyboardEvent(e, originalTarget) && !e.repeat && nodeContains(originalTarget, getEventTarget(e) as Element) && state.target) {
379
384
  triggerPressUpEvent(createEvent(state.target, e), 'keyboard');
380
385
  }
381
386
  };
@@ -393,7 +398,7 @@ export function usePress(props: PressHookProps): PressResult {
393
398
  if (isPointerPressed === 'pointer') {
394
399
  let onPointerUp = (e: PointerEvent) => {
395
400
  if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) {
396
- if (nodeContains(state.target, getEventTarget(e)) && state.pointerType != null) {
401
+ if (nodeContains(state.target, getEventTarget(e) as Element) && state.pointerType != null) {
397
402
  // Wait for onClick to fire onPress. This avoids browser issues when the DOM
398
403
  // is mutated between onPointerUp and onClick, and is more compatible with third party libraries.
399
404
  // https://github.com/adobe/react-spectrum/issues/1513
@@ -417,7 +422,9 @@ export function usePress(props: PressHookProps): PressResult {
417
422
  }, 80);
418
423
  // Use a capturing listener to track if a click occurred.
419
424
  // If stopPropagation is called it may never reach our handler.
420
- addGlobalListener(e.currentTarget as Document, 'click', () => clicked = true, true);
425
+ if (e.currentTarget) {
426
+ addGlobalListener(e.currentTarget, 'click', () => clicked = true, true);
427
+ }
421
428
  state.disposables.push(() => clearTimeout(timeout));
422
429
  } else {
423
430
  cancelEvent(e);
@@ -450,7 +457,7 @@ export function usePress(props: PressHookProps): PressResult {
450
457
  return;
451
458
  }
452
459
 
453
- if (state.target && state.target.contains(e.target as Element) && state.pointerType != null) {
460
+ if (state.target && nodeContains(state.target, e.target as Element) && state.pointerType != null) {
454
461
  // Wait for onClick to fire onPress. This avoids browser issues when the DOM
455
462
  // is mutated between onMouseUp and onClick, and is more compatible with third party libraries.
456
463
  } else {
@@ -466,7 +473,7 @@ export function usePress(props: PressHookProps): PressResult {
466
473
  };
467
474
  } else if (isPointerPressed === 'touch' && process.env.NODE_ENV === 'test') {
468
475
  let onScroll = (e: Event) => {
469
- if (state.isPressed && nodeContains(getEventTarget(e), state.target)) {
476
+ if (state.isPressed && nodeContains(getEventTarget(e) as Element, state.target)) {
470
477
  cancelEvent({
471
478
  currentTarget: state.target,
472
479
  shiftKey: false,
@@ -488,8 +495,8 @@ export function usePress(props: PressHookProps): PressResult {
488
495
  let state = ref.current;
489
496
  let pressProps: DOMAttributes = {
490
497
  onKeyDown(e) {
491
- if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
492
- if (shouldPreventDefaultKeyboard(getEventTarget(e.nativeEvent), e.key)) {
498
+ if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget as Element) && nodeContains(e.currentTarget as Element, getEventTarget(e) as Element)) {
499
+ if (shouldPreventDefaultKeyboard(getEventTarget(e) as Element, e.key)) {
493
500
  e.preventDefault();
494
501
  }
495
502
 
@@ -524,7 +531,7 @@ export function usePress(props: PressHookProps): PressResult {
524
531
  }
525
532
  },
526
533
  onClick(e) {
527
- if (e && !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
534
+ if (e && !nodeContains(e.currentTarget, getEventTarget(e))) {
528
535
  return;
529
536
  }
530
537
 
@@ -563,7 +570,7 @@ export function usePress(props: PressHookProps): PressResult {
563
570
  if (typeof PointerEvent !== 'undefined') {
564
571
  pressProps.onPointerDown = (e) => {
565
572
  // Only handle left clicks, and ignore events that bubbled through portals.
566
- if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
573
+ if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e))) {
567
574
  return;
568
575
  }
569
576
 
@@ -594,9 +601,15 @@ export function usePress(props: PressHookProps): PressResult {
594
601
 
595
602
  // Release pointer capture so that touch interactions can leave the original target.
596
603
  // This enables onPointerLeave and onPointerEnter to fire.
597
- let target = getEventTarget(e.nativeEvent);
604
+ let target = getEventTarget(e);
598
605
  if ('releasePointerCapture' in target) {
599
- target.releasePointerCapture(e.pointerId);
606
+ if ('hasPointerCapture' in target) {
607
+ if (target.hasPointerCapture(e.pointerId)) {
608
+ target.releasePointerCapture(e.pointerId);
609
+ }
610
+ } else {
611
+ (target as Element).releasePointerCapture(e.pointerId);
612
+ }
600
613
  }
601
614
  }
602
615
 
@@ -606,7 +619,7 @@ export function usePress(props: PressHookProps): PressResult {
606
619
  };
607
620
 
608
621
  pressProps.onMouseDown = (e) => {
609
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
622
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
610
623
  return;
611
624
  }
612
625
 
@@ -624,7 +637,7 @@ export function usePress(props: PressHookProps): PressResult {
624
637
 
625
638
  pressProps.onPointerUp = (e) => {
626
639
  // iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown.
627
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent)) || state.pointerType === 'virtual') {
640
+ if (!nodeContains(e.currentTarget, getEventTarget(e)) || state.pointerType === 'virtual') {
628
641
  return;
629
642
  }
630
643
 
@@ -651,7 +664,7 @@ export function usePress(props: PressHookProps): PressResult {
651
664
 
652
665
 
653
666
  pressProps.onDragStart = (e) => {
654
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
667
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
655
668
  return;
656
669
  }
657
670
 
@@ -664,7 +677,7 @@ export function usePress(props: PressHookProps): PressResult {
664
677
 
665
678
  pressProps.onMouseDown = (e) => {
666
679
  // Only handle left clicks
667
- if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
680
+ if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e))) {
668
681
  return;
669
682
  }
670
683
 
@@ -694,7 +707,7 @@ export function usePress(props: PressHookProps): PressResult {
694
707
  };
695
708
 
696
709
  pressProps.onMouseEnter = (e) => {
697
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
710
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
698
711
  return;
699
712
  }
700
713
 
@@ -710,7 +723,7 @@ export function usePress(props: PressHookProps): PressResult {
710
723
  };
711
724
 
712
725
  pressProps.onMouseLeave = (e) => {
713
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
726
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
714
727
  return;
715
728
  }
716
729
 
@@ -727,7 +740,7 @@ export function usePress(props: PressHookProps): PressResult {
727
740
  };
728
741
 
729
742
  pressProps.onMouseUp = (e) => {
730
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
743
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
731
744
  return;
732
745
  }
733
746
 
@@ -737,7 +750,7 @@ export function usePress(props: PressHookProps): PressResult {
737
750
  };
738
751
 
739
752
  pressProps.onTouchStart = (e) => {
740
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
753
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
741
754
  return;
742
755
  }
743
756
 
@@ -764,7 +777,7 @@ export function usePress(props: PressHookProps): PressResult {
764
777
  };
765
778
 
766
779
  pressProps.onTouchMove = (e) => {
767
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
780
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
768
781
  return;
769
782
  }
770
783
 
@@ -792,7 +805,7 @@ export function usePress(props: PressHookProps): PressResult {
792
805
  };
793
806
 
794
807
  pressProps.onTouchEnd = (e) => {
795
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
808
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
796
809
  return;
797
810
  }
798
811
 
@@ -827,7 +840,7 @@ export function usePress(props: PressHookProps): PressResult {
827
840
  };
828
841
 
829
842
  pressProps.onTouchCancel = (e) => {
830
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
843
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
831
844
  return;
832
845
  }
833
846
 
@@ -838,7 +851,7 @@ export function usePress(props: PressHookProps): PressResult {
838
851
  };
839
852
 
840
853
  pressProps.onDragStart = (e) => {
841
- if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
854
+ if (!nodeContains(e.currentTarget, getEventTarget(e))) {
842
855
  return;
843
856
  }
844
857
 
@@ -911,7 +924,7 @@ function isHTMLAnchorLink(target: Element): target is HTMLAnchorElement {
911
924
  return target.tagName === 'A' && target.hasAttribute('href');
912
925
  }
913
926
 
914
- function isValidKeyboardEvent(event: KeyboardEvent, currentTarget: Element): boolean {
927
+ function isValidKeyboardEvent(event: KeyboardEvent | globalThis.KeyboardEvent, currentTarget: Element): boolean {
915
928
  const {key, code} = event;
916
929
  const element = currentTarget as HTMLElement;
917
930
  const role = element.getAttribute('role');
@@ -977,7 +990,8 @@ function createEvent(target: FocusableElement, e: EventBase): EventBase {
977
990
  metaKey: e.metaKey,
978
991
  altKey: e.altKey,
979
992
  clientX,
980
- clientY
993
+ clientY,
994
+ key: e.key
981
995
  };
982
996
  }
983
997
 
package/src/utils.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import {FocusableElement} from '@react-types/shared';
14
- import {focusWithoutScrolling, getOwnerWindow, isFocusable, useLayoutEffect} from '@react-aria/utils';
14
+ import {focusWithoutScrolling, getActiveElement, getEventTarget, getOwnerWindow, isFocusable, useLayoutEffect} from '@react-aria/utils';
15
15
  import {FocusEvent as ReactFocusEvent, SyntheticEvent, useCallback, useRef} from 'react';
16
16
 
17
17
  // Turn a native event into a React synthetic event.
@@ -54,15 +54,16 @@ export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur:
54
54
  // Most browsers fire a native focusout event in this case, except for Firefox. In that case, we use a
55
55
  // MutationObserver to watch for the disabled attribute, and dispatch these events ourselves.
56
56
  // For browsers that do, focusout fires before the MutationObserver, so onBlur should not fire twice.
57
+ let eventTarget = getEventTarget(e);
57
58
  if (
58
- e.target instanceof HTMLButtonElement ||
59
- e.target instanceof HTMLInputElement ||
60
- e.target instanceof HTMLTextAreaElement ||
61
- e.target instanceof HTMLSelectElement
59
+ eventTarget instanceof HTMLButtonElement ||
60
+ eventTarget instanceof HTMLInputElement ||
61
+ eventTarget instanceof HTMLTextAreaElement ||
62
+ eventTarget instanceof HTMLSelectElement
62
63
  ) {
63
64
  stateRef.current.isFocused = true;
64
65
 
65
- let target = e.target;
66
+ let target = eventTarget;
66
67
  let onBlurHandler: EventListenerOrEventListenerObject | null = (e) => {
67
68
  stateRef.current.isFocused = false;
68
69
 
@@ -84,7 +85,7 @@ export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur:
84
85
  stateRef.current.observer = new MutationObserver(() => {
85
86
  if (stateRef.current.isFocused && target.disabled) {
86
87
  stateRef.current.observer?.disconnect();
87
- let relatedTargetEl = target === document.activeElement ? null : document.activeElement;
88
+ let relatedTargetEl = target === getActiveElement() ? null : getActiveElement();
88
89
  target.dispatchEvent(new FocusEvent('blur', {relatedTarget: relatedTargetEl}));
89
90
  target.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget: relatedTargetEl}));
90
91
  }
@@ -117,13 +118,13 @@ export function preventFocus(target: FocusableElement | null): (() => void) | un
117
118
  ignoreFocusEvent = true;
118
119
  let isRefocusing = false;
119
120
  let onBlur = (e: FocusEvent) => {
120
- if (e.target === activeElement || isRefocusing) {
121
+ if (getEventTarget(e) === activeElement || isRefocusing) {
121
122
  e.stopImmediatePropagation();
122
123
  }
123
124
  };
124
125
 
125
126
  let onFocusOut = (e: FocusEvent) => {
126
- if (e.target === activeElement || isRefocusing) {
127
+ if (getEventTarget(e) === activeElement || isRefocusing) {
127
128
  e.stopImmediatePropagation();
128
129
 
129
130
  // If there was no focusable ancestor, we don't expect a focus event.
@@ -137,13 +138,13 @@ export function preventFocus(target: FocusableElement | null): (() => void) | un
137
138
  };
138
139
 
139
140
  let onFocus = (e: FocusEvent) => {
140
- if (e.target === target || isRefocusing) {
141
+ if (getEventTarget(e) === target || isRefocusing) {
141
142
  e.stopImmediatePropagation();
142
143
  }
143
144
  };
144
145
 
145
146
  let onFocusIn = (e: FocusEvent) => {
146
- if (e.target === target || isRefocusing) {
147
+ if (getEventTarget(e) === target || isRefocusing) {
147
148
  e.stopImmediatePropagation();
148
149
 
149
150
  if (!isRefocusing) {