Something went wrong on our end
Select Git revision
state-button.mjs
-
Volker Schukai authoredVolker Schukai authored
message.mjs 8.09 KiB
/**
* 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: {
* clear: 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.clear show clear 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: {
clear: 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 + "=remove]",
);
}
/**
* @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, "remove");
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.clear | ?::hidden "
data-monster-role="close" tabindex="-1"></div>
</div>
</div>
`;
}
registerCustomElement(Message);