@highcharts/dashboards
Version:
Highcharts Dashboards framework
1,280 lines (1,272 loc) • 201 kB
JavaScript
/**
* @license Highcharts Dashboards Layout 3.6.0 (2025-09-10)
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*/
(function (factory) {
if (typeof module === 'object' && module.exports) {
factory['default'] = factory;
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define('dashboards/modules/layout', ['dashboards'], function (Dashboards) {
factory(Dashboards);
factory.Dashboards = Dashboards;
return factory;
});
} else {
factory(typeof Dashboards !== 'undefined' ? Dashboards : undefined);
}
}(function (Dashboards) {
'use strict';
var _modules = Dashboards ? Dashboards._modules : {};
function _registerModule(obj, path, args, fn) {
if (!obj.hasOwnProperty(path)) {
obj[path] = fn.apply(null, args);
if (typeof CustomEvent === 'function') {
Dashboards.win.dispatchEvent(new CustomEvent(
'DashboardsModuleLoaded',
{ detail: { path: path, module: obj[path] } }
));
}
}
}
_registerModule(_modules, 'Dashboards/EditMode/EditRenderer.js', [_modules['Dashboards/EditMode/EditGlobals.js'], _modules['Core/Utilities.js']], function (EditGlobals, U) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { merge, createElement, defined } = U;
/* *
*
* Functions
*
* */
/**
* Function to create a context button.
* @internal
*
* @param parentElement
* The element to which the new element should be appended.
*
* @param editMode
* EditMode instance.
*
* @returns
* Context button element.
*/
function renderContextButton(parentNode, editMode) {
const contextMenuOptions = editMode.options.contextMenu;
let contextButton;
if (contextMenuOptions) {
contextButton = createElement('button', {
className: EditGlobals.classNames.contextMenuBtn,
onclick: function (event) {
event.stopPropagation();
editMode.onContextBtnClick();
}
}, {}, parentNode);
// Add the icon if defined.
if (contextMenuOptions.icon) {
createElement('img', {
src: contextMenuOptions.icon,
className: EditGlobals.classNames.icon
}, {}, contextButton);
}
// Add text next to the icon if defined.
if (contextMenuOptions.text) {
createElement('span', {
className: EditGlobals.classNames.contextMenuBtnText,
textContent: contextMenuOptions.text
}, {}, contextButton);
}
contextButton.setAttribute('aria-label', editMode.lang.accessibility.contextMenu.button);
contextButton.setAttribute('aria-expanded', 'false');
}
return contextButton;
}
/**
* Creates the collapsable header element.
* @internal
*
* @param parentElement
* The HTMLElement to which the element should be rendered to.
*
* @param options
* Nested header options.
*
* @returns the outer element and content in the collapsable div.
*/
function renderCollapseHeader(parentElement, options) {
const { name, showToggle, onchange, isEnabled, isNested, isStandalone, lang } = options;
const accordion = createElement('div', {
className: EditGlobals.classNames[(isNested ? 'accordionNestedWrapper' : 'accordionContainer')] + ' ' +
(isStandalone ?
EditGlobals.classNames.accordionStandaloneWrapper : '') + ' ' + EditGlobals.classNames.collapsableContentHeader
}, {}, parentElement);
const header = createElement('div', {
className: EditGlobals.classNames.accordionHeader
}, {}, accordion);
let headerBtn;
if (!isStandalone || showToggle) {
headerBtn = createElement(isStandalone && showToggle ? 'span' : 'button', {
className: EditGlobals.classNames[isStandalone ?
'accordionHeaderWrapper' : 'accordionHeaderBtn']
}, {}, header);
}
createElement('span', {
textContent: lang[name] || name
}, {}, headerBtn);
if (showToggle && header) {
renderToggle(header, {
enabledOnOffLabels: true,
id: name,
name: '',
onchange: onchange,
value: isEnabled || false,
lang
});
}
if (!isStandalone) {
const headerIcon = createElement('span', {
className: EditGlobals.classNames.accordionHeaderIcon + ' ' +
EditGlobals.classNames.collapsedElement
}, {}, headerBtn);
headerBtn?.addEventListener('click', function () {
content.classList.toggle(EditGlobals.classNames.hiddenElement);
headerIcon?.classList.toggle(EditGlobals.classNames.collapsedElement);
});
}
const content = createElement('div', {
className: EditGlobals.classNames.accordionContent + ' ' +
(isStandalone ?
EditGlobals.classNames.standaloneElement :
EditGlobals.classNames.hiddenElement)
}, {}, accordion);
return { outerElement: accordion, content: content };
}
/**
* Function to create select element.
*
* @param parentElement
* The element to which the new element should be appended.
*
* @param options
* Select form field options.
*
* @returns
* Select element
*/
function renderSelect(parentElement, options) {
if (!parentElement) {
return;
}
if (options.name) {
renderText(parentElement, { title: options.name, isLabel: true });
}
const iconsURLPrefix = options.iconsURLPrefix || '';
const customSelect = createElement('div', {
className: EditGlobals.classNames.dropdown +
' ' +
EditGlobals.classNames.collapsableContentHeader
}, {}, parentElement);
const btn = createElement('button', {
className: EditGlobals.classNames.dropdownButton
}, {}, customSelect);
const btnContent = createElement('div', {
className: EditGlobals.classNames.dropdownButtonContent
}, {}, btn);
const iconURL = (U.find(options.selectOptions, (item) => item.name === options.value) || {}).iconURL;
let headerIcon;
if (iconURL) {
headerIcon = createElement('img', {
src: iconsURLPrefix + iconURL,
className: EditGlobals.classNames.icon
}, {}, btnContent);
}
const placeholder = createElement('span', {
textContent: options.value,
id: options.id || ''
}, {}, btnContent);
const dropdownPointer = createElement('img', {
className: EditGlobals.classNames.dropdownIcon +
' ' +
EditGlobals.classNames.collapsedElement,
src: iconsURLPrefix + 'dropdown-pointer.svg'
}, {}, btn);
const dropdown = createElement('ul', {
className: EditGlobals.classNames.dropdownContent +
' ' +
EditGlobals.classNames.hiddenElement
}, {}, customSelect);
btn.addEventListener('click', function () {
dropdown.classList.toggle(EditGlobals.classNames.hiddenElement);
dropdownPointer.classList.toggle(EditGlobals.classNames.collapsedElement);
});
for (let i = 0, iEnd = options.selectOptions.length; i < iEnd; ++i) {
renderSelectElement(merge(options.selectOptions[i] || {}, { iconsURLPrefix }), dropdown, placeholder, options.id, dropdownPointer, headerIcon, options.onchange);
}
return customSelect;
}
/**
* @internal
*/
function renderSelectElement(option, dropdown, placeholder, id, dropdownPointer, headerIcon, callback) {
const iconURL = option.iconsURLPrefix + option.iconURL;
const selectOption = createElement('li', {}, {}, dropdown);
const selectOptionBtn = createElement('button', { className: EditGlobals.classNames.customSelectButton }, {}, selectOption);
if (option.iconURL) {
createElement('img', {
src: iconURL
}, {}, selectOptionBtn);
}
createElement('span', { textContent: option.name || '' }, {}, selectOptionBtn);
selectOptionBtn.addEventListener('click', function () {
dropdown.classList.add(EditGlobals.classNames.hiddenElement);
dropdownPointer.classList.toggle(EditGlobals.classNames.collapsedElement);
placeholder.textContent = option.name || '';
if (headerIcon && option.iconURL) {
headerIcon.src = iconURL;
}
if (callback) {
return callback(option.name);
}
});
}
/**
* Function to create toggle element.
*
* @param parentElement
* The element to which the new element should be appended.
*
* @param options
* Form field options.
*
* @returns
* Toggle element.
*/
function renderToggle(parentElement, options) {
if (!parentElement) {
return;
}
const lang = options.lang, value = options.value, title = options.title || options.name, langKey = options.langKey;
if (options.isNested) {
const labeledToggleWrapper = createElement('div', {
className: EditGlobals.classNames.labeledToggleWrapper
}, {}, parentElement);
parentElement = labeledToggleWrapper;
}
const toggleContainer = createElement('button', {
className: EditGlobals.classNames.toggleContainer,
type: 'button',
role: 'switch',
ariaChecked: false,
ariaLabel: langKey ? lang.accessibility[langKey][options.name] : ''
}, {}, parentElement);
if (title) {
renderText(options.isNested ? parentElement : toggleContainer, { title });
}
if (options.enabledOnOffLabels) {
renderText(toggleContainer, {
title: lang.off,
className: EditGlobals.classNames.toggleLabels
});
}
const toggle = createElement('label', {
className: EditGlobals.classNames.toggleWrapper +
' ' + (options.className || '')
}, {}, toggleContainer);
const input = renderCheckbox(toggle, value), callbackFn = options.onchange;
callbackFn && toggleContainer.addEventListener('click', (e) => {
callbackFn(!input.checked);
input.checked = !input.checked;
toggleContainer.setAttribute('aria-checked', input.checked);
e.stopPropagation();
});
const slider = createElement('span', {
className: EditGlobals.classNames.toggleSlider
}, {}, toggle);
callbackFn && slider.addEventListener('click', (e) => {
e.preventDefault();
});
if (options.enabledOnOffLabels) {
renderText(toggleContainer, {
title: lang.on,
className: EditGlobals.classNames.toggleLabels
});
}
return toggleContainer;
}
/**
* Function to create text element.
*
* @param parentElement
* The element to which the new element should be appended
*
* @param text
* Text to be displayed
*
* @param callback
* Callback function to be fired on the click
*
* @returns text Element
*/
function renderText(parentElement, options) {
let textElem;
const { title: text, className, isLabel } = options;
if (parentElement) {
const labelFor = isLabel ? { htmlFor: text } : {};
textElem = createElement(isLabel ? 'label' : 'div', {
className: EditGlobals.classNames.labelText + ' ' + (className || ''),
textContent: text,
...labelFor
}, {}, parentElement);
}
return textElem;
}
/**
* Function to create Icon element.
*
* @param parentElement
* The element to which the new element should be appended.
*
* @param icon
* Icon URL
*
* @param callback
* Callback function
*
* @returns
* Icon Element
*/
function renderIcon(parentElement, options) {
const { icon, callback } = options;
if (!parentElement) {
return;
}
const iconElem = createElement('div', {
onclick: callback,
className: options.className || ''
}, {}, parentElement);
iconElem.style['background-image'] = 'url(' + icon + ')';
const mousedown = options.mousedown;
const click = options.click;
if (mousedown) {
iconElem.onmousedown = function () {
mousedown.apply(this, arguments);
};
}
if (click) {
iconElem.addEventListener('click', function () {
click.apply(this, arguments);
});
}
return iconElem;
}
/**
* Function to create input element.
*
* @param parentElement
* the element to which the new element should be appended
*
* @param options
* Form field options
*
* @returns
* Input Element
*/
function renderInput(parentElement, options) {
if (!parentElement) {
return;
}
if (options.name) {
renderText(parentElement, { title: options.name, isLabel: true });
}
const input = createElement('input', {
type: 'text',
onclick: options.callback,
id: options.id || '',
name: options.name || '',
value: ((defined(options.value) &&
options.value.toString().replace(/\"/g, '')) || '')
}, {}, parentElement);
const onchange = options.onchange;
if (onchange) {
input.addEventListener('change', function (e) {
onchange(e.target.value);
});
}
return input;
}
/**
* Function to create textarea element.
*
* @param parentElement
* The element to which the new element should be appended
*
* @param options
* Form field options
*
* @returns
* textarea Element
*/
function renderTextarea(parentElement, options) {
if (!parentElement) {
return;
}
if (options.name) {
renderText(parentElement, { title: options.name, isLabel: true });
}
const textarea = createElement('textarea', {
id: options.id,
name: options.name,
value: options.value || ''
}, {}, parentElement);
const onchange = options.onchange;
if (onchange) {
textarea.addEventListener('change', function (e) {
onchange(e.target.value);
});
}
return textarea;
}
/**
* Function to render the input of type checkbox.
*
* @param parentElement
* An element to which render the checkbox to
*
* @param checked
* Whether the checkbox should be checked or not.
*
* @returns
* The checkbox element
*/
function renderCheckbox(parentElement, checked) {
let input;
if (parentElement) {
input = createElement('input', {
type: 'checkbox',
checked: !!checked
}, {}, parentElement);
}
return input;
}
/**
* Function to create button element.
*
* @param parentElement
* the element to which the new element should be appended
*
* @param options
* Button field options
*
* @returns
* Button Element
*/
function renderButton(parentElement, options) {
if (!parentElement) {
return;
}
const button = createElement('button', {
className: (EditGlobals.classNames.button + ' ' +
(options.className || '')),
onclick: options.callback,
textContent: options.text
}, options.style || {}, parentElement);
if (options.icon) {
button.style['background-image'] =
'url(' + options.icon + ')';
}
return button;
}
/**
* Get the renderer function based on the type of the element to render.
*
* @param type
* Type of the element to render
*
* @returns
* function to render a specific element
*/
function getRendererFunction(type) {
return {
select: renderSelect,
toggle: renderToggle,
text: renderText,
collapse: renderCollapseHeader,
icon: renderIcon,
contextButton: renderContextButton,
input: renderInput,
textarea: renderTextarea,
checkbox: renderCheckbox,
button: renderButton
}[type];
}
const EditRenderer = {
renderSelect,
renderToggle,
renderText,
renderCollapseHeader,
renderIcon,
renderContextButton,
renderInput,
renderTextarea,
renderCheckbox,
renderButton,
getRendererFunction
};
return EditRenderer;
});
_registerModule(_modules, 'Dashboards/EditMode/Menu/MenuItem.js', [_modules['Dashboards/EditMode/EditGlobals.js'], _modules['Core/Utilities.js'], _modules['Dashboards/EditMode/EditRenderer.js']], function (EditGlobals, U, EditRenderer) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { createElement, merge } = U;
class MenuItem {
/* *
*
* Constructor
*
* */
constructor(menu, options) {
this.menu = menu;
this.isActive = false;
this.options = merge(MenuItem.defaultOptions, options);
this.container = this.setContainer();
this.innerElement = this.setInnerElement();
}
/* *
*
* Functions
*
* */
setContainer() {
const item = this, options = item.options;
let className = EditGlobals.classNames.menuItem;
if (item.menu.options.itemsClassName) {
className += ' ' + item.menu.options.itemsClassName;
}
if (options.className) {
className += ' ' + options.className;
}
return createElement('div', { className: className || '' }, merge(this.options.style || {},
// To remove
this.isActive ? { display: 'block' } : {}), this.menu.container);
}
setInnerElement() {
const item = this, options = item.options, container = item.container, langKey = options.langKey;
if (options.type === 'toggle') {
return EditRenderer.renderToggle(container, {
id: options.id,
name: options.id,
title: langKey ?
this.menu.editMode.lang[langKey] :
options.text,
value: !!(options.getValue && options.getValue(item)),
lang: this.menu.editMode.lang,
langKey: langKey,
onchange: options.events?.click?.bind(item)
});
}
if (options.type === 'text') {
return EditRenderer.renderText(container, {
title: langKey ?
this.menu.editMode.lang[langKey] :
options.text || '',
className: options.className || ''
});
}
if (options.type === 'icon') {
return EditRenderer.renderIcon(container, {
icon: options.icon || '',
mousedown: options.events?.onmousedown?.bind(item),
click: options.events?.click?.bind(item)
});
}
if (options.type === 'button') {
return EditRenderer.renderButton(container, {
callback: options.events?.click?.bind(item),
className: options.className || '',
style: options.style || {},
text: langKey ?
this.menu.editMode.lang[langKey] :
(options.text || '')
});
}
}
update() {
const item = this, options = item.options;
if (options.events && options.events.update) {
options.events.update.apply(item, arguments);
}
}
activate() {
const item = this;
item.update();
// Temp.
if (item.container) {
item.isActive = true;
item.container.style.display = 'block';
}
}
deactivate() {
const item = this;
// Temp.
if (item.container) {
item.isActive = false;
item.container.style.display = 'none';
}
}
}
/* *
*
* Static Properties
*
* */
MenuItem.defaultOptions = {
type: 'text'
};
return MenuItem;
});
_registerModule(_modules, 'Dashboards/EditMode/Menu/MenuItemBindings.js', [], function () {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const MenuItemBindings = {
/* *
*
* Context menu
*
* */
viewFullscreen: {
id: 'viewFullscreen',
type: 'button',
langKey: 'viewFullscreen',
events: {
click: function () {
const fullScreen = this.menu.editMode.board.fullscreen;
if (fullScreen) {
fullScreen.toggle();
}
}
}
}
};
return MenuItemBindings;
});
_registerModule(_modules, 'Dashboards/EditMode/Menu/Menu.js', [_modules['Dashboards/EditMode/EditGlobals.js'], _modules['Core/Utilities.js'], _modules['Dashboards/EditMode/Menu/MenuItem.js'], _modules['Dashboards/EditMode/Menu/MenuItemBindings.js']], function (EditGlobals, U, MenuItem, MenuItemBindings) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { createElement, merge } = U;
class Menu {
/* *
*
* Constructor
*
* */
constructor(parentElement, options, editMode, parent) {
this.parentElement = parentElement;
this.isVisible = false;
this.activeItems = [];
this.options = options;
this.items = {};
this.editMode = editMode;
if (parent) {
this.parent = parent;
}
this.container = this.setContainer();
}
/* *
*
* Functions
*
* */
setContainer() {
return createElement('div', {
className: EditGlobals.classNames.menu +
' ' + (this.options.className || '')
}, {}, this.parentElement);
}
// ItemsSchemas - default items definitions.
initItems(itemsSchemas, activeItems) {
const menu = this, optionsItems = menu.options.items || [];
let itemSchema, itemConfig, item, options;
for (let i = 0, iEnd = optionsItems.length; i < iEnd; ++i) {
itemConfig = optionsItems[i];
itemSchema =
typeof itemConfig === 'string' ? itemsSchemas[itemConfig] :
itemConfig.id ? itemsSchemas[itemConfig.id] :
{};
options = typeof itemConfig === 'string' ?
merge(itemSchema, { id: itemConfig }) :
merge(itemSchema, itemConfig);
if (options.id) {
item = new MenuItem(menu, options);
// Save initialized item.
menu.items[item.options.id] = item;
if (activeItems) {
item.activate();
menu.activeItems.push(item);
}
}
else {
// Error - defined item needs an id.
}
}
}
setActiveItems(items) {
const menu = this;
let item;
// Deactivate items.
for (let i = 0, iEnd = menu.activeItems.length; i < iEnd; ++i) {
if (items.indexOf(menu.activeItems[i].options.id) === -1) {
menu.activeItems[i].deactivate();
}
}
menu.activeItems.length = 0;
for (let j = 0, jEnd = items.length; j < jEnd; ++j) {
item = menu.items[items[j]];
if (item) {
// Activate item.
if (!item.isActive) {
item.activate();
}
else {
item.update();
}
menu.activeItems.push(item);
}
}
}
deactivateActiveItems() {
const menu = this;
for (let i = 0, iEnd = menu.activeItems.length; i < iEnd; ++i) {
menu.activeItems[i].deactivate();
}
}
updateActiveItems() {
const activeItems = this.activeItems;
for (let i = 0, iEnd = activeItems.length; i < iEnd; ++i) {
activeItems[i].update();
}
}
destroy() {
this.activeItems.length = 0;
this.container.remove();
this.items = {};
this.options = {};
}
}
/* *
*
* Static Properties
*
* */
Menu.items = MenuItemBindings;
return Menu;
});
_registerModule(_modules, 'Dashboards/EditMode/Toolbar/EditToolbar.js', [_modules['Core/Utilities.js'], _modules['Dashboards/EditMode/Menu/Menu.js']], function (U, Menu) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { defined, createElement, css } = U;
/**
* Abstract Class of Edit Toolbar.
* @internal
*/
class EditToolbar {
/* *
*
* Constructor
*
* */
constructor(editMode, options) {
this.container = createElement('div', {
className: options.className
}, void 0, editMode.board.container);
this.editMode = editMode;
this.iconURLPrefix = editMode.iconsURLPrefix;
this.menu = new Menu(this.container, options.menu, editMode, this);
this.options = options;
this.isVisible = false;
if (this.options.outline) {
this.outline = createElement('div', {
className: options.outlineClassName
}, void 0, this.container);
}
}
/* *
*
* Functions
*
* */
hide() {
this.setPosition(void 0, void 0);
}
refreshOutline(x, y, guiElement, offset = 0) {
const toolbar = this, guiElemCnt = (guiElement || {}).container;
if (toolbar.outline && guiElemCnt) {
css(toolbar.outline, {
display: 'block',
left: x - offset + 'px',
top: y - offset + 'px',
width: guiElemCnt.offsetWidth + offset * 2 + 'px',
height: guiElemCnt.offsetHeight + offset * 2 + 'px'
});
}
}
hideOutline() {
if (this.outline) {
this.outline.style.display = 'none';
}
}
setPosition(x, y) {
const toolbar = this;
if (toolbar.container) {
css(toolbar.container, {
left: (x || '-9999') + 'px',
top: (y || '-9999') + 'px'
});
}
toolbar.isVisible = defined(x) && defined(y);
}
}
return EditToolbar;
});
_registerModule(_modules, 'Dashboards/EditMode/Toolbar/CellEditToolbar.js', [_modules['Dashboards/Layout/Cell.js'], _modules['Dashboards/EditMode/EditGlobals.js'], _modules['Dashboards/EditMode/Toolbar/EditToolbar.js'], _modules['Dashboards/Layout/GUIElement.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Cell, EditGlobals, EditToolbar, GUIElement, H, U) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { isFirefox } = H;
const { merge, fireEvent, objectEach } = U;
/**
* @internal
*/
class CellEditToolbar extends EditToolbar {
static getItemsConfig(options, iconURLPrefix) {
const items = [];
if (options.dragDrop?.enabled) {
items.push({
id: 'drag',
type: 'icon',
icon: iconURLPrefix + 'drag.svg',
events: {
onmousedown: function (e) {
// #22546, workaround for Firefox, where mouseenter
// event is not fired when triggering it while dragging
// another element.
if (isFirefox) {
e.preventDefault();
}
const cellEditToolbar = this.menu
.parent;
const dragDrop = cellEditToolbar.editMode.dragDrop;
if (dragDrop &&
cellEditToolbar.cell &&
Cell.isCell(cellEditToolbar.cell)) {
dragDrop.onDragStart(e, cellEditToolbar.cell);
}
}
}
});
}
if (options.settings?.enabled) {
items.push({
id: 'settings',
type: 'icon',
icon: iconURLPrefix + 'settings.svg',
events: {
click: function () {
this.menu.parent.editMode.setEditOverlay();
this.menu.parent.onCellOptions();
}
}
});
}
items.push({
id: 'destroy',
type: 'icon',
className: EditGlobals.classNames.menuDestroy,
icon: iconURLPrefix + 'destroy.svg',
events: {
click: function () {
const parentNode = this.menu.parent, editMode = this.menu.parent.editMode, popup = editMode.confirmationPopup;
popup.show({
confirmButton: {
value: editMode.lang.confirmButton,
callback: parentNode.onCellDestroy,
context: parentNode
},
cancelButton: {
value: editMode.lang.cancelButton,
callback: () => {
popup.closePopup();
}
},
text: editMode.lang.confirmDestroyCell
});
}
}
});
return items;
}
/* *
*
* Constructor
*
* */
constructor(editMode) {
super(editMode, merge(CellEditToolbar.defaultOptions, (editMode.options.toolbars || {}).cell, {
menu: {
items: CellEditToolbar.getItemsConfig(editMode.options, editMode.iconsURLPrefix)
}
}));
if (editMode.customHTMLMode) {
this.filterOptionsAvailableInCustomHTMLMode();
}
this.menu.initItems({});
}
/* *
*
* Functions
*
* */
/**
* Show toolbar for given cell.
*
* @param cell
* Cell to show toolbar for.
*/
showToolbar(cell) {
const toolbar = this;
const cellCnt = cell.container;
const toolbarWidth = 30;
const toolbarMargin = 10;
const cellToolbar = toolbar.editMode.cellToolbar;
if (!cellToolbar) {
return;
}
if (cellCnt && toolbar.editMode.isActive() &&
!(toolbar.editMode.dragDrop || {}).isActive) {
const cellOffsets = GUIElement.getOffsets(cell, toolbar.editMode.board.container);
const x = cellOffsets.right - toolbarWidth - toolbarMargin;
const y = cellOffsets.top + toolbarMargin;
objectEach(toolbar.menu.items, (item) => {
if (!cell.options?.editMode?.toolbarItems) {
item.activate();
return;
}
const toolbarItems = cell.options.editMode.toolbarItems;
if (toolbarItems[item.options.id]
?.enabled === false) {
item.deactivate();
return;
}
item.activate();
});
toolbar.setPosition(x, y);
toolbar.cell = cell;
toolbar.refreshOutline();
cellToolbar.isVisible = true;
}
else if (toolbar.isVisible) {
toolbar.hide();
cellToolbar.isVisible = false;
}
}
refreshOutline() {
const toolbar = this, offsetWidth = -1;
if (toolbar.cell && toolbar.cell.container && toolbar.outline) {
super.refreshOutline(-toolbar.cell.container.offsetWidth, 0, this.cell, offsetWidth);
}
}
/**
* When options icon is clicked, show sidebar with options.
*/
onCellOptions() {
const toolbar = this;
const editMode = toolbar.editMode;
if (!editMode.sidebar) {
return;
}
editMode.sidebar.show(toolbar.cell);
toolbar.highlightCell();
}
onCellDestroy() {
const toolbar = this;
if (toolbar.cell && Cell.isCell(toolbar.cell)) {
const row = toolbar.cell.row;
const cellId = toolbar.cell.id;
// Disable row highlight.
toolbar.cell.row.setHighlight();
toolbar.resetEditedCell();
toolbar.cell.destroy();
toolbar.cell = void 0;
// Hide row and cell toolbars.
toolbar.editMode.hideToolbars(['cell', 'row']);
// Disable resizer.
toolbar.editMode.resizer?.disableResizer();
// Call cellResize dashboard event.
if (row && row.cells && row.cells.length) {
fireEvent(toolbar.editMode.board, 'cellResize', {
cell: row.cells[0]
});
fireEvent(row, 'cellChange', { cell: row.cells[0], row });
fireEvent(toolbar.editMode, 'layoutChanged', {
type: 'cellDestroyed',
target: cellId,
board: toolbar.editMode.board
});
}
}
}
resetEditedCell() {
this.editedCell = void 0;
}
/**
* Filter options available in custom HTML mode, only settings available.
*/
filterOptionsAvailableInCustomHTMLMode() {
this.options.menu.items = this.options.menu.items?.filter((item) => {
if (typeof item === 'string') {
return false;
}
return item.id === 'settings';
});
}
/**
* Highlight cell and gray out the rest of the dashboard.
*/
highlightCell() {
const toolbar = this;
if (!toolbar.cell) {
return;
}
if (toolbar.cell.setHighlight) {
toolbar.cell.setHighlight();
}
else {
toolbar.cell.container.classList.add(EditGlobals.classNames.cellEditHighlight);
toolbar.editMode.board.container.classList.add(EditGlobals.classNames.dashboardCellEditHighlightActive);
}
}
}
/* *
*
* Static Properties
*
* */
CellEditToolbar.defaultOptions = {
enabled: true,
className: EditGlobals.classNames.editToolbar,
outline: false,
outlineClassName: EditGlobals.classNames.editToolbarCellOutline,
menu: {
className: EditGlobals.classNames.editToolbarCell,
itemsClassName: EditGlobals.classNames.editToolbarItem,
items: []
}
};
return CellEditToolbar;
});
_registerModule(_modules, 'Dashboards/EditMode/Toolbar/RowEditToolbar.js', [_modules['Core/Utilities.js'], _modules['Dashboards/EditMode/EditGlobals.js'], _modules['Dashboards/EditMode/Toolbar/EditToolbar.js'], _modules['Dashboards/Layout/GUIElement.js']], function (U, EditGlobals, EditToolbar, GUIElement) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { fireEvent, merge, objectEach } = U;
/**
* @internal
*/
class RowEditToolbar extends EditToolbar {
static getMenuItemsConfig(options, iconURLPrefix) {
const items = [];
if (options.dragDrop?.enabled) {
items.push({
id: 'drag',
type: 'icon',
icon: iconURLPrefix + 'drag.svg',
events: {
onmousedown: function (e) {
const rowEditToolbar = this.menu
.parent, dragDrop = rowEditToolbar.editMode.dragDrop;
e.preventDefault();
if (dragDrop && rowEditToolbar.row) {
dragDrop.onDragStart(e, rowEditToolbar.row);
}
}
}
});
}
items.push({
id: 'destroy',
type: 'icon',
className: EditGlobals.classNames.menuDestroy,
icon: iconURLPrefix + 'destroy.svg',
events: {
click: function () {
const parentNode = this.menu.parent, editMode = this.menu.parent.editMode, popup = editMode.confirmationPopup;
popup.show({
confirmButton: {
value: editMode.lang.confirmButton,
callback: parentNode.onRowDestroy,
context: parentNode
},
cancelButton: {
value: editMode.lang.cancelButton,
callback: () => {
popup.closePopup();
}
},
text: editMode.lang.confirmDestroyRow
});
}
}
});
return items;
}
/* *
*
* Constructor
*
* */
constructor(editMode) {
super(editMode, merge(RowEditToolbar.defaultOptions, (editMode.options.toolbars || {}).row, {
menu: {
items: RowEditToolbar.getMenuItemsConfig(editMode.options, editMode.iconsURLPrefix)
}
}));
this.menu.initItems({});
}
/* *
*
* Functions
*
* */
refreshOutline(x, y) {
const toolbar = this, offsetWidth = 2;
if (toolbar.row && toolbar.row.container) {
super.refreshOutline(x, y, this.row, offsetWidth);
}
}
showToolbar(row) {
const toolbar = this;
const rowCnt = row.container;
const rowToolbar = toolbar.editMode.rowToolbar;
let x;
let y;
let offsetX;
if (!rowToolbar) {
return;
}
if (rowCnt &&
toolbar.editMode.isActive() &&
!(toolbar.editMode.dragDrop || {}).isActive) {
const rowOffsets = GUIElement.getOffsets(row, toolbar.editMode.board.container);
const rowWidth = rowOffsets.right - rowOffsets.left;
objectEach(toolbar.menu.items, (item) => {
if (!row.options?.editMode?.toolbarItems) {
item.activate();
return;
}
const toolbarItems = row.options.editMode.toolbarItems;
if (toolbarItems[item.options.id]
?.enabled === false) {
item.deactivate();
return;
}
item.activate();
});
offsetX = rowWidth / 2 - toolbar.container.clientWidth / 2;
x = rowOffsets.left + offsetX;
y = rowOffsets.top - toolbar.container.clientHeight;
toolbar.setPosition(x, y);
toolbar.row = row;
toolbar.refreshOutline(-offsetX, toolbar.container.clientHeight);
rowToolbar.isVisible = true;
}