/**
* A menu entry of a {@link Menu}.
* <p>Part of the XUI module which, for now, has an undocumented API.
*/
export class MenuItem extends HTMLElement {
static create(options) {
// Needed because a custom HTML element constructor may not have
// arguments.
let menuItem = document.createElement("xui-menu-item");
if (options !== null) {
// Configure. options is copied. We do not keep a reference to it.
menuItem.options = options;
}
return menuItem;
}
constructor() {
super();
let shadow = this.attachShadow({mode: "open"});
Util.addStylesheetLink(shadow);
this._item = document.createElement("div");
this._item.className = "xui-control xui-menu-item";
shadow.appendChild(this._item);
this._icon = document.createElement("div");
this._icon.className = "xui-menu-item-icon";
this._item.appendChild(this._icon);
this._text = document.createElement("div");
this._text.className = "xui-menu-item-text";
this._item.appendChild(this._text);
this._detail = document.createElement("div");
this._detail.className = "xui-menu-item-detail";
this._item.appendChild(this._detail);
this._parentMenu = null;
this._submenu = null;
this._options = { name: null, type: null, buttonGroup: null,
icon: null, selectedIcon: null, tooltip: null,
text: null, detail: null, items: null,
selected: false, enabled: true, separator: false };
}
get contents() {
return this._item;
}
get options() {
return this._options;
}
set options(options) {
// Merge. Do not replace.
for (const key in options) {
this._options[key] = options[key];
}
this.configure();
}
getOption(key) {
return this._options[key];
}
setOption(key, value) {
switch (key) {
case "name":
this.name = value;
break;
case "tooltip":
this.tooltip = value;
break;
case "selected":
this.selected = value;
break;
case "enabled":
this.enabled = value;
break;
case "separator":
this.separator = value;
break;
default:
this._options[key] = value;
this.configure();
break;
}
}
get itemType() {
return this._options.type;
}
get isIconOnly() {
// Hence cannot be a type=submenu as a submenu may not have an icon.
// Plus it may not have a detail as it has no text.
return (this._options.icon !== null && this._options.text === null);
}
get name() {
return this._options.name;
}
set name(name) {
this._options.name = MenuItem.normalizeString(name);
}
static normalizeString(value) {
if (typeof value === "string") {
// String.trim also trims '\u00A0'.
const preserveNbsp = (value.indexOf("\u00A0") >= 0);
if (preserveNbsp) {
value = value.replace("\u00A0", "\uEDCB");
}
value = value.trim();
if (preserveNbsp) {
value = value.replace("\uEDCB", "\u00A0");
}
if (value.length === 0) {
value = null;
}
} else {
value = null;
}
return value;
}
get tooltip() {
return this._options.tooltip;
}
set tooltip(tooltip) {
this._options.tooltip = MenuItem.normalizeString(tooltip);
if (this._options.tooltip === null) {
this.removeAttribute("title");
} else {
this.setAttribute("title", this._options.tooltip);
}
}
get selected() {
return this._options.selected;
}
set selected(selected) {
this.setSelected(selected);
if (this._options.selected) {
this.onSelected();
}
}
setSelected(selected) {
this._options.selected = MenuItem.normalizeBoolean(selected);
this.updateIcon(this._options.selected, this._options.enabled);
}
static normalizeBoolean(value) {
return value? true : false;
}
updateIcon(selected, enabled) {
this._icon.textContent = ""; // This also removes all child nodes.
let icon = selected? this._options.selectedIcon : this._options.icon;
if (icon !== null) {
let iconName = null;
let iconFg = null;
let iconBg = null;
if (icon.startsWith("url(") && icon.endsWith(")")) {
let iconElem =
MenuItem.createIcon(icon, MenuItem.SMALL_ICON_SIZE);
this._icon.appendChild(iconElem);
} else if (icon.startsWith("icon(") && icon.endsWith(")")) {
let split =
icon.substring(5, icon.length-1).trim().split(/\s*[,]\s*/);
switch (split.length) {
case 3:
iconBg = split[2];
//FALLTHROUGH
case 2:
iconFg = split[1];
//FALLTHROUGH
case 1:
iconName = split[0];
break;
}
} else {
iconName = icon;
}
if (iconName !== null) {
let iconElem =
MenuItem.createEditIcon(iconName, MenuItem.SMALL_ICON_SIZE);
if (iconElem === null) {
iconElem =
MenuItem.createStockIcon(iconName, iconFg, iconBg,
enabled);
if (iconElem === null) {
iconElem =
MenuItem.createStockIcon("picture", "red", iconBg,
enabled);
}
}
this._icon.appendChild(iconElem);
}
}
}
// Low-level utility.
static createIcon(iconURL, iconSize) {
let span = document.createElement("span");
span.className =`xui-edit-icon-${iconSize}`;
span.setAttribute("style", `background: \
${iconURL} 0px 0px/${iconSize}px ${iconSize}px no-repeat;`);
return span;
}
// Low-level utility.
static createEditIcon(iconName, iconSize) {
let classPrefix = EditIcon[iconName];
if (!classPrefix) {
return null;
}
let span = document.createElement("span");
span.className =`xui-edit-icon-${iconSize} ${classPrefix}${iconSize}`;
return span;
}
static createStockIcon(iconName, iconFg, iconBg, enabled) {
let iconChar = StockIcon[iconName];
if (!iconChar) {
return null;
}
let span = document.createElement("span");
span.className = "xui-small-icon";
if (enabled && (iconFg !== null || iconBg !== null)) {
let style = "";
if (iconFg !== null && iconFg !== "transparent") {
style += `color: ${iconFg};`;
}
if (iconBg !== null && iconBg !== "transparent") {
style += `background-color: ${iconBg};`;
}
span.setAttribute("style", style);
}
span.textContent = iconChar;
return span;
}
onSelected() {
const group = this._options.buttonGroup;
if (group !== null && this._parentMenu !== null) {
let all = this._parentMenu.getAllItems();
for (let item of all) {
if (item !== this && item._options.buttonGroup === group) {
item.setSelected(false);
}
}
}
}
get enabled() {
return this._options.enabled;
}
set enabled(enable) {
this._options.enabled = MenuItem.normalizeBoolean(enable);
this._item.classList.remove("xui-control-disabled");
if (!this._options.enabled) {
this._item.classList.add("xui-control-disabled");
}
}
get separator() {
return this._options.separator;
}
set separator(separ) {
this._options.separator = MenuItem.normalizeBoolean(separ);
}
configure() {
let opts = this._options;
MenuItem.normalizeOptions(opts);
this._text.textContent = "";
if (opts.text !== null) {
this._text.textContent = opts.text;
}
this._detail.textContent = "";
if (opts.type === "submenu") {
let span = document.createElement("span");
span.className = "xui-small-icon";
span.textContent = StockIcon["right-dir"];
this._detail.appendChild(span);
} else if (opts.detail !== null) {
this._detail.textContent = opts.detail;
}
this.tooltip = opts.tooltip;
this.enabled = opts.enabled;
this.separator = opts.separator;
// This updates the icon and uses buttonGroup.
this.selected = opts.selected;
this._item.classList.remove("xui-icon-only");
if (this.isIconOnly) {
this._item.classList.add("xui-icon-only");
}
}
static normalizeOptions(opts) {
if (!opts.items || !Array.isArray(opts.items)) {
opts.items = null;
}
let type = opts.type;
switch (type) {
case "checkbox":
case "radiobutton":
case "submenu":
break;
default:
if (opts.items !== null) {
type = "submenu";
} else {
type = "button";
}
break;
}
opts.type = type;
opts.icon = MenuItem.normalizeString(opts.icon);
opts.selectedIcon = MenuItem.normalizeString(opts.selectedIcon);
if (opts.icon === null) {
switch (type) {
case "checkbox":
opts.icon = "icon(check-empty)";
opts.selectedIcon = "icon(check)"
break;
case "radiobutton":
opts.icon = "icon(circle-empty)";
opts.selectedIcon = "icon(dot-circled)";
break;
}
}
if (opts.icon === null) {
opts.selectedIcon = null;
} else {
if (opts.selectedIcon === null) {
opts.selectedIcon = opts.icon;
}
}
opts.text = MenuItem.normalizeString(opts.text);
opts.detail = MenuItem.normalizeString(opts.detail);
opts.selected = MenuItem.normalizeBoolean(opts.selected);
opts.enabled = MenuItem.normalizeBoolean(opts.enabled);
opts.separator = MenuItem.normalizeBoolean(opts.separator);
opts.name = MenuItem.normalizeString(opts.name);
opts.tooltip = MenuItem.normalizeString(opts.tooltip);
opts.buttonGroup = MenuItem.normalizeString(opts.buttonGroup);
if (type === "submenu") {
opts.icon = null;
opts.selectedIcon = null;
opts.detail = null;
opts.buttonGroup = null;
if (opts.items === null) {
opts.items = [];
}
} else {
if (opts.text === null) {
opts.detail = null;
}
if (type !== "radiobutton") {
opts.buttonGroup = null;
}
opts.items = null;
}
}
get parentMenu() {
return this._parentMenu;
}
set parentMenu(menu) {
assertOrError(this._parentMenu === null);
this._parentMenu = menu;
}
get submenu() {
if (this.itemType === "submenu" && this._submenu === null) {
this._submenu = Menu.create(this._options.items);
this._submenu.ownerItem = this;
}
return this._submenu;
}
}
// Change this if you change --xui-small-icon-size in xui.css
MenuItem.SMALL_ICON_SIZE = 16;
window.customElements.define("xui-menu-item", MenuItem);