@@ -5,6 +5,7 @@ import Downshift from '../'
55import DropdownSelect from '../../test/useSelect.test'
66import DropdownCombobox , { colors } from '../../test/useCombobox.test'
77import DropdownMultipleSelect from '../../test/useMultipleSelect.test'
8+ import ComboBox from '../../test/downshift.test'
89import { ReactShadowRoot } from '../../test/react-shadow'
910
1011function _queryAllByRoleDeep ( container , ...rest ) {
@@ -159,3 +160,123 @@ test('DropdownCombobox works correctly in shadow DOM', async () => {
159160 // Menu should close
160161 expect ( input ) . toHaveAttribute ( 'aria-expanded' , 'false' )
161162} )
163+
164+ test ( 'Downshift button blur correctly handles focus moving to external shadow DOM' , async ( ) => {
165+ const user = userEvent . setup ( )
166+ const { container} = render ( < ComboBox /> , { wrapper : Wrapper } )
167+
168+ // Verify Downshift's own shadow root exists
169+ expect ( container . shadowRoot ) . toBeDefined ( )
170+
171+ const comboboxRoot = getByRoleDeep ( 'combobox' )
172+ const toggleButton = container . shadowRoot . querySelector (
173+ '[data-testid="combobox-toggle-button"]' ,
174+ )
175+
176+ // Open the dropdown
177+ await user . click ( toggleButton )
178+ expect ( comboboxRoot ) . toHaveAttribute ( 'aria-expanded' , 'true' )
179+
180+ // Click the element inside the external shadow DOM
181+ // This should focus externalFocusableButton and blur toggleButton (or the root/input depending on what had focus)
182+ const externalHost = document . createElement ( 'div' )
183+ document . body . appendChild ( externalHost )
184+ const shadow = externalHost . attachShadow ( { mode : 'open' } )
185+ const externalFocusableButton = document . createElement ( 'button' )
186+ shadow . appendChild ( externalFocusableButton )
187+ await user . click ( externalFocusableButton )
188+
189+ // Assert that the menu closes due to blur
190+ // Downshift's blur handlers use setTimeout, so wait for the next macrotask
191+ await new Promise ( resolve => setTimeout ( resolve , 0 ) )
192+ expect ( comboboxRoot ) . toHaveAttribute ( 'aria-expanded' , 'false' )
193+
194+ // Cleanup
195+ document . body . removeChild ( externalHost )
196+ } )
197+
198+ test ( 'Downshift input blur correctly handles focus moving to external shadow DOM' , async ( ) => {
199+ const user = userEvent . setup ( )
200+ const { container} = render ( < ComboBox /> , { wrapper : Wrapper } )
201+
202+ // Verify Downshift's own shadow root exists
203+ expect ( container . shadowRoot ) . toBeDefined ( )
204+
205+ const comboboxRoot = getByRoleDeep ( 'combobox' )
206+ const inputField = container . shadowRoot . querySelector (
207+ '[data-testid="combobox-input"]' ,
208+ )
209+ const downshiftToggleButton = container . shadowRoot . querySelector (
210+ '[data-testid="combobox-toggle-button"]' ,
211+ )
212+
213+ // Create an external element with its own shadow DOM
214+ const externalHost = document . createElement ( 'div' )
215+ document . body . appendChild ( externalHost )
216+ const shadow = externalHost . attachShadow ( { mode : 'open' } )
217+ const externalFocusableButton = document . createElement ( 'button' )
218+ shadow . appendChild ( externalFocusableButton )
219+
220+ // Open the dropdown by clicking the toggle button
221+ await user . click ( downshiftToggleButton )
222+ expect ( comboboxRoot ) . toHaveAttribute ( 'aria-expanded' , 'true' )
223+
224+ // Ensure the input itself is focused before it blurs
225+ inputField . focus ( )
226+ await user . type ( inputField , 'b' )
227+ await user . keyboard ( '{Tab}' )
228+ // Click the element inside the external shadow DOM
229+ // This should focus externalFocusableButton and blur the input
230+ await user . click ( externalFocusableButton )
231+
232+ // Assert that the menu closes due to blur
233+ // Downshift's blur handlers use setTimeout, so wait for the next macrotask
234+ await new Promise ( resolve => setTimeout ( resolve , 0 ) )
235+ expect ( comboboxRoot ) . toHaveAttribute ( 'aria-expanded' , 'false' )
236+
237+ // Cleanup
238+ document . body . removeChild ( externalHost )
239+ } )
240+
241+ test ( 'useCombobox input blur correctly handles focus moving to external shadow DOM' , async ( ) => {
242+ const user = userEvent . setup ( )
243+ const { container} = render ( < DropdownCombobox /> , { wrapper : Wrapper } )
244+
245+ // Verify DropdownCombobox's own shadow root exists via the Wrapper
246+ expect ( container . shadowRoot ) . toBeDefined ( )
247+
248+ const input = container . shadowRoot . querySelector (
249+ '[data-testid="combobox-input"]' ,
250+ )
251+ const toggleButton = container . shadowRoot . querySelector (
252+ '[data-testid="combobox-toggle-button"]' ,
253+ )
254+
255+ // Create an external element with its own shadow DOM
256+ const externalHost = document . createElement ( 'div' )
257+ document . body . appendChild ( externalHost )
258+ const shadow = externalHost . attachShadow ( { mode : 'open' } )
259+ const externalFocusableButton = document . createElement ( 'button' )
260+ shadow . appendChild ( externalFocusableButton )
261+
262+ // Open the dropdown by clicking the toggle button
263+ await user . click ( toggleButton )
264+ expect ( input ) . toHaveAttribute ( 'aria-expanded' , 'true' )
265+
266+ // Ensure the input itself is focused before it blurs
267+ input . focus ( )
268+ await user . type ( input , 'b' )
269+ await user . keyboard ( '{Tab}' )
270+
271+ // Click the element inside the external shadow DOM
272+ // This should focus externalFocusableButton and blur the input in DropdownCombobox
273+ await user . click ( externalFocusableButton )
274+
275+ // Assert that the menu closes due to blur
276+ // useCombobox's blur handler uses setTimeout, so wait for the next macrotask
277+ await new Promise ( resolve => setTimeout ( resolve , 0 ) )
278+ expect ( input ) . toHaveAttribute ( 'aria-expanded' , 'false' )
279+
280+ // Cleanup
281+ document . body . removeChild ( externalHost )
282+ } )
0 commit comments