/** * 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, registerCustomElement, } from "../../dom/customelement.mjs"; import { DetailsStyleSheet } from "./stylesheet/details.mjs"; import { ATTRIBUTE_BUTTON_LABEL } from "../host/constants.mjs"; import { isString } from "../../types/is.mjs"; import { generateUniqueConfigKey } from "../host/util.mjs"; import { Collapse, nameSymbol } from "./collapse.mjs"; import { instanceSymbol } from "../../constants.mjs"; export { Details }; /** * @private * @type {symbol} */ const buttonElementSymbol = Symbol("buttonElement"); /** * @private * @type {symbol} */ const buttonEventHandlerSymbol = Symbol("buttonEventHandler"); /** * The Details component is used to show a details. * * <img src="./images/details.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-details />` directly in the HTML or using * Javascript via the `document.createElement('monster-details');` method. * * ```html * <monster-details></monster-details> * ``` * * Or you can create this CustomControl directly in Javascript: * * ```js * import '@schukai/component-state/source/details.mjs'; * document.createElement('monster-details'); * ``` * * The Body should have a class "hidden" to ensure that the styles are applied correctly. * * ```css * body.hidden { * visibility: hidden; * } * ``` * * @startuml details.png * skinparam monochrome true * skinparam shadowing false * HTMLElement <|-- CustomElement * CustomElement <|-- Collapse * Collapse <|-- Details * @enduml * * @copyright schukai GmbH * @memberOf Monster.Components.Layout * @summary A simple details component * @fires Monster.Components.Layout.Details.event:monster-details-before-open * @fires Monster.Components.Layout.Details.event:monster-details-open * @fires Monster.Components.Layout.Details.event:monster-details-before-close * @fires Monster.Components.Layout.Details.event:monster-details-closed */ class Details extends Collapse { /** * This method is called by the `instanceof` operator. * @returns {symbol} */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/components/layout/details@@instance"); } /** * */ constructor() { super(); // the name is only used for the host config and the event name this[nameSymbol] = "details"; } /** * 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 {Object} classes CSS classes * @property {string} classes.button CSS class for the button * @property {Object} button Button configuration * @property {string} button.label Button label * @property {Object} features Feature configuration * @property {boolean} features.accordion Enable accordion mode * @property {boolean} features.persistState Persist the state in the host configuration */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, labels: { button: "Details", }, }); } /** * * @returns {Monster.Components.Layout.Details} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initButtonLabel.call(this); initControlReferences.call(this); initEventHandler.call(this); } connectedCallback() { super.connectedCallback(); const containDocument = this.shadowRoot; if (containDocument !== null) { const previousElement = this.previousElementSibling; if (previousElement && previousElement.tagName === "MONSTER-DETAILS") { this[buttonElementSymbol].style.borderTop = "0"; } } } /** * * @return {string} */ static getTag() { return "monster-details"; } /** * @return {Array<CSSStyleSheet>} */ static getCSSStyleSheet() { const css = super.getCSSStyleSheet(); css.push(DetailsStyleSheet); return css; } } /** * @private * @return {Select} * @throws {Error} no shadow-root is defined */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } this[buttonElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=button]", ); } /** * @private */ function initEventHandler() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } this[buttonEventHandlerSymbol] = (event) => { this.toggle(); }; this[buttonElementSymbol].addEventListener( "click", this[buttonEventHandlerSymbol], ); return this; } /** * @private * @return {string} */ function initButtonLabel() { let label; const setLabel = false; if (this.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) { label = this.getAttribute(ATTRIBUTE_BUTTON_LABEL); } else { label = this.innerText; } if (!isString(label)) { label = ""; } label = label.trim(); if (label === "") { label = this.getOption("labels.button", "Details"); } if (label.length > 100) { label = `${label.substring(0, 99)}…`; } this.setAttribute(ATTRIBUTE_BUTTON_LABEL, label); this.setOption("labels.button", label); return label; } /** * @private * @returns {string} */ function getConfigKey() { return generateUniqueConfigKey("details", this.id, "state"); } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control" class="overflow-hidden"> <div data-monster-role="summary" part="summary"> <button part="button" data-monster-attributes="class path:classes.button" data-monster-role="button" data-monster-replace="path:labels.button | default:click me">click me </button> </div> <div data-monster-role="detail"> <div data-monster-attributes="class path:classes.container" part="container" data-monster-role="container"> <slot></slot> </div> <div class="deco-line" data-monster-role="deco"></div> </div> </div>`; } registerCustomElement(Details);