"use strict"; // Selection within a MenuBar Toolkit.Menu = class Menu extends Toolkit.MenuItem { // Object constructor constructor(parent, options) { super(parent, options); // Configure instance fields this.items = []; this.parent = parent; // Configure menu element this.menu = document.createElement("div"); this.menu.id = Toolkit.id(); this.menu.style.display = "none"; this.menu.style.flexDirection = "column"; this.menu.style.position = "absolute"; this.menu.setAttribute("role", "menu"); this.menu.setAttribute("aria-labelledby", this.id); this.containers.push(this.menu); // Configure element this.element.setAttribute("aria-expanded", "false"); this.element.setAttribute("aria-haspopup", "menu"); this.element.addEventListener("pointerdown", e=>this.onpointerdown(e)); this.element.addEventListener("pointermove", e=>this.onpointermove(e)); } ///////////////////////////// Public Methods ////////////////////////////// // Create a MenuItem and associate it with the application and component newMenuItem(options, index) { let item = new Toolkit.MenuItem(this, options); // Determine the ordinal position of the element within the container index = !(typeof index == "number") ? this.items.length : Math.floor(Math.min(Math.max(0, index), this.items.length)); // Add the item to the menu let ref = this.items[index]; this.menu.insertBefore(item.element, ref ? ref.element : null); this.items.splice(index, 0, item); return item; } // Specify whether the menu is enabled setEnabled(enabled) { super.setEnabled(enabled); if (!this.enabled && this.parent.expanded == this) this.setExpanded(false); } ///////////////////////////// Package Methods ///////////////////////////// // The menu item was activated activate(deeper) { if (!this.enabled) return; this.setExpanded(true); if (deeper && this.items.length > 0) this.items[0].element.focus(); } // Notify of a change to component focus focusChanged(from, to) { if (!this.contains(to)) { let expanded = this.parent.expanded; this.setExpanded(false); this.parent.expanded = expanded == this ? null : expanded; } super.focusChanged(from, to); } // Show or hide the pop-up menu setExpanded(expanded) { // Setting expanded to false if (!expanded) { // Hide the pop-up menu this.element.setAttribute("aria-expanded", "false"); this.menu.style.display = "none"; this.parent.expanded = null; // Close any expanded submenus if (this.expanded != null) this.expanded.setExpanded(false); return; } // Hide the existing submenu of the parent if (this.parent.expanded != null && this.parent.expanded != this) this.parent.expanded.setExpanded(false); this.parent.expanded = this; // Configure element this.element.setAttribute("aria-expanded", "true"); // Configure pop-up menu let barBounds = this.menuBar.element.getBoundingClientRect(); let bounds = this.element.getBoundingClientRect(); this.menu.style.display = "flex"; this.menu.style.left = (bounds.x - barBounds.x) + "px"; this.menu.style.top = (bounds.y + bounds.height - barBounds.y) + "px"; } ///////////////////////////// Private Methods ///////////////////////////// // Key press event handler onkeydown(e) { // Processing by key switch (e.key) { // Open the menu and select its first item (if any) case " ": case "ArrowDown": case "Enter": this.activate(true); break; // Conditional case "ArrowLeft": // Move to the previous item in the menu bar if (this.parent == this.menuBar) { let menu = this.parent.menus[ (this.parent.menus.indexOf(this) + this.parent.menus.length - 1) % this.parent.menus.length ]; if (menu != this && this.parent.expanded != null) menu.activate(true); else menu.element.focus(); } // Close the menu and return to the parent menu else { this.setExpanded(false); this.parent.element.focus(); } break; // Conditional case "ArrowRight": // Move to the next item in the menu bar if (this.parent == this.menuBar) { let menu = this.parent.menus[ (this.parent.menus.indexOf(this) + 1) % this.parent.menus.length ]; if (menu != this) { if (this.parent.expanded != null) { menu.activate(true); } else menu.element.focus(); } } // Open the menu and select its first item (if any) else this.activate(true); break; // Open the menu and select the last item (if any) case "ArrowUp": this.activate(false); if (this.items.length > 0) this.items[this.items.length - 1].element.focus(); break; // Conditional case "End": // Select the Last menu in the menu bar if (this.parent == this.menuBar) { let menu = this.parent.menus[this.parent.menus.length - 1]; if (menu != this) { if (this.menuBar.expanded != null) menu.activate(true); else menu.element.focus(); } } // Select the last item in the parent menu else { let item = this.parent.items[this.parent.items.length - 1]; if (item != this) { this.setExpanded(false); item.element.focus(); } } break; // Return focus to the original element case "Escape": this.menuBar.expanded.setExpanded(false); break; // Conditional case "Home": // Select the first menu in the menu bar if (this.parent == this.menuBar) { let menu = this.parent.menus[0]; if (menu != this) { if (this.menuBar.expanded != null) menu.activate(true); else menu.element.focus(); } } // Select the last item in the parent menu else { let item = this.parent.items[0]; if (item != this) { this.setExpanded(false); item.element.focus(); } } break; default: return; } // Configure event e.preventDefault(); e.stopPropagation(); } // Pointer down event handler onpointerdown(e) { // Error checking if (e.button != 0) return; // Configure event e.preventDefault(); e.stopPropagation(); // Activate the menu this.element.focus(); this.activate(false); } // Pointer move event handler onpointermove(e) { // Error checking if ( this.parent != this.menuBar || this.parent.expanded == null || this.parent.expanded == this ) return; // Activate the menu this.parent.expanded.setExpanded(false); this.parent.expanded = this; this.element.focus(); this.setExpanded(true); } };