/** * 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 { ATTRIBUTE_ROLE } from "../../dom/constants.mjs"; import { assembleMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { isArray, isString } from "../../types/is.mjs"; import { validateString } from "../../types/validate.mjs"; import { Popper } from "./popper.mjs"; import { MessageStateButtonStyleSheet } from "./stylesheet/message-state-button.mjs"; import { StateButtonStyleSheet } from "./stylesheet/state-button.mjs"; import "./state-button.mjs"; import { isFunction } from "../../types/is.mjs"; export { MessageStateButton }; /** * @private * @type {symbol} */ const buttonElementSymbol = Symbol("buttonElement"); /** * A select control that can be used to select one or more options from a list. * * @fragments /fragments/components/form/message-state-button/ * * @example /examples/components/form/message-state-button-simple * * @since 2.11.0 * @copyright schukai GmbH * @summary A beautiful select control that can make your life easier and also looks good. * @fires monster-options-set * @fires monster-selected * @fires monster-change * @fires monster-changed */ class MessageStateButton extends Popper { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/form/message-state-button@@instance", ); } /** * * @param {string} state * @param {number} timeout * @return {Monster.Components.Form.MessageStateButton} * @throws {TypeError} value is not a string * @throws {TypeError} value is not an instance */ setState(state, timeout) { return this[buttonElementSymbol].setState(state, timeout); } /** * * @return {Monster.Components.Form.MessageStateButton} */ removeState() { return this[buttonElementSymbol].removeState(); } /** * @return {Monster.Components.Form.Types.State|undefined} */ getState() { return this[buttonElementSymbol].getState(); } /** * 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} templates Template definitions * @property {string} templates.main Main template * @property {Object} labels Label definitions * @property {string} labels.button Button label * @property {Object} mode Mode definitions (manual, submit) */ get defaults() { return Object.assign({}, super.defaults, { message: { title: undefined, content: undefined, icon: undefined, }, templates: { main: getTemplate(), }, mode: "manual", labels: { button: "<slot></slot>", }, classes: { button: "monster-button-primary", }, actions: { click: (e) => { throw new Error("the click action is not defined"); }, }, features: { disableButton: false, }, }); } /** */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); let modes = null; const modeOption = this.getOption("mode"); if (typeof modeOption === "string") { modes = modeOption.split(" "); } if ( modes === null || modes === undefined || isArray(modes) === false || modes.length === 0 ) { modes = ["manual"]; } for (const [, mode] of Object.entries(modes)) { initEventHandlerByMode.call(this, mode); } return this; } /** * Sets the message * * @param {string|HTMLElement}message * @param {string} title * @param {string} icon * @return {Monster.Components.Form.MessageStateButton} */ setMessage(message, title, icon) { if (isString(message)) { if (message === "") { throw new TypeError("message must not be empty"); } const containerDiv = document.createElement("div"); const messageDiv = document.createElement("div"); const titleDiv = document.createElement("div"); titleDiv.setAttribute("data-monster-role", "message-title-box"); let titleElement, iconElement; if (title !== undefined) { title = validateString(title); titleElement = document.createElement("div"); titleElement.setAttribute("class", ""); titleElement.innerHTML = title; titleElement.setAttribute("data-monster-role", "message-title"); titleDiv.appendChild(titleElement); } if (icon !== undefined) { icon = validateString(icon); iconElement = document.createElement("div"); iconElement.setAttribute("class", ""); iconElement.innerHTML = icon; iconElement.setAttribute("data-monster-role", "message-icon"); titleDiv.appendChild(iconElement); } messageDiv.innerHTML = message; containerDiv.appendChild(titleDiv); containerDiv.appendChild(messageDiv); this.setOption("message.content", containerDiv); } else if (message instanceof HTMLElement) { this.setOption("message.content", message); } else { throw new TypeError( "message must be a string or an instance of HTMLElement", ); } return this; } /** * clears the Message * * @return {Monster.Components.Form.MessageStateButton} */ clearMessage() { this.setOption("message.title", undefined); this.setOption("message.content", undefined); this.setOption("message.icon", undefined); return this; } /** * With this method you can show the popper with timeout feature. * * @param {number} timeout * @return {MessageStateButton} */ showMessage(timeout) { this.showDialog.call(this); if (timeout !== undefined) { setTimeout(() => { super.hideDialog(); }, timeout); } return this; } /** * With this method you can show the popper. * * @return {MessageStateButton} */ showDialog() { if (this.getOption("message.content") === undefined) { return; } super.showDialog(); return this; } /** * * @return {Monster.Components.Form.MessageStateButton} */ hideMessage() { super.hideDialog(); return this; } /** * * @return {Monster.Components.Form.MessageStateButton} */ toggleMessage() { super.toggleDialog(); return this; } /** * * @return {Object} */ getMessage() { return this.getOption("message"); } /** * * @return {string} */ static getTag() { return "monster-message-state-button"; } /** * * @return {Array<CSSStyleSheet>} */ static getCSSStyleSheet() { const styles = Popper.getCSSStyleSheet(); styles.push(StateButtonStyleSheet); styles.push(MessageStateButtonStyleSheet); return styles; } /** * The Button.click() method simulates a click on the internal button element. * * @since 3.27.0 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} */ click() { if (this.getOption("disabled") === true) { return; } if ( this[buttonElementSymbol] && isFunction(this[buttonElementSymbol].click) ) { this[buttonElementSymbol].click(); } } /** * The Button.focus() method sets focus on the internal button element. * * @since 3.27.0 * @param {Object} options * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} */ focus(options) { if (this.getOption("disabled") === true) { return; } if ( this[buttonElementSymbol] && isFunction(this[buttonElementSymbol].focus) ) { this[buttonElementSymbol].focus(options); } } /** * The Button.blur() method removes focus from the internal button element. */ blur() { if ( this[buttonElementSymbol] && isFunction(this[buttonElementSymbol].blur) ) { this[buttonElementSymbol].blur(); } } } function initEventHandlerByMode(mode) { switch (mode) { case "manual": this[buttonElementSymbol].setOption("actions.click", (e) => { const callback = this.getOption("actions.click"); if (isFunction(callback)) { callback(e); } }); break; case "submit": this[buttonElementSymbol].setOption("actions.click", (e) => { const form = this.form; if (form instanceof HTMLFormElement) { form.requestSubmit(); } }); break; } } /** * @private * @return {Select} */ function initControlReferences() { this[buttonElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=button]`, ); } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control"> <monster-state-button exportparts="button:button-button,control:button-control" data-monster-attributes="data-monster-option-classes-button path:classes.button, disabled path:features.disableButton | if:true" part="button" name="button" data-monster-role="button"> <span data-monster-replace="path:labels.button"></span> </monster-state-button> <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1"> <div data-monster-role="arrow"></div> <div data-monster-role="message" part="message" class="flex" data-monster-replace="path:message.content"></div> </div> </div> </div> `; } registerCustomElement(MessageStateButton);