pvbemu/app/toolkit/MenuItem.js

215 lines
6.3 KiB
JavaScript

"use strict";
// Selection within a Menu
Toolkit.MenuItem = class MenuItem extends Toolkit.Component {
// Object constructor
constructor(parent, options) {
super(parent && parent.application, "div");
// Configure instance fields
this.clickListeners = [];
this.enabled = "enabled" in options ? !!options.enabled : true;
this.icon = options.icon || null;
this.menuBar = parent.menuBar;
this.parent = parent;
this.text = options.text || "";
this.shortcut = options.shortcut || null;
// Configure base element
this.element.style.display = "flex";
this.element.setAttribute("role", "menuitem");
this.element.setAttribute("tabindex", "-1");
this.element.addEventListener("blur" , e=>this.onblur (e));
this.element.addEventListener("focus" , e=>this.onfocus (e));
this.element.addEventListener("keydown", e=>this.onkeydown(e));
if (this.parent != this.menuBar)
this.element.addEventListener("pointerup", e=>this.onpointerup(e));
// Configure display text element
this.textElement = document.createElement("div");
this.textElement.style.cursor = "default";
this.textElement.style.flexGrow = "1";
this.textElement.style.userSelect = "none";
this.element.appendChild(this.textElement);
// Configure properties
this.setEnabled(this.enabled);
this.setText (this.text);
this.application.addComponent(this);
}
///////////////////////////// Public Methods //////////////////////////////
// Add a callback for click events
addClickListener(listener) {
if (this.clickListeners.indexOf(listener) == -1)
this.clickListeners.push(listener);
}
// Retrieve the item's display text
getText() {
return this.text;
}
// Determine whether the item is enabled
isEnabled() {
return this.enabled;
}
// Specify whether the item is enabled
setEnabled(enabled) {
this.enabled = enabled = !!enabled;
if (enabled)
this.element.removeAttribute("disabled");
else this.element.setAttribute("disabled", "");
}
// Specify the item's display text
setText(text) {
this.text = text || "";
this.localize();
}
///////////////////////////// Package Methods /////////////////////////////
// Update display text with localized strings
localize() {
let text = this.text;
if (this.application)
text = this.application.translate(text, this);
this.element.setAttribute("aria-label", text);
this.textElement.innerText = text;
}
///////////////////////////// Private Methods /////////////////////////////
// The menu item was activated
activate(e) {
if (!this.enabled)
return;
this.menuBar.restoreFocus();
for (let listener of this.clickListeners)
listener(e, this);
this.menuBar.expanded.setExpanded(false);
}
// Focus lost event handler
onblur(e) {
this.focusChanged(
this, e.relatedTarget ? e.relatedTarget.component : null);
}
// Focus gained event handler
onfocus(e) {
this.focusChanged(
e.relatedTarget ? e.relatedTarget.component : null, this);
}
// Key press event handler
onkeydown(e) {
// Processing by key
switch (e.key) {
// Activate the item
case " ":
case "Enter":
this.activate(e);
break;
// Select the next item
case "ArrowDown":
this.parent.items[
(this.parent.items.indexOf(this) + 1) %
this.parent.items.length
].element.focus();
break;
// Conditional
case "ArrowLeft":
// Move to the previous menu in the menu bar
if (this.parent.parent == this.menuBar) {
let menu = this.menuBar.menus[
(this.menuBar.menus.indexOf(this.parent) +
this.menuBar.menus.length - 1) %
this.menuBar.menus.length
];
if (menu != this.parent)
menu.activate(true);
}
// Close the containing submenu
else {
this.parent.setExpanded(false);
this.parent.parent.element.focus();
}
break;
// Move to the next menu in the menu bar
case "ArrowRight":
let menu = this.menuBar.menus[
(this.menuBar.menus.indexOf(this.menuBar.expanded) + 1) %
this.menuBar.menus.length
];
if (this.menuBar.expanded != menu)
menu.activate(true);
break;
// Select the previous item
case "ArrowUp":
this.parent.items[
(this.parent.items.indexOf(this) +
this.parent.items.length - 1) %
this.parent.items.length
].element.focus();
break;
// Select the last item in the menu
case "End":
this.parent.items[this.parent.items.length-1].element.focus();
break;
// Return focus to the original element
case "Escape":
this.menuBar.expanded.setExpanded(false);
break;
// Select the first item in the menu
case "Home":
this.parent.items[0].element.focus();
break;
default: return;
}
// Configure element
e.preventDefault();
e.stopPropagation();
}
// Pointer up event handler
onpointerup(e) {
// Error checking
if (e.button != 0 || document.activeElement != this.element)
return;
// Configure event
e.preventDefault();
e.stopPropagation();
// Activate the menu item
this.activate(e);
}
};