@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/main.js CHANGED
@@ -1,3 +1,8 @@
1
+ var {
2
+ useLongPress,
3
+ usePress
4
+ } = require("@react-aria/interactions");
5
+
1
6
  var {
2
7
  useLocale,
3
8
  useCollator
@@ -5,8 +10,11 @@ var {
5
10
 
6
11
  var {
7
12
  focusWithoutScrolling,
8
- isMac,
9
- mergeProps
13
+ mergeProps,
14
+ scrollIntoView,
15
+ useEvent,
16
+ isAppleDevice,
17
+ isMac
10
18
  } = require("@react-aria/utils");
11
19
 
12
20
  var {
@@ -26,6 +34,20 @@ function $parcel$interopDefault(a) {
26
34
  return a && a.__esModule ? a.default : a;
27
35
  }
28
36
 
37
+ function $d220314dfe032b5c2a0f0c46ec981e5$export$isNonContiguousSelectionModifier(e) {
38
+ // Ctrl + Arrow Up/Arrow Down has a system wide meaning on macOS, so use Alt instead.
39
+ // On Windows and Ubuntu, Alt + Space has a system wide meaning.
40
+ return isAppleDevice() ? e.altKey : e.ctrlKey;
41
+ }
42
+
43
+ function $d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e) {
44
+ if (isMac()) {
45
+ return e.metaKey;
46
+ }
47
+
48
+ return e.ctrlKey;
49
+ }
50
+
29
51
  /**
30
52
  * Handles typeahead interactions with collections.
31
53
  */
@@ -105,14 +127,6 @@ function $c2e740eb44846c887b3b88306c61$var$getStringForKey(key) {
105
127
  return '';
106
128
  }
107
129
 
108
- function $f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e) {
109
- if (isMac()) {
110
- return e.metaKey;
111
- }
112
-
113
- return e.ctrlKey;
114
- }
115
-
116
130
  /**
117
131
  * Handles interactions with selectable collections.
118
132
  */
@@ -125,20 +139,27 @@ function useSelectableCollection(options) {
125
139
  shouldFocusWrap = false,
126
140
  disallowEmptySelection = false,
127
141
  disallowSelectAll = false,
128
- selectOnFocus = false,
142
+ selectOnFocus = manager.selectionBehavior === 'replace',
129
143
  disallowTypeAhead = false,
130
144
  shouldUseVirtualFocus,
131
- allowsTabNavigation = false
145
+ allowsTabNavigation = false,
146
+ isVirtualized,
147
+ // If no scrollRef is provided, assume the collection ref is the scrollable region
148
+ scrollRef = ref
132
149
  } = options;
133
150
  let {
134
151
  direction
135
152
  } = useLocale();
136
153
 
137
154
  let onKeyDown = e => {
138
- // Let child element (e.g. menu button) handle the event if the Alt key is pressed.
139
- // Keyboard events bubble through portals. Don't handle keyboard events
155
+ // Prevent option + tab from doing anything since it doesn't move focus to the cells, only buttons/checkboxes
156
+ if (e.altKey && e.key === 'Tab') {
157
+ e.preventDefault();
158
+ } // Keyboard events bubble through portals. Don't handle keyboard events
140
159
  // for elements outside the collection (e.g. menus).
141
- if (e.altKey || !ref.current.contains(e.target)) {
160
+
161
+
162
+ if (!ref.current.contains(e.target)) {
142
163
  return;
143
164
  }
144
165
 
@@ -148,7 +169,7 @@ function useSelectableCollection(options) {
148
169
 
149
170
  if (e.shiftKey && manager.selectionMode === 'multiple') {
150
171
  manager.extendSelection(key);
151
- } else if (selectOnFocus) {
172
+ } else if (selectOnFocus && !$d220314dfe032b5c2a0f0c46ec981e5$export$isNonContiguousSelectionModifier(e)) {
152
173
  manager.replaceSelection(key);
153
174
  }
154
175
  }
@@ -212,10 +233,10 @@ function useSelectableCollection(options) {
212
233
  case 'Home':
213
234
  if (delegate.getFirstKey) {
214
235
  e.preventDefault();
215
- let firstKey = delegate.getFirstKey(manager.focusedKey, $f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e));
236
+ let firstKey = delegate.getFirstKey(manager.focusedKey, $d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e));
216
237
  manager.setFocusedKey(firstKey);
217
238
 
218
- if ($f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
239
+ if ($d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
219
240
  manager.extendSelection(firstKey);
220
241
  } else if (selectOnFocus) {
221
242
  manager.replaceSelection(firstKey);
@@ -227,10 +248,10 @@ function useSelectableCollection(options) {
227
248
  case 'End':
228
249
  if (delegate.getLastKey) {
229
250
  e.preventDefault();
230
- let lastKey = delegate.getLastKey(manager.focusedKey, $f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e));
251
+ let lastKey = delegate.getLastKey(manager.focusedKey, $d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e));
231
252
  manager.setFocusedKey(lastKey);
232
253
 
233
- if ($f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
254
+ if ($d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
234
255
  manager.extendSelection(lastKey);
235
256
  } else if (selectOnFocus) {
236
257
  manager.replaceSelection(lastKey);
@@ -258,7 +279,7 @@ function useSelectableCollection(options) {
258
279
  break;
259
280
 
260
281
  case 'a':
261
- if ($f791fefd7189e0e4d903034fb2925$var$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
282
+ if ($d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
262
283
  e.preventDefault();
263
284
  manager.selectAll();
264
285
  }
@@ -309,7 +330,19 @@ function useSelectableCollection(options) {
309
330
  }
310
331
  }
311
332
  }
312
- };
333
+ }; // Store the scroll position so we can restore it later.
334
+
335
+
336
+ let scrollPos = useRef({
337
+ top: 0,
338
+ left: 0
339
+ });
340
+ useEvent(scrollRef, 'scroll', isVirtualized ? null : () => {
341
+ scrollPos.current = {
342
+ top: scrollRef.current.scrollTop,
343
+ left: scrollRef.current.scrollLeft
344
+ };
345
+ });
313
346
 
314
347
  let onFocus = e => {
315
348
  if (manager.isFocused) {
@@ -329,19 +362,41 @@ function useSelectableCollection(options) {
329
362
  manager.setFocused(true);
330
363
 
331
364
  if (manager.focusedKey == null) {
332
- // If the user hasn't yet interacted with the collection, there will be no focusedKey set.
365
+ let navigateToFirstKey = key => {
366
+ if (key != null) {
367
+ manager.setFocusedKey(key);
368
+
369
+ if (selectOnFocus) {
370
+ manager.replaceSelection(key);
371
+ }
372
+ }
373
+ }; // If the user hasn't yet interacted with the collection, there will be no focusedKey set.
333
374
  // Attempt to detect whether the user is tabbing forward or backward into the collection
334
375
  // and either focus the first or last item accordingly.
376
+
377
+
335
378
  let relatedTarget = e.relatedTarget;
336
379
 
337
380
  if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) {
338
381
  var _manager$lastSelected;
339
382
 
340
- manager.setFocusedKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
383
+ navigateToFirstKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
341
384
  } else {
342
385
  var _manager$firstSelecte;
343
386
 
344
- manager.setFocusedKey((_manager$firstSelecte = manager.firstSelectedKey) != null ? _manager$firstSelecte : delegate.getFirstKey());
387
+ navigateToFirstKey((_manager$firstSelecte = manager.firstSelectedKey) != null ? _manager$firstSelecte : delegate.getFirstKey());
388
+ }
389
+ } else if (!isVirtualized) {
390
+ // Restore the scroll position to what it was before.
391
+ scrollRef.current.scrollTop = scrollPos.current.top;
392
+ scrollRef.current.scrollLeft = scrollPos.current.left; // Refocus and scroll the focused item into view if it exists within the scrollable region.
393
+
394
+ let element = scrollRef.current.querySelector("[data-key=\"" + manager.focusedKey + "\"]");
395
+
396
+ if (element) {
397
+ // This prevents a flash of focus on the first/last element in the collection
398
+ focusWithoutScrolling(element);
399
+ scrollIntoView(scrollRef.current, element);
345
400
  }
346
401
  }
347
402
  };
@@ -353,8 +408,9 @@ function useSelectableCollection(options) {
353
408
  }
354
409
  };
355
410
 
411
+ const autoFocusRef = useRef(autoFocus);
356
412
  useEffect(() => {
357
- if (autoFocus) {
413
+ if (autoFocusRef.current) {
358
414
  let focusedKey = null; // Check focus strategy to determine which item to focus
359
415
 
360
416
  if (autoFocus === 'first') {
@@ -378,9 +434,21 @@ function useSelectableCollection(options) {
378
434
  if (focusedKey == null && !shouldUseVirtualFocus) {
379
435
  focusSafely(ref.current);
380
436
  }
381
- } // eslint-disable-next-line react-hooks/exhaustive-deps
437
+ }
382
438
 
383
- }, []);
439
+ autoFocusRef.current = false; // eslint-disable-next-line react-hooks/exhaustive-deps
440
+ }, []); // If not virtualized, scroll the focused element into view when the focusedKey changes.
441
+ // When virtualized, Virtualizer handles this internally.
442
+
443
+ useEffect(() => {
444
+ if (!isVirtualized && manager.focusedKey && scrollRef != null && scrollRef.current) {
445
+ let element = scrollRef.current.querySelector("[data-key=\"" + manager.focusedKey + "\"]");
446
+
447
+ if (element) {
448
+ scrollIntoView(scrollRef.current, element);
449
+ }
450
+ }
451
+ }, [isVirtualized, scrollRef, manager.focusedKey]);
384
452
  let handlers = {
385
453
  onKeyDown,
386
454
  onFocus,
@@ -436,10 +504,35 @@ function useSelectableItem(options) {
436
504
  shouldSelectOnPressUp,
437
505
  isVirtualized,
438
506
  shouldUseVirtualFocus,
439
- focus
507
+ focus,
508
+ isDisabled,
509
+ onAction
440
510
  } = options;
441
511
 
442
- let onSelect = e => manager.select(key, e); // Focus the associated DOM node when this item becomes the focusedKey
512
+ let onSelect = e => {
513
+ if (e.pointerType === 'keyboard' && $d220314dfe032b5c2a0f0c46ec981e5$export$isNonContiguousSelectionModifier(e)) {
514
+ manager.toggleSelection(key);
515
+ } else {
516
+ if (manager.selectionMode === 'none') {
517
+ return;
518
+ }
519
+
520
+ if (manager.selectionMode === 'single') {
521
+ if (manager.isSelected(key) && !manager.disallowEmptySelection) {
522
+ manager.toggleSelection(key);
523
+ } else {
524
+ manager.replaceSelection(key);
525
+ }
526
+ } else if (e && e.shiftKey) {
527
+ manager.extendSelection(key);
528
+ } else if (manager.selectionBehavior === 'toggle' || e && ($d220314dfe032b5c2a0f0c46ec981e5$export$isCtrlKeyPressed(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) {
529
+ // 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
530
+ manager.toggleSelection(key);
531
+ } else {
532
+ manager.replaceSelection(key);
533
+ }
534
+ }
535
+ }; // Focus the associated DOM node when this item becomes the focusedKey
443
536
 
444
537
 
445
538
  let isFocused = key === manager.focusedKey;
@@ -468,7 +561,12 @@ function useSelectableItem(options) {
468
561
  }
469
562
 
470
563
  };
471
- } // By default, selection occurs on pointer down. This can be strange if selecting an
564
+ }
565
+
566
+ let modality = useRef(null);
567
+ let hasPrimaryAction = onAction && manager.selectionMode === 'none';
568
+ let hasSecondaryAction = onAction && manager.selectionMode !== 'none' && manager.selectionBehavior === 'replace';
569
+ let allowsSelection = !isDisabled && manager.canSelectItem(key); // By default, selection occurs on pointer down. This can be strange if selecting an
472
570
  // item causes the UI to disappear immediately (e.g. menus).
473
571
  // If shouldSelectOnPressUp is true, we use onPressUp instead of onPressStart.
474
572
  // onPress requires a pointer down event on the same element as pointer up. For menus,
@@ -476,30 +574,43 @@ function useSelectableItem(options) {
476
574
  // the pointer up on the menu item rather than requiring a separate press.
477
575
  // For keyboard events, selection still occurs on key down.
478
576
 
577
+ let itemPressProps = {};
479
578
 
480
579
  if (shouldSelectOnPressUp) {
481
- itemProps.onPressStart = e => {
580
+ itemPressProps.onPressStart = e => {
581
+ modality.current = e.pointerType;
582
+
482
583
  if (e.pointerType === 'keyboard') {
483
584
  onSelect(e);
484
585
  }
485
586
  };
486
587
 
487
- itemProps.onPressUp = e => {
588
+ itemPressProps.onPressUp = e => {
488
589
  if (e.pointerType !== 'keyboard') {
489
590
  onSelect(e);
490
591
  }
491
592
  };
593
+
594
+ itemPressProps.onPress = hasPrimaryAction ? () => onAction() : null;
492
595
  } else {
493
596
  // On touch, it feels strange to select on touch down, so we special case this.
494
- itemProps.onPressStart = e => {
495
- if (e.pointerType !== 'touch') {
597
+ itemPressProps.onPressStart = e => {
598
+ modality.current = e.pointerType;
599
+
600
+ if (e.pointerType !== 'touch' && e.pointerType !== 'virtual') {
496
601
  onSelect(e);
497
602
  }
498
603
  };
499
604
 
500
- itemProps.onPress = e => {
501
- if (e.pointerType === 'touch') {
502
- onSelect(e);
605
+ itemPressProps.onPress = e => {
606
+ if (e.pointerType === 'touch' || e.pointerType === 'virtual' || hasPrimaryAction) {
607
+ // Single tap on touch with selectionBehavior = 'replace' performs an action, i.e. navigation.
608
+ // Also perform action on press up when selectionMode = 'none'.
609
+ if (hasPrimaryAction || hasSecondaryAction) {
610
+ onAction();
611
+ } else {
612
+ onSelect(e);
613
+ }
503
614
  }
504
615
  };
505
616
  }
@@ -508,8 +619,48 @@ function useSelectableItem(options) {
508
619
  itemProps['data-key'] = key;
509
620
  }
510
621
 
622
+ itemPressProps.preventFocusOnPress = shouldUseVirtualFocus;
623
+ let {
624
+ pressProps,
625
+ isPressed
626
+ } = usePress(itemPressProps); // Double clicking with a mouse with selectionBehavior = 'replace' performs an action.
627
+
628
+ let onDoubleClick = hasSecondaryAction ? e => {
629
+ if (modality.current === 'mouse') {
630
+ e.stopPropagation();
631
+ e.preventDefault();
632
+ onAction();
633
+ }
634
+ } : undefined; // Long pressing an item with touch when selectionBehavior = 'replace' switches the selection behavior
635
+ // to 'toggle'. This changes the single tap behavior from performing an action (i.e. navigating) to
636
+ // selecting, and may toggle the appearance of a UI affordance like checkboxes on each item.
637
+ // TODO: what about when drag and drop is also enabled??
638
+
639
+ let {
640
+ longPressProps
641
+ } = useLongPress({
642
+ isDisabled: !hasSecondaryAction,
643
+
644
+ onLongPress(e) {
645
+ if (e.pointerType === 'touch') {
646
+ onSelect(e);
647
+ manager.setSelectionBehavior('toggle');
648
+ }
649
+ }
650
+
651
+ }); // Pressing the Enter key with selectionBehavior = 'replace' performs an action (i.e. navigation).
652
+
653
+ let onKeyUp = hasSecondaryAction ? e => {
654
+ if (e.key === 'Enter') {
655
+ onAction();
656
+ }
657
+ } : undefined;
511
658
  return {
512
- itemProps
659
+ itemProps: mergeProps(itemProps, allowsSelection || hasPrimaryAction ? pressProps : {}, hasSecondaryAction ? longPressProps : {}, {
660
+ onKeyUp,
661
+ onDoubleClick
662
+ }),
663
+ isPressed
513
664
  };
514
665
  }
515
666
 
@@ -685,18 +836,7 @@ function useSelectableList(props) {
685
836
  usage: 'search',
686
837
  sensitivity: 'base'
687
838
  });
688
- 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.
689
- // When virtualized, Virtualizer handles this internally.
690
-
691
- useEffect(() => {
692
- if (!isVirtualized && selectionManager.focusedKey && ref != null && ref.current) {
693
- let element = ref.current.querySelector("[data-key=\"" + selectionManager.focusedKey + "\"]");
694
-
695
- if (element) {
696
- $e85d8307c59fae2ab45d73201aa19$var$scrollIntoView(ref.current, element);
697
- }
698
- }
699
- }, [isVirtualized, ref, selectionManager.focusedKey]);
839
+ let delegate = useMemo(() => keyboardDelegate || new ListKeyboardDelegate(collection, disabledKeys, ref, collator), [keyboardDelegate, collection, disabledKeys, ref, collator]);
700
840
  let {
701
841
  collectionProps
702
842
  } = useSelectableCollection({
@@ -709,73 +849,14 @@ function useSelectableList(props) {
709
849
  selectOnFocus,
710
850
  disallowTypeAhead,
711
851
  shouldUseVirtualFocus,
712
- allowsTabNavigation
852
+ allowsTabNavigation,
853
+ isVirtualized,
854
+ scrollRef: ref
713
855
  });
714
856
  return {
715
857
  listProps: collectionProps
716
858
  };
717
859
  }
718
- /**
719
- * Scrolls `scrollView` so that `element` is visible.
720
- * Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
721
- * but doesn't affect parents above `scrollView`.
722
- */
723
-
724
860
 
725
861
  exports.useSelectableList = useSelectableList;
726
-
727
- function $e85d8307c59fae2ab45d73201aa19$var$scrollIntoView(scrollView, element) {
728
- let offsetX = $e85d8307c59fae2ab45d73201aa19$var$relativeOffset(scrollView, element, 'left');
729
- let offsetY = $e85d8307c59fae2ab45d73201aa19$var$relativeOffset(scrollView, element, 'top');
730
- let width = element.offsetWidth;
731
- let height = element.offsetHeight;
732
- let x = scrollView.scrollLeft;
733
- let y = scrollView.scrollTop;
734
- let maxX = x + scrollView.offsetWidth;
735
- let maxY = y + scrollView.offsetHeight;
736
-
737
- if (offsetX <= x) {
738
- x = offsetX;
739
- } else if (offsetX + width > maxX) {
740
- x += offsetX + width - maxX;
741
- }
742
-
743
- if (offsetY <= y) {
744
- y = offsetY;
745
- } else if (offsetY + height > maxY) {
746
- y += offsetY + height - maxY;
747
- }
748
-
749
- scrollView.scrollLeft = x;
750
- scrollView.scrollTop = y;
751
- }
752
- /**
753
- * Computes the offset left or top from child to ancestor by accumulating
754
- * offsetLeft or offsetTop through intervening offsetParents.
755
- */
756
-
757
-
758
- function $e85d8307c59fae2ab45d73201aa19$var$relativeOffset(ancestor, child, axis) {
759
- const prop = axis === 'left' ? 'offsetLeft' : 'offsetTop';
760
- let sum = 0;
761
-
762
- while (child.offsetParent) {
763
- sum += child[prop];
764
-
765
- if (child.offsetParent === ancestor) {
766
- // Stop once we have found the ancestor we are interested in.
767
- break;
768
- } else if (child.offsetParent.contains(ancestor)) {
769
- // If the ancestor is not `position:relative`, then we stop at
770
- // _its_ offset parent, and we subtract off _its_ offset, so that
771
- // we end up with the proper offset from child to ancestor.
772
- sum -= ancestor[prop];
773
- break;
774
- }
775
-
776
- child = child.offsetParent;
777
- }
778
-
779
- return sum;
780
- }
781
862
  //# sourceMappingURL=main.js.map