@@ -2205,6 +2205,11 @@ type StoredEventListener = {
22052205 optionsOrUseCapture : void | EventListenerOptionsOrUseCapture ,
22062206} ;
22072207
2208+ type FocusOptions = {
2209+ preventScroll ?: boolean ,
2210+ focusVisible ?: boolean ,
2211+ } ;
2212+
22082213export type FragmentInstanceType = {
22092214 _fragmentFiber : Fiber ,
22102215 _eventListeners : null | Array < StoredEventListener > ,
@@ -2219,7 +2224,9 @@ export type FragmentInstanceType = {
22192224 listener : EventListener ,
22202225 optionsOrUseCapture ?: EventListenerOptionsOrUseCapture ,
22212226 ) : void ,
2222- focus ( ) : void ,
2227+ focus ( focusOptions ?: FocusOptions ) : void ,
2228+ focusLast ( focusOptions ? : FocusOptions ) : void ,
2229+ blur ( ) : void ,
22232230 observeUsing ( observer : IntersectionObserver | ResizeObserver ) : void ,
22242231 unobserveUsing ( observer : IntersectionObserver | ResizeObserver ) : void ,
22252232} ;
@@ -2307,10 +2314,57 @@ function removeEventListenerFromChild(
23072314 return false ;
23082315}
23092316// $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+ ) ;
23122326} ;
23132327// $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]
23142368FragmentInstance . prototype . observeUsing = function (
23152369 this : FragmentInstanceType ,
23162370 observer : IntersectionObserver | ResizeObserver ,
@@ -3190,7 +3244,10 @@ export function isHiddenSubtree(fiber: Fiber): boolean {
31903244 return fiber . tag === HostComponent && fiber . memoizedProps . hidden === true ;
31913245}
31923246
3193- export function setFocusIfFocusable ( node : Instance ) : boolean {
3247+ export function setFocusIfFocusable (
3248+ node : Instance ,
3249+ focusOptions ?: FocusOptions ,
3250+ ) : boolean {
31943251 // The logic for determining if an element is focusable is kind of complex,
31953252 // and since we want to actually change focus anyway- we can just skip it.
31963253 // 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 {
32063263 try {
32073264 element. addEventListener ( 'focus' , handleFocus ) ;
32083265 // $FlowFixMe[method-unbinding]
3209- ( element . focus || HTMLElement . prototype . focus ) . call ( element ) ;
3266+ ( element . focus || HTMLElement . prototype . focus ) . call ( element , focusOptions ) ;
32103267 } finally {
32113268 element . removeEventListener ( 'focus ', handleFocus ) ;
32123269 }
0 commit comments