@react-aria/focus 3.5.3 → 3.6.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.
@@ -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,13 @@ 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
+ let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
514
518
  let node = from || document.activeElement;
515
- let walker = getFocusableTreeWalker(root, {tabbable});
519
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
516
520
  if (root.contains(node)) {
517
521
  walker.currentNode = node;
518
522
  }
@@ -526,11 +530,11 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
526
530
  }
527
531
  return nextNode;
528
532
  },
529
- focusPrevious(opts: FocusManagerOptions = {}) {
533
+ focusPrevious(opts: FocusManagerOptions = defaultOptions) {
530
534
  let root = ref.current;
531
- let {from, tabbable, wrap} = opts;
535
+ let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
532
536
  let node = from || document.activeElement;
533
- let walker = getFocusableTreeWalker(root, {tabbable});
537
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
534
538
  if (root.contains(node)) {
535
539
  walker.currentNode = node;
536
540
  } else {
@@ -550,20 +554,20 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
550
554
  }
551
555
  return previousNode;
552
556
  },
553
- focusFirst(opts = {}) {
557
+ focusFirst(opts = defaultOptions) {
554
558
  let root = ref.current;
555
- let {tabbable} = opts;
556
- let walker = getFocusableTreeWalker(root, {tabbable});
559
+ let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
560
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
557
561
  let nextNode = walker.nextNode() as HTMLElement;
558
562
  if (nextNode) {
559
563
  focusElement(nextNode, true);
560
564
  }
561
565
  return nextNode;
562
566
  },
563
- focusLast(opts = {}) {
567
+ focusLast(opts = defaultOptions) {
564
568
  let root = ref.current;
565
- let {tabbable} = opts;
566
- let walker = getFocusableTreeWalker(root, {tabbable});
569
+ let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
570
+ let walker = getFocusableTreeWalker(root, {tabbable, accept});
567
571
  let next = last(walker);
568
572
  if (next) {
569
573
  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(