2021-08-23 23:56:36 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
// Selection within a Menu
|
2021-08-26 19:23:18 +00:00
|
|
|
Toolkit.MenuItem = class MenuItem extends Toolkit.Panel {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Object constructor
|
2021-08-26 19:23:18 +00:00
|
|
|
constructor(application, options) {
|
|
|
|
super(application, options);
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// 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
|
2021-08-26 19:23:18 +00:00
|
|
|
this.setLayout("flex", {});
|
2021-08-23 23:56:36 +00:00
|
|
|
this.element.setAttribute("role", "menuitem");
|
|
|
|
this.element.setAttribute("tabindex", "-1");
|
|
|
|
this.element.addEventListener("keydown", e=>this.onkeydown(e));
|
2021-08-26 19:23:18 +00:00
|
|
|
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
|
|
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
|
|
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure display text element
|
|
|
|
this.textElement = document.createElement("div");
|
2021-08-26 19:23:18 +00:00
|
|
|
this.textElement.id = Toolkit.id();
|
2021-08-23 23:56:36 +00:00
|
|
|
this.textElement.style.cursor = "default";
|
|
|
|
this.textElement.style.flexGrow = "1";
|
|
|
|
this.textElement.style.userSelect = "none";
|
2021-08-26 19:23:18 +00:00
|
|
|
this.textElement.setAttribute("name", "text");
|
2021-08-23 23:56:36 +00:00
|
|
|
this.element.appendChild(this.textElement);
|
2021-08-26 19:23:18 +00:00
|
|
|
this.element.setAttribute("aria-labelledby", this.textElement.id);
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Request focus on the appropriate element
|
|
|
|
focus() {
|
|
|
|
this.element.focus();
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// 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 /////////////////////////////
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// 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) {
|
2021-08-26 19:23:18 +00:00
|
|
|
let index;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Processing by key
|
|
|
|
switch (e.key) {
|
|
|
|
|
|
|
|
// Activate the item
|
|
|
|
case " ":
|
|
|
|
case "Enter":
|
|
|
|
this.activate(e);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Select the next item
|
|
|
|
case "ArrowDown":
|
2021-08-26 19:23:18 +00:00
|
|
|
index = this.parent.nextChild(
|
|
|
|
this.parent.children.indexOf(this));
|
|
|
|
if (index != -1)
|
|
|
|
this.parent.children[index].focus();
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Conditional
|
|
|
|
case "ArrowLeft":
|
|
|
|
|
|
|
|
// Move to the previous menu in the menu bar
|
2021-08-26 19:23:18 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close the containing submenu
|
|
|
|
else {
|
2021-08-26 19:23:18 +00:00
|
|
|
this.menuItem.setExpanded(false);
|
|
|
|
this.menuItem.focus();
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Move to the next menu in the menu bar
|
|
|
|
case "ArrowRight":
|
2021-08-26 19:23:18 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Select the previous item
|
|
|
|
case "ArrowUp":
|
2021-08-26 19:23:18 +00:00
|
|
|
index = this.parent.previousChild(
|
|
|
|
this.parent.children.indexOf(this));
|
|
|
|
if (index != -1)
|
|
|
|
this.parent.children[index].focus();
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Conditional
|
2021-08-23 23:56:36 +00:00
|
|
|
case "End":
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Return focus to the original element
|
|
|
|
case "Escape":
|
2021-08-26 19:23:18 +00:00
|
|
|
if (this.menuBar.expanded != null)
|
|
|
|
this.menuBar.expanded.setExpanded(false);
|
|
|
|
this.menuBar.restoreFocus();
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Conditional
|
2021-08-23 23:56:36 +00:00
|
|
|
case "Home":
|
2021-08-26 19:23:18 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// Configure event
|
2021-08-23 23:56:36 +00:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// 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();
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Error checking
|
2021-08-26 19:23:18 +00:00
|
|
|
if (!this.element.hasPointerCapture(e.pointerid))
|
2021-08-23 23:56:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// 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) {
|
|
|
|
|
2021-08-23 23:56:36 +00:00
|
|
|
// Configure event
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
2021-08-26 19:23:18 +00:00
|
|
|
// 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");
|
2021-08-23 23:56:36 +00:00
|
|
|
this.activate(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|