UNPKG

@schukai/monster

Version:

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

321 lines (283 loc) 7.64 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 { OverlayStyleSheet } from "./stylesheet/overlay.mjs"; import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs"; import { findTargetElementFromEvent, fireCustomEvent, } from "../../dom/events.mjs"; import { instanceSymbol } from "../../constants.mjs"; export { Overlay }; /** * @private * @type {symbol} */ const overlayElementSymbol = Symbol("overlayElement"); /** * @private * @type {symbol} */ const overlayCloseElementSymbol = Symbol("overlayCloserElement"); /** * @private * @type {symbol} */ const overlayOpenElementSymbol = Symbol("overlayOpenElement"); /** * @private * @type {symbol} */ const closeEventHandlerSymbol = Symbol("closeEventHandler"); /** * @private * @type {symbol} */ const openEventHandlerSymbol = Symbol("openEventHandler"); /** * @private * @type {string} */ const ATTRIBUTE_VALUE_OVERLAY_OPEN = "overlay-open"; /** * The Overlay component * * @fragments /fragments/components/layout/overlay/ * * @example /examples/components/layout/overlay-simple * * @copyright schukai GmbH * @summary The Overlay component is used to show an overlay and a button to open the overlay. * @fires monster-overlay-before-open * @fires monster-overlay-open * @fires monster-overlay-before-close * @fires monster-overlay-closed */ class Overlay extends CustomElement { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/components/host/overlay@@instance"); } /** * To set the options via the HTML tag, the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * * The individual configuration values can be found in the table. * * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {string} overlay Overlay definitions * @property {string} overlay.content Content template * @property {string} overlay.class Class name * @property {string} classes.name Name * @property {Object} classes Class definitions * @property {string} classes.body Class name for the body * @property {string} classes.overlay Class name for the overlay * @property {Object} features Feature definitions * @property {boolean} features.escapeKey If true the overlay can be closed with the escape key * @property {boolean} features.openButton If true the overlay can be opened with a button */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, overlay: [ { name: "content", content: "<slot></slot>", class: "", }, ], classes: { body: "hidden", overlay: "hide-empty", }, features: { escapeKey: true, openButton: false, }, }); } /** * @private */ 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); } } /** * * @return {Overlay} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); } /** * * @return {Overlay} */ toggle() { if (this[overlayElementSymbol].classList.contains("open")) { this.close(); } else { this.open(); } return this; } /** * @return {Overlay} * @fires monster-overlay-before-open * @fires monster-overlay-open * @fires monster-overlay-before-close * @fires monster-overlay-closed */ open() { fireCustomEvent(this, "monster-overlay-before-open", {}); this[overlayElementSymbol].classList.remove("hide-empty"); setTimeout(() => { this[overlayElementSymbol].classList.add("open"); setTimeout(() => { this[overlayCloseElementSymbol].style.position = "fixed"; fireCustomEvent(this, "monster-overlay-open", {}); }, 0); }, 0); return this; } /** * @return {Overlay} */ close() { this[overlayCloseElementSymbol].style.position = "absolute"; fireCustomEvent(this, "monster-overlay-before-close", {}); setTimeout(() => { this[overlayElementSymbol].classList.remove("open"); setTimeout(() => { fireCustomEvent(this, "monster-overlay-closed", {}); }, 0); }, 0); return this; } /** * * @return {string} */ static getTag() { return "monster-overlay"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [OverlayStyleSheet]; } } /** * @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.shadowRoot.getElementById("overlay"); this[overlayCloseElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=overlay-close]", ); this[overlayOpenElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=overlay-open]", ); } /** * @private */ function initEventHandler() { /** * @param {Event} event */ this[closeEventHandlerSymbol] = (event) => { this.close(); }; this[overlayCloseElementSymbol].addEventListener( "click", this[closeEventHandlerSymbol], ); /** * @param {Event} event */ this[openEventHandlerSymbol] = (event) => { const element = findTargetElementFromEvent( event, ATTRIBUTE_ROLE, ATTRIBUTE_VALUE_OVERLAY_OPEN, ); if (element) { this.open(); } }; this.addEventListener("click", this[openEventHandlerSymbol]); if (this.getOption("features.escapeKey") === true) { this.addEventListener("keydown", (event) => { if (event.key === "Escape") { const isNotCombinedKey = !( event.ctrlKey || event.altKey || event.shiftKey ); if (isNotCombinedKey) { this.toggleOverlay(); } } }); } return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <template id="host-overlay"> <div data-monster-replace="path:host-overlay.content" data-monster-attributes="part path:host-overlay.name, data-monster-role path:host-container.overlay"></div> </template> <div data-monster-role="overlay-open" part="open" data-monster-attributes="class path:features.openButton | if:visible:hidden"></div> <div id="overlay" data-monster-role="overlay" part="overlay" data-monster-insert="host-overlay path:overlay" data-monster-attributes="class path:classes.overlay"> <div data-monster-role="overlay-close" part="close"></div> </div>`; } registerCustomElement(Overlay);