@@ -89,6 +89,9 @@ export class RhNavigationPrimary extends LitElement {
89
89
@query ( '#hamburger' )
90
90
private _hamburger ! : HTMLDetailsElement ;
91
91
92
+ @query ( 'summary' )
93
+ private _hamburgerSummary ! : HTMLElement ;
94
+
92
95
/**
93
96
* Sets the mobile toggle (hamburger) text, used for translations, defaults to 'Menu'
94
97
*/
@@ -178,15 +181,16 @@ export class RhNavigationPrimary extends LitElement {
178
181
</ a >
179
182
</ slot >
180
183
</ div >
181
- < details id ="hamburger " ?open ="${ this . _hamburgerOpen } " @toggle ="${ this . #hamburgerToggle} ">
182
- < summary >
184
+ < details id ="hamburger " ?open ="${ this . _hamburgerOpen } " @toggle ="${ this . #hamburgerToggle} " @focusout =" ${ this . #onHamburgerFocusOut } " >
185
+ < summary @blur =" ${ this . #onHamburgerSummaryBlur } " >
183
186
< rh-icon icon ="menu-bars " set ="ui "> </ rh-icon >
184
187
< div id ="summary "> ${ this . mobileToggleLabel } </ div >
185
188
< rh-icon icon ="caret-down " set ="microns "> </ rh-icon >
186
189
</ summary >
187
- < div id ="details-content " role ="list ">
190
+ < div id ="details-content " role ="list " >
188
191
< slot > </ slot >
189
192
</ div >
193
+
190
194
</ details >
191
195
< div id ="secondary ">
192
196
< div id ="event " role ="list "> < slot name ="event "> </ slot > </ div >
@@ -228,22 +232,23 @@ export class RhNavigationPrimary extends LitElement {
228
232
this . #closeOverlay( ) ;
229
233
}
230
234
231
- #primaryDropdowns ( ) : RhNavigationPrimaryItem [ ] {
235
+ #primaryItems ( ) : RhNavigationPrimaryItem [ ] {
232
236
return Array . from (
233
237
this . querySelectorAll (
234
- 'rh-navigation-primary-item[variant="dropdown"] :not([slot="dropdowns" ])' ,
238
+ 'rh-navigation-primary-item:not([slot])' ,
235
239
)
236
240
) ;
237
241
}
238
242
239
- #secondaryDropdowns ( ) : RhNavigationPrimaryItem [ ] {
243
+ #openDropdownItems ( ) : RhNavigationPrimaryItem [ ] {
240
244
return Array . from (
241
245
this . querySelectorAll (
242
- 'rh-navigation-primary-item[variant="dropdown"][slot="dropdowns" ]' ,
246
+ 'rh-navigation-primary-item[variant="dropdown"][open ]' ,
243
247
)
244
248
) ;
245
249
}
246
250
251
+
247
252
async #onDropdownToggle( event : Event ) {
248
253
const item = event . target as RhNavigationPrimaryItem ;
249
254
// if the event came from a secondary link in a compact mode we'll want to close the hamburger first if it is open
@@ -282,6 +287,46 @@ export class RhNavigationPrimary extends LitElement {
282
287
}
283
288
}
284
289
290
+ #hamburgerContains( item : Node ) : boolean {
291
+ const primaryItems = this . #primaryItems( ) ;
292
+ return primaryItems . some ( pi => pi . contains ( item ) ) ;
293
+ }
294
+
295
+ #onHamburgerSummaryBlur( event : FocusEvent ) {
296
+ if ( event . relatedTarget ) {
297
+ if ( this . #hamburgerContains( event . relatedTarget as Node ) ) {
298
+ return ;
299
+ }
300
+ if ( this . compact ) {
301
+ this . #closeHamburger( ) ;
302
+ }
303
+ }
304
+ }
305
+
306
+ #onHamburgerFocusOut( event : FocusEvent ) {
307
+ if ( event . relatedTarget ) {
308
+ if ( event . relatedTarget === this . _hamburgerSummary ) {
309
+ return ;
310
+ }
311
+ if ( this . #hamburgerContains( event . relatedTarget as Node ) ) {
312
+ return ;
313
+ }
314
+ if ( this . compact ) {
315
+ this . #closeHamburger( ) ;
316
+ }
317
+ }
318
+ }
319
+
320
+ async #onFocusout( event : FocusEvent ) {
321
+ const target = event . relatedTarget as HTMLElement ;
322
+ if ( target ?. closest ( 'rh-navigation-primary' ) === this || target === null ) {
323
+ // if the focus is still inside the rh-navigation-secondary exit
324
+ return ;
325
+ } else {
326
+ this . close ( ) ;
327
+ }
328
+ }
329
+
285
330
#onKeydown( event : KeyboardEvent ) {
286
331
switch ( event . key ) {
287
332
case 'Escape' : {
@@ -299,102 +344,29 @@ export class RhNavigationPrimary extends LitElement {
299
344
}
300
345
break ;
301
346
}
302
- case 'Tab' :
303
- this . #onTabKeydown( event ) ;
304
- break ;
305
347
default :
306
348
break ;
307
349
}
308
350
}
309
351
310
352
#onKeyup( event : KeyboardEvent ) {
311
353
switch ( event . key ) {
312
- case 'Tab' :
313
- this . #onTabKeyup( event ) ;
314
- break ;
315
- default :
354
+ case 'Tab' : {
355
+ this . #onTabUp( event ) ;
316
356
break ;
317
- }
318
- }
319
-
320
-
321
- async #onFocusout( event : FocusEvent ) {
322
- const target = event . relatedTarget as HTMLElement ;
323
- if ( target ?. closest ( 'rh-navigation-primary' ) === this || target === null ) {
324
- // if the focus is still inside the rh-navigation-secondary exit
325
- return ;
326
- } else {
327
- this . close ( ) ;
328
- }
329
- }
330
-
331
- #onTabKeydown( event : KeyboardEvent ) {
332
- // target is the element we are leaving with tab press
333
- const target = event . target as HTMLElement ;
334
- // get target parent dropdown
335
- const primaryDropdowns = this . #primaryDropdowns( ) ;
336
- const secondaryDropdowns = this . #secondaryDropdowns( ) ;
337
- // target can be in one of the two dropdown collections, but only 1.
338
- const dropdownContainsTarget =
339
- primaryDropdowns . find ( dropdown => dropdown . contains ( target ) )
340
- ?? secondaryDropdowns . find ( dropdown => dropdown . contains ( target ) ) ;
341
- if ( dropdownContainsTarget ) {
342
- const focusableChildElements =
343
- Array . from ( RhNavigationPrimary . focusableChildElements ( dropdownContainsTarget ) ) ;
344
-
345
- if ( focusableChildElements . length > 0 ) {
346
- const {
347
- 0 : firstChild ,
348
- [ focusableChildElements . length - 1 ] : lastChild ,
349
- } = focusableChildElements ;
350
-
351
- if ( event . shiftKey ) {
352
- if ( event . shiftKey && firstChild === target ) {
353
- return ;
354
- }
355
- // if target is self, close self
356
- if ( event . shiftKey && target === dropdownContainsTarget ) {
357
- dropdownContainsTarget . hide ( ) ;
358
- return ;
359
- }
360
- } else {
361
- if ( ! firstChild ) {
362
- return ;
363
- }
364
- if ( ! lastChild ) {
365
- return ;
366
- } else {
367
- if ( lastChild === target ) {
368
- dropdownContainsTarget . hide ( ) ;
369
- return ;
370
- }
371
- }
372
- }
373
- } else {
374
- this . #closePrimaryDropdowns( ) ;
375
- this . #closeSecondaryDropdowns( ) ;
376
357
}
377
358
}
378
359
}
379
360
380
- #onTabKeyup( event : KeyboardEvent ) {
381
- if ( this . compact && this . _hamburgerOpen ) {
382
- const secondaryDropdowns = this . #secondaryDropdowns( ) ;
383
- const target = event . target as HTMLElement ;
384
- if ( event . shiftKey && target === this ) {
385
- this . #closeHamburger( ) ;
386
- } else {
387
- if ( secondaryDropdowns . some ( dropdown => dropdown . contains ( target ) ) ) {
388
- this . #closeHamburger( ) ;
389
- }
390
- }
361
+ #onTabUp( event : KeyboardEvent ) {
362
+ // target is the element we are entering with tab up press
363
+ const target = event . target as HTMLElement ;
364
+ if ( ! this . #openDropdownItems( ) . some ( item => item . contains ( target ) ) ) {
365
+ this . #closePrimaryDropdowns( ) ;
366
+ this . #closeSecondaryDropdowns( ) ;
391
367
}
392
368
}
393
369
394
- /**
395
- * close all open dropdowns in primary slot
396
- * @param except
397
- */
398
370
#closePrimaryDropdowns( except ?: RhNavigationPrimaryItem ) {
399
371
this . #openPrimaryDropdowns. forEach ( ( dropdown : RhNavigationPrimaryItem ) => {
400
372
if ( dropdown !== except ) {
@@ -431,14 +403,16 @@ export class RhNavigationPrimary extends LitElement {
431
403
#hamburgerToggle( event : Event ) {
432
404
if ( event instanceof ToggleEvent ) {
433
405
if ( event . newState === 'open' ) {
434
- // if we are compact mode and any secondary link dropdowns are open, close them
406
+ this . #openHamburger( ) ;
407
+ // if we are compact mode and any secondary link dropdowns are open, close them
435
408
if ( this . compact && this . #openSecondaryDropdowns. size > 0 ) {
436
409
this . #closeSecondaryDropdowns( ) ;
437
410
}
438
411
if ( this . compact ) {
439
412
this . #openOverlay( ) ;
440
413
}
441
414
} else {
415
+ this . #closeHamburger( ) ;
442
416
if ( this . #openPrimaryDropdowns. size > 0 ) {
443
417
this . #closePrimaryDropdowns( ) ;
444
418
}
0 commit comments