@@ -178,6 +178,46 @@ describe('FocusScope', () => {
178178 } ) ;
179179 } ) ;
180180
181+ describe ( 'given a FocusScope with shadow DOM elements' , ( ) => {
182+ let rendered : RenderResult ;
183+ let tabbableFirst : HTMLButtonElement ;
184+ let tabbableLast : HTMLInputElement ;
185+
186+ beforeEach ( async ( ) => {
187+ rendered = render (
188+ < div >
189+ < FocusScope asChild loop trapped >
190+ < form >
191+ < button > Close</ button >
192+ < ShadowHostField />
193+ </ form >
194+ </ FocusScope >
195+ </ div > ,
196+ ) ;
197+ tabbableFirst = rendered . getByText ( 'Close' ) as HTMLButtonElement ;
198+ // Wait for the useEffect inside ShadowHostField to attach the shadow root
199+ await waitFor ( ( ) => {
200+ const host = rendered . container . querySelector ( '[data-shadow-host]' ) ;
201+ expect ( host ?. shadowRoot ?. querySelector ( 'input' ) ) . not . toBeNull ( ) ;
202+ } ) ;
203+ tabbableLast = rendered . container
204+ . querySelector ( '[data-shadow-host]' ) !
205+ . shadowRoot ! . querySelector ( 'input' ) as HTMLInputElement ;
206+ } ) ;
207+
208+ it ( 'should focus the first element in scope on tab from the last shadow DOM element' , ( ) => {
209+ tabbableLast . focus ( ) ;
210+ userEvent . tab ( ) ;
211+ waitFor ( ( ) => expect ( tabbableFirst ) . toHaveFocus ( ) ) ;
212+ } ) ;
213+
214+ it ( 'should focus the last shadow DOM element on shift+tab from the first element in scope' , ( ) => {
215+ tabbableFirst . focus ( ) ;
216+ userEvent . tab ( { shift : true } ) ;
217+ waitFor ( ( ) => expect ( tabbableLast ) . toHaveFocus ( ) ) ;
218+ } ) ;
219+ } ) ;
220+
181221 describe ( 'given a FocusScope with internal focus handlers' , ( ) => {
182222 const handleLastFocusableElementBlur = vi . fn ( ) ;
183223 let rendered : RenderResult ;
@@ -214,3 +254,16 @@ function TestField({ label, ...props }: { label: string } & React.ComponentProps
214254 </ label >
215255 ) ;
216256}
257+
258+ function ShadowHostField ( ) {
259+ const ref = React . useRef < HTMLDivElement > ( null ) ;
260+ React . useEffect ( ( ) => {
261+ const el = ref . current ;
262+ if ( ! el || el . shadowRoot ) return ;
263+ const shadow = el . attachShadow ( { mode : 'open' } ) ;
264+ const input = document . createElement ( 'input' ) ;
265+ input . type = 'text' ;
266+ shadow . appendChild ( input ) ;
267+ } , [ ] ) ;
268+ return < div ref = { ref } data-shadow-host = "" /> ;
269+ }
0 commit comments