UNPKG

@highcharts/dashboards

Version:
1,280 lines (1,272 loc) 201 kB
/** * @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; }