@react-aria/focus 3.5.4 → 3.6.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.
@@ -44,7 +44,9 @@ interface FocusManagerOptions {
44
44
  /** Whether to only include tabbable elements, or all focusable elements. */
45
45
  tabbable?: boolean,
46
46
  /** Whether focus should wrap around when it reaches the end of the scope. */
47
- wrap?: boolean
47
+ wrap?: boolean,
48
+ /** A callback that determines whether the given element is focused. */
49
+ accept?: (node: Element) => boolean
48
50
  }
49
51
 
50
52
  export interface FocusManager {
@@ -487,7 +489,9 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
487
489
 
488
490
  if ((node as HTMLElement).matches(selector)
489
491
  && isElementVisible(node as HTMLElement)
490
- && (!scope || isElementInScope(node as HTMLElement, scope))) {
492
+ && (!scope || isElementInScope(node as HTMLElement, scope))
493
+ && (!opts?.accept || opts.accept(node as Element))
494
+ ) {
491
495
  return NodeFilter.FILTER_ACCEPT;
492
496
  }
493
497
 
@@ -506,13 +510,16 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
506
510
  /**
507
511
  * Creates a FocusManager object that can be used to move focus within an element.
508
512
  */
509
- export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
513
+ export function createFocusManager(ref: RefObject<HTMLElement>, defaultOptions: FocusManagerOptions = {}): FocusManager {
510
514
  return {
511
515
  focusNext(opts: FocusManagerOptions = {}) {
512
516
  let root = ref.current;
513
- let {from, tabbable, wrap} = opts;
517
+ if (!root) {
518
+ return;
519
+ }
520
+ let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
514
521
  let node = from || document.activeElement;
515
- let walker = getFocusableTreeWalker(root, {tabbable});
522
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
516
523
  if (root.contains(node)) {
517
524
  walker.currentNode = node;
518
525
  }
@@ -526,11 +533,14 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
526
533
  }
527
534
  return nextNode;
528
535
  },
529
- focusPrevious(opts: FocusManagerOptions = {}) {
536
+ focusPrevious(opts: FocusManagerOptions = defaultOptions) {
530
537
  let root = ref.current;
531
- let {from, tabbable, wrap} = opts;
538
+ if (!root) {
539
+ return;
540
+ }
541
+ let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
532
542
  let node = from || document.activeElement;
533
- let walker = getFocusableTreeWalker(root, {tabbable});
543
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
534
544
  if (root.contains(node)) {
535
545
  walker.currentNode = node;
536
546
  } else {
@@ -550,20 +560,26 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
550
560
  }
551
561
  return previousNode;
552
562
  },
553
- focusFirst(opts = {}) {
563
+ focusFirst(opts = defaultOptions) {
554
564
  let root = ref.current;
555
- let {tabbable} = opts;
556
- let walker = getFocusableTreeWalker(root, {tabbable});
565
+ if (!root) {
566
+ return;
567
+ }
568
+ let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
569
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
557
570
  let nextNode = walker.nextNode() as HTMLElement;
558
571
  if (nextNode) {
559
572
  focusElement(nextNode, true);
560
573
  }
561
574
  return nextNode;
562
575
  },
563
- focusLast(opts = {}) {
576
+ focusLast(opts = defaultOptions) {
564
577
  let root = ref.current;
565
- let {tabbable} = opts;
566
- let walker = getFocusableTreeWalker(root, {tabbable});
578
+ if (!root) {
579
+ return;
580
+ }
581
+ let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
582
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
567
583
  let next = last(walker);
568
584
  if (next) {
569
585
  focusElement(next, true);
@@ -1,4 +1,4 @@
1
- import {HTMLAttributes, useState} from 'react';
1
+ import {HTMLAttributes, useCallback, useState} from 'react';
2
2
  import {isFocusVisible, useFocus, useFocusVisibleListener, useFocusWithin} from '@react-aria/interactions';
3
3
  import {useRef} from 'react';
4
4
 
@@ -43,20 +43,20 @@ export function useFocusRing(props: FocusRingProps = {}): FocusRingAria {
43
43
  let state = useRef({
44
44
  isFocused: false,
45
45
  isFocusVisible: autoFocus || isFocusVisible()
46
- }).current;
46
+ });
47
47
  let [isFocused, setFocused] = useState(false);
48
- let [isFocusVisibleState, setFocusVisible] = useState(() => state.isFocused && state.isFocusVisible);
48
+ let [isFocusVisibleState, setFocusVisible] = useState(() => state.current.isFocused && state.current.isFocusVisible);
49
49
 
50
- let updateState = () => setFocusVisible(state.isFocused && state.isFocusVisible);
50
+ let updateState = useCallback(() => setFocusVisible(state.current.isFocused && state.current.isFocusVisible), []);
51
51
 
52
- let onFocusChange = isFocused => {
53
- state.isFocused = isFocused;
52
+ let onFocusChange = useCallback(isFocused => {
53
+ state.current.isFocused = isFocused;
54
54
  setFocused(isFocused);
55
55
  updateState();
56
- };
56
+ }, [updateState]);
57
57
 
58
58
  useFocusVisibleListener((isFocusVisible) => {
59
- state.isFocusVisible = isFocusVisible;
59
+ state.current.isFocusVisible = isFocusVisible;
60
60
  updateState();
61
61
  }, [], {isTextInput});
62
62
 
@@ -72,7 +72,7 @@ export function useFocusRing(props: FocusRingProps = {}): FocusRingAria {
72
72
 
73
73
  return {
74
74
  isFocused,
75
- isFocusVisible: state.isFocused && isFocusVisibleState,
75
+ isFocusVisible: state.current.isFocused && isFocusVisibleState,
76
76
  focusProps: within ? focusWithinProps : focusProps
77
77
  };
78
78
  }
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import {FocusableDOMProps, FocusableProps} from '@react-types/shared';
14
+ import {focusSafely} from './';
14
15
  import {mergeProps, useSyncRef} from '@react-aria/utils';
15
16
  import React, {HTMLAttributes, MutableRefObject, ReactNode, RefObject, useContext, useEffect, useRef} from 'react';
16
17
  import {useFocus, useKeyboard} from '@react-aria/interactions';
@@ -60,10 +61,15 @@ function FocusableProvider(props: FocusableProviderProps, ref: RefObject<HTMLEle
60
61
  let _FocusableProvider = React.forwardRef(FocusableProvider);
61
62
  export {_FocusableProvider as FocusableProvider};
62
63
 
64
+ interface FocusableAria {
65
+ /** Props for the focusable element. */
66
+ focusableProps: HTMLAttributes<HTMLElement>
67
+ }
68
+
63
69
  /**
64
70
  * Used to make an element focusable and capable of auto focus.
65
71
  */
66
- export function useFocusable(props: FocusableOptions, domRef: RefObject<HTMLElement>) {
72
+ export function useFocusable(props: FocusableOptions, domRef: RefObject<HTMLElement>): FocusableAria {
67
73
  let {focusProps} = useFocus(props);
68
74
  let {keyboardProps} = useKeyboard(props);
69
75
  let interactions = mergeProps(focusProps, keyboardProps);
@@ -73,10 +79,10 @@ export function useFocusable(props: FocusableOptions, domRef: RefObject<HTMLElem
73
79
 
74
80
  useEffect(() => {
75
81
  if (autoFocusRef.current && domRef.current) {
76
- domRef.current.focus();
82
+ focusSafely(domRef.current);
77
83
  }
78
84
  autoFocusRef.current = false;
79
- }, []);
85
+ }, [domRef]);
80
86
 
81
87
  return {
82
88
  focusableProps: mergeProps(