/** * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact schukai GmbH. * * SPDX-License-Identifier: AGPL-3.0 */ import { instanceSymbol } from "../../constants.mjs"; import { assembleMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { PopperButton } from "./popper-button.mjs"; import { ActionButtonStyleSheet } from "./stylesheet/action-button.mjs"; import { isObject, isIterable, isString, isFunction } from "../../types/is.mjs"; import { Observer } from "../../types/observer.mjs"; import { getDocumentTranslations } from "../../i18n/translations.mjs"; import { findTargetElementFromEvent, fireCustomEvent, } from "../../dom/events.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { addErrorAttribute } from "../../dom/error.mjs"; export { ActionButton }; /** * @private * @type {symbol} */ const containerElementSymbol = Symbol("containerElement"); /** * A button that opens a popper element with possible actions. * * @fragments /fragments/components/form/action-button * * @example /examples/components/form/action-button * * @issue https://localhost.alvine.dev:8443/development/issues/closed/264.html * * @since 3.32.0 * @copyright schukai GmbH * @summary The ActionButton is a button that opens a popper element with possible actions */ class ActionButton extends PopperButton { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/form/action-button@@instance", ); } /** * To set the options via the HTML tag, the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * * The individual configuration values can be found in the table. * * @property {object} labels * @property {string} labels.button The label of the button * @property {object[]} buttons * @property {string} buttons[].label The label of the button * @property {string} buttons[].class The CSS class of the button * @property {function} buttons[].action The action of the button * @property {object} templates * @property {string} templates.main The template of the button * @extends {PopperButton.defaults} */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, labels: { button: "<slot></slot>", }, classes: { button: "monster-button-primary", }, buttons: [], }); } /** * * @return {ActionButton} * @fires monster-action-button-show-dialog */ showDialog() { if (this.getOption("buttons").length === 0) { return this; } return super.showDialog(); } /** * * @return {ActionButton} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); return this; } /** * @return {string} */ static getTag() { return "monster-action-button"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { const styles = super.getCSSStyleSheet(); styles.push(ActionButtonStyleSheet); return styles; } } /** * @private * @return {ActionButton} */ function initEventHandler() { this[containerElementSymbol].addEventListener("click", (event) => { const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button"); const attr = element.getAttribute("data-monster-insert-reference"); if (attr) { const index = attr.split("-")[1]; const b = this.getOption("buttons." + index); if (isObject(b) && isFunction(b?.action)) { b.action(event, element, this); } } }); let memButtons = ""; this.attachObserver( new Observer(() => { if (JSON.stringify(this.getOption("buttons")) !== memButtons) { try { updateButtonsI18n.call(this); } catch (e) { addErrorAttribute(this, e.message); } memButtons = JSON.stringify(this.getOption("buttons")); } }), ); return this; } /** * @private * @returns {updateButtonsI18n} */ function updateButtonsI18n() { const translations = getDocumentTranslations(); if (!translations) { return this; } const buttons = this.getOption("buttons"); if (!isIterable(buttons)) { return this; } for (const key in buttons) { const def = buttons[key]["label"]; if (isString(def)) { const text = translations.getText(def, def); if (text !== def) { this.setOption(`buttons.${key}.label`, text); } continue; } throw new Error("Invalid labels definition"); } return this; } /** * @private * @return {ActionButton} */ function initControlReferences() { this[containerElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=container]`, ); return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <template id="btn"> <monster-message-state-button data-monster-role="button" part="button" data-monster-attributes="id path:btn.id, data-monster-option-classes-button path:btn.class, disabled path:btn.disabled | if:true, data-monster-url path:btn.url" data-monster-option-actions-click="" data-monster-option-popper-placement="right" data-monster-replace="path:btn.label"></monster-message-state-button> </template> <div data-monster-role="control" part="control"> <button data-monster-attributes="class path:classes.button" data-monster-role="button" part="button" data-monster-replace="path:labels.button" ></button> <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1"> <div data-monster-role="arrow"></div> <div part="content" class="flex" data-monster-role="container"> <div part="buttons" data-monster-role="buttons" data-monster-insert="btn path:buttons" tabindex="-1"></div> </div> </div> </div> `; } registerCustomElement(ActionButton);