@amcharts/amcharts4
Version:
amCharts 4
1,257 lines • 43.6 kB
JavaScript
/**
* ExportMenu provides functionality for building Export menu
*/
import { __extends } from "tslib";
/**
* ============================================================================
* IMPORTS
* ============================================================================
* @hidden
*/
import exportCSS from "./ExportCSS";
import { Adapter } from "../utils/Adapter";
import { List } from "../utils/List";
import { getInteraction } from "../interaction/Interaction";
import { MutableValueDisposer } from "../utils/Disposer";
import { Language } from "../utils/Language";
import { Validatable } from "../utils/Validatable";
import { keyboard } from "../utils/Keyboard";
import * as $utils from "../utils/Utils";
import * as $iter from "../utils/Iterator";
import * as $dom from "../utils/DOM";
import * as $type from "../utils/Type";
/**
* ============================================================================
* MAIN CLASS
* ============================================================================
* @hidden
*/
/**
* Creates a menu for Export operations.
*
* To add an export menu to a chart, set this to a new instance of
* [[ExportMenu]].
*
* ```TypeScript
* chart.exporting.menu = new am4core.ExportMenu();
* ```
* ```JavaScript
* chart.exporting.menu = new am4core.ExportMenu();
* ```
* ```JSON
* {
* // ...
* "exporting": {
* "menu": {}
* }
* }
* ```
* @important
*/
var ExportMenu = /** @class */ (function (_super) {
__extends(ExportMenu, _super);
/**
* Constructor
*/
function ExportMenu() {
var _this = _super.call(this) || this;
/**
* An [[Adapter]].
*/
_this.adapter = new Adapter(_this);
/**
* How many milliseconds to hold menu/sub-menu open after it loses focus or
* hover, before auto-closing it.
*
* @default 1000
*/
_this.closeDelay = 1000;
/**
* Close the menu automatically when some export operation is triggered.
*
* @default true
* @since 4.2.2
*/
_this.closeOnClick = true;
/**
* An instance of [[Language]].
*/
_this._language = new MutableValueDisposer();
/**
* What HTML tags to use to build menu.
*/
_this._menuTag = "ul";
/**
* Which tag to use to enclose individual menu items.
*/
_this._itemTag = "li";
/**
* Tag to wrap menu item labels in.
*/
_this._labelTag = "a";
/**
* Tag to use for icons
*/
_this._iconTag = "img";
/**
* Prefix for class names applied to menu elements.
*/
_this._classPrefix = "amexport";
/**
* If set to `true` [[ExportMenu]] will load it's own external CSS when
* instantiated.
*/
_this._defaultStyles = true;
/**
* Horizontal positioning.
*/
_this._align = "right";
/**
* Vertical positioning.
*/
_this._verticalAlign = "top";
/**
* A tabindex to apply to Export Menu.
*/
_this._tabindex = 0;
/**
* Whether next menu close event should be ignored.
*/
_this._ignoreNextClose = false;
/**
* Default menu items.
*/
_this._items = [
{
"label": "...",
"menu": [
{
"label": "Image",
"menu": [
{ "type": "png", "label": "PNG" },
{ "type": "jpg", "label": "JPG" },
{ "type": "svg", "label": "SVG" },
{ "type": "pdf", "label": "PDF" }
]
}, {
"label": "Data",
"menu": [
{ "type": "json", "label": "JSON" },
{ "type": "csv", "label": "CSV" },
{ "type": "xlsx", "label": "XLSX" },
{ "type": "html", "label": "HTML" },
{ "type": "pdfdata", "label": "PDF" }
]
}, {
"label": "Print", "type": "print"
}
]
}
];
_this.className = "ExportMenu";
_this._disposers.push(_this._language);
_this.invalidate();
_this.applyTheme();
return _this;
}
/**
* (Re)draws the Export menu.
*
* @ignore Exclude from docs
*/
ExportMenu.prototype.validate = function () {
this.draw();
_super.prototype.validate.call(this);
};
/**
* Draws the menu based on current items.
*
* Normally, there's no need to call this explicitly. The chart, if it has
* export menu enabled, will automatically draw the menu.
*/
ExportMenu.prototype.draw = function () {
var _this = this;
// Create top-level menu item, or clear it
if (!this._element) {
this._element = this.createMenuElement(0);
}
else {
this._element.innerHTML = "";
this._element.className = this.getMenuItemClass(0);
}
// See if we're loading external CSS
// Hide it until CSS is loaded
if (this.defaultStyles) {
this._element.style.display = "none";
}
// Append to container
$type.getValue(this._container).appendChild(this._element);
// Apply adapter to menu items before processing
var items = this.adapter.apply("items", {
items: this._items
}).items;
for (var len = items.length, i = 0; i < len; i++) {
this.drawBranch(this._element, items[i], 0);
}
// Apply adapter to finalized menu element
this._element = this.adapter.apply("menuElement", {
menuElement: this._element
}).menuElement;
// Set up global "down" event
this._disposers.push(getInteraction().body.events.on("down", function (ev) {
if (!ev.pointer.touch) {
_this._ignoreNextClose = false;
}
_this.close();
}));
// Set up global event on ESC press to close the menu
this._disposers.push(getInteraction().body.events.on("keydown", function (ev) {
var key = keyboard.getEventKey(ev.event);
switch (key) {
case "esc":
_this.close();
break;
case "up":
case "down":
case "left":
case "right":
if (_this._currentSelection) {
ev.event.preventDefault();
}
_this.moveSelection(key);
break;
}
}));
if (this.defaultStyles) {
this.loadDefaultCSS();
}
};
/**
* Creates a new branch in export menu. This function is recursive for
* building multi-level menus.
*
* @ignore Exclude from docs
* @param container Container to put branch elements in
* @param branch Menu item
* @param level Current nesting level
*/
ExportMenu.prototype.drawBranch = function (container, branch, level) {
var _this = this;
// Apply adapter
branch = this.adapter.apply("branch", {
branch: branch,
level: level
}).branch;
// Unsupported?
// ExportMenu does not check or know for specific browser/system
// capabilities. It must happen in some other code and applied via Adapter.
// Export itself will check compatibility, but there might be other plugins
// that influence it or even add any specific export functionality.
if (branch.unsupported === true) {
return;
}
// Init ascendants
if (!branch.ascendants) {
branch.ascendants = new List();
}
// Get type
var type = branch.type;
// Create an item
var element = this.createItemElement(level, type);
// Create label
var label;
// Create icon
if (branch.icon) {
label = this.createIconElement(level, type);
label.src = branch.icon;
if (branch.label || branch.title) {
label.title = branch.title || branch.label;
}
}
else if (branch.svg) {
label = this.createSvgElement(level, type, branch.svg);
if (branch.label || branch.title) {
label.title = branch.title || branch.label;
}
}
else {
label = this.createLabelElement(level, type);
label.innerHTML = (branch.label ? this.language.translate(branch.label) : "");
if (branch.title) {
label.title = branch.title;
}
}
// Apply reader text to label
var readerLabel = this.getReaderLabel(branch, label.innerHTML);
label.setAttribute("aria-label", readerLabel);
// Add Label
element.appendChild(label);
// Create interaction object
// TODO clean this up when it's disposed
branch.interactions = getInteraction().getInteraction(element);
branch.element = element;
// Create interaction manager we can set event listeners to
if (this.typeClickable(type)) {
//branch.interactions.clickable = true;
// TODO clean this up when it's disposed
branch.interactions.events.on("hit", function (ev) {
if (_this.events.isEnabled("hit") && !_this.isDisposed()) {
var event_1 = {
"type": "hit",
"event": ev.event,
"target": _this,
"branch": branch
};
_this.events.dispatchImmediately("hit", event_1);
}
});
// TODO clean this up when it's disposed
branch.interactions.events.on("keyup", function (ev) {
if (keyboard.isKey(ev.event, "enter")) {
if (_this.events.isEnabled("enter")) {
var event_2 = {
"type": "enter",
"event": ev.event,
"target": _this,
"branch": branch
};
_this.events.dispatchImmediately("enter", event_2);
}
}
});
}
{
var submenu_1 = this.getSubMenu(branch);
// Add ENTER event to open sub-menus
if (submenu_1 != null) {
// TODO clean this up when it's disposed
branch.interactions.events.on("keyup", function (ev) {
if (keyboard.isKey(ev.event, "enter")) {
// This is item has sub-menu, activate the first child on ENTER
_this.selectBranch(submenu_1[0]);
// Attempt to set focus
_this.setFocus(submenu_1[0]);
}
});
branch.interactions.events.on("hit", function (ev) {
_this.selectBranch(branch);
});
}
}
// Add events
// TODO clean this up when it's disposed
branch.interactions.events.on("over", function (ev) {
if (ev.pointer.touch) {
// Cancel pending menu closure
_this._ignoreNextClose = true;
}
_this.selectBranch(branch);
if (_this.events.isEnabled("over")) {
var event_3 = {
"type": "over",
"event": ev.event,
"target": _this,
"branch": branch
};
_this.events.dispatchImmediately("over", event_3);
}
});
// TODO clean this up when it's disposed
branch.interactions.events.on("out", function (ev) {
if (_this.isDisposed()) {
return;
}
if (!ev.pointer.touch) {
_this.delayUnselectBranch(branch);
}
if (_this.events.isEnabled("out")) {
var event_4 = {
"type": "out",
"event": ev.event,
"target": _this,
"branch": branch
};
_this.events.dispatchImmediately("out", event_4);
}
});
// TODO clean this up when it's disposed
branch.interactions.events.on("focus", function (ev) {
_this.selectBranch(branch);
});
// TODO clean this up when it's disposed
branch.interactions.events.on("blur", function (ev) {
_this.delayUnselectBranch(branch);
});
// Increment level
var local_level = level + 1;
// Has sub-menu?
if (branch.menu) {
var submenu = this.createMenuElement(local_level);
branch.submenuElement = submenu;
for (var len = branch.menu.length, i = 0; i < len; i++) {
var ascendants = new List();
branch.menu[i].ascendants = ascendants;
if (branch.ascendants.length) {
ascendants.copyFrom(branch.ascendants);
}
ascendants.push(branch);
this.drawBranch(submenu, branch.menu[i], local_level);
}
// Sub-menu is empty (all items are not supported)
// Do not draw this menu item at all
if (submenu.innerHTML == "") {
return;
}
element.appendChild(submenu);
}
// Should this item be hidden?
if (branch.hidden) {
this.hideBranch(branch);
}
// Add id?
if (branch.id) {
element.setAttribute("id", branch.id);
}
// Background color?
if (branch.color) {
element.style.backgroundColor = branch.color.hex;
}
// Append to container
container.appendChild(element);
};
/**
* Creates a menu element to hold its elements in. Usually it's an `<ul>`
* tag.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @return HTML element reference
*/
ExportMenu.prototype.createMenuElement = function (level) {
var element = document.createElement(this.menuTag);
element.className = this.getMenuItemClass(level);
// Accessibility
if (level === 0) {
element.setAttribute("role", "menubar");
}
else {
element.setAttribute("role", "menu");
}
return element;
};
/**
* Generates a class name for the menu element based on its nesting level.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @return Class name(s)
*/
ExportMenu.prototype.getMenuItemClass = function (level) {
var className = this.classPrefix + "-menu " + this.classPrefix + "-menu-level-" + level;
if (level === 0) {
className += " " + this.classPrefix + "-menu-root " +
this.classPrefix + "-" + this.align + " " +
this.classPrefix + "-" + this.verticalAlign;
}
return this.adapter.apply("menuClass", {
className: className,
level: level
}).className;
};
/**
* Creates menu item. Usually `<li>` tag. Its label and sub-elements will go
* into this element.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @param type Type of the menu item
* @return HTML element reference
*/
ExportMenu.prototype.createItemElement = function (level, type) {
var element = document.createElement(this.itemTag);
var className = this.classPrefix + "-item " + this.classPrefix
+ "-item-level-" + level
+ " " + this.classPrefix + "-item-" + (type || "blank");
element.className = this.adapter.apply("itemClass", {
className: className,
level: level,
type: type
}).className;
element.setAttribute("role", "menuitem");
element.setAttribute("tabindex", this.tabindex.toString());
return element;
};
/**
* Creates a "label" part of the menu item. It could be text or any HTML
* content.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @param type Type of the menu item
* @return An HTML Element
*/
ExportMenu.prototype.createLabelElement = function (level, type) {
var element = document.createElement(this.labelTag);
var className = this.classPrefix + "-label " + this.classPrefix
+ "-label-level-" + level
+ " " + this.classPrefix + "-item-" + (type || "blank");
if (this.typeClickable(type)) {
className += " " + this.classPrefix + "-clickable";
}
element.className = this.adapter.apply("labelClass", {
className: className,
level: level,
type: type
}).className;
// Accessible navigation
//element.setAttribute("tabindex", this.tabindex.toString());
//element.setAttribute("role", "menuitem");
return element;
};
/**
* Creates a "icon" part of the menu item.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @param type Type of the menu item
* @return An HTML Element
*/
ExportMenu.prototype.createIconElement = function (level, type) {
var element = document.createElement(this.iconTag);
var className = this.classPrefix + "-icon " + this.classPrefix
+ "-icon-level-" + level
+ " " + this.classPrefix + "-item-" + (type || "blank");
if (this.typeClickable(type)) {
className += " " + this.classPrefix + "-clickable";
}
element.className = this.adapter.apply("labelClass", {
className: className,
level: level,
type: type
}).className;
// Accessible navigation
element.setAttribute("tabindex", this.tabindex.toString());
element.setAttribute("role", "menuitem");
return element;
};
/**
* Creates a a custom element out of raw HTML.
*
* @ignore Exclude from docs
* @param level Current nesting level
* @param type Type of the menu item
* @return An HTML Element
*/
ExportMenu.prototype.createSvgElement = function (level, type, svg) {
var parser = new DOMParser();
var element = parser.parseFromString(svg, "image/svg+xml").documentElement;
var className = this.classPrefix + "-icon " + this.classPrefix
+ "-icon-level-" + level
+ " " + this.classPrefix + "-item-" + (type || "blank");
if (this.typeClickable(type)) {
className += " " + this.classPrefix + "-clickable";
}
element.setAttribute("class", this.adapter.apply("labelClass", {
className: className,
level: level,
type: type
}).className);
// Accessible navigation
element.setAttribute("tabindex", this.tabindex.toString());
element.setAttribute("role", "menuitem");
return element;
};
/**
* Destroys the menu and all its elements.
*/
ExportMenu.prototype.dispose = function () {
if (!this._disposed) {
_super.prototype.dispose.call(this);
if (this._element && this._element.parentNode) {
this._element.parentNode.removeChild(this._element);
}
}
};
/**
* Checks whether menu item type is supposed to be clickable.
*
* @ignore Exclude from docs
* @param type Menu item type
* @return Is clickable?
*/
ExportMenu.prototype.typeClickable = function (type) {
return $type.hasValue(type);
};
/**
* Checks whether menu item has any sub-items.
*
* @ignore Exclude from docs
* @param branch A menu item
* @return Has sub-items?
*/
ExportMenu.prototype.hasSubMenu = function (branch) {
return (branch.menu && branch.menu.length) ? true : false;
};
/**
* Returns sub-items (if they exist).
*
* @ignore Exclude from docs
* @param branch A menu item
* @return Submenus
*/
ExportMenu.prototype.getSubMenu = function (branch) {
if (branch.menu && branch.menu.length) {
return branch.menu;
}
};
/**
* Generates and returns an applicable label to be used for screen readers.
*
* @ignore Exclude from docs
* @param item A menu item instance
* @param label Current label
* @return Reader text
*/
ExportMenu.prototype.getReaderLabel = function (branch, label) {
// Strip any HTML from the label
label = $utils.stripTags(label);
// Add textual note if the branch is clickable
if (branch.ascendants.length == 0) {
label = label == "..." ? this.language.translate("Export") : label;
label += " [" + this.language.translate("Press ENTER or use arrow keys to navigate") + "]";
}
else if (this.hasSubMenu(branch)) {
label += " [" + this.language.translate("Click, tap or press ENTER to open") + "]";
}
else if (branch.type == "print") {
label = this.language.translate("Click, tap or press ENTER to print.");
}
else if (this.typeClickable(branch.type)) {
label = this.language.translate("Click, tap or press ENTER to export as %1.", undefined, label);
}
return this.adapter.apply("rederLabel", {
label: label,
branch: branch
}).label;
};
Object.defineProperty(ExportMenu.prototype, "container", {
/**
* @return Container
*/
get: function () {
return this._container;
},
/**
* Getters and setters
*/
/**
* An HTML container to place the Menu in.
*
* A container must be an HTML element, because menu itself is HTML, and
* cannot be placed into SVG.
*
* @param container Reference to container element
* @todo Check if menu is already build. If it is, just move it to a new container
*/
set: function (container) {
this._container = container;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "items", {
/**
* @return Menu items
*/
get: function () {
return this._items;
},
/**
* A list of menu items. Can be nested.
*
* @param items Menu items
*/
set: function (items) {
this._items = items;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "tag", {
/**
* Sets main menu tag to place menu in.
*
* This also sets up how menu items are built.
*
* If you set this to "ul", menu items will be wrapped into `<li>` tags.
*
* If set to "div", menu items will be wrapped in `<div>` tags.
*
* @default "ul"
* @param tag Tag to use for menu
*/
set: function (tag) {
this._menuTag = tag;
this._itemTag = tag == "ul" ? "li" : "div";
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "menuTag", {
/**
* Returns current menu tag.
*
* @ignore Exclude from docs
* @return Menu tag (item that contains sub-items)
*/
get: function () {
return this.adapter.apply("menuTag", {
tag: this._menuTag
}).tag;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "itemTag", {
/**
* Returns tag to wrap items into.
*
* @ignore Exclude from docs
* @return Item tag
*/
get: function () {
return this.adapter.apply("itemTag", {
tag: this._itemTag
}).tag;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "labelTag", {
/**
* Returns menu label tag.
*
* @ignore Exclude from docs
* @return Label tag
*/
get: function () {
return this.adapter.apply("labelTag", {
tag: this._labelTag
}).tag;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "iconTag", {
/**
* Returns icon tag.
*
* @ignore Exclude from docs
* @return Icon tag
*/
get: function () {
return this.adapter.apply("iconTag", {
tag: this._iconTag
}).tag;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "align", {
/**
* @return Horizontal alignment
*/
get: function () {
return this.adapter.apply("align", {
align: this._align
}).align;
},
/**
* A horizontal alignment for the menu placement.
*
* @param value Horizontal alignment
*/
set: function (value) {
this._align = value;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "verticalAlign", {
/**
* @return Vertical alignment
*/
get: function () {
return this.adapter.apply("verticalAlign", {
verticalAlign: this._verticalAlign
}).verticalAlign;
},
/**
* A vertical alignment for the menu placement.
*
* @param value Vertical alignment
*/
set: function (value) {
this._verticalAlign = value;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "classPrefix", {
/**
* @return Class name prefix
*/
get: function () {
return this.adapter.apply("classPrefix", {
classPrefix: this._classPrefix
}).classPrefix;
},
/**
* Class name prefix.
*
* @default "amexport"
* @param value Class name prefix
*/
set: function (value) {
this._classPrefix = value;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "defaultStyles", {
/**
* @return Should ExportMenu load its own CSS?
*/
get: function () {
return this.adapter.apply("defaultStyles", {
defaultStyles: this._defaultStyles
}).defaultStyles;
},
/**
* Indicates whether [[ExportMenu]] should load external CSS to style itself.
*
* If set to `false`, the menu will not be styled, and will rely on some
* external CSS.
*
* @default true
* @param Should ExportMenu load its own CSS?
*/
set: function (value) {
if (this._defaultStyles != value) {
this._defaultStyles = value;
if (value) {
this.loadDefaultCSS();
}
}
this.invalidate();
},
enumerable: true,
configurable: true
});
/**
* Loads the default CSS.
*
* @ignore Exclude from docs
*/
ExportMenu.prototype.loadDefaultCSS = function () {
this._disposers.push(exportCSS($dom.getShadowRoot(this.container), this.classPrefix));
if (this._element) {
this._element.style.display = "";
}
};
Object.defineProperty(ExportMenu.prototype, "tabindex", {
/**
* @return Tab index
*/
get: function () {
return this.adapter.apply("tabindex", {
tabindex: this._tabindex
}).tabindex;
},
/**
* A tab index for the menu.
*
* Tab index will influence the order in which elements on the chart and
* the whole page are selected when pressing TAB key.
*
* @param value Tab index
*/
set: function (value) {
this._tabindex = value;
this.invalidate();
},
enumerable: true,
configurable: true
});
Object.defineProperty(ExportMenu.prototype, "language", {
/**
* @return A [[Language]] instance to be used
*/
get: function () {
var _this = this;
var language = this._language.get();
if (language == null) {
language = new Language();
// TODO code duplication with `set language()`
this._language.set(language, language.events.on("localechanged", function (ev) {
_this.invalidate();
}));
}
return language;
},
/**
* A [[Language]] instance.
*
* @param value An instance of [[Language]]
*/
set: function (value) {
var _this = this;
this._language.set(value, value.events.on("localechanged", function (ev) {
_this.invalidate();
}));
this.invalidate();
},
enumerable: true,
configurable: true
});
/**
* Controlling the menu
*/
/**
* Removes all active classes from menu items. Useful on touch devices and
* keyboard navigation where open menu can be closed instantly by clicking or
* tapping outside it.
*
* @ignore Exclude from docs
*/
ExportMenu.prototype.close = function () {
var _this = this;
if (this.isDisposed()) {
return;
}
if (this._ignoreNextClose) {
this._ignoreNextClose = false;
return;
}
if (this.closeOnClick) {
this._element.style.pointerEvents = "none";
setTimeout(function () {
_this._element.style.pointerEvents = "auto";
}, 100);
}
if (this._currentSelection) {
this.setBlur(this._currentSelection);
this._currentSelection = undefined;
}
if (this._element) {
var items = this._element.getElementsByClassName("active");
for (var len = items.length, i = len - 1; i >= 0; i--) {
if (items[i]) {
$dom.removeClass(items[i], "active");
}
}
}
this.events.dispatchImmediately("closed", {
type: "closed",
target: this
});
};
/**
* Selects a branch in the menu.
*
* Handles closing of currently open branch.
*
* @ignore Exclude from docs
* @param branch Branch to select
*/
ExportMenu.prototype.selectBranch = function (branch) {
var _this = this;
if (this.isDisposed()) {
return;
}
// Cancel previous closure
if (branch.closeTimeout) {
this.removeDispose(branch.closeTimeout);
branch.closeTimeout = undefined;
}
// Add active class
$dom.addClass(branch.element, "active");
// Set expanded
if (branch.submenuElement) {
branch.submenuElement.setAttribute("aria-expanded", "true");
}
// Remove current selection
if (this._currentSelection && this._currentSelection !== branch && this._currentSelection.ascendants) {
$iter.each($iter.concat($iter.fromArray([this._currentSelection]), this._currentSelection.ascendants.iterator()), function (ascendant) {
if (!branch.ascendants.contains(ascendant) && branch !== ascendant) {
_this.unselectBranch(ascendant, true);
}
});
}
// Select and/or cancel timeout for current ascendants
$iter.each(branch.ascendants.iterator(), function (ascendant) {
if (ascendant.closeTimeout) {
_this.removeDispose(ascendant.closeTimeout);
ascendant.closeTimeout = undefined;
}
$dom.addClass(ascendant.element, "active");
});
// Log current selection
this._currentSelection = branch;
// Invoke event
if (this.events.isEnabled("branchselected")) {
var event_5 = {
type: "branchselected",
target: this,
branch: branch
};
this.events.dispatchImmediately("branchselected", event_5);
}
};
/**
* Unselects a branch. Also selects a branch one level up if necessary.
*
* @ignore Exclude from docs
* @param branch Branch to unselect
* @param simple If `true`, only the branch will be unselected without selecting parent branch
*/
ExportMenu.prototype.unselectBranch = function (branch, simple) {
if (this.isDisposed()) {
return;
}
// Remove active class
$dom.removeClass(branch.element, "active");
// Set expanded
if (branch.submenuElement) {
branch.submenuElement.removeAttribute("aria-expanded");
}
// Remove current selection
if (this._currentSelection == branch) {
this._currentSelection = undefined;
}
// Invoke event
if (this.events.isEnabled("branchunselected")) {
var event_6 = {
type: "branchunselected",
target: this,
branch: branch
};
this.events.dispatchImmediately("branchunselected", event_6);
}
};
/**
* Delay unselection of a branch. This can still be cancelled in some other
* place if the branch or its children regain focus.
*
* @ignore Exclude from docs
* @param branch Branch to unselect
* @param simple If `true`, only the branch will be unselected without selecting parent branch
*/
ExportMenu.prototype.delayUnselectBranch = function (branch, simple) {
var _this = this;
if (this.isDisposed()) {
return;
}
// Schedule branch unselection
if (branch.closeTimeout) {
this.removeDispose(branch.closeTimeout);
branch.closeTimeout = undefined;
}
branch.closeTimeout = this.setTimeout(function () {
_this.unselectBranch(branch, simple);
}, this.closeDelay);
// Schedule unselection of all ascendants
// In case focus went away from the export menu altogether, this will ensure
// that all items will be closed.
// In case we're jumping to other menu item, those delayed unselections will
// be cancelled by `selectBranch`
if (simple !== true && branch.ascendants) {
$iter.each(branch.ascendants.iterator(), function (ascendant) {
_this.delayUnselectBranch(ascendant, true);
});
}
};
/**
* Navigates the menu based on which direction kayboard key was pressed.
*
* @ignore Exclude from docs
* @param key A key that was pressed
*/
ExportMenu.prototype.moveSelection = function (key) {
if (this.isDisposed()) {
return;
}
// Check if there's a current selection
if (!this._currentSelection) {
return;
}
var newSelection;
if (key == "up") {
// Try moving up in current menu list, or to the last item if already
// at the top
newSelection = this.getPrevSibling(this._currentSelection);
}
else if (key == "down") {
// Try moving down in current menu list, or to the top item if already
// at the bottom
newSelection = this.getNextSibling(this._currentSelection);
}
else if ((key == "left" && this.align == "right") || (key == "right" && this.align == "left")) {
var menu = this.getSubMenu(this._currentSelection);
// Go one level-deeper
if (menu != null) {
newSelection = menu[0];
}
}
else if ((key == "right" && this.align == "right") || (key == "left" && this.align == "left")) {
// Go one level-deeper
newSelection = this.getParentItem(this._currentSelection);
}
if (newSelection && newSelection !== this._currentSelection) {
this.selectBranch(newSelection);
this.setFocus(newSelection);
this._currentSelection = newSelection;
}
};
/**
* Returns all siblings of a menu item, including this same menu item.
*
* @ignore Exclude from docs
* @param branch Menu item
* @return List of sibling menu items
*/
ExportMenu.prototype.getSiblings = function (branch) {
var parent = this.getParentItem(branch);
if (parent && parent.menu) {
return parent.menu;
}
else {
return [];
}
};
/**
* Returns menu items parent item.
*
* @ignore Exclude from docs
* @param branch Menu item
* @return Parent menu item
*/
ExportMenu.prototype.getParentItem = function (branch) {
if (branch.ascendants && branch.ascendants.length) {
return branch.ascendants.getIndex(branch.ascendants.length - 1);
}
else {
return undefined;
}
};
/**
* Returns next sibling in the same menu branch. If there is no next sibling,
* the first one is returned. If there is just one item, that item is
* returned. Unsupported menu items are skipped.
*
* @ignore Exclude from docs
* @param branch Menu item to search siblings for
* @return Menu item
*/
ExportMenu.prototype.getNextSibling = function (branch) {
var siblings = this.getSiblings(branch);
if (siblings.length > 1) {
var next = siblings.indexOf(branch) + 1;
next = siblings.length == next ? 0 : next;
return siblings[next].unsupported ? this.getNextSibling(siblings[next]) : siblings[next];
}
else {
return branch;
}
};
/**
* Returns previous sibling in the same menu branch. If there is no next
* sibling, the first one is returned. If there is just one item, that item is
* returned. Unsupported menu items are skipped.
*
* @ignore Exclude from docs
* @param branch Menu item to search siblings for
* @return Menu item
*/
ExportMenu.prototype.getPrevSibling = function (branch) {
var siblings = this.getSiblings(branch);
if (siblings.length > 1) {
var prev = siblings.indexOf(branch) - 1;
prev = prev == -1 ? siblings.length - 1 : prev;
return siblings[prev].unsupported ? this.getPrevSibling(siblings[prev]) : siblings[prev];
}
else {
return branch;
}
};
/**
* Attempts to set focus on particular menu element.
*
* @ignore Exclude from docs
* @param branch Menu item
*/
ExportMenu.prototype.setFocus = function (branch) {
if (branch.interactions) {
try {
branch.interactions.element.focus();
}
catch (e) {
// nothing
}
}
};
/**
* Attempts to remove focus from the menu element.
*
* @ignore Exclude from docs
* @param branch Menu item
*/
ExportMenu.prototype.setBlur = function (branch) {
if (branch.interactions) {
try {
branch.interactions.element.blur();
}
catch (e) {
// nothing
}
}
};
/**
* Hides the whole branch of menu.
*
* @param branch branch
*/
ExportMenu.prototype.hideBranch = function (branch) {
branch.element.style.display = "none";
};
/**
* Show the branch of menu.
*
* @param branch branch
*/
ExportMenu.prototype.showBranch = function (branch) {
branch.element.style.display = "";
};
Object.defineProperty(ExportMenu.prototype, "element", {
/**
* The main element o fthe menu - usually `<ul>`.
*
* @since 4.10.6
* @return Menu element
*/
get: function () {
return this._element;
},
enumerable: true,
configurable: true
});
return ExportMenu;
}(Validatable));
export { ExportMenu };
//# sourceMappingURL=ExportMenu.js.map