@react-aria/selection 3.4.1 → 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/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 { isMac, mergeProps } from "@react-aria/utils";
3
+ import { focusWithoutScrolling, mergeProps, 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,48 +108,54 @@ 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
 
135
+ const navigateToKey = (key, childFocus) => {
136
+ if (key != null) {
137
+ manager.setFocusedKey(key, childFocus);
138
+
139
+ if (e.shiftKey && manager.selectionMode === 'multiple') {
140
+ manager.extendSelection(key);
141
+ } else if (selectOnFocus && !$d9657c365c2f735bcaf048eee8599a4a$export$isNonContiguousSelectionModifier(e)) {
142
+ manager.replaceSelection(key);
143
+ }
144
+ }
145
+ };
146
+
121
147
  switch (e.key) {
122
148
  case 'ArrowDown':
123
149
  {
124
150
  if (delegate.getKeyBelow) {
125
151
  e.preventDefault();
126
- let nextKey = manager.focusedKey != null ? delegate.getKeyBelow(manager.focusedKey) : delegate.getFirstKey();
127
-
128
- if (nextKey != null) {
129
- manager.setFocusedKey(nextKey);
130
-
131
- if (manager.selectionMode === 'single' && selectOnFocus) {
132
- manager.replaceSelection(nextKey);
133
- }
134
- } else if (shouldFocusWrap) {
135
- let wrapKey = delegate.getFirstKey(manager.focusedKey);
136
- manager.setFocusedKey(wrapKey);
152
+ let nextKey = manager.focusedKey != null ? delegate.getKeyBelow(manager.focusedKey) : delegate.getFirstKey == null ? void 0 : delegate.getFirstKey();
137
153
 
138
- if (manager.selectionMode === 'single' && selectOnFocus) {
139
- manager.replaceSelection(wrapKey);
140
- }
154
+ if (nextKey == null && shouldFocusWrap) {
155
+ nextKey = delegate.getFirstKey == null ? void 0 : delegate.getFirstKey(manager.focusedKey);
141
156
  }
142
157
 
143
- if (e.shiftKey && manager.selectionMode === 'multiple') {
144
- manager.extendSelection(nextKey);
145
- }
158
+ navigateToKey(nextKey);
146
159
  }
147
160
 
148
161
  break;
@@ -152,26 +165,13 @@ export function useSelectableCollection(options) {
152
165
  {
153
166
  if (delegate.getKeyAbove) {
154
167
  e.preventDefault();
155
- let nextKey = manager.focusedKey != null ? delegate.getKeyAbove(manager.focusedKey) : delegate.getLastKey();
168
+ let nextKey = manager.focusedKey != null ? delegate.getKeyAbove(manager.focusedKey) : delegate.getLastKey == null ? void 0 : delegate.getLastKey();
156
169
 
157
- if (nextKey != null) {
158
- manager.setFocusedKey(nextKey);
159
-
160
- if (manager.selectionMode === 'single' && selectOnFocus) {
161
- manager.replaceSelection(nextKey);
162
- }
163
- } else if (shouldFocusWrap) {
164
- let wrapKey = delegate.getLastKey(manager.focusedKey);
165
- manager.setFocusedKey(wrapKey);
166
-
167
- if (manager.selectionMode === 'single' && selectOnFocus) {
168
- manager.replaceSelection(wrapKey);
169
- }
170
+ if (nextKey == null && shouldFocusWrap) {
171
+ nextKey = delegate.getLastKey == null ? void 0 : delegate.getLastKey(manager.focusedKey);
170
172
  }
171
173
 
172
- if (e.shiftKey && manager.selectionMode === 'multiple') {
173
- manager.extendSelection(nextKey);
174
- }
174
+ navigateToKey(nextKey);
175
175
  }
176
176
 
177
177
  break;
@@ -182,18 +182,7 @@ export function useSelectableCollection(options) {
182
182
  if (delegate.getKeyLeftOf) {
183
183
  e.preventDefault();
184
184
  let nextKey = delegate.getKeyLeftOf(manager.focusedKey);
185
-
186
- if (nextKey != null) {
187
- manager.setFocusedKey(nextKey, direction === 'rtl' ? 'first' : 'last');
188
-
189
- if (manager.selectionMode === 'single' && selectOnFocus) {
190
- manager.replaceSelection(nextKey);
191
- }
192
- }
193
-
194
- if (e.shiftKey && manager.selectionMode === 'multiple') {
195
- manager.extendSelection(nextKey);
196
- }
185
+ navigateToKey(nextKey, direction === 'rtl' ? 'first' : 'last');
197
186
  }
198
187
 
199
188
  break;
@@ -204,18 +193,7 @@ export function useSelectableCollection(options) {
204
193
  if (delegate.getKeyRightOf) {
205
194
  e.preventDefault();
206
195
  let nextKey = delegate.getKeyRightOf(manager.focusedKey);
207
-
208
- if (nextKey != null) {
209
- manager.setFocusedKey(nextKey, direction === 'rtl' ? 'last' : 'first');
210
-
211
- if (manager.selectionMode === 'single' && selectOnFocus) {
212
- manager.replaceSelection(nextKey);
213
- }
214
- }
215
-
216
- if (e.shiftKey && manager.selectionMode === 'multiple') {
217
- manager.extendSelection(nextKey);
218
- }
196
+ navigateToKey(nextKey, direction === 'rtl' ? 'last' : 'first');
219
197
  }
220
198
 
221
199
  break;
@@ -224,15 +202,13 @@ export function useSelectableCollection(options) {
224
202
  case 'Home':
225
203
  if (delegate.getFirstKey) {
226
204
  e.preventDefault();
227
- let firstKey = delegate.getFirstKey(manager.focusedKey, $a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e));
205
+ let firstKey = delegate.getFirstKey(manager.focusedKey, $d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e));
228
206
  manager.setFocusedKey(firstKey);
229
207
 
230
- if (manager.selectionMode === 'single' && selectOnFocus) {
231
- manager.replaceSelection(firstKey);
232
- }
233
-
234
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
208
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
235
209
  manager.extendSelection(firstKey);
210
+ } else if (selectOnFocus) {
211
+ manager.replaceSelection(firstKey);
236
212
  }
237
213
  }
238
214
 
@@ -241,15 +217,13 @@ export function useSelectableCollection(options) {
241
217
  case 'End':
242
218
  if (delegate.getLastKey) {
243
219
  e.preventDefault();
244
- let lastKey = delegate.getLastKey(manager.focusedKey, $a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e));
220
+ let lastKey = delegate.getLastKey(manager.focusedKey, $d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e));
245
221
  manager.setFocusedKey(lastKey);
246
222
 
247
- if (manager.selectionMode === 'single' && selectOnFocus) {
248
- manager.replaceSelection(lastKey);
249
- }
250
-
251
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
223
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') {
252
224
  manager.extendSelection(lastKey);
225
+ } else if (selectOnFocus) {
226
+ manager.replaceSelection(lastKey);
253
227
  }
254
228
  }
255
229
 
@@ -259,14 +233,7 @@ export function useSelectableCollection(options) {
259
233
  if (delegate.getKeyPageBelow) {
260
234
  e.preventDefault();
261
235
  let nextKey = delegate.getKeyPageBelow(manager.focusedKey);
262
-
263
- if (nextKey != null) {
264
- manager.setFocusedKey(nextKey);
265
-
266
- if (e.shiftKey && manager.selectionMode === 'multiple') {
267
- manager.extendSelection(nextKey);
268
- }
269
- }
236
+ navigateToKey(nextKey);
270
237
  }
271
238
 
272
239
  break;
@@ -275,20 +242,13 @@ export function useSelectableCollection(options) {
275
242
  if (delegate.getKeyPageAbove) {
276
243
  e.preventDefault();
277
244
  let nextKey = delegate.getKeyPageAbove(manager.focusedKey);
278
-
279
- if (nextKey != null) {
280
- manager.setFocusedKey(nextKey);
281
-
282
- if (e.shiftKey && manager.selectionMode === 'multiple') {
283
- manager.extendSelection(nextKey);
284
- }
285
- }
245
+ navigateToKey(nextKey);
286
246
  }
287
247
 
288
248
  break;
289
249
 
290
250
  case 'a':
291
- if ($a9b9aa71af07c56ab1d89ca45381f4b$var$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
251
+ if ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) {
292
252
  e.preventDefault();
293
253
  manager.selectAll();
294
254
  }
@@ -331,7 +291,7 @@ export function useSelectableCollection(options) {
331
291
  } while (last);
332
292
 
333
293
  if (next && !next.contains(document.activeElement)) {
334
- next.focus();
294
+ focusWithoutScrolling(next);
335
295
  }
336
296
  }
337
297
 
@@ -339,7 +299,19 @@ export function useSelectableCollection(options) {
339
299
  }
340
300
  }
341
301
  }
342
- };
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
+ });
343
315
 
344
316
  let onFocus = e => {
345
317
  if (manager.isFocused) {
@@ -359,19 +331,41 @@ export function useSelectableCollection(options) {
359
331
  manager.setFocused(true);
360
332
 
361
333
  if (manager.focusedKey == null) {
362
- // 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.
363
343
  // Attempt to detect whether the user is tabbing forward or backward into the collection
364
344
  // and either focus the first or last item accordingly.
345
+
346
+
365
347
  let relatedTarget = e.relatedTarget;
366
348
 
367
349
  if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) {
368
350
  var _manager$lastSelected;
369
351
 
370
- manager.setFocusedKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
352
+ navigateToFirstKey((_manager$lastSelected = manager.lastSelectedKey) != null ? _manager$lastSelected : delegate.getLastKey());
371
353
  } else {
372
354
  var _manager$firstSelecte;
373
355
 
374
- 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
+ $a9b9aa71af07c56ab1d89ca45381f4b$var$scrollIntoView(scrollRef.current, element);
375
369
  }
376
370
  }
377
371
  };
@@ -383,8 +377,9 @@ export function useSelectableCollection(options) {
383
377
  }
384
378
  };
385
379
 
380
+ const autoFocusRef = useRef(autoFocus);
386
381
  useEffect(() => {
387
- if (autoFocus) {
382
+ if (autoFocusRef.current) {
388
383
  let focusedKey = null; // Check focus strategy to determine which item to focus
389
384
 
390
385
  if (autoFocus === 'first') {
@@ -408,17 +403,32 @@ export function useSelectableCollection(options) {
408
403
  if (focusedKey == null && !shouldUseVirtualFocus) {
409
404
  focusSafely(ref.current);
410
405
  }
411
- } // eslint-disable-next-line react-hooks/exhaustive-deps
406
+ }
407
+
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.
412
411
 
413
- }, []);
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
+ $a9b9aa71af07c56ab1d89ca45381f4b$var$scrollIntoView(scrollRef.current, element);
418
+ }
419
+ }
420
+ }, [isVirtualized, scrollRef, manager.focusedKey]);
414
421
  let handlers = {
415
422
  onKeyDown,
416
423
  onFocus,
417
424
  onBlur,
418
425
 
419
426
  onMouseDown(e) {
420
- // Prevent focus going to the collection when clicking on the scrollbar.
421
- e.preventDefault();
427
+ // Ignore events that bubbled through portals.
428
+ if (e.currentTarget.contains(e.target)) {
429
+ // Prevent focus going to the collection when clicking on the scrollbar.
430
+ e.preventDefault();
431
+ }
422
432
  }
423
433
 
424
434
  };
@@ -449,6 +459,66 @@ export function useSelectableCollection(options) {
449
459
  })
450
460
  };
451
461
  }
462
+ /**
463
+ * Scrolls `scrollView` so that `element` is visible.
464
+ * Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
465
+ * but doesn't affect parents above `scrollView`.
466
+ */
467
+
468
+ function $a9b9aa71af07c56ab1d89ca45381f4b$var$scrollIntoView(scrollView, element) {
469
+ let offsetX = $a9b9aa71af07c56ab1d89ca45381f4b$var$relativeOffset(scrollView, element, 'left');
470
+ let offsetY = $a9b9aa71af07c56ab1d89ca45381f4b$var$relativeOffset(scrollView, element, 'top');
471
+ let width = element.offsetWidth;
472
+ let height = element.offsetHeight;
473
+ let x = scrollView.scrollLeft;
474
+ let y = scrollView.scrollTop;
475
+ let maxX = x + scrollView.offsetWidth;
476
+ let maxY = y + scrollView.offsetHeight;
477
+
478
+ if (offsetX <= x) {
479
+ x = offsetX;
480
+ } else if (offsetX + width > maxX) {
481
+ x += offsetX + width - maxX;
482
+ }
483
+
484
+ if (offsetY <= y) {
485
+ y = offsetY;
486
+ } else if (offsetY + height > maxY) {
487
+ y += offsetY + height - maxY;
488
+ }
489
+
490
+ scrollView.scrollLeft = x;
491
+ scrollView.scrollTop = y;
492
+ }
493
+ /**
494
+ * Computes the offset left or top from child to ancestor by accumulating
495
+ * offsetLeft or offsetTop through intervening offsetParents.
496
+ */
497
+
498
+
499
+ function $a9b9aa71af07c56ab1d89ca45381f4b$var$relativeOffset(ancestor, child, axis) {
500
+ const prop = axis === 'left' ? 'offsetLeft' : 'offsetTop';
501
+ let sum = 0;
502
+
503
+ while (child.offsetParent) {
504
+ sum += child[prop];
505
+
506
+ if (child.offsetParent === ancestor) {
507
+ // Stop once we have found the ancestor we are interested in.
508
+ break;
509
+ } else if (child.offsetParent.contains(ancestor)) {
510
+ // If the ancestor is not `position:relative`, then we stop at
511
+ // _its_ offset parent, and we subtract off _its_ offset, so that
512
+ // we end up with the proper offset from child to ancestor.
513
+ sum -= ancestor[prop];
514
+ break;
515
+ }
516
+
517
+ child = child.offsetParent;
518
+ }
519
+
520
+ return sum;
521
+ }
452
522
 
453
523
  /**
454
524
  * Handles interactions with an item in a selectable collection.
@@ -461,10 +531,35 @@ export function useSelectableItem(options) {
461
531
  shouldSelectOnPressUp,
462
532
  isVirtualized,
463
533
  shouldUseVirtualFocus,
464
- focus
534
+ focus,
535
+ isDisabled,
536
+ onAction
465
537
  } = options;
466
538
 
467
- let onSelect = e => manager.select(key, e); // Focus the associated DOM node when this item becomes the focusedKey
539
+ let onSelect = e => {
540
+ if (e.pointerType === 'keyboard' && $d9657c365c2f735bcaf048eee8599a4a$export$isNonContiguousSelectionModifier(e)) {
541
+ manager.toggleSelection(key);
542
+ } else {
543
+ if (manager.selectionMode === 'none') {
544
+ return;
545
+ }
546
+
547
+ if (manager.selectionMode === 'single') {
548
+ if (manager.isSelected(key) && !manager.disallowEmptySelection) {
549
+ manager.toggleSelection(key);
550
+ } else {
551
+ manager.replaceSelection(key);
552
+ }
553
+ } else if (e && e.shiftKey) {
554
+ manager.extendSelection(key);
555
+ } else if (manager.selectionBehavior === 'toggle' || e && ($d9657c365c2f735bcaf048eee8599a4a$export$isCtrlKeyPressed(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) {
556
+ // 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
557
+ manager.toggleSelection(key);
558
+ } else {
559
+ manager.replaceSelection(key);
560
+ }
561
+ }
562
+ }; // Focus the associated DOM node when this item becomes the focusedKey
468
563
 
469
564
 
470
565
  let isFocused = key === manager.focusedKey;
@@ -493,7 +588,12 @@ export function useSelectableItem(options) {
493
588
  }
494
589
 
495
590
  };
496
- } // By default, selection occurs on pointer down. This can be strange if selecting an
591
+ }
592
+
593
+ let modality = useRef(null);
594
+ let hasPrimaryAction = onAction && manager.selectionMode === 'none';
595
+ let hasSecondaryAction = onAction && manager.selectionMode !== 'none' && manager.selectionBehavior === 'replace';
596
+ let allowsSelection = !isDisabled && manager.canSelectItem(key); // By default, selection occurs on pointer down. This can be strange if selecting an
497
597
  // item causes the UI to disappear immediately (e.g. menus).
498
598
  // If shouldSelectOnPressUp is true, we use onPressUp instead of onPressStart.
499
599
  // onPress requires a pointer down event on the same element as pointer up. For menus,
@@ -501,30 +601,43 @@ export function useSelectableItem(options) {
501
601
  // the pointer up on the menu item rather than requiring a separate press.
502
602
  // For keyboard events, selection still occurs on key down.
503
603
 
604
+ let itemPressProps = {};
504
605
 
505
606
  if (shouldSelectOnPressUp) {
506
- itemProps.onPressStart = e => {
607
+ itemPressProps.onPressStart = e => {
608
+ modality.current = e.pointerType;
609
+
507
610
  if (e.pointerType === 'keyboard') {
508
611
  onSelect(e);
509
612
  }
510
613
  };
511
614
 
512
- itemProps.onPressUp = e => {
615
+ itemPressProps.onPressUp = e => {
513
616
  if (e.pointerType !== 'keyboard') {
514
617
  onSelect(e);
515
618
  }
516
619
  };
620
+
621
+ itemPressProps.onPress = hasPrimaryAction ? () => onAction() : null;
517
622
  } else {
518
623
  // On touch, it feels strange to select on touch down, so we special case this.
519
- itemProps.onPressStart = e => {
520
- if (e.pointerType !== 'touch') {
624
+ itemPressProps.onPressStart = e => {
625
+ modality.current = e.pointerType;
626
+
627
+ if (e.pointerType !== 'touch' && e.pointerType !== 'virtual') {
521
628
  onSelect(e);
522
629
  }
523
630
  };
524
631
 
525
- itemProps.onPress = e => {
526
- if (e.pointerType === 'touch') {
527
- onSelect(e);
632
+ itemPressProps.onPress = e => {
633
+ if (e.pointerType === 'touch' || e.pointerType === 'virtual' || hasPrimaryAction) {
634
+ // Single tap on touch with selectionBehavior = 'replace' performs an action, i.e. navigation.
635
+ // Also perform action on press up when selectionMode = 'none'.
636
+ if (hasPrimaryAction || hasSecondaryAction) {
637
+ onAction();
638
+ } else {
639
+ onSelect(e);
640
+ }
528
641
  }
529
642
  };
530
643
  }
@@ -533,8 +646,48 @@ export function useSelectableItem(options) {
533
646
  itemProps['data-key'] = key;
534
647
  }
535
648
 
649
+ itemPressProps.preventFocusOnPress = shouldUseVirtualFocus;
650
+ let {
651
+ pressProps,
652
+ isPressed
653
+ } = usePress(itemPressProps); // Double clicking with a mouse with selectionBehavior = 'replace' performs an action.
654
+
655
+ let onDoubleClick = hasSecondaryAction ? e => {
656
+ if (modality.current === 'mouse') {
657
+ e.stopPropagation();
658
+ e.preventDefault();
659
+ onAction();
660
+ }
661
+ } : undefined; // Long pressing an item with touch when selectionBehavior = 'replace' switches the selection behavior
662
+ // to 'toggle'. This changes the single tap behavior from performing an action (i.e. navigating) to
663
+ // selecting, and may toggle the appearance of a UI affordance like checkboxes on each item.
664
+ // TODO: what about when drag and drop is also enabled??
665
+
666
+ let {
667
+ longPressProps
668
+ } = useLongPress({
669
+ isDisabled: !hasSecondaryAction,
670
+
671
+ onLongPress(e) {
672
+ if (e.pointerType === 'touch') {
673
+ onSelect(e);
674
+ manager.setSelectionBehavior('toggle');
675
+ }
676
+ }
677
+
678
+ }); // Pressing the Enter key with selectionBehavior = 'replace' performs an action (i.e. navigation).
679
+
680
+ let onKeyUp = hasSecondaryAction ? e => {
681
+ if (e.key === 'Enter') {
682
+ onAction();
683
+ }
684
+ } : undefined;
536
685
  return {
537
- itemProps
686
+ itemProps: mergeProps(itemProps, allowsSelection || hasPrimaryAction ? pressProps : {}, hasSecondaryAction ? longPressProps : {}, {
687
+ onKeyUp,
688
+ onDoubleClick
689
+ }),
690
+ isPressed
538
691
  };
539
692
  }
540
693
 
@@ -706,18 +859,7 @@ export function useSelectableList(props) {
706
859
  usage: 'search',
707
860
  sensitivity: 'base'
708
861
  });
709
- 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.
710
- // When virtualized, Virtualizer handles this internally.
711
-
712
- useEffect(() => {
713
- if (!isVirtualized && selectionManager.focusedKey && (ref == null ? void 0 : ref.current)) {
714
- let element = ref.current.querySelector("[data-key=\"" + selectionManager.focusedKey + "\"]");
715
-
716
- if (element) {
717
- $a09ba753e08b703267f2392f7fc8e96$var$scrollIntoView(ref.current, element);
718
- }
719
- }
720
- }, [isVirtualized, ref, selectionManager.focusedKey]);
862
+ let delegate = useMemo(() => keyboardDelegate || new ListKeyboardDelegate(collection, disabledKeys, ref, collator), [keyboardDelegate, collection, disabledKeys, ref, collator]);
721
863
  let {
722
864
  collectionProps
723
865
  } = useSelectableCollection({
@@ -730,41 +872,12 @@ export function useSelectableList(props) {
730
872
  selectOnFocus,
731
873
  disallowTypeAhead,
732
874
  shouldUseVirtualFocus,
733
- allowsTabNavigation
875
+ allowsTabNavigation,
876
+ isVirtualized,
877
+ scrollRef: ref
734
878
  });
735
879
  return {
736
880
  listProps: collectionProps
737
881
  };
738
882
  }
739
- /**
740
- * Scrolls `scrollView` so that `element` is visible.
741
- * Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
742
- * but doesn't affect parents above `scrollView`.
743
- */
744
-
745
- function $a09ba753e08b703267f2392f7fc8e96$var$scrollIntoView(scrollView, element) {
746
- let offsetX = element.offsetLeft - scrollView.offsetLeft;
747
- let offsetY = element.offsetTop - scrollView.offsetTop;
748
- let width = element.offsetWidth;
749
- let height = element.offsetHeight;
750
- let x = scrollView.scrollLeft;
751
- let y = scrollView.scrollTop;
752
- let maxX = x + scrollView.offsetWidth;
753
- let maxY = y + scrollView.offsetHeight;
754
-
755
- if (offsetX <= x) {
756
- x = offsetX;
757
- } else if (offsetX + width > maxX) {
758
- x += offsetX + width - maxX;
759
- }
760
-
761
- if (offsetY <= y) {
762
- y = offsetY;
763
- } else if (offsetY + height > maxY) {
764
- y += offsetY + height - maxY;
765
- }
766
-
767
- scrollView.scrollLeft = x;
768
- scrollView.scrollTop = y;
769
- }
770
883
  //# sourceMappingURL=module.js.map