@react-aria/focus 3.5.5 → 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/main.js +69 -78
- package/dist/main.js.map +1 -1
- package/dist/module.js +59 -60
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +26 -24
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/FocusRing.tsx +1 -1
- package/src/FocusScope.tsx +93 -66
- package/src/focusSafely.ts +2 -1
- package/src/index.ts +10 -5
- package/src/useFocusRing.ts +14 -13
- package/src/useFocusable.tsx +10 -10
package/dist/types.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { FocusableElement, DOMAttributes, FocusableDOMProps, FocusableProps } from "@react-types/shared";
|
|
2
|
+
import React, { ReactNode, RefObject, ReactElement } from "react";
|
|
3
3
|
/**
|
|
4
4
|
* A utility function that focuses an element while avoiding undesired side effects such
|
|
5
5
|
* as page scrolling and screen reader issues with CSS transitions.
|
|
6
6
|
*/
|
|
7
|
-
export function focusSafely(element:
|
|
8
|
-
interface FocusScopeProps {
|
|
7
|
+
export function focusSafely(element: FocusableElement): void;
|
|
8
|
+
export interface FocusScopeProps {
|
|
9
9
|
/** The contents of the focus scope. */
|
|
10
10
|
children: ReactNode;
|
|
11
11
|
/**
|
|
@@ -21,23 +21,25 @@ interface FocusScopeProps {
|
|
|
21
21
|
/** Whether to auto focus the first focusable element in the focus scope on mount. */
|
|
22
22
|
autoFocus?: boolean;
|
|
23
23
|
}
|
|
24
|
-
interface FocusManagerOptions {
|
|
24
|
+
export interface FocusManagerOptions {
|
|
25
25
|
/** The element to start searching from. The currently focused element by default. */
|
|
26
|
-
from?:
|
|
26
|
+
from?: Element;
|
|
27
27
|
/** Whether to only include tabbable elements, or all focusable elements. */
|
|
28
28
|
tabbable?: boolean;
|
|
29
29
|
/** Whether focus should wrap around when it reaches the end of the scope. */
|
|
30
30
|
wrap?: boolean;
|
|
31
|
+
/** A callback that determines whether the given element is focused. */
|
|
32
|
+
accept?: (node: Element) => boolean;
|
|
31
33
|
}
|
|
32
34
|
export interface FocusManager {
|
|
33
35
|
/** Moves focus to the next focusable or tabbable element in the focus scope. */
|
|
34
|
-
focusNext(opts?: FocusManagerOptions):
|
|
36
|
+
focusNext(opts?: FocusManagerOptions): FocusableElement;
|
|
35
37
|
/** Moves focus to the previous focusable or tabbable element in the focus scope. */
|
|
36
|
-
focusPrevious(opts?: FocusManagerOptions):
|
|
38
|
+
focusPrevious(opts?: FocusManagerOptions): FocusableElement;
|
|
37
39
|
/** Moves focus to the first focusable or tabbable element in the focus scope. */
|
|
38
|
-
focusFirst(opts?: FocusManagerOptions):
|
|
40
|
+
focusFirst(opts?: FocusManagerOptions): FocusableElement;
|
|
39
41
|
/** Moves focus to the last focusable or tabbable element in the focus scope. */
|
|
40
|
-
focusLast(opts?: FocusManagerOptions):
|
|
42
|
+
focusLast(opts?: FocusManagerOptions): FocusableElement;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* A FocusScope manages focus for its descendants. It supports containing focus inside
|
|
@@ -57,12 +59,12 @@ export function useFocusManager(): FocusManager;
|
|
|
57
59
|
* Create a [TreeWalker]{@link https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker}
|
|
58
60
|
* that matches all focusable/tabbable elements.
|
|
59
61
|
*/
|
|
60
|
-
export function getFocusableTreeWalker(root:
|
|
62
|
+
export function getFocusableTreeWalker(root: Element, opts?: FocusManagerOptions, scope?: Element[]): TreeWalker;
|
|
61
63
|
/**
|
|
62
64
|
* Creates a FocusManager object that can be used to move focus within an element.
|
|
63
65
|
*/
|
|
64
|
-
export function createFocusManager(ref: RefObject<
|
|
65
|
-
interface
|
|
66
|
+
export function createFocusManager(ref: RefObject<Element>, defaultOptions?: FocusManagerOptions): FocusManager;
|
|
67
|
+
export interface AriaFocusRingProps {
|
|
66
68
|
/**
|
|
67
69
|
* Whether to show the focus ring when something
|
|
68
70
|
* inside the container element has focus (true), or
|
|
@@ -75,21 +77,21 @@ interface FocusRingProps {
|
|
|
75
77
|
/** Whether the element will be auto focused. */
|
|
76
78
|
autoFocus?: boolean;
|
|
77
79
|
}
|
|
78
|
-
interface FocusRingAria {
|
|
80
|
+
export interface FocusRingAria {
|
|
79
81
|
/** Whether the element is currently focused. */
|
|
80
82
|
isFocused: boolean;
|
|
81
83
|
/** Whether keyboard focus should be visible. */
|
|
82
84
|
isFocusVisible: boolean;
|
|
83
85
|
/** Props to apply to the container element with the focus ring. */
|
|
84
|
-
focusProps:
|
|
86
|
+
focusProps: DOMAttributes;
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
87
89
|
* Determines whether a focus ring should be shown to indicate keyboard focus.
|
|
88
90
|
* Focus rings are visible only when the user is interacting with a keyboard,
|
|
89
91
|
* not with a mouse, touch, or other input methods.
|
|
90
92
|
*/
|
|
91
|
-
export function useFocusRing(props?:
|
|
92
|
-
interface
|
|
93
|
+
export function useFocusRing(props?: AriaFocusRingProps): FocusRingAria;
|
|
94
|
+
export interface FocusRingProps {
|
|
93
95
|
/** Child element to apply CSS classes to. */
|
|
94
96
|
children: ReactElement;
|
|
95
97
|
/** CSS class to apply when the element is focused. */
|
|
@@ -113,23 +115,23 @@ interface _FocusRingProps1 {
|
|
|
113
115
|
* Focus rings are visible only when the user is interacting with a keyboard,
|
|
114
116
|
* not with a mouse, touch, or other input methods.
|
|
115
117
|
*/
|
|
116
|
-
export function FocusRing(props:
|
|
117
|
-
interface FocusableOptions extends FocusableProps, FocusableDOMProps {
|
|
118
|
+
export function FocusRing(props: FocusRingProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
119
|
+
export interface FocusableOptions extends FocusableProps, FocusableDOMProps {
|
|
118
120
|
/** Whether focus should be disabled. */
|
|
119
121
|
isDisabled?: boolean;
|
|
120
122
|
}
|
|
121
|
-
interface FocusableProviderProps extends
|
|
123
|
+
export interface FocusableProviderProps extends DOMAttributes {
|
|
122
124
|
/** The child element to provide DOM props to. */
|
|
123
125
|
children?: ReactNode;
|
|
124
126
|
}
|
|
125
|
-
export let FocusableProvider: React.ForwardRefExoticComponent<FocusableProviderProps & React.RefAttributes<
|
|
126
|
-
interface FocusableAria {
|
|
127
|
+
export let FocusableProvider: React.ForwardRefExoticComponent<FocusableProviderProps & React.RefAttributes<FocusableElement>>;
|
|
128
|
+
export interface FocusableAria {
|
|
127
129
|
/** Props for the focusable element. */
|
|
128
|
-
focusableProps:
|
|
130
|
+
focusableProps: DOMAttributes;
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
133
|
* Used to make an element focusable and capable of auto focus.
|
|
132
134
|
*/
|
|
133
|
-
export function useFocusable(props: FocusableOptions, domRef: RefObject<
|
|
135
|
+
export function useFocusable(props: FocusableOptions, domRef: RefObject<FocusableElement>): FocusableAria;
|
|
134
136
|
|
|
135
137
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;
|
|
1
|
+
{"mappings":";;AAgBA;;;GAGG;AACH,4BAA4B,OAAO,EAAE,gBAAgB,QAiBpD;AEhBD;IACE,uCAAuC;IACvC,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;IACE,qFAAqF;IACrF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;CACpC;AAED;IACE,gFAAgF;IAChF,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAAC;IACxD,oFAAoF;IACpF,aAAa,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAAC;IAC5D,iFAAiF;IACjF,UAAU,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAAC;IACvD,gFAAgF;IAClF,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAAA;CACxD;AAkBD;;;;;;GAMG;AACH,2BAA2B,KAAK,EAAE,eAAe,eAiDhD;AAED;;;;GAIG;AACH,mCAAmC,YAAY,CAE9C;AAoVD;;;GAGG;AACH,uCAAuC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,mBAAmB,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,cA8BlG;AAED;;GAEG;AACH,mCAAmC,GAAG,EAAE,UAAU,OAAO,CAAC,EAAE,cAAc,GAAE,mBAAwB,GAAG,YAAY,CA6ElH;ACnlBD;IACE;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;IACE,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;IAEnB,gDAAgD;IAChD,cAAc,EAAE,OAAO,CAAC;IAExB,mEAAmE;IACnE,UAAU,EAAE,aAAa,CAAA;CAC1B;AAED;;;;GAIG;AACH,6BAA6B,KAAK,GAAE,kBAAuB,GAAG,aAAa,CAyC1E;AC7DD;IACE,6CAA6C;IAC7C,QAAQ,EAAE,YAAY,CAAC;IACvB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,0BAA0B,KAAK,EAAE,cAAc,sEAY9C;ACpCD,iCAAkC,SAAQ,cAAc,EAAE,iBAAiB;IACzE,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,uCAAwC,SAAQ,aAAa;IAC3D,iDAAiD;IACjD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAkCD,OAAA,IAAI,kHAAwD,CAAC;AAG7D;IACE,uCAAuC;IACvC,cAAc,EAAE,aAAa,CAAA;CAC9B;AAED;;GAEG;AACH,6BAA6B,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,gBAAgB,CAAC,GAAG,aAAa,CAwBxG","sources":["packages/@react-aria/focus/src/packages/@react-aria/focus/src/focusSafely.ts","packages/@react-aria/focus/src/packages/@react-aria/focus/src/isElementVisible.ts","packages/@react-aria/focus/src/packages/@react-aria/focus/src/FocusScope.tsx","packages/@react-aria/focus/src/packages/@react-aria/focus/src/useFocusRing.ts","packages/@react-aria/focus/src/packages/@react-aria/focus/src/FocusRing.tsx","packages/@react-aria/focus/src/packages/@react-aria/focus/src/useFocusable.tsx","packages/@react-aria/focus/src/packages/@react-aria/focus/src/index.ts","packages/@react-aria/focus/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport {FocusScope, useFocusManager, getFocusableTreeWalker, createFocusManager} from './FocusScope';\nexport {FocusRing} from './FocusRing';\nexport {FocusableProvider, useFocusable} from './useFocusable';\nexport {useFocusRing} from './useFocusRing';\nexport {focusSafely} from './focusSafely';\n\nexport type {FocusScopeProps, FocusManager, FocusManagerOptions} from './FocusScope';\nexport type {FocusRingProps} from './FocusRing';\nexport type {FocusableAria, FocusableOptions, FocusableProviderProps} from './useFocusable';\nexport type {AriaFocusRingProps, FocusRingAria} from './useFocusRing';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/focus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.6.2",
|
|
21
|
-
"@react-aria/interactions": "^3.
|
|
22
|
-
"@react-aria/utils": "^3.
|
|
23
|
-
"@react-types/shared": "^3.
|
|
21
|
+
"@react-aria/interactions": "^3.10.0",
|
|
22
|
+
"@react-aria/utils": "^3.13.2",
|
|
23
|
+
"@react-types/shared": "^3.14.0",
|
|
24
24
|
"clsx": "^1.1.1"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"react": "^16.8.0 || ^17.0.0-rc.1"
|
|
27
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "cd7c0ec917122c7612f653c22f8ed558f8b66ecd"
|
|
33
33
|
}
|
package/src/FocusRing.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import {mergeProps} from '@react-aria/utils';
|
|
|
15
15
|
import React, {ReactElement} from 'react';
|
|
16
16
|
import {useFocusRing} from './useFocusRing';
|
|
17
17
|
|
|
18
|
-
interface FocusRingProps {
|
|
18
|
+
export interface FocusRingProps {
|
|
19
19
|
/** Child element to apply CSS classes to. */
|
|
20
20
|
children: ReactElement,
|
|
21
21
|
/** CSS class to apply when the element is focused. */
|
package/src/FocusScope.tsx
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {FocusableElement} from '@react-types/shared';
|
|
13
14
|
import {focusSafely} from './focusSafely';
|
|
14
15
|
import {isElementVisible} from './isElementVisible';
|
|
15
16
|
import React, {ReactNode, RefObject, useContext, useEffect, useRef} from 'react';
|
|
@@ -18,7 +19,7 @@ import {useLayoutEffect} from '@react-aria/utils';
|
|
|
18
19
|
// import {FocusScope, useFocusScope} from 'react-events/focus-scope';
|
|
19
20
|
// export {FocusScope};
|
|
20
21
|
|
|
21
|
-
interface FocusScopeProps {
|
|
22
|
+
export interface FocusScopeProps {
|
|
22
23
|
/** The contents of the focus scope. */
|
|
23
24
|
children: ReactNode,
|
|
24
25
|
|
|
@@ -38,27 +39,29 @@ interface FocusScopeProps {
|
|
|
38
39
|
autoFocus?: boolean
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
interface FocusManagerOptions {
|
|
42
|
+
export interface FocusManagerOptions {
|
|
42
43
|
/** The element to start searching from. The currently focused element by default. */
|
|
43
|
-
from?:
|
|
44
|
+
from?: Element,
|
|
44
45
|
/** Whether to only include tabbable elements, or all focusable elements. */
|
|
45
46
|
tabbable?: boolean,
|
|
46
47
|
/** Whether focus should wrap around when it reaches the end of the scope. */
|
|
47
|
-
wrap?: boolean
|
|
48
|
+
wrap?: boolean,
|
|
49
|
+
/** A callback that determines whether the given element is focused. */
|
|
50
|
+
accept?: (node: Element) => boolean
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
export interface FocusManager {
|
|
51
54
|
/** Moves focus to the next focusable or tabbable element in the focus scope. */
|
|
52
|
-
focusNext(opts?: FocusManagerOptions):
|
|
55
|
+
focusNext(opts?: FocusManagerOptions): FocusableElement,
|
|
53
56
|
/** Moves focus to the previous focusable or tabbable element in the focus scope. */
|
|
54
|
-
focusPrevious(opts?: FocusManagerOptions):
|
|
57
|
+
focusPrevious(opts?: FocusManagerOptions): FocusableElement,
|
|
55
58
|
/** Moves focus to the first focusable or tabbable element in the focus scope. */
|
|
56
|
-
focusFirst(opts?: FocusManagerOptions):
|
|
59
|
+
focusFirst(opts?: FocusManagerOptions): FocusableElement,
|
|
57
60
|
/** Moves focus to the last focusable or tabbable element in the focus scope. */
|
|
58
|
-
focusLast(opts?: FocusManagerOptions):
|
|
61
|
+
focusLast(opts?: FocusManagerOptions): FocusableElement
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
type ScopeRef = RefObject<
|
|
64
|
+
type ScopeRef = RefObject<Element[]>;
|
|
62
65
|
interface IFocusContext {
|
|
63
66
|
scopeRef: ScopeRef,
|
|
64
67
|
focusManager: FocusManager
|
|
@@ -85,7 +88,7 @@ export function FocusScope(props: FocusScopeProps) {
|
|
|
85
88
|
let {children, contain, restoreFocus, autoFocus} = props;
|
|
86
89
|
let startRef = useRef<HTMLSpanElement>();
|
|
87
90
|
let endRef = useRef<HTMLSpanElement>();
|
|
88
|
-
let scopeRef = useRef<
|
|
91
|
+
let scopeRef = useRef<Element[]>([]);
|
|
89
92
|
let ctx = useContext(FocusContext);
|
|
90
93
|
let parentScope = ctx?.scopeRef;
|
|
91
94
|
|
|
@@ -141,19 +144,19 @@ export function useFocusManager(): FocusManager {
|
|
|
141
144
|
return useContext(FocusContext)?.focusManager;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
function createFocusManagerForScope(scopeRef: React.RefObject<
|
|
147
|
+
function createFocusManagerForScope(scopeRef: React.RefObject<Element[]>): FocusManager {
|
|
145
148
|
return {
|
|
146
149
|
focusNext(opts: FocusManagerOptions = {}) {
|
|
147
150
|
let scope = scopeRef.current;
|
|
148
|
-
let {from, tabbable, wrap} = opts;
|
|
151
|
+
let {from, tabbable, wrap, accept} = opts;
|
|
149
152
|
let node = from || document.activeElement;
|
|
150
153
|
let sentinel = scope[0].previousElementSibling;
|
|
151
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
154
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
152
155
|
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
|
|
153
|
-
let nextNode = walker.nextNode() as
|
|
156
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
154
157
|
if (!nextNode && wrap) {
|
|
155
158
|
walker.currentNode = sentinel;
|
|
156
|
-
nextNode = walker.nextNode() as
|
|
159
|
+
nextNode = walker.nextNode() as FocusableElement;
|
|
157
160
|
}
|
|
158
161
|
if (nextNode) {
|
|
159
162
|
focusElement(nextNode, true);
|
|
@@ -162,15 +165,15 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
162
165
|
},
|
|
163
166
|
focusPrevious(opts: FocusManagerOptions = {}) {
|
|
164
167
|
let scope = scopeRef.current;
|
|
165
|
-
let {from, tabbable, wrap} = opts;
|
|
168
|
+
let {from, tabbable, wrap, accept} = opts;
|
|
166
169
|
let node = from || document.activeElement;
|
|
167
170
|
let sentinel = scope[scope.length - 1].nextElementSibling;
|
|
168
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
171
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
169
172
|
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
|
|
170
|
-
let previousNode = walker.previousNode() as
|
|
173
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
171
174
|
if (!previousNode && wrap) {
|
|
172
175
|
walker.currentNode = sentinel;
|
|
173
|
-
previousNode = walker.previousNode() as
|
|
176
|
+
previousNode = walker.previousNode() as FocusableElement;
|
|
174
177
|
}
|
|
175
178
|
if (previousNode) {
|
|
176
179
|
focusElement(previousNode, true);
|
|
@@ -179,10 +182,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
179
182
|
},
|
|
180
183
|
focusFirst(opts = {}) {
|
|
181
184
|
let scope = scopeRef.current;
|
|
182
|
-
let {tabbable} = opts;
|
|
183
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
185
|
+
let {tabbable, accept} = opts;
|
|
186
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
184
187
|
walker.currentNode = scope[0].previousElementSibling;
|
|
185
|
-
let nextNode = walker.nextNode() as
|
|
188
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
186
189
|
if (nextNode) {
|
|
187
190
|
focusElement(nextNode, true);
|
|
188
191
|
}
|
|
@@ -190,10 +193,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
190
193
|
},
|
|
191
194
|
focusLast(opts = {}) {
|
|
192
195
|
let scope = scopeRef.current;
|
|
193
|
-
let {tabbable} = opts;
|
|
194
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
196
|
+
let {tabbable, accept} = opts;
|
|
197
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
195
198
|
walker.currentNode = scope[scope.length - 1].nextElementSibling;
|
|
196
|
-
let previousNode = walker.previousNode() as
|
|
199
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
197
200
|
if (previousNode) {
|
|
198
201
|
focusElement(previousNode, true);
|
|
199
202
|
}
|
|
@@ -223,17 +226,22 @@ const FOCUSABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]),') + '
|
|
|
223
226
|
focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
|
|
224
227
|
const TABBABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
|
|
225
228
|
|
|
226
|
-
function getScopeRoot(scope:
|
|
229
|
+
function getScopeRoot(scope: Element[]) {
|
|
227
230
|
return scope[0].parentElement;
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
function useFocusContainment(scopeRef: RefObject<
|
|
231
|
-
let focusedNode = useRef<
|
|
233
|
+
function useFocusContainment(scopeRef: RefObject<Element[]>, contain: boolean) {
|
|
234
|
+
let focusedNode = useRef<FocusableElement>();
|
|
232
235
|
|
|
233
236
|
let raf = useRef(null);
|
|
234
237
|
useLayoutEffect(() => {
|
|
235
238
|
let scope = scopeRef.current;
|
|
236
239
|
if (!contain) {
|
|
240
|
+
// if contain was changed, then we should cancel any ongoing waits to pull focus back into containment
|
|
241
|
+
if (raf.current) {
|
|
242
|
+
cancelAnimationFrame(raf.current);
|
|
243
|
+
raf.current = null;
|
|
244
|
+
}
|
|
237
245
|
return;
|
|
238
246
|
}
|
|
239
247
|
|
|
@@ -243,7 +251,7 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
243
251
|
return;
|
|
244
252
|
}
|
|
245
253
|
|
|
246
|
-
let focusedElement = document.activeElement
|
|
254
|
+
let focusedElement = document.activeElement;
|
|
247
255
|
let scope = scopeRef.current;
|
|
248
256
|
if (!isElementInScope(focusedElement, scope)) {
|
|
249
257
|
return;
|
|
@@ -251,10 +259,10 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
251
259
|
|
|
252
260
|
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable: true}, scope);
|
|
253
261
|
walker.currentNode = focusedElement;
|
|
254
|
-
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
262
|
+
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
255
263
|
if (!nextElement) {
|
|
256
264
|
walker.currentNode = e.shiftKey ? scope[scope.length - 1].nextElementSibling : scope[0].previousElementSibling;
|
|
257
|
-
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
265
|
+
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
258
266
|
}
|
|
259
267
|
|
|
260
268
|
e.preventDefault();
|
|
@@ -308,7 +316,11 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
308
316
|
|
|
309
317
|
// eslint-disable-next-line arrow-body-style
|
|
310
318
|
useEffect(() => {
|
|
311
|
-
return () =>
|
|
319
|
+
return () => {
|
|
320
|
+
if (raf.current) {
|
|
321
|
+
cancelAnimationFrame(raf.current);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
312
324
|
}, [raf]);
|
|
313
325
|
}
|
|
314
326
|
|
|
@@ -321,7 +333,7 @@ function isElementInAnyScope(element: Element) {
|
|
|
321
333
|
return false;
|
|
322
334
|
}
|
|
323
335
|
|
|
324
|
-
function isElementInScope(element: Element, scope:
|
|
336
|
+
function isElementInScope(element: Element, scope: Element[]) {
|
|
325
337
|
return scope.some(node => node.contains(element));
|
|
326
338
|
}
|
|
327
339
|
|
|
@@ -350,7 +362,7 @@ function isAncestorScope(ancestor: ScopeRef, scope: ScopeRef) {
|
|
|
350
362
|
return isAncestorScope(ancestor, parent);
|
|
351
363
|
}
|
|
352
364
|
|
|
353
|
-
function focusElement(element:
|
|
365
|
+
function focusElement(element: FocusableElement | null, scroll = false) {
|
|
354
366
|
if (element != null && !scroll) {
|
|
355
367
|
try {
|
|
356
368
|
focusSafely(element);
|
|
@@ -366,14 +378,14 @@ function focusElement(element: HTMLElement | null, scroll = false) {
|
|
|
366
378
|
}
|
|
367
379
|
}
|
|
368
380
|
|
|
369
|
-
function focusFirstInScope(scope:
|
|
381
|
+
function focusFirstInScope(scope: Element[]) {
|
|
370
382
|
let sentinel = scope[0].previousElementSibling;
|
|
371
383
|
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable: true}, scope);
|
|
372
384
|
walker.currentNode = sentinel;
|
|
373
|
-
focusElement(walker.nextNode() as
|
|
385
|
+
focusElement(walker.nextNode() as FocusableElement);
|
|
374
386
|
}
|
|
375
387
|
|
|
376
|
-
function useAutoFocus(scopeRef: RefObject<
|
|
388
|
+
function useAutoFocus(scopeRef: RefObject<Element[]>, autoFocus: boolean) {
|
|
377
389
|
const autoFocusRef = React.useRef(autoFocus);
|
|
378
390
|
useEffect(() => {
|
|
379
391
|
if (autoFocusRef.current) {
|
|
@@ -386,9 +398,9 @@ function useAutoFocus(scopeRef: RefObject<HTMLElement[]>, autoFocus: boolean) {
|
|
|
386
398
|
}, []);
|
|
387
399
|
}
|
|
388
400
|
|
|
389
|
-
function useRestoreFocus(scopeRef: RefObject<
|
|
401
|
+
function useRestoreFocus(scopeRef: RefObject<Element[]>, restoreFocus: boolean, contain: boolean) {
|
|
390
402
|
// create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
|
|
391
|
-
const nodeToRestoreRef = useRef(typeof document !== 'undefined' ? document.activeElement as
|
|
403
|
+
const nodeToRestoreRef = useRef(typeof document !== 'undefined' ? document.activeElement as FocusableElement : null);
|
|
392
404
|
|
|
393
405
|
// useLayoutEffect instead of useEffect so the active element is saved synchronously instead of asynchronously.
|
|
394
406
|
useLayoutEffect(() => {
|
|
@@ -406,7 +418,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
406
418
|
return;
|
|
407
419
|
}
|
|
408
420
|
|
|
409
|
-
let focusedElement = document.activeElement as
|
|
421
|
+
let focusedElement = document.activeElement as FocusableElement;
|
|
410
422
|
if (!isElementInScope(focusedElement, scopeRef.current)) {
|
|
411
423
|
return;
|
|
412
424
|
}
|
|
@@ -416,7 +428,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
416
428
|
|
|
417
429
|
// Find the next tabbable element after the currently focused element
|
|
418
430
|
walker.currentNode = focusedElement;
|
|
419
|
-
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
431
|
+
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
420
432
|
|
|
421
433
|
if (!document.body.contains(nodeToRestore) || nodeToRestore === document.body) {
|
|
422
434
|
nodeToRestore = null;
|
|
@@ -429,7 +441,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
429
441
|
|
|
430
442
|
// Skip over elements within the scope, in case the scope immediately follows the node to restore.
|
|
431
443
|
do {
|
|
432
|
-
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
444
|
+
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
433
445
|
} while (isElementInScope(nextElement, scopeRef.current));
|
|
434
446
|
|
|
435
447
|
e.preventDefault();
|
|
@@ -460,7 +472,8 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
460
472
|
|
|
461
473
|
if (restoreFocus && nodeToRestore && isElementInScope(document.activeElement, scopeRef.current)) {
|
|
462
474
|
requestAnimationFrame(() => {
|
|
463
|
-
if
|
|
475
|
+
// Only restore focus if we've lost focus to the body, the alternative is that focus has been purposefully moved elsewhere
|
|
476
|
+
if (document.body.contains(nodeToRestore) && document.activeElement === document.body) {
|
|
464
477
|
focusElement(nodeToRestore);
|
|
465
478
|
}
|
|
466
479
|
});
|
|
@@ -473,7 +486,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
473
486
|
* Create a [TreeWalker]{@link https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker}
|
|
474
487
|
* that matches all focusable/tabbable elements.
|
|
475
488
|
*/
|
|
476
|
-
export function getFocusableTreeWalker(root:
|
|
489
|
+
export function getFocusableTreeWalker(root: Element, opts?: FocusManagerOptions, scope?: Element[]) {
|
|
477
490
|
let selector = opts?.tabbable ? TABBABLE_ELEMENT_SELECTOR : FOCUSABLE_ELEMENT_SELECTOR;
|
|
478
491
|
let walker = document.createTreeWalker(
|
|
479
492
|
root,
|
|
@@ -485,9 +498,11 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
|
|
|
485
498
|
return NodeFilter.FILTER_REJECT;
|
|
486
499
|
}
|
|
487
500
|
|
|
488
|
-
if ((node as
|
|
489
|
-
&& isElementVisible(node as
|
|
490
|
-
&& (!scope || isElementInScope(node as
|
|
501
|
+
if ((node as Element).matches(selector)
|
|
502
|
+
&& isElementVisible(node as Element)
|
|
503
|
+
&& (!scope || isElementInScope(node as Element, scope))
|
|
504
|
+
&& (!opts?.accept || opts.accept(node as Element))
|
|
505
|
+
) {
|
|
491
506
|
return NodeFilter.FILTER_ACCEPT;
|
|
492
507
|
}
|
|
493
508
|
|
|
@@ -506,31 +521,37 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
|
|
|
506
521
|
/**
|
|
507
522
|
* Creates a FocusManager object that can be used to move focus within an element.
|
|
508
523
|
*/
|
|
509
|
-
export function createFocusManager(ref: RefObject<
|
|
524
|
+
export function createFocusManager(ref: RefObject<Element>, defaultOptions: FocusManagerOptions = {}): FocusManager {
|
|
510
525
|
return {
|
|
511
526
|
focusNext(opts: FocusManagerOptions = {}) {
|
|
512
527
|
let root = ref.current;
|
|
513
|
-
|
|
528
|
+
if (!root) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
|
|
514
532
|
let node = from || document.activeElement;
|
|
515
|
-
let walker = getFocusableTreeWalker(root, {tabbable});
|
|
533
|
+
let walker = getFocusableTreeWalker(root, {tabbable, accept});
|
|
516
534
|
if (root.contains(node)) {
|
|
517
535
|
walker.currentNode = node;
|
|
518
536
|
}
|
|
519
|
-
let nextNode = walker.nextNode() as
|
|
537
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
520
538
|
if (!nextNode && wrap) {
|
|
521
539
|
walker.currentNode = root;
|
|
522
|
-
nextNode = walker.nextNode() as
|
|
540
|
+
nextNode = walker.nextNode() as FocusableElement;
|
|
523
541
|
}
|
|
524
542
|
if (nextNode) {
|
|
525
543
|
focusElement(nextNode, true);
|
|
526
544
|
}
|
|
527
545
|
return nextNode;
|
|
528
546
|
},
|
|
529
|
-
focusPrevious(opts: FocusManagerOptions =
|
|
547
|
+
focusPrevious(opts: FocusManagerOptions = defaultOptions) {
|
|
530
548
|
let root = ref.current;
|
|
531
|
-
|
|
549
|
+
if (!root) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
let {from, tabbable = defaultOptions.tabbable, wrap = defaultOptions.wrap, accept = defaultOptions.accept} = opts;
|
|
532
553
|
let node = from || document.activeElement;
|
|
533
|
-
let walker = getFocusableTreeWalker(root, {tabbable});
|
|
554
|
+
let walker = getFocusableTreeWalker(root, {tabbable, accept});
|
|
534
555
|
if (root.contains(node)) {
|
|
535
556
|
walker.currentNode = node;
|
|
536
557
|
} else {
|
|
@@ -540,7 +561,7 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
|
|
|
540
561
|
}
|
|
541
562
|
return next;
|
|
542
563
|
}
|
|
543
|
-
let previousNode = walker.previousNode() as
|
|
564
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
544
565
|
if (!previousNode && wrap) {
|
|
545
566
|
walker.currentNode = root;
|
|
546
567
|
previousNode = last(walker);
|
|
@@ -550,20 +571,26 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
|
|
|
550
571
|
}
|
|
551
572
|
return previousNode;
|
|
552
573
|
},
|
|
553
|
-
focusFirst(opts =
|
|
574
|
+
focusFirst(opts = defaultOptions) {
|
|
554
575
|
let root = ref.current;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
576
|
+
if (!root) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
|
|
580
|
+
let walker = getFocusableTreeWalker(root, {tabbable, accept});
|
|
581
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
558
582
|
if (nextNode) {
|
|
559
583
|
focusElement(nextNode, true);
|
|
560
584
|
}
|
|
561
585
|
return nextNode;
|
|
562
586
|
},
|
|
563
|
-
focusLast(opts =
|
|
587
|
+
focusLast(opts = defaultOptions) {
|
|
564
588
|
let root = ref.current;
|
|
565
|
-
|
|
566
|
-
|
|
589
|
+
if (!root) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
|
|
593
|
+
let walker = getFocusableTreeWalker(root, {tabbable, accept});
|
|
567
594
|
let next = last(walker);
|
|
568
595
|
if (next) {
|
|
569
596
|
focusElement(next, true);
|
|
@@ -574,10 +601,10 @@ export function createFocusManager(ref: RefObject<HTMLElement>): FocusManager {
|
|
|
574
601
|
}
|
|
575
602
|
|
|
576
603
|
function last(walker: TreeWalker) {
|
|
577
|
-
let next:
|
|
578
|
-
let last:
|
|
604
|
+
let next: FocusableElement;
|
|
605
|
+
let last: FocusableElement;
|
|
579
606
|
do {
|
|
580
|
-
last = walker.lastChild() as
|
|
607
|
+
last = walker.lastChild() as FocusableElement;
|
|
581
608
|
if (last) {
|
|
582
609
|
next = last;
|
|
583
610
|
}
|
package/src/focusSafely.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {FocusableElement} from '@react-types/shared';
|
|
13
14
|
import {focusWithoutScrolling, runAfterTransition} from '@react-aria/utils';
|
|
14
15
|
import {getInteractionModality} from '@react-aria/interactions';
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ import {getInteractionModality} from '@react-aria/interactions';
|
|
|
17
18
|
* A utility function that focuses an element while avoiding undesired side effects such
|
|
18
19
|
* as page scrolling and screen reader issues with CSS transitions.
|
|
19
20
|
*/
|
|
20
|
-
export function focusSafely(element:
|
|
21
|
+
export function focusSafely(element: FocusableElement) {
|
|
21
22
|
// If the user is interacting with a virtual cursor, e.g. screen reader, then
|
|
22
23
|
// wait until after any animated transitions that are currently occurring on
|
|
23
24
|
// the page before shifting focus. This avoids issues with VoiceOver on iOS
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,13 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
13
|
+
export {FocusScope, useFocusManager, getFocusableTreeWalker, createFocusManager} from './FocusScope';
|
|
14
|
+
export {FocusRing} from './FocusRing';
|
|
15
|
+
export {FocusableProvider, useFocusable} from './useFocusable';
|
|
16
|
+
export {useFocusRing} from './useFocusRing';
|
|
17
|
+
export {focusSafely} from './focusSafely';
|
|
18
|
+
|
|
19
|
+
export type {FocusScopeProps, FocusManager, FocusManagerOptions} from './FocusScope';
|
|
20
|
+
export type {FocusRingProps} from './FocusRing';
|
|
21
|
+
export type {FocusableAria, FocusableOptions, FocusableProviderProps} from './useFocusable';
|
|
22
|
+
export type {AriaFocusRingProps, FocusRingAria} from './useFocusRing';
|