@@ -102,11 +102,20 @@ export function triggerFocus(
102102// ======================================================
103103let lastFocusElement : HTMLElement | null = null ;
104104let focusElements : HTMLElement [ ] = [ ] ;
105+ const ignoredElementMap = new Map < HTMLElement , HTMLElement | null > ( ) ;
105106
106107function getLastElement ( ) {
107108 return focusElements [ focusElements . length - 1 ] ;
108109}
109110
111+ function isIgnoredElement ( element : HTMLElement | null ) : boolean {
112+ if ( ! element ) return false ;
113+ const ignoredEle = ignoredElementMap . get ( getLastElement ( ) ) ;
114+ return (
115+ ! ! ignoredEle && ( ignoredEle === element || ignoredEle . contains ( element ) )
116+ ) ;
117+ }
118+
110119function hasFocus ( element : HTMLElement ) {
111120 const { activeElement } = document ;
112121 return element === activeElement || element . contains ( activeElement ) ;
@@ -116,6 +125,11 @@ function syncFocus() {
116125 const lastElement = getLastElement ( ) ;
117126 const { activeElement } = document ;
118127
128+ // If current focus is on an ignored element, don't force it back
129+ if ( isIgnoredElement ( activeElement as HTMLElement ) ) {
130+ return ;
131+ }
132+
119133 if ( lastElement && ! hasFocus ( lastElement ) ) {
120134 const focusableList = getFocusNodeList ( lastElement ) ;
121135
@@ -166,6 +180,7 @@ export function lockFocus(element: HTMLElement): VoidFunction {
166180 return ( ) => {
167181 lastFocusElement = null ;
168182 focusElements = focusElements . filter ( ele => ele !== element ) ;
183+ ignoredElementMap . delete ( element ) ;
169184 if ( focusElements . length === 0 ) {
170185 window . removeEventListener ( 'focusin' , syncFocus ) ;
171186 window . removeEventListener ( 'keydown' , onWindowKeyDown , true ) ;
@@ -177,11 +192,12 @@ export function lockFocus(element: HTMLElement): VoidFunction {
177192 * Lock focus within an element.
178193 * When locked, focus will be restricted to focusable elements within the specified element.
179194 * If multiple elements are locked, only the last locked element will be effective.
195+ * @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
180196 */
181197export function useLockFocus (
182198 lock : boolean ,
183199 getElement : ( ) => HTMLElement | null ,
184- ) {
200+ ) : [ ignoreElement : ( ele : HTMLElement ) => void ] {
185201 useEffect ( ( ) => {
186202 if ( lock ) {
187203 const element = getElement ( ) ;
@@ -190,4 +206,15 @@ export function useLockFocus(
190206 }
191207 }
192208 } , [ lock ] ) ;
209+
210+ const ignoreElement = ( ele : HTMLElement ) => {
211+ const element = getElement ( ) ;
212+ if ( element && ele ) {
213+ // Set the ignored element for current lock element
214+ // Only one element can be ignored at a time for this lock
215+ ignoredElementMap . set ( element , ele ) ;
216+ }
217+ } ;
218+
219+ return [ ignoreElement ] ;
193220}
0 commit comments