215 lines
6.3 KiB
JavaScript
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);
|
||
|
}
|
||
|
|
||
|
};
|