@react-aria/interactions 3.25.6 → 3.27.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 (63) 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 +4 -4
  7. package/dist/focusSafely.main.js.map +1 -1
  8. package/dist/focusSafely.mjs +4 -4
  9. package/dist/focusSafely.module.js +4 -4
  10. package/dist/focusSafely.module.js.map +1 -1
  11. package/dist/import.mjs +2 -2
  12. package/dist/main.js +1 -0
  13. package/dist/main.js.map +1 -1
  14. package/dist/module.js +2 -2
  15. package/dist/module.js.map +1 -1
  16. package/dist/types.d.ts +4 -1
  17. package/dist/types.d.ts.map +1 -1
  18. package/dist/useFocusVisible.main.js +19 -8
  19. package/dist/useFocusVisible.main.js.map +1 -1
  20. package/dist/useFocusVisible.mjs +20 -10
  21. package/dist/useFocusVisible.module.js +20 -10
  22. package/dist/useFocusVisible.module.js.map +1 -1
  23. package/dist/useFocusWithin.main.js +3 -3
  24. package/dist/useFocusWithin.main.js.map +1 -1
  25. package/dist/useFocusWithin.mjs +4 -4
  26. package/dist/useFocusWithin.module.js +4 -4
  27. package/dist/useFocusWithin.module.js.map +1 -1
  28. package/dist/useHover.main.js +3 -3
  29. package/dist/useHover.main.js.map +1 -1
  30. package/dist/useHover.mjs +4 -4
  31. package/dist/useHover.module.js +4 -4
  32. package/dist/useHover.module.js.map +1 -1
  33. package/dist/useInteractOutside.main.js +2 -4
  34. package/dist/useInteractOutside.main.js.map +1 -1
  35. package/dist/useInteractOutside.mjs +3 -5
  36. package/dist/useInteractOutside.module.js +3 -5
  37. package/dist/useInteractOutside.module.js.map +1 -1
  38. package/dist/useMove.main.js +110 -74
  39. package/dist/useMove.main.js.map +1 -1
  40. package/dist/useMove.mjs +112 -76
  41. package/dist/useMove.module.js +112 -76
  42. package/dist/useMove.module.js.map +1 -1
  43. package/dist/usePress.main.js +197 -117
  44. package/dist/usePress.main.js.map +1 -1
  45. package/dist/usePress.mjs +199 -119
  46. package/dist/usePress.module.js +199 -119
  47. package/dist/usePress.module.js.map +1 -1
  48. package/dist/utils.main.js +2 -5
  49. package/dist/utils.main.js.map +1 -1
  50. package/dist/utils.mjs +3 -6
  51. package/dist/utils.module.js +3 -6
  52. package/dist/utils.module.js.map +1 -1
  53. package/package.json +4 -4
  54. package/src/PressResponder.tsx +3 -4
  55. package/src/focusSafely.ts +4 -4
  56. package/src/index.ts +1 -0
  57. package/src/useFocusVisible.ts +21 -5
  58. package/src/useFocusWithin.ts +3 -3
  59. package/src/useHover.ts +3 -3
  60. package/src/useInteractOutside.ts +3 -3
  61. package/src/useMove.ts +85 -57
  62. package/src/usePress.ts +199 -151
  63. package/src/utils.ts +3 -7
package/src/usePress.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  openLink,
30
30
  useEffectEvent,
31
31
  useGlobalListeners,
32
+ useLayoutEffect,
32
33
  useSyncRef
33
34
  } from '@react-aria/utils';
34
35
  import {createSyntheticEvent, preventFocus, setEventTarget} from './utils';
@@ -36,7 +37,7 @@ import {disableTextSelection, restoreTextSelection} from './textSelection';
36
37
  import {DOMAttributes, FocusableElement, PressEvent as IPressEvent, PointerType, PressEvents, RefObject} from '@react-types/shared';
37
38
  import {flushSync} from 'react-dom';
38
39
  import {PressResponderContext} from './context';
39
- import {MouseEvent as RMouseEvent, TouchEvent as RTouchEvent, useContext, useEffect, useMemo, useRef, useState} from 'react';
40
+ import {MouseEvent as RMouseEvent, TouchEvent as RTouchEvent, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
40
41
 
41
42
  export interface PressProps extends PressEvents {
42
43
  /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */
@@ -83,7 +84,8 @@ interface EventBase {
83
84
  altKey: boolean,
84
85
  clientX?: number,
85
86
  clientY?: number,
86
- targetTouches?: Array<{clientX?: number, clientY?: number}>
87
+ targetTouches?: Array<{clientX?: number, clientY?: number}>,
88
+ key?: string
87
89
  }
88
90
 
89
91
  export interface PressResult {
@@ -97,7 +99,9 @@ function usePressResponderContext(props: PressHookProps): PressHookProps {
97
99
  // Consume context from <PressResponder> and merge with props.
98
100
  let context = useContext(PressResponderContext);
99
101
  if (context) {
100
- 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;
101
105
  props = mergeProps(contextProps, props) as PressHookProps;
102
106
  register();
103
107
  }
@@ -116,6 +120,7 @@ class PressEvent implements IPressEvent {
116
120
  altKey: boolean;
117
121
  x: number;
118
122
  y: number;
123
+ key: string | undefined;
119
124
  #shouldStopPropagation = true;
120
125
 
121
126
  constructor(type: IPressEvent['type'], pointerType: PointerType, originalEvent: EventBase, state?: PressState) {
@@ -145,6 +150,7 @@ class PressEvent implements IPressEvent {
145
150
  this.altKey = originalEvent.altKey;
146
151
  this.x = x;
147
152
  this.y = y;
153
+ this.key = originalEvent.key;
148
154
  }
149
155
 
150
156
  continuePropagation() {
@@ -195,9 +201,9 @@ export function usePress(props: PressHookProps): PressResult {
195
201
  disposables: []
196
202
  });
197
203
 
198
- let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
204
+ let {addGlobalListener, removeAllGlobalListeners, removeGlobalListener} = useGlobalListeners();
199
205
 
200
- let triggerPressStart = useEffectEvent((originalEvent: EventBase, pointerType: PointerType) => {
206
+ let triggerPressStart = useCallback((originalEvent: EventBase, pointerType: PointerType) => {
201
207
  let state = ref.current;
202
208
  if (isDisabled || state.didFirePressStart) {
203
209
  return false;
@@ -219,9 +225,9 @@ export function usePress(props: PressHookProps): PressResult {
219
225
  state.didFirePressStart = true;
220
226
  setPressed(true);
221
227
  return shouldStopPropagation;
222
- });
228
+ }, [isDisabled, onPressStart, onPressChange]);
223
229
 
224
- let triggerPressEnd = useEffectEvent((originalEvent: EventBase, pointerType: PointerType, wasPressed = true) => {
230
+ let triggerPressEnd = useCallback((originalEvent: EventBase, pointerType: PointerType, wasPressed = true) => {
225
231
  let state = ref.current;
226
232
  if (!state.didFirePressStart) {
227
233
  return false;
@@ -251,9 +257,10 @@ export function usePress(props: PressHookProps): PressResult {
251
257
 
252
258
  state.isTriggeringEvent = false;
253
259
  return shouldStopPropagation;
254
- });
260
+ }, [isDisabled, onPressEnd, onPressChange, onPress]);
261
+ let triggerPressEndEvent = useEffectEvent(triggerPressEnd);
255
262
 
256
- let triggerPressUp = useEffectEvent((originalEvent: EventBase, pointerType: PointerType) => {
263
+ let triggerPressUp = useCallback((originalEvent: EventBase, pointerType: PointerType) => {
257
264
  let state = ref.current;
258
265
  if (isDisabled) {
259
266
  return false;
@@ -268,15 +275,17 @@ export function usePress(props: PressHookProps): PressResult {
268
275
  }
269
276
 
270
277
  return true;
271
- });
278
+ }, [isDisabled, onPressUp]);
279
+ let triggerPressUpEvent = useEffectEvent(triggerPressUp);
272
280
 
273
- let cancel = useEffectEvent((e: EventBase) => {
281
+ let cancel = useCallback((e: EventBase) => {
274
282
  let state = ref.current;
275
283
  if (state.isPressed && state.target) {
276
284
  if (state.didFirePressStart && state.pointerType != null) {
277
285
  triggerPressEnd(createEvent(state.target, e), state.pointerType, false);
278
286
  }
279
287
  state.isPressed = false;
288
+ setIsPointerPressed(null);
280
289
  state.isOverTarget = false;
281
290
  state.activePointerId = null;
282
291
  state.pointerType = null;
@@ -289,23 +298,24 @@ export function usePress(props: PressHookProps): PressResult {
289
298
  }
290
299
  state.disposables = [];
291
300
  }
292
- });
301
+ }, [allowTextSelectionOnPress, removeAllGlobalListeners, triggerPressEnd]);
302
+ let cancelEvent = useEffectEvent(cancel);
293
303
 
294
- let cancelOnPointerExit = useEffectEvent((e: EventBase) => {
304
+ let cancelOnPointerExit = useCallback((e: EventBase) => {
295
305
  if (shouldCancelOnPointerExit) {
296
306
  cancel(e);
297
307
  }
298
- });
308
+ }, [shouldCancelOnPointerExit, cancel]);
299
309
 
300
- let triggerClick = useEffectEvent((e: RMouseEvent<FocusableElement>) => {
310
+ let triggerClick = useCallback((e: RMouseEvent<FocusableElement>) => {
301
311
  if (isDisabled) {
302
312
  return;
303
313
  }
304
314
 
305
315
  onClick?.(e);
306
- });
316
+ }, [isDisabled, onClick]);
307
317
 
308
- let triggerSyntheticClick = useEffectEvent((e: KeyboardEvent | TouchEvent, target: FocusableElement) => {
318
+ let triggerSyntheticClick = useCallback((e: KeyboardEvent | TouchEvent, target: FocusableElement) => {
309
319
  if (isDisabled) {
310
320
  return;
311
321
  }
@@ -320,7 +330,164 @@ export function usePress(props: PressHookProps): PressResult {
320
330
  setEventTarget(event, target);
321
331
  onClick(createSyntheticEvent(event));
322
332
  }
323
- });
333
+ }, [isDisabled, onClick]);
334
+ let triggerSyntheticClickEvent = useEffectEvent(triggerSyntheticClick);
335
+
336
+ let [isElemKeyPressed, setIsElemKeyPressed] = useState<boolean>(false);
337
+ useLayoutEffect(() => {
338
+ let state = ref.current;
339
+ if (isElemKeyPressed) {
340
+ let onKeyUp = (e: KeyboardEvent) => {
341
+ if (state.isPressed && state.target && isValidKeyboardEvent(e, state.target)) {
342
+ if (shouldPreventDefaultKeyboard(getEventTarget(e), e.key)) {
343
+ e.preventDefault();
344
+ }
345
+
346
+ let target = getEventTarget(e);
347
+ let wasPressed = nodeContains(state.target, getEventTarget(e));
348
+ triggerPressEndEvent(createEvent(state.target, e), 'keyboard', wasPressed);
349
+ if (wasPressed) {
350
+ triggerSyntheticClickEvent(e, state.target);
351
+ }
352
+ removeAllGlobalListeners();
353
+
354
+ // If a link was triggered with a key other than Enter, open the URL ourselves.
355
+ // This means the link has a role override, and the default browser behavior
356
+ // only applies when using the Enter key.
357
+ if (e.key !== 'Enter' && isHTMLAnchorLink(state.target) && nodeContains(state.target, target) && !e[LINK_CLICKED]) {
358
+ // Store a hidden property on the event so we only trigger link click once,
359
+ // even if there are multiple usePress instances attached to the element.
360
+ e[LINK_CLICKED] = true;
361
+ openLink(state.target, e, false);
362
+ }
363
+
364
+ state.isPressed = false;
365
+ setIsElemKeyPressed(false);
366
+ state.metaKeyEvents?.delete(e.key);
367
+ } else if (e.key === 'Meta' && state.metaKeyEvents?.size) {
368
+ // If we recorded keydown events that occurred while the Meta key was pressed,
369
+ // and those haven't received keyup events already, fire keyup events ourselves.
370
+ // See comment above for more info about the macOS bug causing this.
371
+ let events = state.metaKeyEvents;
372
+ state.metaKeyEvents = undefined;
373
+ for (let event of events.values()) {
374
+ state.target?.dispatchEvent(new KeyboardEvent('keyup', event));
375
+ }
376
+ }
377
+ };
378
+ // Focus may move before the key up event, so register the event on the document
379
+ // instead of the same element where the key down event occurred. Make it capturing so that it will trigger
380
+ // before stopPropagation from useKeyboard on a child element may happen and thus we can still call triggerPress for the parent element.
381
+ let originalTarget = state.target;
382
+ let pressUp = (e) => {
383
+ if (originalTarget && isValidKeyboardEvent(e, originalTarget) && !e.repeat && nodeContains(originalTarget, getEventTarget(e)) && state.target) {
384
+ triggerPressUpEvent(createEvent(state.target, e), 'keyboard');
385
+ }
386
+ };
387
+ let listener = chain(pressUp, onKeyUp);
388
+ addGlobalListener(getOwnerDocument(state.target), 'keyup', listener, true);
389
+ return () => {
390
+ removeGlobalListener(getOwnerDocument(state.target), 'keyup', listener, true);
391
+ };
392
+ }
393
+ }, [isElemKeyPressed, addGlobalListener, removeAllGlobalListeners, removeGlobalListener]);
394
+
395
+ let [isPointerPressed, setIsPointerPressed] = useState<'pointer' | 'mouse' | 'touch' | null>(null);
396
+ useLayoutEffect(() => {
397
+ let state = ref.current;
398
+ if (isPointerPressed === 'pointer') {
399
+ let onPointerUp = (e: PointerEvent) => {
400
+ if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) {
401
+ if (nodeContains(state.target, getEventTarget(e)) && state.pointerType != null) {
402
+ // Wait for onClick to fire onPress. This avoids browser issues when the DOM
403
+ // is mutated between onPointerUp and onClick, and is more compatible with third party libraries.
404
+ // https://github.com/adobe/react-spectrum/issues/1513
405
+ // https://issues.chromium.org/issues/40732224
406
+ // However, iOS and Android do not focus or fire onClick after a long press.
407
+ // We work around this by triggering a click ourselves after a timeout.
408
+ // This timeout is canceled during the click event in case the real one fires first.
409
+ // The timeout must be at least 32ms, because Safari on iOS delays the click event on
410
+ // non-form elements without certain ARIA roles (for hover emulation).
411
+ // https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892
412
+ let clicked = false;
413
+ let timeout = setTimeout(() => {
414
+ if (state.isPressed && state.target instanceof HTMLElement) {
415
+ if (clicked) {
416
+ cancelEvent(e);
417
+ } else {
418
+ focusWithoutScrolling(state.target);
419
+ state.target.click();
420
+ }
421
+ }
422
+ }, 80);
423
+ // Use a capturing listener to track if a click occurred.
424
+ // If stopPropagation is called it may never reach our handler.
425
+ addGlobalListener(e.currentTarget as Document, 'click', () => clicked = true, true);
426
+ state.disposables.push(() => clearTimeout(timeout));
427
+ } else {
428
+ cancelEvent(e);
429
+ }
430
+
431
+ // Ignore subsequent onPointerLeave event before onClick on touch devices.
432
+ state.isOverTarget = false;
433
+ }
434
+ };
435
+
436
+ let onPointerCancel = (e: PointerEvent) => {
437
+ cancelEvent(e);
438
+ };
439
+
440
+ addGlobalListener(getOwnerDocument(state.target), 'pointerup', onPointerUp, false);
441
+ addGlobalListener(getOwnerDocument(state.target), 'pointercancel', onPointerCancel, false);
442
+ return () => {
443
+ removeGlobalListener(getOwnerDocument(state.target), 'pointerup', onPointerUp, false);
444
+ removeGlobalListener(getOwnerDocument(state.target), 'pointercancel', onPointerCancel, false);
445
+ };
446
+ } else if (isPointerPressed === 'mouse' && process.env.NODE_ENV === 'test') {
447
+ let onMouseUp = (e: MouseEvent) => {
448
+ // Only handle left clicks
449
+ if (e.button !== 0) {
450
+ return;
451
+ }
452
+
453
+ if (state.ignoreEmulatedMouseEvents) {
454
+ state.ignoreEmulatedMouseEvents = false;
455
+ return;
456
+ }
457
+
458
+ if (state.target && nodeContains(state.target, e.target as Element) && state.pointerType != null) {
459
+ // Wait for onClick to fire onPress. This avoids browser issues when the DOM
460
+ // is mutated between onMouseUp and onClick, and is more compatible with third party libraries.
461
+ } else {
462
+ cancelEvent(e);
463
+ }
464
+
465
+ state.isOverTarget = false;
466
+ };
467
+
468
+ addGlobalListener(getOwnerDocument(state.target), 'mouseup', onMouseUp, false);
469
+ return () => {
470
+ removeGlobalListener(getOwnerDocument(state.target), 'mouseup', onMouseUp, false);
471
+ };
472
+ } else if (isPointerPressed === 'touch' && process.env.NODE_ENV === 'test') {
473
+ let onScroll = (e: Event) => {
474
+ if (state.isPressed && nodeContains(getEventTarget(e), state.target)) {
475
+ cancelEvent({
476
+ currentTarget: state.target,
477
+ shiftKey: false,
478
+ ctrlKey: false,
479
+ metaKey: false,
480
+ altKey: false
481
+ });
482
+ }
483
+ };
484
+
485
+ addGlobalListener(getOwnerWindow(state.target), 'scroll', onScroll, true);
486
+ return () => {
487
+ removeGlobalListener(getOwnerWindow(state.target), 'scroll', onScroll, true);
488
+ };
489
+ }
490
+ }, [isPointerPressed, addGlobalListener, removeGlobalListener]);
324
491
 
325
492
  let pressProps = useMemo(() => {
326
493
  let state = ref.current;
@@ -338,20 +505,9 @@ export function usePress(props: PressHookProps): PressResult {
338
505
  if (!state.isPressed && !e.repeat) {
339
506
  state.target = e.currentTarget;
340
507
  state.isPressed = true;
508
+ setIsElemKeyPressed(true);
341
509
  state.pointerType = 'keyboard';
342
510
  shouldStopPropagation = triggerPressStart(e, 'keyboard');
343
-
344
- // Focus may move before the key up event, so register the event on the document
345
- // instead of the same element where the key down event occurred. Make it capturing so that it will trigger
346
- // before stopPropagation from useKeyboard on a child element may happen and thus we can still call triggerPress for the parent element.
347
- let originalTarget = e.currentTarget;
348
- let pressUp = (e) => {
349
- if (isValidKeyboardEvent(e, originalTarget) && !e.repeat && nodeContains(originalTarget, getEventTarget(e)) && state.target) {
350
- triggerPressUp(createEvent(state.target, e), 'keyboard');
351
- }
352
- };
353
-
354
- addGlobalListener(getOwnerDocument(e.currentTarget), 'keyup', chain(pressUp, onKeyUp), true);
355
511
  }
356
512
 
357
513
  if (shouldStopPropagation) {
@@ -409,44 +565,6 @@ export function usePress(props: PressHookProps): PressResult {
409
565
  }
410
566
  };
411
567
 
412
- let onKeyUp = (e: KeyboardEvent) => {
413
- if (state.isPressed && state.target && isValidKeyboardEvent(e, state.target)) {
414
- if (shouldPreventDefaultKeyboard(getEventTarget(e), e.key)) {
415
- e.preventDefault();
416
- }
417
-
418
- let target = getEventTarget(e);
419
- let wasPressed = nodeContains(state.target, getEventTarget(e));
420
- triggerPressEnd(createEvent(state.target, e), 'keyboard', wasPressed);
421
- if (wasPressed) {
422
- triggerSyntheticClick(e, state.target);
423
- }
424
- removeAllGlobalListeners();
425
-
426
- // If a link was triggered with a key other than Enter, open the URL ourselves.
427
- // This means the link has a role override, and the default browser behavior
428
- // only applies when using the Enter key.
429
- if (e.key !== 'Enter' && isHTMLAnchorLink(state.target) && nodeContains(state.target, target) && !e[LINK_CLICKED]) {
430
- // Store a hidden property on the event so we only trigger link click once,
431
- // even if there are multiple usePress instances attached to the element.
432
- e[LINK_CLICKED] = true;
433
- openLink(state.target, e, false);
434
- }
435
-
436
- state.isPressed = false;
437
- state.metaKeyEvents?.delete(e.key);
438
- } else if (e.key === 'Meta' && state.metaKeyEvents?.size) {
439
- // If we recorded keydown events that occurred while the Meta key was pressed,
440
- // and those haven't received keyup events already, fire keyup events ourselves.
441
- // See comment above for more info about the macOS bug causing this.
442
- let events = state.metaKeyEvents;
443
- state.metaKeyEvents = undefined;
444
- for (let event of events.values()) {
445
- state.target?.dispatchEvent(new KeyboardEvent('keyup', event));
446
- }
447
- }
448
- };
449
-
450
568
  if (typeof PointerEvent !== 'undefined') {
451
569
  pressProps.onPointerDown = (e) => {
452
570
  // Only handle left clicks, and ignore events that bubbled through portals.
@@ -468,6 +586,7 @@ export function usePress(props: PressHookProps): PressResult {
468
586
  let shouldStopPropagation = true;
469
587
  if (!state.isPressed) {
470
588
  state.isPressed = true;
589
+ setIsPointerPressed('pointer');
471
590
  state.isOverTarget = true;
472
591
  state.activePointerId = e.pointerId;
473
592
  state.target = e.currentTarget as FocusableElement;
@@ -482,11 +601,14 @@ export function usePress(props: PressHookProps): PressResult {
482
601
  // This enables onPointerLeave and onPointerEnter to fire.
483
602
  let target = getEventTarget(e.nativeEvent);
484
603
  if ('releasePointerCapture' in target) {
485
- target.releasePointerCapture(e.pointerId);
604
+ if ('hasPointerCapture' in target) {
605
+ if (target.hasPointerCapture(e.pointerId)) {
606
+ target.releasePointerCapture(e.pointerId);
607
+ }
608
+ } else {
609
+ (target as Element).releasePointerCapture(e.pointerId);
610
+ }
486
611
  }
487
-
488
- addGlobalListener(getOwnerDocument(e.currentTarget), 'pointerup', onPointerUp, false);
489
- addGlobalListener(getOwnerDocument(e.currentTarget), 'pointercancel', onPointerCancel, false);
490
612
  }
491
613
 
492
614
  if (shouldStopPropagation) {
@@ -538,46 +660,6 @@ export function usePress(props: PressHookProps): PressResult {
538
660
  }
539
661
  };
540
662
 
541
- let onPointerUp = (e: PointerEvent) => {
542
- if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) {
543
- if (nodeContains(state.target, getEventTarget(e)) && state.pointerType != null) {
544
- // Wait for onClick to fire onPress. This avoids browser issues when the DOM
545
- // is mutated between onPointerUp and onClick, and is more compatible with third party libraries.
546
- // https://github.com/adobe/react-spectrum/issues/1513
547
- // https://issues.chromium.org/issues/40732224
548
- // However, iOS and Android do not focus or fire onClick after a long press.
549
- // We work around this by triggering a click ourselves after a timeout.
550
- // This timeout is canceled during the click event in case the real one fires first.
551
- // The timeout must be at least 32ms, because Safari on iOS delays the click event on
552
- // non-form elements without certain ARIA roles (for hover emulation).
553
- // https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892
554
- let clicked = false;
555
- let timeout = setTimeout(() => {
556
- if (state.isPressed && state.target instanceof HTMLElement) {
557
- if (clicked) {
558
- cancel(e);
559
- } else {
560
- focusWithoutScrolling(state.target);
561
- state.target.click();
562
- }
563
- }
564
- }, 80);
565
- // Use a capturing listener to track if a click occurred.
566
- // If stopPropagation is called it may never reach our handler.
567
- addGlobalListener(e.currentTarget as Document, 'click', () => clicked = true, true);
568
- state.disposables.push(() => clearTimeout(timeout));
569
- } else {
570
- cancel(e);
571
- }
572
-
573
- // Ignore subsequent onPointerLeave event before onClick on touch devices.
574
- state.isOverTarget = false;
575
- }
576
- };
577
-
578
- let onPointerCancel = (e: PointerEvent) => {
579
- cancel(e);
580
- };
581
663
 
582
664
  pressProps.onDragStart = (e) => {
583
665
  if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
@@ -603,6 +685,7 @@ export function usePress(props: PressHookProps): PressResult {
603
685
  }
604
686
 
605
687
  state.isPressed = true;
688
+ setIsPointerPressed('mouse');
606
689
  state.isOverTarget = true;
607
690
  state.target = e.currentTarget;
608
691
  state.pointerType = isVirtualClick(e.nativeEvent) ? 'virtual' : 'mouse';
@@ -619,8 +702,6 @@ export function usePress(props: PressHookProps): PressResult {
619
702
  state.disposables.push(dispose);
620
703
  }
621
704
  }
622
-
623
- addGlobalListener(getOwnerDocument(e.currentTarget), 'mouseup', onMouseUp, false);
624
705
  };
625
706
 
626
707
  pressProps.onMouseEnter = (e) => {
@@ -666,27 +747,6 @@ export function usePress(props: PressHookProps): PressResult {
666
747
  }
667
748
  };
668
749
 
669
- let onMouseUp = (e: MouseEvent) => {
670
- // Only handle left clicks
671
- if (e.button !== 0) {
672
- return;
673
- }
674
-
675
- if (state.ignoreEmulatedMouseEvents) {
676
- state.ignoreEmulatedMouseEvents = false;
677
- return;
678
- }
679
-
680
- if (state.target && state.target.contains(e.target as Element) && state.pointerType != null) {
681
- // Wait for onClick to fire onPress. This avoids browser issues when the DOM
682
- // is mutated between onMouseUp and onClick, and is more compatible with third party libraries.
683
- } else {
684
- cancel(e);
685
- }
686
-
687
- state.isOverTarget = false;
688
- };
689
-
690
750
  pressProps.onTouchStart = (e) => {
691
751
  if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
692
752
  return;
@@ -700,6 +760,7 @@ export function usePress(props: PressHookProps): PressResult {
700
760
  state.ignoreEmulatedMouseEvents = true;
701
761
  state.isOverTarget = true;
702
762
  state.isPressed = true;
763
+ setIsPointerPressed('touch');
703
764
  state.target = e.currentTarget;
704
765
  state.pointerType = 'touch';
705
766
 
@@ -711,8 +772,6 @@ export function usePress(props: PressHookProps): PressResult {
711
772
  if (shouldStopPropagation) {
712
773
  e.stopPropagation();
713
774
  }
714
-
715
- addGlobalListener(getOwnerWindow(e.currentTarget), 'scroll', onScroll, true);
716
775
  };
717
776
 
718
777
  pressProps.onTouchMove = (e) => {
@@ -768,6 +827,7 @@ export function usePress(props: PressHookProps): PressResult {
768
827
  }
769
828
 
770
829
  state.isPressed = false;
830
+ setIsPointerPressed(null);
771
831
  state.activePointerId = null;
772
832
  state.isOverTarget = false;
773
833
  state.ignoreEmulatedMouseEvents = true;
@@ -788,18 +848,6 @@ export function usePress(props: PressHookProps): PressResult {
788
848
  }
789
849
  };
790
850
 
791
- let onScroll = (e: Event) => {
792
- if (state.isPressed && nodeContains(getEventTarget(e), state.target)) {
793
- cancel({
794
- currentTarget: state.target,
795
- shiftKey: false,
796
- ctrlKey: false,
797
- metaKey: false,
798
- altKey: false
799
- });
800
- }
801
- };
802
-
803
851
  pressProps.onDragStart = (e) => {
804
852
  if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) {
805
853
  return;
@@ -811,7 +859,6 @@ export function usePress(props: PressHookProps): PressResult {
811
859
 
812
860
  return pressProps;
813
861
  }, [
814
- addGlobalListener,
815
862
  isDisabled,
816
863
  preventFocusOnPress,
817
864
  removeAllGlobalListeners,
@@ -941,7 +988,8 @@ function createEvent(target: FocusableElement, e: EventBase): EventBase {
941
988
  metaKey: e.metaKey,
942
989
  altKey: e.altKey,
943
990
  clientX,
944
- clientY
991
+ clientY,
992
+ key: e.key
945
993
  };
946
994
  }
947
995
 
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, useEffectEvent, useLayoutEffect} from '@react-aria/utils';
14
+ import {focusWithoutScrolling, 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.
@@ -48,10 +48,6 @@ export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur:
48
48
  };
49
49
  }, []);
50
50
 
51
- let dispatchBlur = useEffectEvent((e: ReactFocusEvent<Target>) => {
52
- onBlur?.(e);
53
- });
54
-
55
51
  // This function is called during a React onFocus event.
56
52
  return useCallback((e: ReactFocusEvent<Target>) => {
57
53
  // React does not fire onBlur when an element is disabled. https://github.com/facebook/react/issues/9142
@@ -73,7 +69,7 @@ export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur:
73
69
  if (target.disabled) {
74
70
  // For backward compatibility, dispatch a (fake) React synthetic event.
75
71
  let event = createSyntheticEvent<ReactFocusEvent<Target>>(e);
76
- dispatchBlur(event);
72
+ onBlur?.(event);
77
73
  }
78
74
 
79
75
  // We no longer need the MutationObserver once the target is blurred.
@@ -96,7 +92,7 @@ export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur:
96
92
 
97
93
  stateRef.current.observer.observe(target, {attributes: true, attributeFilter: ['disabled']});
98
94
  }
99
- }, [dispatchBlur]);
95
+ }, [onBlur]);
100
96
  }
101
97
 
102
98
  export let ignoreFocusEvent = false;