UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

412 lines (356 loc) 9.33 kB
/** * 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 { assembleMethodSymbol, CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; import "../notify/notify.mjs"; import { HostStyleSheet } from "./stylesheet/host.mjs"; import { getLocaleOfDocument } from "../../dom/locale.mjs"; import { Embed } from "../../i18n/providers/embed.mjs"; import { getDocumentTranslations } from "../../i18n/translations.mjs"; import { windowReady } from "../../dom/ready.mjs"; import { FocusManager } from "../../dom/focusmanager.mjs"; import { ResourceManager } from "../../dom/resourcemanager.mjs"; import { fireCustomEvent } from "../../dom/events.mjs"; import { isIterable } from "../../types/is.mjs"; import "./config-manager.mjs"; import { instanceSymbol } from "../../constants.mjs"; export { Host }; /** * @private * @type {symbol} */ const promisesSymbol = Symbol("promisesSymbol"); /** * @private * @type {symbol} */ const notifyElementSymbol = Symbol("notifyElement"); /** * @private * @type {symbol} */ const overlayElementSymbol = Symbol("overlayElement"); /** * @private * @type {symbol} */ const configManagerElementSymbol = Symbol("configManagerElement"); /** * @private * @type {symbol} */ const focusManagerSymbol = Symbol("focusManager"); /** * @private * @type {symbol} */ const resourceManagerSymbol = Symbol("resourceManager"); /** * The Host component is used to encapsulate the content of a web app. * * @copyright schukai GmbH * @summary A simple host component * @fires monster-host-connected * @fires monster-host-disconnected */ class Host extends CustomElement { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for("@schukai/component-host/Host@@instance"); } /** * The individual configuration values can be found in the table. * * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {Object} features Feature definitions */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, }); } /** * @param key * @return {Promise} */ getConfig(key) { if (this[configManagerElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no config manager element"); } return this[configManagerElementSymbol].getConfig(key); } /** * @param {string} key * @returns {*} */ hasConfig(key) { if (this[configManagerElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no config manager element"); } return this[configManagerElementSymbol].hasConfig(key); } /** * * @param {key} key * @returns {*} */ deleteConfig(key) { if (this[configManagerElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no config manager element"); } return this[configManagerElementSymbol].deleteConfig(key); } /** * * @param {string} key * @param {*} value * @return {Promise} */ setConfig(key, value) { if (this[configManagerElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no config manager element"); } return this[configManagerElementSymbol].setConfig(key, value); } /** * @private * @fires Host#monster-host-connected */ connectedCallback() { super.connectedCallback(); /** * show the scroll bar always * @type {string} */ document.documentElement.style.overflowY = "scroll"; const classNames = this.getOption("classes.body"); if (document.body.classList.contains(classNames)) { document.body.classList.remove(classNames); } fireCustomEvent(this, "monster-host-connected"); } /** * @private * @fires Host#monster-host-disconnected */ disconnectedCallback() { super.disconnectedCallback(); document.documentElement.style.overflowY = ""; const classNames = this.getOption("classes.body"); if (!document.body.classList.contains(classNames)) { document.body.classList.add(classNames); } if (isIterable(this[promisesSymbol]) === false) { this[promisesSymbol] = []; } this[promisesSymbol].push( new Promise((resolve, reject) => { this.addEventListener( "monster-host-connected", () => { resolve(); }, { once: true }, ); }), ); fireCustomEvent(this, "monster-host-disconnected"); } /** * * @return {Host} */ [assembleMethodSymbol]() { this[promisesSymbol] = []; this[promisesSymbol].push(windowReady); super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); initTranslations.call(this); this[focusManagerSymbol] = new FocusManager(this); this[resourceManagerSymbol] = new ResourceManager(this); try { this[promisesSymbol].push(this[resourceManagerSymbol].available()); } catch (e) { return Promise.reject(e); } if (this.isConnected === false) { this[promisesSymbol].push( new Promise((resolve, reject) => { this.addEventListener( "monster-host-connected", () => { resolve(); }, { once: true }, ); }), ); } } /** * The Promise is resolved when the element is connected to the DOM and all resources are available. * If the element is not connected to the DOM, the Promise is rejected. * * @return {Promise} */ onReady() { if (isIterable(this[promisesSymbol]) === false) { this[promisesSymbol] = []; } return Promise.all(this[promisesSymbol]).then(() => { this[promisesSymbol] = []; return this; }); } /** * @see {@link https://monsterjs.org/en/doc/monster/Monster.DOM.FocusManager.html|Monster.DOM.FocusManager} * @return {*} */ get focusManager() { return this[focusManagerSymbol]; } /** * @see {@link https://monsterjs.org/en/doc/monster/Monster.DOM.ResourceManager.html|Monster.DOM.ResourceManager} * @return {*} */ get resourceManager() { return this[resourceManagerSymbol]; } /** * * @return {Host} * @throws {Error} There is no overlay element defined. */ toggleOverlay() { if (this[overlayElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no overlay element defined."); } this[overlayElementSymbol].toggle(); return this; } /** * @return {Host} * @throws {Error} There is no overlay element defined. */ openOverlay() { if (this[overlayElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no overlay element defined."); } this[overlayElementSymbol].open(); return this; } /** * @return {Host} * @throws {Error} There is no overlay element defined. */ closeOverlay() { if (this[overlayElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no overlay element defined."); } this[overlayElementSymbol].close(); return this; } /** * @return {string} */ static getTag() { return "monster-host"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [HostStyleSheet]; } /** * @return {Locale} */ get locale() { return getLocaleOfDocument(); } /** * * @return {Translations} */ get translations() { return getDocumentTranslations(); } /** * * @param {string|Message} message */ pushNotification(message) { if (this[notifyElementSymbol] instanceof HTMLElement === false) { throw new Error("There is no notify element defined."); } this[notifyElementSymbol].push(message); return this; } } /** * @private * @return {Select} * @throws {Error} no shadow-root is defined */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } this[overlayElementSymbol] = this.querySelector("monster-overlay"); this[notifyElementSymbol] = this.querySelector("monster-notify"); this[configManagerElementSymbol] = this.querySelector( "monster-config-manager", ); } /** * @private */ function initTranslations() { if (isIterable(this[promisesSymbol]) === false) { this[promisesSymbol] = []; } this[promisesSymbol].push(Embed.assignTranslationsToElement()); } /** * @private */ function initEventHandler() { return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <template id="host-container"> <div data-monster-replace="path:host-container.content" data-monster-attributes="part path:host-container.name, data-monster-role path:host-container.name"></div> </template> <div data-monster-role="host-container"> <slot></slot> </div>`; } registerCustomElement(Host);