@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
321 lines (283 loc) • 7.65 kB
JavaScript
/**
* Copyright © Volker Schukai 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 Volker Schukai.
*
* 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 Volker Schukai
* @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);