@react-aria/focus 3.6.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/main.js +36 -53
- package/dist/main.js.map +1 -1
- package/dist/module.js +25 -34
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +24 -24
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/FocusRing.tsx +1 -1
- package/src/FocusScope.tsx +65 -54
- package/src/focusSafely.ts +2 -1
- package/src/index.ts +10 -5
- package/src/useFocusRing.ts +6 -5
- package/src/useFocusable.tsx +10 -10
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,9 +39,9 @@ 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. */
|
|
@@ -51,16 +52,16 @@ interface FocusManagerOptions {
|
|
|
51
52
|
|
|
52
53
|
export interface FocusManager {
|
|
53
54
|
/** Moves focus to the next focusable or tabbable element in the focus scope. */
|
|
54
|
-
focusNext(opts?: FocusManagerOptions):
|
|
55
|
+
focusNext(opts?: FocusManagerOptions): FocusableElement,
|
|
55
56
|
/** Moves focus to the previous focusable or tabbable element in the focus scope. */
|
|
56
|
-
focusPrevious(opts?: FocusManagerOptions):
|
|
57
|
+
focusPrevious(opts?: FocusManagerOptions): FocusableElement,
|
|
57
58
|
/** Moves focus to the first focusable or tabbable element in the focus scope. */
|
|
58
|
-
focusFirst(opts?: FocusManagerOptions):
|
|
59
|
+
focusFirst(opts?: FocusManagerOptions): FocusableElement,
|
|
59
60
|
/** Moves focus to the last focusable or tabbable element in the focus scope. */
|
|
60
|
-
focusLast(opts?: FocusManagerOptions):
|
|
61
|
+
focusLast(opts?: FocusManagerOptions): FocusableElement
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
type ScopeRef = RefObject<
|
|
64
|
+
type ScopeRef = RefObject<Element[]>;
|
|
64
65
|
interface IFocusContext {
|
|
65
66
|
scopeRef: ScopeRef,
|
|
66
67
|
focusManager: FocusManager
|
|
@@ -87,7 +88,7 @@ export function FocusScope(props: FocusScopeProps) {
|
|
|
87
88
|
let {children, contain, restoreFocus, autoFocus} = props;
|
|
88
89
|
let startRef = useRef<HTMLSpanElement>();
|
|
89
90
|
let endRef = useRef<HTMLSpanElement>();
|
|
90
|
-
let scopeRef = useRef<
|
|
91
|
+
let scopeRef = useRef<Element[]>([]);
|
|
91
92
|
let ctx = useContext(FocusContext);
|
|
92
93
|
let parentScope = ctx?.scopeRef;
|
|
93
94
|
|
|
@@ -143,19 +144,19 @@ export function useFocusManager(): FocusManager {
|
|
|
143
144
|
return useContext(FocusContext)?.focusManager;
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function createFocusManagerForScope(scopeRef: React.RefObject<
|
|
147
|
+
function createFocusManagerForScope(scopeRef: React.RefObject<Element[]>): FocusManager {
|
|
147
148
|
return {
|
|
148
149
|
focusNext(opts: FocusManagerOptions = {}) {
|
|
149
150
|
let scope = scopeRef.current;
|
|
150
|
-
let {from, tabbable, wrap} = opts;
|
|
151
|
+
let {from, tabbable, wrap, accept} = opts;
|
|
151
152
|
let node = from || document.activeElement;
|
|
152
153
|
let sentinel = scope[0].previousElementSibling;
|
|
153
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
154
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
154
155
|
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
|
|
155
|
-
let nextNode = walker.nextNode() as
|
|
156
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
156
157
|
if (!nextNode && wrap) {
|
|
157
158
|
walker.currentNode = sentinel;
|
|
158
|
-
nextNode = walker.nextNode() as
|
|
159
|
+
nextNode = walker.nextNode() as FocusableElement;
|
|
159
160
|
}
|
|
160
161
|
if (nextNode) {
|
|
161
162
|
focusElement(nextNode, true);
|
|
@@ -164,15 +165,15 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
164
165
|
},
|
|
165
166
|
focusPrevious(opts: FocusManagerOptions = {}) {
|
|
166
167
|
let scope = scopeRef.current;
|
|
167
|
-
let {from, tabbable, wrap} = opts;
|
|
168
|
+
let {from, tabbable, wrap, accept} = opts;
|
|
168
169
|
let node = from || document.activeElement;
|
|
169
170
|
let sentinel = scope[scope.length - 1].nextElementSibling;
|
|
170
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
171
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
171
172
|
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
|
|
172
|
-
let previousNode = walker.previousNode() as
|
|
173
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
173
174
|
if (!previousNode && wrap) {
|
|
174
175
|
walker.currentNode = sentinel;
|
|
175
|
-
previousNode = walker.previousNode() as
|
|
176
|
+
previousNode = walker.previousNode() as FocusableElement;
|
|
176
177
|
}
|
|
177
178
|
if (previousNode) {
|
|
178
179
|
focusElement(previousNode, true);
|
|
@@ -181,10 +182,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
181
182
|
},
|
|
182
183
|
focusFirst(opts = {}) {
|
|
183
184
|
let scope = scopeRef.current;
|
|
184
|
-
let {tabbable} = opts;
|
|
185
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
185
|
+
let {tabbable, accept} = opts;
|
|
186
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
186
187
|
walker.currentNode = scope[0].previousElementSibling;
|
|
187
|
-
let nextNode = walker.nextNode() as
|
|
188
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
188
189
|
if (nextNode) {
|
|
189
190
|
focusElement(nextNode, true);
|
|
190
191
|
}
|
|
@@ -192,10 +193,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
|
|
|
192
193
|
},
|
|
193
194
|
focusLast(opts = {}) {
|
|
194
195
|
let scope = scopeRef.current;
|
|
195
|
-
let {tabbable} = opts;
|
|
196
|
-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
|
|
196
|
+
let {tabbable, accept} = opts;
|
|
197
|
+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
|
|
197
198
|
walker.currentNode = scope[scope.length - 1].nextElementSibling;
|
|
198
|
-
let previousNode = walker.previousNode() as
|
|
199
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
199
200
|
if (previousNode) {
|
|
200
201
|
focusElement(previousNode, true);
|
|
201
202
|
}
|
|
@@ -225,17 +226,22 @@ const FOCUSABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]),') + '
|
|
|
225
226
|
focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
|
|
226
227
|
const TABBABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
|
|
227
228
|
|
|
228
|
-
function getScopeRoot(scope:
|
|
229
|
+
function getScopeRoot(scope: Element[]) {
|
|
229
230
|
return scope[0].parentElement;
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
function useFocusContainment(scopeRef: RefObject<
|
|
233
|
-
let focusedNode = useRef<
|
|
233
|
+
function useFocusContainment(scopeRef: RefObject<Element[]>, contain: boolean) {
|
|
234
|
+
let focusedNode = useRef<FocusableElement>();
|
|
234
235
|
|
|
235
236
|
let raf = useRef(null);
|
|
236
237
|
useLayoutEffect(() => {
|
|
237
238
|
let scope = scopeRef.current;
|
|
238
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
|
+
}
|
|
239
245
|
return;
|
|
240
246
|
}
|
|
241
247
|
|
|
@@ -245,7 +251,7 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
245
251
|
return;
|
|
246
252
|
}
|
|
247
253
|
|
|
248
|
-
let focusedElement = document.activeElement
|
|
254
|
+
let focusedElement = document.activeElement;
|
|
249
255
|
let scope = scopeRef.current;
|
|
250
256
|
if (!isElementInScope(focusedElement, scope)) {
|
|
251
257
|
return;
|
|
@@ -253,10 +259,10 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
253
259
|
|
|
254
260
|
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable: true}, scope);
|
|
255
261
|
walker.currentNode = focusedElement;
|
|
256
|
-
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
262
|
+
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
257
263
|
if (!nextElement) {
|
|
258
264
|
walker.currentNode = e.shiftKey ? scope[scope.length - 1].nextElementSibling : scope[0].previousElementSibling;
|
|
259
|
-
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
265
|
+
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
e.preventDefault();
|
|
@@ -310,7 +316,11 @@ function useFocusContainment(scopeRef: RefObject<HTMLElement[]>, contain: boolea
|
|
|
310
316
|
|
|
311
317
|
// eslint-disable-next-line arrow-body-style
|
|
312
318
|
useEffect(() => {
|
|
313
|
-
return () =>
|
|
319
|
+
return () => {
|
|
320
|
+
if (raf.current) {
|
|
321
|
+
cancelAnimationFrame(raf.current);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
314
324
|
}, [raf]);
|
|
315
325
|
}
|
|
316
326
|
|
|
@@ -323,7 +333,7 @@ function isElementInAnyScope(element: Element) {
|
|
|
323
333
|
return false;
|
|
324
334
|
}
|
|
325
335
|
|
|
326
|
-
function isElementInScope(element: Element, scope:
|
|
336
|
+
function isElementInScope(element: Element, scope: Element[]) {
|
|
327
337
|
return scope.some(node => node.contains(element));
|
|
328
338
|
}
|
|
329
339
|
|
|
@@ -352,7 +362,7 @@ function isAncestorScope(ancestor: ScopeRef, scope: ScopeRef) {
|
|
|
352
362
|
return isAncestorScope(ancestor, parent);
|
|
353
363
|
}
|
|
354
364
|
|
|
355
|
-
function focusElement(element:
|
|
365
|
+
function focusElement(element: FocusableElement | null, scroll = false) {
|
|
356
366
|
if (element != null && !scroll) {
|
|
357
367
|
try {
|
|
358
368
|
focusSafely(element);
|
|
@@ -368,14 +378,14 @@ function focusElement(element: HTMLElement | null, scroll = false) {
|
|
|
368
378
|
}
|
|
369
379
|
}
|
|
370
380
|
|
|
371
|
-
function focusFirstInScope(scope:
|
|
381
|
+
function focusFirstInScope(scope: Element[]) {
|
|
372
382
|
let sentinel = scope[0].previousElementSibling;
|
|
373
383
|
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable: true}, scope);
|
|
374
384
|
walker.currentNode = sentinel;
|
|
375
|
-
focusElement(walker.nextNode() as
|
|
385
|
+
focusElement(walker.nextNode() as FocusableElement);
|
|
376
386
|
}
|
|
377
387
|
|
|
378
|
-
function useAutoFocus(scopeRef: RefObject<
|
|
388
|
+
function useAutoFocus(scopeRef: RefObject<Element[]>, autoFocus: boolean) {
|
|
379
389
|
const autoFocusRef = React.useRef(autoFocus);
|
|
380
390
|
useEffect(() => {
|
|
381
391
|
if (autoFocusRef.current) {
|
|
@@ -388,9 +398,9 @@ function useAutoFocus(scopeRef: RefObject<HTMLElement[]>, autoFocus: boolean) {
|
|
|
388
398
|
}, []);
|
|
389
399
|
}
|
|
390
400
|
|
|
391
|
-
function useRestoreFocus(scopeRef: RefObject<
|
|
401
|
+
function useRestoreFocus(scopeRef: RefObject<Element[]>, restoreFocus: boolean, contain: boolean) {
|
|
392
402
|
// create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
|
|
393
|
-
const nodeToRestoreRef = useRef(typeof document !== 'undefined' ? document.activeElement as
|
|
403
|
+
const nodeToRestoreRef = useRef(typeof document !== 'undefined' ? document.activeElement as FocusableElement : null);
|
|
394
404
|
|
|
395
405
|
// useLayoutEffect instead of useEffect so the active element is saved synchronously instead of asynchronously.
|
|
396
406
|
useLayoutEffect(() => {
|
|
@@ -408,7 +418,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
408
418
|
return;
|
|
409
419
|
}
|
|
410
420
|
|
|
411
|
-
let focusedElement = document.activeElement as
|
|
421
|
+
let focusedElement = document.activeElement as FocusableElement;
|
|
412
422
|
if (!isElementInScope(focusedElement, scopeRef.current)) {
|
|
413
423
|
return;
|
|
414
424
|
}
|
|
@@ -418,7 +428,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
418
428
|
|
|
419
429
|
// Find the next tabbable element after the currently focused element
|
|
420
430
|
walker.currentNode = focusedElement;
|
|
421
|
-
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
431
|
+
let nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
422
432
|
|
|
423
433
|
if (!document.body.contains(nodeToRestore) || nodeToRestore === document.body) {
|
|
424
434
|
nodeToRestore = null;
|
|
@@ -431,7 +441,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
431
441
|
|
|
432
442
|
// Skip over elements within the scope, in case the scope immediately follows the node to restore.
|
|
433
443
|
do {
|
|
434
|
-
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as
|
|
444
|
+
nextElement = (e.shiftKey ? walker.previousNode() : walker.nextNode()) as FocusableElement;
|
|
435
445
|
} while (isElementInScope(nextElement, scopeRef.current));
|
|
436
446
|
|
|
437
447
|
e.preventDefault();
|
|
@@ -462,7 +472,8 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
462
472
|
|
|
463
473
|
if (restoreFocus && nodeToRestore && isElementInScope(document.activeElement, scopeRef.current)) {
|
|
464
474
|
requestAnimationFrame(() => {
|
|
465
|
-
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) {
|
|
466
477
|
focusElement(nodeToRestore);
|
|
467
478
|
}
|
|
468
479
|
});
|
|
@@ -475,7 +486,7 @@ function useRestoreFocus(scopeRef: RefObject<HTMLElement[]>, restoreFocus: boole
|
|
|
475
486
|
* Create a [TreeWalker]{@link https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker}
|
|
476
487
|
* that matches all focusable/tabbable elements.
|
|
477
488
|
*/
|
|
478
|
-
export function getFocusableTreeWalker(root:
|
|
489
|
+
export function getFocusableTreeWalker(root: Element, opts?: FocusManagerOptions, scope?: Element[]) {
|
|
479
490
|
let selector = opts?.tabbable ? TABBABLE_ELEMENT_SELECTOR : FOCUSABLE_ELEMENT_SELECTOR;
|
|
480
491
|
let walker = document.createTreeWalker(
|
|
481
492
|
root,
|
|
@@ -487,9 +498,9 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
|
|
|
487
498
|
return NodeFilter.FILTER_REJECT;
|
|
488
499
|
}
|
|
489
500
|
|
|
490
|
-
if ((node as
|
|
491
|
-
&& isElementVisible(node as
|
|
492
|
-
&& (!scope || isElementInScope(node as
|
|
501
|
+
if ((node as Element).matches(selector)
|
|
502
|
+
&& isElementVisible(node as Element)
|
|
503
|
+
&& (!scope || isElementInScope(node as Element, scope))
|
|
493
504
|
&& (!opts?.accept || opts.accept(node as Element))
|
|
494
505
|
) {
|
|
495
506
|
return NodeFilter.FILTER_ACCEPT;
|
|
@@ -510,7 +521,7 @@ export function getFocusableTreeWalker(root: HTMLElement, opts?: FocusManagerOpt
|
|
|
510
521
|
/**
|
|
511
522
|
* Creates a FocusManager object that can be used to move focus within an element.
|
|
512
523
|
*/
|
|
513
|
-
export function createFocusManager(ref: RefObject<
|
|
524
|
+
export function createFocusManager(ref: RefObject<Element>, defaultOptions: FocusManagerOptions = {}): FocusManager {
|
|
514
525
|
return {
|
|
515
526
|
focusNext(opts: FocusManagerOptions = {}) {
|
|
516
527
|
let root = ref.current;
|
|
@@ -523,10 +534,10 @@ export function createFocusManager(ref: RefObject<HTMLElement>, defaultOptions:
|
|
|
523
534
|
if (root.contains(node)) {
|
|
524
535
|
walker.currentNode = node;
|
|
525
536
|
}
|
|
526
|
-
let nextNode = walker.nextNode() as
|
|
537
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
527
538
|
if (!nextNode && wrap) {
|
|
528
539
|
walker.currentNode = root;
|
|
529
|
-
nextNode = walker.nextNode() as
|
|
540
|
+
nextNode = walker.nextNode() as FocusableElement;
|
|
530
541
|
}
|
|
531
542
|
if (nextNode) {
|
|
532
543
|
focusElement(nextNode, true);
|
|
@@ -550,7 +561,7 @@ export function createFocusManager(ref: RefObject<HTMLElement>, defaultOptions:
|
|
|
550
561
|
}
|
|
551
562
|
return next;
|
|
552
563
|
}
|
|
553
|
-
let previousNode = walker.previousNode() as
|
|
564
|
+
let previousNode = walker.previousNode() as FocusableElement;
|
|
554
565
|
if (!previousNode && wrap) {
|
|
555
566
|
walker.currentNode = root;
|
|
556
567
|
previousNode = last(walker);
|
|
@@ -567,7 +578,7 @@ export function createFocusManager(ref: RefObject<HTMLElement>, defaultOptions:
|
|
|
567
578
|
}
|
|
568
579
|
let {tabbable = defaultOptions.tabbable, accept = defaultOptions.accept} = opts;
|
|
569
580
|
let walker = getFocusableTreeWalker(root, {tabbable, accept});
|
|
570
|
-
let nextNode = walker.nextNode() as
|
|
581
|
+
let nextNode = walker.nextNode() as FocusableElement;
|
|
571
582
|
if (nextNode) {
|
|
572
583
|
focusElement(nextNode, true);
|
|
573
584
|
}
|
|
@@ -590,10 +601,10 @@ export function createFocusManager(ref: RefObject<HTMLElement>, defaultOptions:
|
|
|
590
601
|
}
|
|
591
602
|
|
|
592
603
|
function last(walker: TreeWalker) {
|
|
593
|
-
let next:
|
|
594
|
-
let last:
|
|
604
|
+
let next: FocusableElement;
|
|
605
|
+
let last: FocusableElement;
|
|
595
606
|
do {
|
|
596
|
-
last = walker.lastChild() as
|
|
607
|
+
last = walker.lastChild() as FocusableElement;
|
|
597
608
|
if (last) {
|
|
598
609
|
next = last;
|
|
599
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';
|
package/src/useFocusRing.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {DOMAttributes} from '@react-types/shared';
|
|
2
2
|
import {isFocusVisible, useFocus, useFocusVisibleListener, useFocusWithin} from '@react-aria/interactions';
|
|
3
|
+
import {useCallback, useState} from 'react';
|
|
3
4
|
import {useRef} from 'react';
|
|
4
5
|
|
|
5
|
-
interface
|
|
6
|
+
export interface AriaFocusRingProps {
|
|
6
7
|
/**
|
|
7
8
|
* Whether to show the focus ring when something
|
|
8
9
|
* inside the container element has focus (true), or
|
|
@@ -18,7 +19,7 @@ interface FocusRingProps {
|
|
|
18
19
|
autoFocus?: boolean
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
interface FocusRingAria {
|
|
22
|
+
export interface FocusRingAria {
|
|
22
23
|
/** Whether the element is currently focused. */
|
|
23
24
|
isFocused: boolean,
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ interface FocusRingAria {
|
|
|
26
27
|
isFocusVisible: boolean,
|
|
27
28
|
|
|
28
29
|
/** Props to apply to the container element with the focus ring. */
|
|
29
|
-
focusProps:
|
|
30
|
+
focusProps: DOMAttributes
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -34,7 +35,7 @@ interface FocusRingAria {
|
|
|
34
35
|
* Focus rings are visible only when the user is interacting with a keyboard,
|
|
35
36
|
* not with a mouse, touch, or other input methods.
|
|
36
37
|
*/
|
|
37
|
-
export function useFocusRing(props:
|
|
38
|
+
export function useFocusRing(props: AriaFocusRingProps = {}): FocusRingAria {
|
|
38
39
|
let {
|
|
39
40
|
autoFocus = false,
|
|
40
41
|
isTextInput,
|
package/src/useFocusable.tsx
CHANGED
|
@@ -10,29 +10,29 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {FocusableDOMProps, FocusableProps} from '@react-types/shared';
|
|
13
|
+
import {DOMAttributes, FocusableDOMProps, FocusableElement, FocusableProps} from '@react-types/shared';
|
|
14
14
|
import {focusSafely} from './';
|
|
15
15
|
import {mergeProps, useSyncRef} from '@react-aria/utils';
|
|
16
|
-
import React, {
|
|
16
|
+
import React, {MutableRefObject, ReactNode, RefObject, useContext, useEffect, useRef} from 'react';
|
|
17
17
|
import {useFocus, useKeyboard} from '@react-aria/interactions';
|
|
18
18
|
|
|
19
|
-
interface FocusableOptions extends FocusableProps, FocusableDOMProps {
|
|
19
|
+
export interface FocusableOptions extends FocusableProps, FocusableDOMProps {
|
|
20
20
|
/** Whether focus should be disabled. */
|
|
21
21
|
isDisabled?: boolean
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
interface FocusableProviderProps extends
|
|
24
|
+
export interface FocusableProviderProps extends DOMAttributes {
|
|
25
25
|
/** The child element to provide DOM props to. */
|
|
26
26
|
children?: ReactNode
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
interface FocusableContextValue extends FocusableProviderProps {
|
|
30
|
-
ref?: MutableRefObject<
|
|
30
|
+
ref?: MutableRefObject<FocusableElement>
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
let FocusableContext = React.createContext<FocusableContextValue>(null);
|
|
34
34
|
|
|
35
|
-
function useFocusableContext(ref: RefObject<
|
|
35
|
+
function useFocusableContext(ref: RefObject<FocusableElement>): FocusableContextValue {
|
|
36
36
|
let context = useContext(FocusableContext) || {};
|
|
37
37
|
useSyncRef(context, ref);
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ function useFocusableContext(ref: RefObject<HTMLElement>): FocusableContextValue
|
|
|
44
44
|
/**
|
|
45
45
|
* Provides DOM props to the nearest focusable child.
|
|
46
46
|
*/
|
|
47
|
-
function FocusableProvider(props: FocusableProviderProps, ref: RefObject<
|
|
47
|
+
function FocusableProvider(props: FocusableProviderProps, ref: RefObject<FocusableElement>) {
|
|
48
48
|
let {children, ...otherProps} = props;
|
|
49
49
|
let context = {
|
|
50
50
|
...otherProps,
|
|
@@ -61,15 +61,15 @@ function FocusableProvider(props: FocusableProviderProps, ref: RefObject<HTMLEle
|
|
|
61
61
|
let _FocusableProvider = React.forwardRef(FocusableProvider);
|
|
62
62
|
export {_FocusableProvider as FocusableProvider};
|
|
63
63
|
|
|
64
|
-
interface FocusableAria {
|
|
64
|
+
export interface FocusableAria {
|
|
65
65
|
/** Props for the focusable element. */
|
|
66
|
-
focusableProps:
|
|
66
|
+
focusableProps: DOMAttributes
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Used to make an element focusable and capable of auto focus.
|
|
71
71
|
*/
|
|
72
|
-
export function useFocusable(props: FocusableOptions, domRef: RefObject<
|
|
72
|
+
export function useFocusable(props: FocusableOptions, domRef: RefObject<FocusableElement>): FocusableAria {
|
|
73
73
|
let {focusProps} = useFocus(props);
|
|
74
74
|
let {keyboardProps} = useKeyboard(props);
|
|
75
75
|
let interactions = mergeProps(focusProps, keyboardProps);
|