@react-aria/interactions 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -13,12 +13,14 @@ var {
13
13
 
14
14
  var {
15
15
  mergeProps,
16
+ isIOS,
16
17
  runAfterTransition,
17
18
  focusWithoutScrolling,
18
19
  useGlobalListeners,
19
20
  useSyncRef,
20
21
  isMac,
21
- useEvent
22
+ useEvent,
23
+ useDescription
22
24
  } = require("@react-aria/utils");
23
25
 
24
26
  var _babelRuntimeHelpersObjectWithoutPropertiesLoose = $parcel$interopDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
@@ -29,43 +31,71 @@ function $parcel$interopDefault(a) {
29
31
  return a && a.__esModule ? a.default : a;
30
32
  }
31
33
 
34
+ // Note that state only matters here for iOS. Non-iOS gets user-select: none applied to the target element
35
+ // rather than at the document level so we just need to apply/remove user-select: none for each pressed element individually
32
36
  let $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'default';
33
37
  let $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect = '';
38
+ let $ce801cc8e3ff24b95c928e0152c7b7f2$var$modifiedElementMap = new WeakMap();
34
39
 
35
- function $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection() {
36
- if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state === 'default') {
37
- $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect = document.documentElement.style.webkitUserSelect;
38
- document.documentElement.style.webkitUserSelect = 'none';
39
- }
40
+ function $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection(target) {
41
+ if (isIOS()) {
42
+ if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state === 'default') {
43
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect = document.documentElement.style.webkitUserSelect;
44
+ document.documentElement.style.webkitUserSelect = 'none';
45
+ }
40
46
 
41
- $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'disabled';
47
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'disabled';
48
+ } else if (target) {
49
+ // If not iOS, store the target's original user-select and change to user-select: none
50
+ // Ignore state since it doesn't apply for non iOS
51
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$modifiedElementMap.set(target, target.style.userSelect);
52
+ target.style.userSelect = 'none';
53
+ }
42
54
  }
43
55
 
44
- function $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection() {
45
- // If the state is already default, there's nothing to do.
46
- // If it is restoring, then there's no need to queue a second restore.
47
- if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state !== 'disabled') {
48
- return;
49
- }
56
+ function $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection(target) {
57
+ if (isIOS()) {
58
+ // If the state is already default, there's nothing to do.
59
+ // If it is restoring, then there's no need to queue a second restore.
60
+ if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state !== 'disabled') {
61
+ return;
62
+ }
50
63
 
51
- $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'restoring'; // There appears to be a delay on iOS where selection still might occur
52
- // after pointer up, so wait a bit before removing user-select.
64
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'restoring'; // There appears to be a delay on iOS where selection still might occur
65
+ // after pointer up, so wait a bit before removing user-select.
66
+
67
+ setTimeout(() => {
68
+ // Wait for any CSS transitions to complete so we don't recompute style
69
+ // for the whole page in the middle of the animation and cause jank.
70
+ runAfterTransition(() => {
71
+ // Avoid race conditions
72
+ if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state === 'restoring') {
73
+ if (document.documentElement.style.webkitUserSelect === 'none') {
74
+ document.documentElement.style.webkitUserSelect = $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect || '';
75
+ }
53
76
 
54
- setTimeout(() => {
55
- // Wait for any CSS transitions to complete so we don't recompute style
56
- // for the whole page in the middle of the animation and cause jank.
57
- runAfterTransition(() => {
58
- // Avoid race conditions
59
- if ($ce801cc8e3ff24b95c928e0152c7b7f2$var$state === 'restoring') {
60
- if (document.documentElement.style.webkitUserSelect === 'none') {
61
- document.documentElement.style.webkitUserSelect = $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect || '';
77
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect = '';
78
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'default';
62
79
  }
80
+ });
81
+ }, 300);
82
+ } else {
83
+ // If not iOS, restore the target's original user-select if any
84
+ // Ignore state since it doesn't apply for non iOS
85
+ if (target && $ce801cc8e3ff24b95c928e0152c7b7f2$var$modifiedElementMap.has(target)) {
86
+ let targetOldUserSelect = $ce801cc8e3ff24b95c928e0152c7b7f2$var$modifiedElementMap.get(target);
63
87
 
64
- $ce801cc8e3ff24b95c928e0152c7b7f2$var$savedUserSelect = '';
65
- $ce801cc8e3ff24b95c928e0152c7b7f2$var$state = 'default';
88
+ if (target.style.userSelect === 'none') {
89
+ target.style.userSelect = targetOldUserSelect;
66
90
  }
67
- });
68
- }, 300);
91
+
92
+ if (target.getAttribute('style') === '') {
93
+ target.removeAttribute('style');
94
+ }
95
+
96
+ $ce801cc8e3ff24b95c928e0152c7b7f2$var$modifiedElementMap.delete(target);
97
+ }
98
+ }
69
99
  }
70
100
 
71
101
  /*
@@ -136,9 +166,10 @@ function usePress(props) {
136
166
  isDisabled,
137
167
  isPressed: isPressedProp,
138
168
  preventFocusOnPress,
139
- shouldCancelOnPointerExit
169
+ shouldCancelOnPointerExit,
170
+ allowTextSelectionOnPress
140
171
  } = _usePressResponderCon,
141
- domProps = _babelRuntimeHelpersObjectWithoutPropertiesLoose(_usePressResponderCon, ["onPress", "onPressChange", "onPressStart", "onPressEnd", "onPressUp", "isDisabled", "isPressed", "preventFocusOnPress", "shouldCancelOnPointerExit", "ref"]);
172
+ domProps = _babelRuntimeHelpersObjectWithoutPropertiesLoose(_usePressResponderCon, ["onPress", "onPressChange", "onPressStart", "onPressEnd", "onPressUp", "isDisabled", "isPressed", "preventFocusOnPress", "shouldCancelOnPointerExit", "allowTextSelectionOnPress", "ref"]);
142
173
 
143
174
  let propsRef = useRef(null);
144
175
  propsRef.current = {
@@ -283,7 +314,10 @@ function usePress(props) {
283
314
  state.activePointerId = null;
284
315
  state.pointerType = null;
285
316
  removeAllGlobalListeners();
286
- $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection();
317
+
318
+ if (!allowTextSelectionOnPress) {
319
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection(state.target);
320
+ }
287
321
  }
288
322
  };
289
323
 
@@ -326,7 +360,7 @@ function usePress(props) {
326
360
  // trigger as if it were a keyboard click.
327
361
 
328
362
 
329
- if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && $eda9c464f45e6c61a293990c493$export$isVirtualClick(e.nativeEvent)) {
363
+ if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && (state.pointerType === 'virtual' || $eda9c464f45e6c61a293990c493$export$isVirtualClick(e.nativeEvent))) {
330
364
  // Ensure the element receives focus (VoiceOver on iOS does not do this)
331
365
  if (!isDisabled && !preventFocusOnPress) {
332
366
  focusWithoutScrolling(e.currentTarget);
@@ -349,11 +383,12 @@ function usePress(props) {
349
383
  e.preventDefault();
350
384
  e.stopPropagation();
351
385
  state.isPressed = false;
352
- triggerPressEnd($ed8d760564e19d8c7d03a6a4$var$createEvent(state.target, e), 'keyboard', e.target === state.target);
386
+ let target = e.target;
387
+ triggerPressEnd($ed8d760564e19d8c7d03a6a4$var$createEvent(state.target, e), 'keyboard', state.target.contains(target));
353
388
  removeAllGlobalListeners(); // If the target is a link, trigger the click method to open the URL,
354
389
  // but defer triggering pressEnd until onClick event handler.
355
390
 
356
- if (e.target === state.target && $ed8d760564e19d8c7d03a6a4$var$isHTMLAnchorLink(state.target) || state.target.getAttribute('role') === 'link') {
391
+ if (state.target.contains(target) && $ed8d760564e19d8c7d03a6a4$var$isHTMLAnchorLink(state.target) || state.target.getAttribute('role') === 'link') {
357
392
  state.target.click();
358
393
  }
359
394
  }
@@ -364,17 +399,24 @@ function usePress(props) {
364
399
  // Only handle left clicks, and ignore events that bubbled through portals.
365
400
  if (e.button !== 0 || !e.currentTarget.contains(e.target)) {
366
401
  return;
402
+ } // iOS safari fires pointer events from VoiceOver with incorrect coordinates/target.
403
+ // Ignore and let the onClick handler take care of it instead.
404
+ // https://bugs.webkit.org/show_bug.cgi?id=222627
405
+ // https://bugs.webkit.org/show_bug.cgi?id=223202
406
+
407
+
408
+ if ($ed8d760564e19d8c7d03a6a4$var$isVirtualPointerEvent(e.nativeEvent)) {
409
+ state.pointerType = 'virtual';
410
+ return;
367
411
  } // Due to browser inconsistencies, especially on mobile browsers, we prevent
368
412
  // default on pointer down and handle focusing the pressable element ourselves.
369
413
 
370
414
 
371
415
  if ($ed8d760564e19d8c7d03a6a4$var$shouldPreventDefault(e.target)) {
372
416
  e.preventDefault();
373
- } // iOS safari fires pointer events from VoiceOver (but only when outside an iframe...)
374
- // https://bugs.webkit.org/show_bug.cgi?id=222627
375
-
417
+ }
376
418
 
377
- state.pointerType = $ed8d760564e19d8c7d03a6a4$var$isVirtualPointerEvent(e.nativeEvent) ? 'virtual' : e.pointerType;
419
+ state.pointerType = e.pointerType;
378
420
  e.stopPropagation();
379
421
 
380
422
  if (!state.isPressed) {
@@ -387,7 +429,10 @@ function usePress(props) {
387
429
  focusWithoutScrolling(e.currentTarget);
388
430
  }
389
431
 
390
- $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection();
432
+ if (!allowTextSelectionOnPress) {
433
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection(state.target);
434
+ }
435
+
391
436
  triggerPressStart(e, state.pointerType);
392
437
  addGlobalListener(document, 'pointermove', onPointerMove, false);
393
438
  addGlobalListener(document, 'pointerup', onPointerUp, false);
@@ -413,7 +458,8 @@ function usePress(props) {
413
458
  };
414
459
 
415
460
  pressProps.onPointerUp = e => {
416
- if (!e.currentTarget.contains(e.target)) {
461
+ // iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown.
462
+ if (!e.currentTarget.contains(e.target) || state.pointerType === 'virtual') {
417
463
  return;
418
464
  } // Only handle left clicks
419
465
  // Safari on iOS sometimes fires pointerup events, even
@@ -421,7 +467,7 @@ function usePress(props) {
421
467
 
422
468
 
423
469
  if (e.button === 0 && $ed8d760564e19d8c7d03a6a4$var$isOverTarget(e, e.currentTarget)) {
424
- triggerPressUp(e, state.pointerType || ($ed8d760564e19d8c7d03a6a4$var$isVirtualPointerEvent(e.nativeEvent) ? 'virtual' : e.pointerType));
470
+ triggerPressUp(e, state.pointerType || e.pointerType);
425
471
  }
426
472
  }; // Safari on iOS < 13.2 does not implement pointerenter/pointerleave events correctly.
427
473
  // Use pointer move events instead to implement our own hit testing.
@@ -461,7 +507,10 @@ function usePress(props) {
461
507
  state.activePointerId = null;
462
508
  state.pointerType = null;
463
509
  removeAllGlobalListeners();
464
- $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection();
510
+
511
+ if (!allowTextSelectionOnPress) {
512
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection(state.target);
513
+ }
465
514
  }
466
515
  };
467
516
 
@@ -596,7 +645,10 @@ function usePress(props) {
596
645
  focusWithoutScrolling(e.currentTarget);
597
646
  }
598
647
 
599
- $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection();
648
+ if (!allowTextSelectionOnPress) {
649
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$disableTextSelection(state.target);
650
+ }
651
+
600
652
  triggerPressStart(e, state.pointerType);
601
653
  addGlobalListener(window, 'scroll', onScroll, true);
602
654
  };
@@ -653,7 +705,11 @@ function usePress(props) {
653
705
  state.activePointerId = null;
654
706
  state.isOverTarget = false;
655
707
  state.ignoreEmulatedMouseEvents = true;
656
- $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection();
708
+
709
+ if (!allowTextSelectionOnPress) {
710
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection(state.target);
711
+ }
712
+
657
713
  removeAllGlobalListeners();
658
714
  };
659
715
 
@@ -691,12 +747,16 @@ function usePress(props) {
691
747
  }
692
748
 
693
749
  return pressProps;
694
- }, [addGlobalListener, isDisabled, preventFocusOnPress, removeAllGlobalListeners]); // Remove user-select: none in case component unmounts immediately after pressStart
750
+ }, [addGlobalListener, isDisabled, preventFocusOnPress, removeAllGlobalListeners, allowTextSelectionOnPress]); // Remove user-select: none in case component unmounts immediately after pressStart
695
751
  // eslint-disable-next-line arrow-body-style
696
752
 
697
753
  useEffect(() => {
698
- return () => $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection();
699
- }, []);
754
+ return () => {
755
+ if (!allowTextSelectionOnPress) {
756
+ $ce801cc8e3ff24b95c928e0152c7b7f2$export$restoreTextSelection(ref.current.target);
757
+ }
758
+ };
759
+ }, [allowTextSelectionOnPress]);
700
760
  return {
701
761
  isPressed: isPressedProp || isPressed,
702
762
  pressProps: mergeProps(domProps, pressProps)
@@ -712,6 +772,7 @@ function $ed8d760564e19d8c7d03a6a4$var$isHTMLAnchorLink(target) {
712
772
  function $ed8d760564e19d8c7d03a6a4$var$isValidKeyboardEvent(event) {
713
773
  const {
714
774
  key,
775
+ code,
715
776
  target
716
777
  } = event;
717
778
  const element = target;
@@ -722,7 +783,7 @@ function $ed8d760564e19d8c7d03a6a4$var$isValidKeyboardEvent(event) {
722
783
  const role = element.getAttribute('role'); // Accessibility for keyboards. Space and Enter only.
723
784
  // "Spacebar" is for IE 11
724
785
 
725
- return (key === 'Enter' || key === ' ' || key === 'Spacebar') && tagName !== 'INPUT' && tagName !== 'TEXTAREA' && isContentEditable !== true && ( // A link with a valid href should be handled natively,
786
+ return (key === 'Enter' || key === ' ' || key === 'Spacebar' || code === 'Space') && tagName !== 'INPUT' && tagName !== 'TEXTAREA' && isContentEditable !== true && ( // A link with a valid href should be handled natively,
726
787
  // unless it also has role='button' and was triggered using Space.
727
788
  !$ed8d760564e19d8c7d03a6a4$var$isHTMLAnchorLink(element) || role === 'button' && key !== 'Enter') && // An element with role='link' should only trigger with Enter key
728
789
  !(role === 'link' && key !== 'Enter');
@@ -802,7 +863,11 @@ function $ed8d760564e19d8c7d03a6a4$var$shouldPreventDefault(target) {
802
863
 
803
864
  function $ed8d760564e19d8c7d03a6a4$var$isVirtualPointerEvent(event) {
804
865
  // If the pointer size is zero, then we assume it's from a screen reader.
805
- return event.width === 0 && event.height === 0;
866
+ // Android TalkBack double tap will sometimes return a event with width and height of 1
867
+ // and pointerType === 'mouse' so we need to check for a specific combination of event attributes.
868
+ // Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0
869
+ // instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216
870
+ return event.width === 0 && event.height === 0 || event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0;
806
871
  }
807
872
 
808
873
  const Pressable = /*#__PURE__*/_react.forwardRef((_ref, ref) => {
@@ -940,7 +1005,7 @@ function $b83372066b2b4e1d9257843b2455c$var$triggerChangeHandlers(modality, e) {
940
1005
 
941
1006
  function $b83372066b2b4e1d9257843b2455c$var$isValidKey(e) {
942
1007
  // Control and Shift keys trigger when navigating back to the tab with keyboard.
943
- return !(e.metaKey || !isMac() && e.altKey || e.ctrlKey || e.type === 'keyup' && (e.key === 'Control' || e.key === 'Shift'));
1008
+ return !(e.metaKey || !isMac() && e.altKey || e.ctrlKey || e.key === 'Control' || e.key === 'Shift' || e.key === 'Meta');
944
1009
  }
945
1010
 
946
1011
  function $b83372066b2b4e1d9257843b2455c$var$handleKeyboardEvent(e) {
@@ -1281,7 +1346,7 @@ function useHover(props) {
1281
1346
  }
1282
1347
 
1283
1348
  state.isHovered = true;
1284
- let target = event.target;
1349
+ let target = event.currentTarget;
1285
1350
  state.target = target;
1286
1351
 
1287
1352
  if (onHoverStart) {
@@ -1308,7 +1373,7 @@ function useHover(props) {
1308
1373
  }
1309
1374
 
1310
1375
  state.isHovered = false;
1311
- let target = event.target;
1376
+ let target = event.currentTarget;
1312
1377
 
1313
1378
  if (onHoverEnd) {
1314
1379
  onHoverEnd({
@@ -1371,7 +1436,7 @@ function useHover(props) {
1371
1436
  // Safe to call triggerHoverEnd, it will early return if we aren't currently hovering
1372
1437
  if (isDisabled) {
1373
1438
  triggerHoverEnd({
1374
- target: state.target
1439
+ currentTarget: state.target
1375
1440
  }, state.pointerType);
1376
1441
  }
1377
1442
  }, [isDisabled]);
@@ -1811,4 +1876,93 @@ function useScrollWheel(props, ref) {
1811
1876
  }
1812
1877
 
1813
1878
  exports.useScrollWheel = useScrollWheel;
1879
+ const $c770c63e4e3d986910ca483d5a$var$DEFAULT_THRESHOLD = 500;
1880
+ /**
1881
+ * Handles long press interactions across mouse and touch devices. Supports a customizable time threshold,
1882
+ * accessibility description, and normalizes behavior across browsers and devices.
1883
+ */
1884
+
1885
+ function useLongPress(props) {
1886
+ let {
1887
+ isDisabled,
1888
+ onLongPressStart,
1889
+ onLongPressEnd,
1890
+ onLongPress,
1891
+ threshold = $c770c63e4e3d986910ca483d5a$var$DEFAULT_THRESHOLD,
1892
+ accessibilityDescription
1893
+ } = props;
1894
+ const timeRef = useRef(null);
1895
+ let {
1896
+ addGlobalListener,
1897
+ removeGlobalListener
1898
+ } = useGlobalListeners();
1899
+ let {
1900
+ pressProps
1901
+ } = usePress({
1902
+ isDisabled,
1903
+
1904
+ onPressStart(e) {
1905
+ if (e.pointerType === 'mouse' || e.pointerType === 'touch') {
1906
+ if (onLongPressStart) {
1907
+ onLongPressStart(_babelRuntimeHelpersExtends({}, e, {
1908
+ type: 'longpressstart'
1909
+ }));
1910
+ }
1911
+
1912
+ timeRef.current = setTimeout(() => {
1913
+ // Prevent other usePress handlers from also handling this event.
1914
+ e.target.dispatchEvent(new PointerEvent('pointercancel', {
1915
+ bubbles: true
1916
+ }));
1917
+
1918
+ if (onLongPress) {
1919
+ onLongPress(_babelRuntimeHelpersExtends({}, e, {
1920
+ type: 'longpress'
1921
+ }));
1922
+ }
1923
+
1924
+ timeRef.current = null;
1925
+ }, threshold); // Prevent context menu, which may be opened on long press on touch devices
1926
+
1927
+ if (e.pointerType === 'touch') {
1928
+ let onContextMenu = e => {
1929
+ e.preventDefault();
1930
+ };
1931
+
1932
+ addGlobalListener(e.target, 'contextmenu', onContextMenu, {
1933
+ once: true
1934
+ });
1935
+ addGlobalListener(window, 'pointerup', () => {
1936
+ // If no contextmenu event is fired quickly after pointerup, remove the handler
1937
+ // so future context menu events outside a long press are not prevented.
1938
+ setTimeout(() => {
1939
+ removeGlobalListener(e.target, 'contextmenu', onContextMenu);
1940
+ }, 30);
1941
+ }, {
1942
+ once: true
1943
+ });
1944
+ }
1945
+ }
1946
+ },
1947
+
1948
+ onPressEnd(e) {
1949
+ if (timeRef.current) {
1950
+ clearTimeout(timeRef.current);
1951
+ }
1952
+
1953
+ if (onLongPressEnd && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
1954
+ onLongPressEnd(_babelRuntimeHelpersExtends({}, e, {
1955
+ type: 'longpressend'
1956
+ }));
1957
+ }
1958
+ }
1959
+
1960
+ });
1961
+ let descriptionProps = useDescription(onLongPress && !isDisabled ? accessibilityDescription : null);
1962
+ return {
1963
+ longPressProps: mergeProps(pressProps, descriptionProps)
1964
+ };
1965
+ }
1966
+
1967
+ exports.useLongPress = useLongPress;
1814
1968
  //# sourceMappingURL=main.js.map