pvbemu/app/toolkit/MenuItem.js

321 lines
9.8 KiB
JavaScript

"use strict";
// Selection within a Menu
Toolkit.MenuItem = class MenuItem extends Toolkit.Panel {
// Object constructor
constructor(application, options) {
super(application, options);
// Configure instance fields
this.clickListeners = [];
this.enabled = "enabled" in options ? !!options.enabled : true;
this.icon = options.icon || null;
this.text = options.text || "";
this.shortcut = options.shortcut || null;
// Configure base element
this.setLayout("flex", {});
this.element.setAttribute("role", "menuitem");
this.element.setAttribute("tabindex", "-1");
this.element.addEventListener("keydown", e=>this.onkeydown(e));
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
// Configure display text element
this.textElement = document.createElement("div");
this.textElement.id = Toolkit.id();
this.textElement.style.cursor = "default";
this.textElement.style.flexGrow = "1";
this.textElement.style.userSelect = "none";
this.textElement.setAttribute("name", "text");
this.element.appendChild(this.textElement);
this.element.setAttribute("aria-labelledby", this.textElement.id);
// 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);
}
// Request focus on the appropriate element
focus() {
this.element.focus();
}
// 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 /////////////////////////////
// Configure this component to be a child of its parent
child() {
this.menuBar = this.parent;
while (!(this.menuBar instanceof Toolkit.MenuBar))
this.menuBar = this.menuBar.parent;
this.menuItem = this instanceof Toolkit.Menu ? this : this.parent;
while (!(this.menuItem instanceof Toolkit.Menu))
this.menuItem = this.menuItem.parent;
this.menuTop = this instanceof Toolkit.Menu ? this : this.parent;
while (this.menuTop.parent != this.menuBar)
this.menuTop = this.menuTop.parent;
}
// Update display text with localized strings
localize() {
let text = this.text;
if (this.application)
text = this.application.translate(text, this);
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);
}
// Key press event handler
onkeydown(e) {
let index;
// Processing by key
switch (e.key) {
// Activate the item
case " ":
case "Enter":
this.activate(e);
break;
// Select the next item
case "ArrowDown":
index = this.parent.nextChild(
this.parent.children.indexOf(this));
if (index != -1)
this.parent.children[index].focus();
break;
// Conditional
case "ArrowLeft":
// Move to the previous menu in the menu bar
if (this.menuItem.parent == this.menuBar) {
index = this.menuBar.previousChild(
this.menuBar.children.indexOf(this.menuItem));
if (index != -1) {
let menu = this.menuBar.children[index];
if (menu != this.menuTop) {
if (this.menuBar.expanded != null)
menu.activate(true);
else menu.focus();
}
}
}
// Close the containing submenu
else {
this.menuItem.setExpanded(false);
this.menuItem.focus();
}
break;
// Move to the next menu in the menu bar
case "ArrowRight":
index = this.menuBar.nextChild(
this.menuBar.children.indexOf(this.menuTop));
if (index != -1) {
let menu = this.menuBar.children[index];
if (menu != this.menuTop) {
if (this.menuBar.expanded != null)
menu.activate(true);
else menu.focus();
}
}
break;
// Select the previous item
case "ArrowUp":
index = this.parent.previousChild(
this.parent.children.indexOf(this));
if (index != -1)
this.parent.children[index].focus();
break;
// Conditional
case "End":
// Select the last menu in the menu bar
if (this.parent == this.menuBar) {
index = this.menuBar.previousChild(
this.menuBar.children.length);
if (index != -1) {
let menu = this.menuBar.children[index];
if (menu != this.menuTop) {
if (this.menuBar.expanded != null)
menu.activate(true);
else menu.focus();
}
}
}
// Select the last item in the menu
else {
index = this.menuItem.previousChild(
this.menuItem.children.length);
if (index != -1)
this.menuItem.children[index].focus();
}
break;
// Return focus to the original element
case "Escape":
if (this.menuBar.expanded != null)
this.menuBar.expanded.setExpanded(false);
this.menuBar.restoreFocus();
break;
// Conditional
case "Home":
// Select the first menu in the menu bar
if (this.parent == this.menuBar) {
index = this.menuBar.nextChild(-1);
if (index != -1) {
let menu = this.menuBar.children[index];
if (menu != this.menuTop) {
if (this.menuBar.expanded != null)
menu.activate(true);
else menu.focus();
}
}
}
// Select the last item in the menu
else {
index = this.menuItem.nextChild(-1);
if (index != -1)
this.menuItem.children[index].focus();
}
break;
default: return;
}
// Configure event
e.preventDefault();
e.stopPropagation();
}
// Pointer down event handler
onpointerdown(e) {
// Configure event
e.preventDefault();
e.stopPropagation();
// Configure focus
if (this.enabled)
this.focus();
else return;
// Error checking
if (e.button != 0 || this.element.hasPointerCapture(e.pointerId))
return;
// Configure element
this.element.setPointerCapture(e.pointerId);
this.element.setAttribute("active", "");
}
// Pointer move event handler
onpointermove(e) {
// Configure event
e.preventDefault();
e.stopPropagation();
// Error checking
if (!this.element.hasPointerCapture(e.pointerid))
return;
// Working variables
let bounds = this.getBounds();
let active =
e.x >= bounds.x && e.x < bounds.x + bounds.width &&
e.y >= bounds.y && e.y < bounds.y + bounds.height
;
// Configure element
if (active)
this.element.setAttribute("active", "");
else this.element.removeAttribute("active");
}
// Pointer up event handler
onpointerup(e) {
// Configure event
e.preventDefault();
e.stopPropagation();
// Error checking
if (!this.element.hasPointerCapture(e.pointerId))
return;
// Configure element
this.element.releasePointerCapture(e.pointerId);
// Activate the menu item if it is active
if (!this.element.hasAttribute("active"))
return;
this.element.removeAttribute("active");
this.activate(e);
}
};