11import { closest , storageKey , writeStorageItem } from "./state.js" ;
2- import { isInsideExtensions , shouldCloseExtensionsMenu } from "./extensions.js" ;
32import {
43 focusToolbarElement ,
54 focusToolbarTrigger ,
65 shouldCloseToolbarDrawer ,
76} from "./focus.js" ;
7+ import {
8+ isInsideToolbarMenu ,
9+ shouldCloseToolbarMenu ,
10+ toolbarMenu ,
11+ toolbarMenus ,
12+ } from "./menu.js" ;
813import { shouldOpenToolbarDrawer } from "./panel.js" ;
914import { toolbarDrawerHeight , toolbarDrawerHeightForKey } from "./position.js" ;
1015import { renderDrawer } from "./render.js" ;
@@ -21,12 +26,13 @@ var minimumDrawerHeight = 120;
2126
2227/**
2328 * Owns the interactive layers of the toolbar: the panel drawer, its resize
24- * affordances and the Extensions menu .
29+ * affordances and the bar menus (AJAX and Extensions) .
2530 *
26- * `bind()` attaches the document-level listener that dismisses the menu, and
27- * `dispose()` releases it together with whatever a drag in flight still holds.
28- * Shadow-root wiring stays with the render cycle: `bindDelegatedEvents()` runs
29- * once on the skeleton and `bindEvents()` re-binds the controls each render.
31+ * `bind()` attaches the document-level listener that dismisses the open menu,
32+ * and `dispose()` releases it together with whatever a drag in flight still
33+ * holds. Shadow-root wiring stays with the render cycle:
34+ * `bindDelegatedEvents()` runs once on the skeleton and `bindEvents()` re-binds
35+ * the controls each render.
3036 *
3137 * Usage example:
3238 *
@@ -54,12 +60,14 @@ export function createToolbarDrawer(toolbar) {
5460 }
5561
5662 /**
57- * A pointer landing anywhere but the menu dismisses it. Registered on the
58- * document by `bind()` and released by `dispose()`, so the registration
63+ * A pointer landing anywhere but the open menu dismisses it. Registered on
64+ * the document by `bind()` and released by `dispose()`, so the registration
5965 * follows the element lifecycle.
6066 */
61- function extensionsPointerDown ( event ) {
62- if ( ! toolbar . extensionsOpen ) {
67+ function menuPointerDown ( event ) {
68+ var menu = toolbarMenu ( toolbar . openMenu ) ;
69+
70+ if ( ! menu ) {
6371 return ;
6472 }
6573
@@ -71,14 +79,14 @@ export function createToolbarDrawer(toolbar) {
7179 typeof event . composedPath === "function" ? event . composedPath ( ) : [ ] ;
7280 var target = path . length > 0 ? path [ 0 ] : event . target ;
7381
74- if ( ! isInsideExtensions ( target , closest ) ) {
75- controller . closeExtensions ( false ) ;
82+ if ( ! isInsideToolbarMenu ( target , menu , closest ) ) {
83+ controller . closeMenu ( false ) ;
7684 }
7785 }
7886
7987 controller = {
8088 /** Document handler dismissing the menu, exposed for the lifecycle tests. */
81- extensionsPointerDown : extensionsPointerDown ,
89+ menuPointerDown : menuPointerDown ,
8290 /**
8391 * Applies the stored drawer height once, then republishes the resize
8492 * handle's value range.
@@ -105,23 +113,25 @@ export function createToolbarDrawer(toolbar) {
105113
106114 controller . updateResizeHandleAccessibility ( ) ;
107115 } ,
108- /** Starts the lifecycle: the menu answers pointers landing outside it. */
116+ /** Starts the lifecycle: the open menu answers pointers landing outside it. */
109117 bind : function ( ) {
110- document . addEventListener ( "pointerdown" , extensionsPointerDown , false ) ;
118+ document . addEventListener ( "pointerdown" , menuPointerDown , false ) ;
111119 } ,
112120 /**
113121 * Binds the shadow-root delegation installed once on the skeleton: chips
114- * open the drawer, the Extensions toggle flips the menu, and Escape closes
115- * exactly one layer.
122+ * open the drawer, a menu chip flips its menu, and Escape closes exactly
123+ * one layer.
116124 */
117125 bindDelegatedEvents : function ( ) {
118126 var root = toolbar . shadowRoot ;
119127
120128 root . addEventListener ( "click" , function ( event ) {
121- if ( closest ( event . target , ".extensions-toggle" ) ) {
129+ var toggle = closest ( event . target , ".menu-toggle" ) ;
130+
131+ if ( toggle ) {
122132 event . preventDefault ( ) ;
123133 event . stopPropagation ( ) ;
124- controller . toggleExtensions ( ) ;
134+ controller . toggleMenu ( toggle . getAttribute ( "data-menu" ) ) ;
125135
126136 return ;
127137 }
@@ -143,10 +153,10 @@ export function createToolbarDrawer(toolbar) {
143153 * The menu answers first, so a single Escape never collapses both the
144154 * menu and the drawer behind it.
145155 */
146- if ( shouldCloseExtensionsMenu ( event , toolbar . extensionsOpen ) ) {
156+ if ( shouldCloseToolbarMenu ( event , toolbar . openMenu !== null ) ) {
147157 event . preventDefault ( ) ;
148158 event . stopPropagation ( ) ;
149- controller . closeExtensions ( true ) ;
159+ controller . closeMenu ( true ) ;
150160
151161 return ;
152162 }
@@ -208,20 +218,32 @@ export function createToolbarDrawer(toolbar) {
208218 var restoreFocusUrl = toolbar . restoreFocusUrl ;
209219
210220 toolbar . drawerOpen = false ;
211- toolbar . extensionsOpen = false ;
221+ toolbar . openMenu = null ;
212222 toolbar . restoreFocusUrl = null ;
213223 toolbar . render ( ) ;
214224
215225 if ( ! focusToolbarTrigger ( toolbar . shadowRoot , restoreFocusUrl ) ) {
216226 focusToolbarElement ( toolbar . shadowRoot , ".toggle-toolbar" ) ;
217227 }
218228 } ,
219- closeExtensions : function ( focusToggle ) {
220- toolbar . extensionsOpen = false ;
221- controller . syncExtensions ( ) ;
229+ /**
230+ * Closes the open menu, if any, without re-rendering the bar, and returns
231+ * focus to its chip when asked to.
232+ */
233+ closeMenu : function ( focusToggle ) {
234+ var name = toolbar . openMenu ;
235+ var menu = toolbarMenu ( name ) ;
236+
237+ toolbar . openMenu = null ;
238+
239+ if ( ! menu ) {
240+ return ;
241+ }
242+
243+ controller . syncMenu ( name ) ;
222244
223245 if ( focusToggle ) {
224- focusToolbarElement ( toolbar . shadowRoot , ".extensions- toggle" ) ;
246+ focusToolbarElement ( toolbar . shadowRoot , menu . toggle ) ;
225247 }
226248 } ,
227249 /**
@@ -232,7 +254,7 @@ export function createToolbarDrawer(toolbar) {
232254 toolbar . resizing = false ;
233255 document . removeEventListener ( "pointermove" , onPointerMove , false ) ;
234256 document . removeEventListener ( "pointerup" , onPointerUp , false ) ;
235- document . removeEventListener ( "pointerdown" , extensionsPointerDown , false ) ;
257+ document . removeEventListener ( "pointerdown" , menuPointerDown , false ) ;
236258 } ,
237259 onPointerMove : function ( event ) {
238260 if ( ! toolbar . resizing ) {
@@ -293,7 +315,7 @@ export function createToolbarDrawer(toolbar) {
293315
294316 toolbar . expanded = true ;
295317 toolbar . drawerOpen = true ;
296- toolbar . extensionsOpen = false ;
318+ toolbar . openMenu = null ;
297319 toolbar . activeUrl = normalizedUrl ;
298320 toolbar . restoreFocusUrl = normalizedUrl ;
299321 writeStorageItem ( storageKey , "1" ) ;
@@ -363,21 +385,21 @@ export function createToolbarDrawer(toolbar) {
363385
364386 toolbar . drawerPosition = position ;
365387 } ,
366- /** Mirrors the menu state onto the rendered wrapper and its toggle. */
367- syncExtensions : function ( ) {
368- var wrapper = toolbar . shadowRoot . querySelector ( ".extensions" ) ;
388+ /** Mirrors the state of the named menu onto its wrapper and its chip. */
389+ syncMenu : function ( name ) {
390+ var menu = toolbarMenus [ name ] ;
391+ var wrapper = toolbar . shadowRoot . querySelector ( menu . wrapper ) ;
369392
370393 if ( ! wrapper ) {
371394 return ;
372395 }
373396
374- wrapper . classList . toggle ( "is-open" , toolbar . extensionsOpen ) ;
397+ var open = toolbar . openMenu === name ;
398+
399+ wrapper . classList . toggle ( "is-open" , open ) ;
375400 wrapper
376- . querySelector ( ".extensions-toggle" )
377- ?. setAttribute (
378- "aria-expanded" ,
379- toolbar . extensionsOpen ? "true" : "false" ,
380- ) ;
401+ . querySelector ( menu . toggle )
402+ ?. setAttribute ( "aria-expanded" , open ? "true" : "false" ) ;
381403 } ,
382404 /** Collapses or expands the bar, persisting the choice. */
383405 toggleExpanded : function ( ) {
@@ -390,20 +412,30 @@ export function createToolbarDrawer(toolbar) {
390412 focusToolbarElement ( toolbar . shadowRoot , ".toggle-toolbar" ) ;
391413 } ,
392414 /**
393- * Flips the menu without re-rendering the bar, so focus stays on the
394- * toggle.
415+ * Flips the named menu without re-rendering the bar, so focus stays on
416+ * the chip. Opening a menu closes the other one first: only one is open at
417+ * a time.
395418 */
396- toggleExtensions : function ( ) {
397- toolbar . extensionsOpen = ! toolbar . extensionsOpen ;
398- controller . syncExtensions ( ) ;
419+ toggleMenu : function ( name ) {
420+ var previous = toolbar . openMenu ;
421+
422+ if ( previous === name ) {
423+ controller . closeMenu ( false ) ;
399424
400- if ( ! toolbar . extensionsOpen ) {
401425 return ;
402426 }
403427
404- var menu = toolbar . shadowRoot . querySelector ( ".extensions-menu" ) ;
428+ toolbar . openMenu = name ;
429+
430+ if ( previous !== null ) {
431+ controller . syncMenu ( previous ) ;
432+ }
433+
434+ controller . syncMenu ( name ) ;
435+
436+ var list = toolbar . shadowRoot . querySelector ( toolbarMenus [ name ] . menu ) ;
405437
406- menu ?. querySelector ( "[data-debug-url], a, button" ) ?. focus ?. ( ) ;
438+ list ?. querySelector ( "[data-debug-url], a, button" ) ?. focus ?. ( ) ;
407439 } ,
408440 /** Republishes the resize handle's value range after a height change. */
409441 updateResizeHandleAccessibility : function ( ) {
0 commit comments