/** * Copyright schukai GmbH and contributors 2022. All Rights Reserved. * Node module: @schukai/monster * This file is licensed under the AGPLv3 License. * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html */ 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"); /** * This CustomControl creates a notification element with a variety of options. * * <img src="./images/message.png"> * * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library. * * You can create this control either by specifying the HTML tag `<monster-notify-message />` directly in the HTML * * ```html * <monster-notify-message></monster-notify-message> * ``` * * or using Javascript via the `document.createElement('monster-notify');` method. * * ```javascript * import '@schukai/monster/source/components/notify/message.js'; * document.createElement('monster-notify-message'); * ``` * * @externalExample ../../../example/components/notify/message.mjs * @startuml message.png * skinparam monochrome true * skinparam shadowing false * HTMLElement <|-- CustomElement * CustomElement <|-- Message * @enduml * @since 1.0.0 * @copyright schukai GmbH * @memberOf Monster.Components.Notify * @summary A highly configurable select control * @fires Monster.Components.Notify.event:monster-xxxx */ 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 {string} templates Template definitions * @property {Object} templates Template definitions * @property {integer} timeout time in milliseconds until the message should be removed. The timeout can be disabled via the feature `disappear`. * @property {Object} features * @property {boolean} features.close show close button * @property {boolean} features.disappear automatically remove the message after the timeout * @property {string} templates.main Main template * */ get defaults() { return Object.assign( {}, super.defaults, { timeout: 6000, features: { close: true, disappear: true, }, content: "<slot></slot>", templates: { main: getTemplate(), }, }, initOptionsFromArguments.call(this), ); } /** * * @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; } } /** * This attribute can be used to pass a URL to this select. * * @private * @return {object} */ function initOptionsFromArguments() { const options = {}; const timeout = this.getAttribute(ATTRIBUTE_PREFIX + "timeout"); if (isString(timeout)) { try { options["timeout"] = parseInt(timeout, 10); } catch (e) { this.setAttribute( ATTRIBUTE_ERRORMESSAGE, this.getAttribute(ATTRIBUTE_ERRORMESSAGE + ", " + e.toString()), ); } } return options; } /** * @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);