@react-aria/selection 3.5.0 → 3.7.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.
package/dist/module.js CHANGED
@@ -1,9 +1,24 @@
1
+ import { useLongPress, usePress } from "@react-aria/interactions";
1
2
  import { useLocale, useCollator } from "@react-aria/i18n";
2
- import { focusWithoutScrolling, isMac, mergeProps } from "@react-aria/utils";
3
+ import { focusWithoutScrolling, mergeProps, scrollIntoView, useEvent, isAppleDevice, isMac } from "@react-aria/utils";
3
4
  import { focusSafely, getFocusableTreeWalker } from "@react-aria/focus";
4
5
  import { useEffect, useRef, useMemo } from "react";
5
6
  import _babelRuntimeHelpersEsmExtends from "@babel/runtime/helpers/esm/extends";
6
7
 
8
+ function $d9657c365c2f735bcaf048eee8599a4a$export$isNonContiguousSelectionModifier(e) {
9
+ // Ctrl + Arrow Up/Arrow Down has a system wide meaning on macOS, so use Alt instead.
10
+ // On Windows and Ubuntu, Alt + Space has a system wide meaning.
11
+ return isAppleDevice() ? e.altKey : e.ctrlKey;
12
+ }
13
+
14
+ function $d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) {
15
+ if (isMac()) {
16
+ return e.metaKey;
17
+ }
18
+
19
+ return e.ctrlKey;
20
+ }
21
+
7
22
  /**
8
23
  * Handles typeahead interactions with collections.
9
24
  */
@@ -81,14 +96,6 @@ function $c78d7fa5f7d5832f9b4f97b33a679865$var$getStringForKey(key) {
81
96
  return '';
82
97
  }
83
98
 
84
- function $a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) {
85
- if (isMac()) {
86
- return e.metaKey;
87
- }
88
-
89
- return e.ctrlKey;
90
- }
91
-
92
99
  /**
93
100
  * Handles interactions with selectable collections.
94
101
  */
@@ -101,20 +108,27 @@ export function useSelectableCollection(options) {
101
108
  shouldFocusWrap = false,
102
109
  disallowEmptySelection = false,
103
110
  disallowSelectAll = false,
104
- selectOnFocus = false,
111
+ selectOnFocus = manager.selectionBehavior === 'replace',
105
112
  disallowTypeAhead = false,
106
113
  shouldUseVirtualFocus,
107
- allowsTabNavigation = false
114
+ allowsTabNavigation = false,
115
+ isVirtualized,
116
+ // If no scrollRef is provided, assume the collection ref is the scrollable region
117
+ scrollRef = ref
108
118
  } = options;
109
119
  let {
110
120
  direction
111
121
  } = useLocale();
112
122
 
113
123
  let onKeyDown = e => {
114
- // Let child element (e.g. menu button) handle the event if the Alt key is pressed.
115
- // Keyboard events bubble through portals. Don't handle keyboard events
124
+ // Prevent option + tab from doing anything since it doesn't move focus to the cells, only buttons/checkboxes
125
+ if (e.altKey && e.key === 'Tab') {
126
+ e.preventDefault();
127
+ } // Keyboard events bubble through portals. Don't handle keyboard events
116
128
  // for elements outside the collection (e.g. menus).
117
- if (e.altKey || !ref.current.contains(e.target)) {
129
+
130
+
131
+ if (!ref.current.contains(e.target)) {
118
132
  return;
119
133
  }
120
134
 
@@ -124,7 +138,7 @@ export function useSelectableCollection(options) {
124
138
 
125
139
  if (e.shiftKey && manager.selectionMode === 'multiple') {
126
140
  manager.extendSelection(key);
127
- } else if (selectOnFocus) {
141
+ } else if (selectOnFocus && !$d9657c365c2f735bcaf048eee8599a4a$export$isNonContiguousSelectionModifier(e)) {
128
142
  manager.replaceSelection(key);
129
143
  }
130
144
  }
@@ -188,10 +202,10 @@ export function useSelectableCollection(options) {
188
202
  case 'Home':
189
203
  if (delegate.getFirstKey) {
190
204
  e.preventDefault();
191
- let firstKey = delegate.getFirstKey(manager.focusedKey, $a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e));
205
+ let firstKey = delegate.getFirstKey(manager.focusedKey, $d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e));
192
206
  manager.setFocusedKey(firstKey);
193
207
 
194
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
208
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
195
209
  manager.extendSelection(firstKey);
196
210
  } else if (selectOnFocus) {
197
211
  manager.replaceSelection(firstKey);
@@ -203,10 +217,10 @@ export function useSelectableCollection(options) {
203
217
  case 'End':
204
218
  if (delegate.getLastKey) {
205
219
  e.preventDefault();
206
- let lastKey = delegate.getLastKey(manager.focusedKey, $a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e));
220
+ let lastKey = delegate.getLastKey(manager.focusedKey, $d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e));
207
221
  manager.setFocusedKey(lastKey);
208
222
 
209
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
223
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
210
224
  manager.extendSelection(lastKey);
211
225
  } else if (selectOnFocus) {
212
226
  manager.replaceSelection(lastKey);
@@ -234,7 +248,7 @@ export function useSelectableCollection(options) {
234
248
  break;
235
249
 
236
250
  case 'a':
237
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
251
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
238
252
  e.preventDefault();
239
253
  manager.selectAll();
240
254
  }
@@ -285,7 +299,19 @@ export function useSelectableCollection(options) {
285
299
  }
286
300
  }
287
301
  }
288
- };
302
+ }; // Store the scroll position so we can restore it later.
303
+
304
+
305
+ let scrollPos = useRef({
306
+ top: 0,
307
+ left: 0
308
+ });
309
+ useEvent(scrollRef, 'scroll', isVirtualized ? null : () => {
310
+ scrollPos.current = {
311
+ top: scrollRef.current.scrollTop,
312
+ left: scrollRef.current.scrollLeft
313
+ };
314
+ });
289
315
 
290
316
  let onFocus = e => {
291
317
  if (manager.isFocused) {
@@ -305,19 +331,41 @@ export function useSelectableCollection(options) {
305
331
  manager.setFocused(true);
306
332
 
307
333
  if (manager.focusedKey == null) {
308
- // If the user hasn't yet interacted with the collection, there will be no focusedKey set.
334
+ let navigateToFirstKey = key => {
335
+ if (key != null) {
336
+ manager.setFocusedKey(key);
337
+
338
+ if (selectOnFocus) {
339
+ manager.replaceSelection(key);
340
+ }
341
+ }
342
+ }; // If the user hasn't yet interacted with the collection, there will be no focusedKey set.
309
343
  // Attempt to detect whether the user is tabbing forward or backward into the collection
310
344
  // and either focus the first or last item accordingly.
345
+
346
+
311
347
  let relatedTarget = e.relatedTarget;
312
348
 
313
349
  if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) {
314
350
  var _manager$lastSelected;
315
351
 
316
- manager.setFocusedKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
352
+ navigateToFirstKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
317
353
  } else {
318
354
  var _manager$firstSelecte;
319
355
 
320
- manager.setFocusedKey((_manager$firstSelecte = manager.firstSelectedKey) != null ? _manager$firstSelecte : delegate.getFirstKey());
356
+ navigateToFirstKey((_manager$firstSelecte = manager.firstSelectedKey) != null ? _manager$firstSelecte : delegate.getFirstKey());
357
+ }
358
+ } else if (!isVirtualized) {
359
+ // Restore the scroll position to what it was before.
360
+ scrollRef.current.scrollTop = scrollPos.current.top;
361
+ scrollRef.current.scrollLeft = scrollPos.current.left; // Refocus and scroll the focused item into view if it exists within the scrollable region.
362
+
363
+ let element = scrollRef.current.querySelector("[data-key=\"" + manager.focusedKey + "\"]");
364
+
365
+ if (element) {
366
+ // This prevents a flash of focus on the first/last element in the collection
367
+ focusWithoutScrolling(element);
368
+ scrollIntoView(scrollRef.current, element);
321
369
  }
322
370
  }
323
371
  };
@@ -329,8 +377,9 @@ export function useSelectableCollection(options) {
329
377
  }
330
378
  };
331
379
 
380
+ const autoFocusRef = useRef(autoFocus);
332
381
  useEffect(() => {
333
- if (autoFocus) {
382
+ if (autoFocusRef.current) {
334
383
  let focusedKey = null; // Check focus strategy to determine which item to focus
335
384
 
336
385
  if (autoFocus === 'first') {
@@ -354,9 +403,21 @@ export function useSelectableCollection(options) {
354
403
  if (focusedKey == null && !shouldUseVirtualFocus) {
355
404
  focusSafely(ref.current);
356
405
  }
357
- } // eslint-disable-next-line react-hooks/exhaustive-deps
406
+ }
358
407
 
359
- }, []);
408
+ autoFocusRef.current = false; // eslint-disable-next-line react-hooks/exhaustive-deps
409
+ }, []); // If not virtualized, scroll the focused element into view when the focusedKey changes.
410
+ // When virtualized, Virtualizer handles this internally.
411
+
412
+ useEffect(() => {
413
+ if (!isVirtualized && manager.focusedKey && scrollRef != null && scrollRef.current) {
414
+ let element = scrollRef.current.querySelector("[data-key=\"" + manager.focusedKey + "\"]");
415
+
416
+ if (element) {
417
+ scrollIntoView(scrollRef.current, element);
418
+ }
419
+ }
420
+ }, [isVirtualized, scrollRef, manager.focusedKey]);
360
421
  let handlers = {
361
422
  onKeyDown,
362
423
  onFocus,
@@ -410,10 +471,35 @@ export function useSelectableItem(options) {
410
471
  shouldSelectOnPressUp,
411
472
  isVirtualized,
412
473
  shouldUseVirtualFocus,
413
- focus
474
+ focus,
475
+ isDisabled,
476
+ onAction
414
477
  } = options;
415
478
 
416
- let onSelect = e => manager.select(key, e); // Focus the associated DOM node when this item becomes the focusedKey
479
+ let onSelect = e => {
480
+ if (e.pointerType === 'keyboard' && $d9657c365c2f735bcaf048eee8599a4a$export$isNonContiguousSelectionModifier(e)) {
481
+ manager.toggleSelection(key);
482
+ } else {
483
+ if (manager.selectionMode === 'none') {
484
+ return;
485
+ }
486
+
487
+ if (manager.selectionMode === 'single') {
488
+ if (manager.isSelected(key) && !manager.disallowEmptySelection) {
489
+ manager.toggleSelection(key);
490
+ } else {
491
+ manager.replaceSelection(key);
492
+ }
493
+ } else if (e && e.shiftKey) {
494
+ manager.extendSelection(key);
495
+ } else if (manager.selectionBehavior === 'toggle' || e && ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) {
496
+ // if touch or virtual (VO) then we just want to toggle, otherwise it's impossible to multi select because they don't have modifier keys
497
+ manager.toggleSelection(key);
498
+ } else {
499
+ manager.replaceSelection(key);
500
+ }
501
+ }
502
+ }; // Focus the associated DOM node when this item becomes the focusedKey
417
503
 
418
504
 
419
505
  let isFocused = key === manager.focusedKey;
@@ -442,7 +528,12 @@ export function useSelectableItem(options) {
442
528
  }
443
529
 
444
530
  };
445
- } // By default, selection occurs on pointer down. This can be strange if selecting an
531
+ }
532
+
533
+ let modality = useRef(null);
534
+ let hasPrimaryAction = onAction && manager.selectionMode === 'none';
535
+ let hasSecondaryAction = onAction && manager.selectionMode !== 'none' && manager.selectionBehavior === 'replace';
536
+ let allowsSelection = !isDisabled && manager.canSelectItem(key); // By default, selection occurs on pointer down. This can be strange if selecting an
446
537
  // item causes the UI to disappear immediately (e.g. menus).
447
538
  // If shouldSelectOnPressUp is true, we use onPressUp instead of onPressStart.
448
539
  // onPress requires a pointer down event on the same element as pointer up. For menus,
@@ -450,30 +541,43 @@ export function useSelectableItem(options) {
450
541
  // the pointer up on the menu item rather than requiring a separate press.
451
542
  // For keyboard events, selection still occurs on key down.
452
543
 
544
+ let itemPressProps = {};
453
545
 
454
546
  if (shouldSelectOnPressUp) {
455
- itemProps.onPressStart = e => {
547
+ itemPressProps.onPressStart = e => {
548
+ modality.current = e.pointerType;
549
+
456
550
  if (e.pointerType === 'keyboard') {
457
551
  onSelect(e);
458
552
  }
459
553
  };
460
554
 
461
- itemProps.onPressUp = e => {
555
+ itemPressProps.onPressUp = e => {
462
556
  if (e.pointerType !== 'keyboard') {
463
557
  onSelect(e);
464
558
  }
465
559
  };
560
+
561
+ itemPressProps.onPress = hasPrimaryAction ? () => onAction() : null;
466
562
  } else {
467
563
  // On touch, it feels strange to select on touch down, so we special case this.
468
- itemProps.onPressStart = e => {
469
- if (e.pointerType !== 'touch') {
564
+ itemPressProps.onPressStart = e => {
565
+ modality.current = e.pointerType;
566
+
567
+ if (e.pointerType !== 'touch' && e.pointerType !== 'virtual') {
470
568
  onSelect(e);
471
569
  }
472
570
  };
473
571
 
474
- itemProps.onPress = e => {
475
- if (e.pointerType === 'touch') {
476
- onSelect(e);
572
+ itemPressProps.onPress = e => {
573
+ if (e.pointerType === 'touch' || e.pointerType === 'virtual' || hasPrimaryAction) {
574
+ // Single tap on touch with selectionBehavior = 'replace' performs an action, i.e. navigation.
575
+ // Also perform action on press up when selectionMode = 'none'.
576
+ if (hasPrimaryAction || hasSecondaryAction) {
577
+ onAction();
578
+ } else {
579
+ onSelect(e);
580
+ }
477
581
  }
478
582
  };
479
583
  }
@@ -482,8 +586,48 @@ export function useSelectableItem(options) {
482
586
  itemProps['data-key'] = key;
483
587
  }
484
588
 
589
+ itemPressProps.preventFocusOnPress = shouldUseVirtualFocus;
590
+ let {
591
+ pressProps,
592
+ isPressed
593
+ } = usePress(itemPressProps); // Double clicking with a mouse with selectionBehavior = 'replace' performs an action.
594
+
595
+ let onDoubleClick = hasSecondaryAction ? e => {
596
+ if (modality.current === 'mouse') {
597
+ e.stopPropagation();
598
+ e.preventDefault();
599
+ onAction();
600
+ }
601
+ } : undefined; // Long pressing an item with touch when selectionBehavior = 'replace' switches the selection behavior
602
+ // to 'toggle'. This changes the single tap behavior from performing an action (i.e. navigating) to
603
+ // selecting, and may toggle the appearance of a UI affordance like checkboxes on each item.
604
+ // TODO: what about when drag and drop is also enabled??
605
+
606
+ let {
607
+ longPressProps
608
+ } = useLongPress({
609
+ isDisabled: !hasSecondaryAction,
610
+
611
+ onLongPress(e) {
612
+ if (e.pointerType === 'touch') {
613
+ onSelect(e);
614
+ manager.setSelectionBehavior('toggle');
615
+ }
616
+ }
617
+
618
+ }); // Pressing the Enter key with selectionBehavior = 'replace' performs an action (i.e. navigation).
619
+
620
+ let onKeyUp = hasSecondaryAction ? e => {
621
+ if (e.key === 'Enter') {
622
+ onAction();
623
+ }
624
+ } : undefined;
485
625
  return {
486
- itemProps
626
+ itemProps: mergeProps(itemProps, allowsSelection || hasPrimaryAction ? pressProps : {}, hasSecondaryAction ? longPressProps : {}, {
627
+ onKeyUp,
628
+ onDoubleClick
629
+ }),
630
+ isPressed
487
631
  };
488
632
  }
489
633
 
@@ -655,18 +799,7 @@ export function useSelectableList(props) {
655
799
  usage: 'search',
656
800
  sensitivity: 'base'
657
801
  });
658
- let delegate = useMemo(() => keyboardDelegate || new ListKeyboardDelegate(collection, disabledKeys, ref, collator), [keyboardDelegate, collection, disabledKeys, ref, collator]); // If not virtualized, scroll the focused element into view when the focusedKey changes.
659
- // When virtualized, Virtualizer handles this internally.
660
-
661
- useEffect(() => {
662
- if (!isVirtualized && selectionManager.focusedKey && ref != null && ref.current) {
663
- let element = ref.current.querySelector("[data-key=\"" + selectionManager.focusedKey + "\"]");
664
-
665
- if (element) {
666
- $a09ba753e08b703267f2392f7fc8e96$var$scrollIntoView(ref.current, element);
667
- }
668
- }
669
- }, [isVirtualized, ref, selectionManager.focusedKey]);
802
+ let delegate = useMemo(() => keyboardDelegate || new ListKeyboardDelegate(collection, disabledKeys, ref, collator), [keyboardDelegate, collection, disabledKeys, ref, collator]);
670
803
  let {
671
804
  collectionProps
672
805
  } = useSelectableCollection({
@@ -679,70 +812,12 @@ export function useSelectableList(props) {
679
812
  selectOnFocus,
680
813
  disallowTypeAhead,
681
814
  shouldUseVirtualFocus,
682
- allowsTabNavigation
815
+ allowsTabNavigation,
816
+ isVirtualized,
817
+ scrollRef: ref
683
818
  });
684
819
  return {
685
820
  listProps: collectionProps
686
821
  };
687
822
  }
688
- /**
689
- * Scrolls `scrollView` so that `element` is visible.
690
- * Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
691
- * but doesn't affect parents above `scrollView`.
692
- */
693
-
694
- function $a09ba753e08b703267f2392f7fc8e96$var$scrollIntoView(scrollView, element) {
695
- let offsetX = $a09ba753e08b703267f2392f7fc8e96$var$relativeOffset(scrollView, element, 'left');
696
- let offsetY = $a09ba753e08b703267f2392f7fc8e96$var$relativeOffset(scrollView, element, 'top');
697
- let width = element.offsetWidth;
698
- let height = element.offsetHeight;
699
- let x = scrollView.scrollLeft;
700
- let y = scrollView.scrollTop;
701
- let maxX = x + scrollView.offsetWidth;
702
- let maxY = y + scrollView.offsetHeight;
703
-
704
- if (offsetX <= x) {
705
- x = offsetX;
706
- } else if (offsetX + width > maxX) {
707
- x += offsetX + width - maxX;
708
- }
709
-
710
- if (offsetY <= y) {
711
- y = offsetY;
712
- } else if (offsetY + height > maxY) {
713
- y += offsetY + height - maxY;
714
- }
715
-
716
- scrollView.scrollLeft = x;
717
- scrollView.scrollTop = y;
718
- }
719
- /**
720
- * Computes the offset left or top from child to ancestor by accumulating
721
- * offsetLeft or offsetTop through intervening offsetParents.
722
- */
723
-
724
-
725
- function $a09ba753e08b703267f2392f7fc8e96$var$relativeOffset(ancestor, child, axis) {
726
- const prop = axis === 'left' ? 'offsetLeft' : 'offsetTop';
727
- let sum = 0;
728
-
729
- while (child.offsetParent) {
730
- sum += child[prop];
731
-
732
- if (child.offsetParent === ancestor) {
733
- // Stop once we have found the ancestor we are interested in.
734
- break;
735
- } else if (child.offsetParent.contains(ancestor)) {
736
- // If the ancestor is not `position:relative`, then we stop at
737
- // _its_ offset parent, and we subtract off _its_ offset, so that
738
- // we end up with the proper offset from child to ancestor.
739
- sum -= ancestor[prop];
740
- break;
741
- }
742
-
743
- child = child.offsetParent;
744
- }
745
-
746
- return sum;
747
- }
748
823
  //# sourceMappingURL=module.js.map