@react-aria/selection 3.25.1 → 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.
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, focusWithoutScrolling, getActiveElement, isCtrlKeyPressed, mergeProps, scrollIntoView, scrollIntoViewport, useEffectEvent, useEvent, useRouter, useUpdateLayoutEffect} from '@react-aria/utils';
13
+ import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, focusWithoutScrolling, getActiveElement, isCtrlKeyPressed, isTabbable, mergeProps, scrollIntoView, scrollIntoViewport, useEvent, useRouter, useUpdateLayoutEffect} from '@react-aria/utils';
14
14
  import {dispatchVirtualFocus, getFocusableTreeWalker, moveVirtualFocus} from '@react-aria/focus';
15
15
  import {DOMAttributes, FocusableElement, FocusStrategy, Key, KeyboardDelegate, RefObject} from '@react-types/shared';
16
16
  import {flushSync} from 'react-dom';
@@ -312,7 +312,10 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
312
312
  }
313
313
  } while (last);
314
314
 
315
- if (next && !next.contains(document.activeElement)) {
315
+ // If the active element is NOT tabbable but is contained by an element that IS tabbable (aka the cell), the browser will actually move focus to
316
+ // the containing element. We need to special case this so that tab will move focus out of the grid instead of looping between
317
+ // focusing the containing cell and back to the non-tabbable child element
318
+ if (next && (!next.contains(document.activeElement) || (document.activeElement && !isTabbable(document.activeElement)))) {
316
319
  focusWithoutScrolling(next);
317
320
  }
318
321
  }
@@ -413,49 +416,42 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
413
416
  }
414
417
  });
415
418
 
416
- let updateActiveDescendant = useEffectEvent(() => {
417
- let keyToFocus = delegate.getFirstKey?.() ?? null;
418
-
419
- // If no focusable items exist in the list, make sure to clear any activedescendant that may still exist and move focus back to
420
- // the original active element (e.g. the autocomplete input)
421
- if (keyToFocus == null) {
422
- let previousActiveElement = getActiveElement();
423
- moveVirtualFocus(ref.current);
424
- dispatchVirtualFocus(previousActiveElement!, null);
425
-
426
- // If there wasn't a focusable key but the collection had items, then that means we aren't in an intermediate load state and all keys are disabled.
427
- // Reset shouldVirtualFocusFirst so that we don't erronously autofocus an item when the collection is filtered again.
428
- if (manager.collection.size > 0) {
419
+ // update active descendant
420
+ useUpdateLayoutEffect(() => {
421
+ if (shouldVirtualFocusFirst.current) {
422
+ let keyToFocus = delegate.getFirstKey?.() ?? null;
423
+
424
+ // If no focusable items exist in the list, make sure to clear any activedescendant that may still exist and move focus back to
425
+ // the original active element (e.g. the autocomplete input)
426
+ if (keyToFocus == null) {
427
+ let previousActiveElement = getActiveElement();
428
+ moveVirtualFocus(ref.current);
429
+ dispatchVirtualFocus(previousActiveElement!, null);
430
+
431
+ // If there wasn't a focusable key but the collection had items, then that means we aren't in an intermediate load state and all keys are disabled.
432
+ // Reset shouldVirtualFocusFirst so that we don't erronously autofocus an item when the collection is filtered again.
433
+ if (manager.collection.size > 0) {
434
+ shouldVirtualFocusFirst.current = false;
435
+ }
436
+ } else {
437
+ manager.setFocusedKey(keyToFocus);
438
+ // Only set shouldVirtualFocusFirst to false if we've successfully set the first key as the focused key
439
+ // If there wasn't a key to focus, we might be in a temporary loading state so we'll want to still focus the first key
440
+ // after the collection updates after load
429
441
  shouldVirtualFocusFirst.current = false;
430
442
  }
431
- } else {
432
- manager.setFocusedKey(keyToFocus);
433
- // Only set shouldVirtualFocusFirst to false if we've successfully set the first key as the focused key
434
- // If there wasn't a key to focus, we might be in a temporary loading state so we'll want to still focus the first key
435
- // after the collection updates after load
436
- shouldVirtualFocusFirst.current = false;
437
443
  }
438
- });
444
+ }, [manager.collection]);
439
445
 
446
+ // reset focus first flag
440
447
  useUpdateLayoutEffect(() => {
441
- if (shouldVirtualFocusFirst.current) {
442
- updateActiveDescendant();
443
- }
444
-
445
- }, [manager.collection, updateActiveDescendant]);
446
-
447
- let resetFocusFirstFlag = useEffectEvent(() => {
448
448
  // If user causes the focused key to change in any other way, clear shouldVirtualFocusFirst so we don't
449
449
  // accidentally move focus from under them. Skip this if the collection was empty because we might be in a load
450
450
  // state and will still want to focus the first item after load
451
451
  if (manager.collection.size > 0) {
452
452
  shouldVirtualFocusFirst.current = false;
453
453
  }
454
- });
455
-
456
- useUpdateLayoutEffect(() => {
457
- resetFocusFirstFlag();
458
- }, [manager.focusedKey, resetFocusFirstFlag]);
454
+ }, [manager.focusedKey]);
459
455
 
460
456
  useEvent(ref, CLEAR_FOCUS_EVENT, !shouldUseVirtualFocus ? undefined : (e: any) => {
461
457
  e.stopPropagation();
@@ -205,8 +205,9 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
205
205
  // With highlight selection, onAction is secondary, and occurs on double click. Single click selects the row.
206
206
  // With touch, onAction occurs on single tap, and long press enters selection mode.
207
207
  let isLinkOverride = manager.isLink(key) && linkBehavior === 'override';
208
+ let isActionOverride = onAction && options['UNSTABLE_itemBehavior'] === 'action';
208
209
  let hasLinkAction = manager.isLink(key) && linkBehavior !== 'selection' && linkBehavior !== 'none';
209
- let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride;
210
+ let allowsSelection = !isDisabled && manager.canSelectItem(key) && !isLinkOverride && !isActionOverride;
210
211
  let allowsActions = (onAction || hasLinkAction) && !isDisabled;
211
212
  let hasPrimaryAction = allowsActions && (
212
213
  manager.selectionBehavior === 'replace'
@@ -225,6 +226,7 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
225
226
  let performAction = (e) => {
226
227
  if (onAction) {
227
228
  onAction();
229
+ ref.current?.dispatchEvent(new CustomEvent('react-aria-item-action', {bubbles: true}));
228
230
  }
229
231
 
230
232
  if (hasLinkAction && ref.current) {