/** * 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 { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_PREFIX, ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { assembleMethodSymbol, CustomElement, initMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { findTargetElementFromEvent } from "../../dom/events.mjs"; import { isString } from "../../types/is.mjs"; import { MessageStyleSheet } from "./stylesheet/message.mjs"; export { Message }; /** * @private * @type {symbol} */ const controlElementSymbol = Symbol("controlElement"); /** * @private * @type {symbol} */ const removeElementSymbol = Symbol("removeElement"); /** * @private * @type {symbol} */ const timerSymbol = Symbol("timer"); /** * @private * @type {symbol} */ const mouseenterEventHandlerSymbol = Symbol("mouseenterEventHandler"); /** * @private * @type {symbol} */ const mouseleaveEventHandlerSymbol = Symbol("mouseleaveEventHandler"); /** * @private * @type {symbol} */ const removeEventHandlerSymbol = Symbol("removeEventHandler"); /** * A Message is a notification element that can be used to display messages to the user. * * @fragments /fragments/components/notify/message * * @example /examples/components/notify/message-simple * * @issue https://localhost.alvine.dev:8443/development/issues/closed/269.html * * @since 1.0.0 * @copyright schukai GmbH * @summary The message is a notification element that can be used to display messages to the user. Typically, it is only used in conjunction with the notify container. */ class Message extends CustomElement { /** * The defaults can be set either directly in the object or via an attribute in the HTML tag. * The value of the attribute `data-monster-options` in the HTML tag must be a JSON string. * * ``` * <monster-message data-monster-options="{}"></monster-message> * ``` * * Since 1.18.0 the JSON can be specified as a DataURI. * * ``` * new Monster.Types.DataUrl(btoa(JSON.stringify({ * timeout: 3000, * features: { * close: true, * disappear: true * } * })),'application/json',true).toString() * ``` * * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {number} timeout The time in milliseconds after which the message disappears * @property {Object} features The features of the message * @property {boolean} features.close Whether the message can be closed * @property {boolean} features.disappear Whether the message disappears after a certain time * @property {string} content The content of the message */ get defaults() { return Object.assign( {}, super.defaults, { timeout: 6000, features: { close: true, disappear: true, }, content: "<slot></slot>", templates: { main: getTemplate(), }, }, ); } /** * * @return {Monster.Components.Notify.Message} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); return this; } /** * * @return {string} */ static getTag() { return "monster-notify-message"; } /** * * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [MessageStyleSheet]; } /** * */ [initMethodSymbol]() { super[initMethodSymbol](); } /** * @return {void} */ connectedCallback() { super.connectedCallback(); if (this.getOption("features.disappear") === true) { startFadeout.call(this); this.addEventListener("mouseenter", this[mouseenterEventHandlerSymbol]); } } /** * @return {void} */ disconnectedCallback() { super.disconnectedCallback(); stopFadeout.call(this); if (this.getOption("features.disappear") === true) { this.removeEventListener( "mouseenter", this[mouseenterEventHandlerSymbol], ); this.removeEventListener( "mouseenter", this[mouseleaveEventHandlerSymbol], ); } } } /** * @private */ function startFadeout() { if (!this?.[timerSymbol]) { this[timerSymbol] = setTimeout( () => { removeSelf.call(this); }, this.getOption("timeout", 1000), ); } } function removeSelf() { stopFadeout(); this.classList.add("fadeout"); setTimeout(() => { this.remove(); }, 200); } /** * @private */ function stopFadeout() { if (this?.[timerSymbol]) { clearTimeout(this[timerSymbol]); this[timerSymbol] = undefined; } } /** * @private * @return {Message} * @throws {Error} no shadow-root is defined */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } this[controlElementSymbol] = this.shadowRoot.querySelector( "[" + ATTRIBUTE_ROLE + "=control]", ); this[removeElementSymbol] = this.shadowRoot.querySelector( "[" + ATTRIBUTE_ROLE + "=close]", ); } /** * @private */ function initEventHandler() { /** * @param {Event} event */ this[mouseenterEventHandlerSymbol] = (event) => { const element = findTargetElementFromEvent( event, ATTRIBUTE_ROLE, "control", ); if (element instanceof HTMLElement) { this.removeEventListener( "mouseenter", this[mouseenterEventHandlerSymbol], ); this.addEventListener("mouseleave", this[mouseleaveEventHandlerSymbol]); stopFadeout.call(this); } }; /** * @param {Event} event */ this[mouseleaveEventHandlerSymbol] = (event) => { const element = findTargetElementFromEvent( event, ATTRIBUTE_ROLE, "control", ); if (element instanceof HTMLElement) { this.removeEventListener( "mouseleave", this[mouseleaveEventHandlerSymbol], ); this.addEventListener("mouseenter", this[mouseenterEventHandlerSymbol]); startFadeout.call(this); } }; /** * @param {Event} event */ this[removeEventHandlerSymbol] = (event) => { const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "close"); if (element instanceof HTMLElement) { removeSelf.call(this); } }; if (this.getOption("features.close") === true) { this[removeElementSymbol].addEventListener( "click", this[removeEventHandlerSymbol], ); this[removeElementSymbol].addEventListener( "touch", this[removeEventHandlerSymbol], ); } return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control" class="center"> <div data-monster-role="message" part="message" data-monster-attributes="data-monster-orientation path:orientation"> <div data-monster-replace="path:content" part="content" data-monster-role="content"> </div> <div part="remove" data-monster-attributes="class path:features.close | ?::hidden " data-monster-role="close" tabindex="-1"></div> </div> </div> `; } registerCustomElement(Message);