@@ -2205,6 +2205,11 @@ type StoredEventListener = {
2205
2205
optionsOrUseCapture : void | EventListenerOptionsOrUseCapture ,
2206
2206
} ;
2207
2207
2208
+ type FocusOptions = {
2209
+ preventScroll ?: boolean ,
2210
+ focusVisible ?: boolean ,
2211
+ } ;
2212
+
2208
2213
export type FragmentInstanceType = {
2209
2214
_fragmentFiber : Fiber ,
2210
2215
_eventListeners : null | Array < StoredEventListener > ,
@@ -2219,7 +2224,9 @@ export type FragmentInstanceType = {
2219
2224
listener : EventListener ,
2220
2225
optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
2221
2226
) : void ,
2222
- focus ( ) : void ,
2227
+ focus ( focusOptions ?: FocusOptions ) : void ,
2228
+ focusLast ( focusOptions ? : FocusOptions ) : void ,
2229
+ blur ( ) : void ,
2223
2230
observeUsing ( observer : IntersectionObserver | ResizeObserver ) : void ,
2224
2231
unobserveUsing ( observer : IntersectionObserver | ResizeObserver ) : void ,
2225
2232
} ;
@@ -2307,10 +2314,57 @@ function removeEventListenerFromChild(
2307
2314
return false ;
2308
2315
}
2309
2316
// $FlowFixMe[prop-missing]
2310
- FragmentInstance . prototype . focus = function ( this : FragmentInstanceType ) {
2311
- traverseFragmentInstance ( this . _fragmentFiber , setFocusIfFocusable ) ;
2317
+ FragmentInstance . prototype . focus = function (
2318
+ this : FragmentInstanceType ,
2319
+ focusOptions ?: FocusOptions ,
2320
+ ) : void {
2321
+ traverseFragmentInstance (
2322
+ this . _fragmentFiber ,
2323
+ setFocusIfFocusable ,
2324
+ focusOptions ,
2325
+ ) ;
2312
2326
} ;
2313
2327
// $FlowFixMe[prop-missing]
2328
+ FragmentInstance . prototype . focusLast = function (
2329
+ this : FragmentInstanceType ,
2330
+ focusOptions ?: FocusOptions ,
2331
+ ) {
2332
+ const children : Array < Instance > = [];
2333
+ traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2334
+ for (let i = children.length - 1; i >= 0 ; i -- ) {
2335
+ const child = children [ i ] ;
2336
+ if ( setFocusIfFocusable ( child , focusOptions ) ) {
2337
+ break ;
2338
+ }
2339
+ }
2340
+ } ;
2341
+ function collectChildren (
2342
+ child : Instance ,
2343
+ collection : Array < Instance > ,
2344
+ ) : boolean {
2345
+ collection . push ( child ) ;
2346
+ return false ;
2347
+ }
2348
+ // $FlowFixMe[prop-missing]
2349
+ FragmentInstance . prototype . blur = function ( this : FragmentInstanceType ) : void {
2350
+ // TODO: When we have a parent element reference, we can skip traversal if the fragment's parent
2351
+ // does not contain document.activeElement
2352
+ traverseFragmentInstance (
2353
+ this . _fragmentFiber ,
2354
+ blurActiveElementWithinFragment ,
2355
+ ) ;
2356
+ } ;
2357
+ function blurActiveElementWithinFragment ( child : Instance ) : boolean {
2358
+ // TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2359
+ const ownerDocument = child . ownerDocument ;
2360
+ if ( child === ownerDocument . activeElement ) {
2361
+ // $FlowFixMe[prop-missing]
2362
+ child . blur ( ) ;
2363
+ return true ;
2364
+ }
2365
+ return false ;
2366
+ }
2367
+ // $FlowFixMe[prop-missing]
2314
2368
FragmentInstance . prototype . observeUsing = function (
2315
2369
this : FragmentInstanceType ,
2316
2370
observer : IntersectionObserver | ResizeObserver ,
@@ -3190,7 +3244,10 @@ export function isHiddenSubtree(fiber: Fiber): boolean {
3190
3244
return fiber . tag === HostComponent && fiber . memoizedProps . hidden === true ;
3191
3245
}
3192
3246
3193
- export function setFocusIfFocusable ( node : Instance ) : boolean {
3247
+ export function setFocusIfFocusable (
3248
+ node : Instance ,
3249
+ focusOptions ?: FocusOptions ,
3250
+ ) : boolean {
3194
3251
// The logic for determining if an element is focusable is kind of complex,
3195
3252
// and since we want to actually change focus anyway- we can just skip it.
3196
3253
// Instead we'll just listen for a "focus" event to verify that focus was set.
@@ -3206,7 +3263,7 @@ export function setFocusIfFocusable(node: Instance): boolean {
3206
3263
try {
3207
3264
element. addEventListener ( 'focus' , handleFocus ) ;
3208
3265
// $FlowFixMe[method-unbinding]
3209
- ( element . focus || HTMLElement . prototype . focus ) . call ( element ) ;
3266
+ ( element . focus || HTMLElement . prototype . focus ) . call ( element , focusOptions ) ;
3210
3267
} finally {
3211
3268
element . removeEventListener ( 'focus ', handleFocus ) ;
3212
3269
}
0 commit comments