2022-04-15 01:51:09 +00:00
|
|
|
import { Component } from /**/"./Component.js";
|
|
|
|
let Toolkit;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Menu //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Pop-up menu container, child of MenuItem
|
|
|
|
class Menu extends Component {
|
|
|
|
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className : "tk tk-menu",
|
|
|
|
role : "menu",
|
|
|
|
tagName : "div",
|
|
|
|
visibility: true,
|
|
|
|
visible : false,
|
|
|
|
style : {
|
|
|
|
position: "absolute",
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Trap pointer events
|
|
|
|
this.addEventListener("pointerdown", e=>{
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Replacement behavior for parent.add()
|
|
|
|
addedHook(parent) {
|
|
|
|
this.setAttribute("aria-labelledby", parent.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// MenuSeparator //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Separator between groups of menu items
|
|
|
|
class MenuSeparator extends Component {
|
|
|
|
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className: "tk tk-menu-separator",
|
|
|
|
role : "separator",
|
|
|
|
tagName : "div"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// MenuItem //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Individual menu selection
|
|
|
|
class MenuItem extends Component {
|
|
|
|
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className: "tk tk-menu-item",
|
|
|
|
focusable: true,
|
|
|
|
tabStop : false,
|
|
|
|
tagName : "div"
|
|
|
|
});
|
|
|
|
options = options || {};
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2022-04-15 01:51:09 +00:00
|
|
|
this.isEnabled = null;
|
|
|
|
this.isExpanded = false;
|
|
|
|
this.menu = null;
|
|
|
|
this.menuBar = null;
|
|
|
|
this.text = null;
|
|
|
|
this.type = null;
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
// Configure element
|
2022-04-15 01:51:09 +00:00
|
|
|
this.contents = document.createElement("div");
|
|
|
|
this.append(this.contents);
|
|
|
|
this.eicon = document.createElement("div");
|
|
|
|
this.eicon.className = "tk tk-icon";
|
|
|
|
this.contents.append(this.eicon);
|
|
|
|
this.etext = document.createElement("div");
|
|
|
|
this.etext.className = "tk tk-text";
|
|
|
|
this.contents.append(this.etext);
|
|
|
|
|
|
|
|
// Configure event handlers
|
|
|
|
this.addEventListener("blur" , e=>this.onBlur (e));
|
|
|
|
this.addEventListener("keydown" , e=>this.onKeyDown (e));
|
|
|
|
this.addEventListener("pointerdown", e=>this.onPointerDown(e));
|
|
|
|
this.addEventListener("pointermove", e=>this.onPointerMove(e));
|
|
|
|
this.addEventListener("pointerup" , e=>this.onPointerUp (e));
|
|
|
|
|
|
|
|
// Configure widget
|
|
|
|
this.gui.localize(this);
|
|
|
|
this.setEnabled("enabled" in options ? !!options.enabled : true);
|
|
|
|
this.setId (Toolkit.id());
|
|
|
|
this.setText (options.text);
|
|
|
|
this.setType (options.type, options.checked);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Event Handlers //////////////////////////////
|
|
|
|
|
|
|
|
// Focus lost
|
|
|
|
onBlur(e) {
|
|
|
|
|
|
|
|
// An item in a different menu is receiving focus
|
|
|
|
if (this.menu != null) {
|
|
|
|
if (
|
|
|
|
!this .contains(e.relatedTarget) &&
|
|
|
|
!this.menu.contains(e.relatedTarget)
|
|
|
|
) this.setExpanded(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Item is an action
|
|
|
|
else if (e.component == this)
|
|
|
|
this.element.classList.remove("active");
|
|
|
|
|
|
|
|
// Simulate a bubbling event sequence
|
|
|
|
if (this.parent)
|
|
|
|
this.parent.onBlur(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Key press
|
|
|
|
onKeyDown(e) {
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Processing by key
|
|
|
|
switch (e.key) {
|
|
|
|
|
|
|
|
case "ArrowDown":
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Top-level: open the menu and focus its first item
|
|
|
|
if (this.parent == this.menuBar) {
|
|
|
|
if (this.menu == null)
|
|
|
|
return;
|
|
|
|
this.setExpanded(true);
|
|
|
|
this.listItems()[0].focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sub-menu: cycle to the next sibling
|
|
|
|
else {
|
|
|
|
let items = this.parent.listItems();
|
|
|
|
items[(items.indexOf(this) + 1) % items.length].focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "ArrowLeft":
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Sub-menu: close and focus parent
|
|
|
|
if (
|
|
|
|
this.parent != this.menuBar &&
|
|
|
|
this.parent.parent != this.menuBar
|
|
|
|
) {
|
|
|
|
this.parent.setExpanded(false);
|
|
|
|
this.parent.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Top-level: cycle to previous sibling
|
|
|
|
else {
|
|
|
|
let menu = this.parent == this.menuBar ?
|
|
|
|
this : this.parent;
|
|
|
|
let items = this.menuBar.listItems();
|
|
|
|
let prev = items[(items.indexOf(menu) +
|
|
|
|
items.length - 1) % items.length];
|
|
|
|
if (menu.isExpanded)
|
|
|
|
prev.setExpanded(true);
|
|
|
|
prev.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "ArrowRight":
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Sub-menu: open the menu and focus its first item
|
|
|
|
if (this.menu != null && this.parent != this.menuBar) {
|
|
|
|
this.setExpanded(true);
|
|
|
|
(this.listItems()[0] || this).focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Top level: cycle to next sibling
|
|
|
|
else {
|
|
|
|
let menu = this;
|
|
|
|
while (menu.parent != this.menuBar)
|
|
|
|
menu = menu.parent;
|
|
|
|
let expanded = this.menuBar.expandedMenu() != null;
|
|
|
|
let items = this.menuBar.listItems();
|
|
|
|
let next = items[(items.indexOf(menu) + 1) % items.length];
|
|
|
|
next.focus();
|
|
|
|
if (expanded)
|
|
|
|
next.setExpanded(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "ArrowUp":
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Top-level: open the menu and focus its last item
|
|
|
|
if (this.parent == this.menuBar) {
|
|
|
|
if (this.menu == null)
|
|
|
|
return;
|
|
|
|
this.setExpanded(true);
|
|
|
|
let items = this.listItems();
|
|
|
|
(items[items.length - 1] || this).focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sub-menu: cycle to previous sibling
|
|
|
|
else {
|
|
|
|
let items = this.parent.listItems();
|
|
|
|
items[(items.indexOf(this) +
|
|
|
|
items.length - 1) % items.length].focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "End":
|
|
|
|
{
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Focus last sibling
|
|
|
|
let expanded = this.isExpanded &&
|
|
|
|
this.parent == this.menuBar;
|
|
|
|
let items = this.parent.listItems();
|
|
|
|
let last = items[items.length - 1] || this;
|
|
|
|
last.focus();
|
|
|
|
if (expanded)
|
|
|
|
last.setExpanded(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Enter":
|
|
|
|
case " ":
|
|
|
|
|
|
|
|
// Do nothing
|
|
|
|
if (!this.isEnabled)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Action item: activate the menu item
|
|
|
|
if (this.menu == null)
|
|
|
|
this.activate(this.type == "check" && e.key == " ");
|
|
|
|
|
|
|
|
// Sub-menu: open the menu and focus its first item
|
|
|
|
else {
|
|
|
|
this.setExpanded(true);
|
|
|
|
let items = this.listItems();
|
|
|
|
if (items[0])
|
|
|
|
items[0].focus();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Escape":
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Top-level (not specified by WAI-ARIA)
|
|
|
|
if (this.parent == this.menuBar) {
|
|
|
|
if (this.isExpanded)
|
|
|
|
this.setExpanded(false);
|
|
|
|
else this.menuBar.exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sub-menu: close and focus parent
|
|
|
|
else {
|
|
|
|
this.parent.setExpanded(false);
|
|
|
|
this.parent.focus();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Home":
|
|
|
|
{
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (!this.parent)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Focus first sibling
|
|
|
|
let expanded = this.isExpanded &&
|
|
|
|
this.parent == this.menuBar;
|
|
|
|
let first = this.parent.listItems()[0] || this;
|
|
|
|
first.focus();
|
|
|
|
if (expanded)
|
|
|
|
first.setExpanded(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Do not handle the event
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The event was handled
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pointer press
|
|
|
|
onPointerDown(e) {
|
|
|
|
this.focus();
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (
|
|
|
|
!this.isEnabled ||
|
|
|
|
this.element.hasPointerCapture(e.pointerId) ||
|
|
|
|
e.button != 0
|
|
|
|
) return;
|
|
|
|
|
|
|
|
// Configure event
|
|
|
|
if (this.menu == null)
|
|
|
|
this.element.setPointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
// Configure component
|
|
|
|
if (this.menu != null)
|
|
|
|
this.setExpanded(!this.isExpanded);
|
|
|
|
else this.element.classList.add("active");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pointer move
|
|
|
|
onPointerMove(e) {
|
|
|
|
|
|
|
|
// Hovering over a menu when a sibling menu is already open
|
|
|
|
let expanded = this.parent && this.parent.expandedMenu();
|
|
|
|
if (this.menu != null && expanded != null && expanded != this) {
|
|
|
|
|
|
|
|
// Configure component
|
|
|
|
this.setExpanded(true);
|
|
|
|
this.focus();
|
|
|
|
|
|
|
|
// Configure event
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not dragging
|
|
|
|
if (!this.element.hasPointerCapture(e.pointerId))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Configure event
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
// Not an action item
|
|
|
|
if (this.menu != null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check if the cursor is within the bounds of the component
|
|
|
|
this.element.classList[
|
|
|
|
Toolkit.isInside(this.element, e) ? "add" : "remove"]("active");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pointer release
|
|
|
|
onPointerUp(e) {
|
|
|
|
|
|
|
|
// Error checking
|
|
|
|
if (
|
|
|
|
!this.isEnabled ||
|
|
|
|
e.button != 0 ||
|
|
|
|
(this.parent && this.parent.hasFocus() ?
|
|
|
|
this.menu != null :
|
|
|
|
!this.element.hasPointerCapture(e.pointerId)
|
|
|
|
)
|
|
|
|
) return;
|
|
|
|
|
|
|
|
// Configure event
|
|
|
|
this.element.releasePointerCapture(e.pointerId);
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
// Item is an action
|
|
|
|
let bounds = this.getBounds();
|
|
|
|
if (this.menu == null && Toolkit.isInside(this.element, e))
|
|
|
|
this.activate();
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Invoke an action command
|
|
|
|
activate(noExit) {
|
|
|
|
if (this.menu != null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this.type == "check")
|
|
|
|
this.setChecked(!this.isChecked);
|
|
|
|
|
|
|
|
if (!noExit)
|
|
|
|
this.menuBar.exit();
|
|
|
|
|
|
|
|
this.element.dispatchEvent(Toolkit.event("action", this));
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Add a separator between groups of menu items
|
|
|
|
addSeparator(options) {
|
|
|
|
let sep = new Toolkit.MenuSeparator(this, options);
|
|
|
|
this.add(sep);
|
|
|
|
return sep;
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Produce a list of child items
|
|
|
|
listItems(invisible) {
|
|
|
|
return this.children.filter(c=>
|
|
|
|
c instanceof Toolkit.MenuItem &&
|
|
|
|
(invisible || c.isVisible())
|
|
|
|
);
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify whether the menu item is checked
|
|
|
|
setChecked(checked) {
|
|
|
|
if (this.type != "check")
|
|
|
|
return;
|
|
|
|
this.isChecked = !!checked;
|
|
|
|
this.setAttribute("aria-checked", this.isChecked);
|
|
|
|
}
|
2021-08-23 23:56:36 +00:00
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify whether the menu item can be activated
|
|
|
|
setEnabled(enabled) {
|
|
|
|
this.isEnabled = enabled = !!enabled;
|
|
|
|
this.setAttribute("aria-disabled", enabled ? null : "true");
|
|
|
|
if (!enabled)
|
|
|
|
this.setExpanded(false);
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify whether the sub-menu is open
|
|
|
|
setExpanded(expanded) {
|
|
|
|
|
|
|
|
// State is not changing
|
|
|
|
expanded = !!expanded;
|
|
|
|
if (this.menu == null || expanded === this.isExpanded)
|
2021-08-26 19:23:18 +00:00
|
|
|
return;
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
// Position the sub-menu
|
|
|
|
if (expanded) {
|
|
|
|
let bndGUI = this.gui .getBounds();
|
|
|
|
let bndMenu = this.menu.getBounds();
|
|
|
|
let bndThis = this .getBounds();
|
|
|
|
let bndParent = !this.parent ? bndThis : (
|
|
|
|
this.parent == this.menuBar ? this.parent : this.parent.menu
|
|
|
|
).getBounds();
|
|
|
|
this.menu.element.style.left = Math.max(0,
|
|
|
|
Math.min(
|
|
|
|
(this.parent && this.parent == this.menuBar ?
|
|
|
|
bndThis.left : bndThis.right) - bndParent.left,
|
|
|
|
bndGUI.right - bndMenu.width
|
|
|
|
)
|
|
|
|
) + "px";
|
|
|
|
this.menu.element.style.top = Math.max(0,
|
|
|
|
Math.min(
|
|
|
|
(this.parent && this.parent == this.menuBar ?
|
|
|
|
bndThis.bottom : bndThis.top) - bndParent.top,
|
|
|
|
bndGUI.bottom - bndMenu.height
|
|
|
|
)
|
|
|
|
) + "px";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close all open sub-menus
|
|
|
|
else for (let child of this.listItems())
|
|
|
|
child.setExpanded(false);
|
|
|
|
|
|
|
|
// Configure component
|
|
|
|
this.isExpanded = expanded;
|
|
|
|
this.setAttribute("aria-expanded", expanded);
|
|
|
|
this.menu.setVisible(expanded);
|
|
|
|
if (expanded)
|
|
|
|
this.element.classList.add("active");
|
|
|
|
else this.element.classList.remove("active");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Specify the widget's display text
|
|
|
|
setText(text) {
|
|
|
|
this.text = (text || "").toString().trim();
|
|
|
|
this.translate();
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Specify what kind of menu item this is
|
|
|
|
setType(type, arg) {
|
|
|
|
this.type = type = (type || "").toString().trim() || "normal";
|
|
|
|
switch (type) {
|
|
|
|
case "check":
|
|
|
|
this.setAttribute("role", "menuitemcheckbox");
|
|
|
|
this.setChecked(arg);
|
|
|
|
break;
|
|
|
|
default: // normal
|
|
|
|
this.setAttribute("role", "menuitem");
|
|
|
|
this.setAttribute("aria-checked", null);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (this.parent && "checkIcons" in this.parent)
|
|
|
|
this.parent.checkIcons();
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Replacement behavior for add()
|
|
|
|
addHook(component) {
|
|
|
|
|
|
|
|
// Convert to sub-menu
|
|
|
|
if (this.menu == null) {
|
|
|
|
this.menu = new Toolkit.Menu(this);
|
|
|
|
this.after(this.menu);
|
|
|
|
this.setAttribute("aria-haspopup", "menu");
|
|
|
|
this.setAttribute("aria-expanded", "false");
|
|
|
|
if (this.parent && "checkIcons" in this.parent)
|
|
|
|
this.parent.checkIcons();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the child component
|
|
|
|
component.menuBar = this.menuBar;
|
|
|
|
this.menu.append(component);
|
|
|
|
if (component instanceof Toolkit.MenuItem && component.menu != null)
|
|
|
|
this.menu.append(component.menu);
|
|
|
|
|
|
|
|
// Configure icon mode
|
|
|
|
this.checkIcons();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether any child menu items contain icons
|
|
|
|
checkIcons() {
|
|
|
|
if (this.menu == null)
|
2021-08-26 19:23:18 +00:00
|
|
|
return;
|
2022-04-15 01:51:09 +00:00
|
|
|
if (this.children.filter(c=>
|
|
|
|
c instanceof Toolkit.MenuItem &&
|
|
|
|
c.menu == null &&
|
|
|
|
c.type != "normal"
|
|
|
|
).length != 0)
|
|
|
|
this.menu.element.classList.add("icons");
|
|
|
|
else this.menu.element.classList.remove("icons");
|
2021-08-26 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Replacement behavior for remove()
|
|
|
|
removeHook(component) {
|
|
|
|
|
|
|
|
// Remove the child component
|
|
|
|
component.element.remove();
|
|
|
|
if (component instanceof Toolkit.MenuItem && component.menu != null)
|
|
|
|
component.menu.element.remove();
|
|
|
|
|
|
|
|
// Convert to action item
|
|
|
|
if (this.children.length == 0) {
|
|
|
|
this.menu.element.remove();
|
|
|
|
this.menu = null;
|
|
|
|
this.setAttribute("aria-haspopup", null);
|
|
|
|
this.setAttribute("aria-expanded", "false");
|
|
|
|
if (this.parent && "checkIcons" in this.parent)
|
|
|
|
this.parent.checkIcons();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regenerate localized display text
|
|
|
|
translate() {
|
|
|
|
super.translate();
|
|
|
|
if (!("contents" in this))
|
|
|
|
return;
|
|
|
|
this.etext.innerText = this.gui.translate(this.text, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Retrieve the currently expanded sub-menu, if any
|
|
|
|
expandedMenu() {
|
|
|
|
return this.children.filter(c=>c.isExpanded)[0] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
2021-08-23 23:56:36 +00:00
|
|
|
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// MenuBar //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Application menu bar
|
|
|
|
class MenuBar extends Component {
|
|
|
|
static Component = Component;
|
|
|
|
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
|
|
|
|
constructor(gui, options) {
|
|
|
|
super(gui, options, {
|
|
|
|
className: "tk tk-menu-bar",
|
|
|
|
focusable: false,
|
|
|
|
tagName : "div",
|
|
|
|
tabStop : true,
|
|
|
|
role : "menubar",
|
|
|
|
style : {
|
|
|
|
position: "relative",
|
|
|
|
zIndex : "1"
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Configure instance fields
|
|
|
|
this.focusTarget = null;
|
|
|
|
this.menuBar = this;
|
|
|
|
|
|
|
|
// Configure event handlers
|
|
|
|
this.addEventListener("blur" , e=>this.onBlur (e), true);
|
|
|
|
this.addEventListener("focus" , e=>this.onFocus (e), true);
|
|
|
|
this.addEventListener("keydown", e=>this.onKeyDown(e), true);
|
|
|
|
|
|
|
|
// Configure widget
|
|
|
|
this.gui.localize(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Event Handlers //////////////////////////////
|
|
|
|
|
|
|
|
// Focus lost
|
|
|
|
onBlur(e) {
|
2021-08-26 19:23:18 +00:00
|
|
|
if (this.contains(e.relatedTarget))
|
|
|
|
return;
|
2022-04-15 01:51:09 +00:00
|
|
|
let items = this.listItems();
|
|
|
|
if (items[0])
|
|
|
|
items[0].setFocusable(true, true);
|
|
|
|
let menu = this.expandedMenu();
|
|
|
|
if (menu != null)
|
|
|
|
menu.setExpanded(false);
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 01:51:09 +00:00
|
|
|
// Focus gained
|
|
|
|
onFocus(e) {
|
|
|
|
if (this.contains(e.relatedTarget))
|
|
|
|
return;
|
|
|
|
let items = this.listItems();
|
|
|
|
if (items[0])
|
|
|
|
items[0].setFocusable(true, false);
|
|
|
|
this.focusTarget = e.relatedTarget;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Key pressed
|
|
|
|
onKeyDown(e) {
|
|
|
|
if (e.key != "Tab")
|
|
|
|
return;
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
this.exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
|
|
|
|
// Produce a list of child items
|
|
|
|
listItems(invisible) {
|
|
|
|
return this.children.filter(c=>
|
|
|
|
c instanceof Toolkit.MenuItem &&
|
|
|
|
(invisible || c.isVisible())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Replacement behavior for add()
|
|
|
|
addHook(component) {
|
|
|
|
component.menuBar = this.menuBar;
|
|
|
|
this.append(component);
|
|
|
|
if (component instanceof Toolkit.MenuItem && component.menu != null)
|
|
|
|
this.append(component.menu);
|
|
|
|
let items = this.listItems();
|
|
|
|
if (items[0])
|
|
|
|
items[0].setFocusable(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return control to the application
|
|
|
|
exit() {
|
|
|
|
this.onBlur({ relatedTarget: null });
|
|
|
|
if (this.focusTarget)
|
|
|
|
this.focusTarget.focus();
|
|
|
|
else document.activeElement.blur();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replacement behavior for remove()
|
|
|
|
removeHook(component) {
|
|
|
|
component.element.remove();
|
|
|
|
if (component instanceof Toolkit.MenuItem && component.menu != null)
|
|
|
|
component.menu.element.remove();
|
|
|
|
let items = this.listItems();
|
|
|
|
if (items[0])
|
|
|
|
items[0].setFocusable(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the global Toolkit object
|
|
|
|
static setToolkit(toolkit) {
|
|
|
|
Toolkit = toolkit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Retrieve the currently expanded menu, if any
|
|
|
|
expandedMenu() {
|
|
|
|
return this.children.filter(c=>c.isExpanded)[0] || null;
|
2021-08-23 23:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
2022-04-15 01:51:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export { Menu, MenuBar, MenuItem, MenuSeparator };
|