@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
412 lines (356 loc) • 9.33 kB
JavaScript
/**
* 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);