UNPKG

@schukai/monster

Version:

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

410 lines (361 loc) 10.6 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 { instanceSymbol } from "../../constants.mjs"; import { assembleMethodSymbol, CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; import { findTargetElementFromEvent } from "../../dom/events.mjs"; import { clone } from "../../util/clone.mjs"; import { ColumnBarStyleSheet } from "./stylesheet/column-bar.mjs"; import { createPopper } from "@popperjs/core"; import { getLocaleOfDocument } from "../../dom/locale.mjs"; import { hasObjectLink } from "../../dom/attributes.mjs"; import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs"; import { getGlobalObject } from "../../types/global.mjs"; export { ColumnBar }; /** * @private * @type {symbol} */ const settingsButtonElementSymbol = Symbol("settingButtonElement"); /** * @private * @type {symbol} */ const settingsButtonEventHandlerSymbol = Symbol("settingsButtonEventHandler"); /** * @private * @type {symbol} */ const settingsLayerElementSymbol = Symbol("settingsLayerElement"); /** * @private * @type {symbol} */ const dotsContainerElementSymbol = Symbol("dotsContainerElement"); /** * @private * @type {symbol} */ const popperInstanceSymbol = Symbol("popperInstance"); /** * @private * @type {symbol} */ const closeEventHandlerSymbol = Symbol("closeEventHandler"); /** * A column bar for a datatable * * @fragments /fragments/components/datatable/datatable/ * * @example /examples/components/datatable/empty * * @copyright schukai GmbH * @summary The ColumnBar component is used to show and configure the columns of a datatable. **/ class ColumnBar extends CustomElement { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/components/column-bar@@instance"); } /** * This method is called to customize the component. * @returns {Map<unknown, unknown>} */ get customization() { return new Map([...super.customization, ["templateFormatter.i18n", true]]); } /** * 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} labels Locale definitions * @property {string} locale.settings The text for the settings button */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, labels: getTranslations(), columns: [], }); } /** * Called every time the element is added to the DOM. Useful for running initialization code. * @return {void} * @since 4.14.0 */ connectedCallback() { super.connectedCallback(); this[closeEventHandlerSymbol] = (event) => { const path = event.composedPath(); const isOutsideElement = !path.includes(this); const isOutsideShadow = !path.includes(this.shadowRoot); if ( isOutsideElement && isOutsideShadow && this[settingsLayerElementSymbol] ) { this[settingsLayerElementSymbol].classList.remove("visible"); } }; getGlobalObject("document").addEventListener( "click", this[closeEventHandlerSymbol], ); getGlobalObject("document").addEventListener( "touch", this[closeEventHandlerSymbol], ); } /** * Called every time the element is removed from the DOM. Useful for running clean up code. * * @return {void} * @since 4.14.0 */ disconnectedCallback() { super.disconnectedCallback(); if (this[closeEventHandlerSymbol]) { getGlobalObject("document").removeEventListener( "click", this[closeEventHandlerSymbol], ); getGlobalObject("document").removeEventListener( "touch", this[closeEventHandlerSymbol], ); this[closeEventHandlerSymbol] = null; } } /** * @return {string} */ static getTag() { return "monster-column-bar"; } /** * * @return {void} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [ColumnBarStyleSheet]; } } /** * @private * @returns {{settings: string}} */ function getTranslations() { const locale = getLocaleOfDocument(); switch (locale.language) { case "de": // German return { settings: "Einstellungen" }; case "fr": // French return { settings: "Paramètres" }; case "es": // Spanish return { settings: "Configuración" }; case "zh": // Mandarin (Chinese) return { settings: "设置" }; case "hi": // Hindi return { settings: "सेटिंग्स" }; case "bn": // Bengali return { settings: "সেটিংস" }; case "pt": // Portuguese return { settings: "Configurações" }; case "ru": // Russian return { settings: "Настройки" }; case "ja": // Japanese return { settings: "設定" }; case "pa": // Western Punjabi return { settings: "ਸੈਟਿੰਗਾਂ" }; case "mr": // Marathi return { settings: "सेटिंग्ज" }; case "it": // Italian return { settings: "Impostazioni" }; case "nl": // Dutch return { settings: "Instellingen" }; case "sv": // Swedish return { settings: "Inställningar" }; case "pl": // Polish return { settings: "Ustawienia" }; case "da": // Danish return { settings: "Indstillinger" }; case "fi": // Finnish return { settings: "Asetukset" }; case "no": // Norwegian return { settings: "Innstillinger" }; case "cs": // Czech return { settings: "Nastavení" }; default: // English fallback case "en": return { settings: "Settings" }; } } /** * @private * @return {ColumnBar} */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } this[settingsButtonElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=settings-button]", ); this[settingsLayerElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=settings-layer]", ); this[dotsContainerElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=dots]", ); return this; } /** * @private */ function initEventHandler() { const self = this; self[popperInstanceSymbol] = createPopper( self[settingsButtonElementSymbol], self[settingsLayerElementSymbol], { placement: "auto", modifiers: [ { name: "offset", options: { offset: [10, 10], }, }, ], }, ); self[dotsContainerElementSymbol].addEventListener("click", function (event) { const element = findTargetElementFromEvent( event, "data-monster-role", "column", ); if (element) { const index = element.getAttribute("data-monster-index"); event.preventDefault(); const columns = clone(self.getOption("columns")); const column = columns.find((col) => { return parseInt(col.index) === parseInt(index); }); column.visible = !column.visible; self.setOption("columns", columns); } }); self[settingsButtonEventHandlerSymbol] = (event) => { const clickTarget = event.composedPath()?.[0]; if ( self[settingsLayerElementSymbol] === clickTarget || self[settingsLayerElementSymbol].contains(clickTarget) ) { return; } document.body.removeEventListener( "click", self[settingsButtonEventHandlerSymbol], ); }; self[settingsButtonElementSymbol].addEventListener("click", function (event) { const element = findTargetElementFromEvent( event, "data-monster-role", "settings-button", ); if (element) { self[settingsLayerElementSymbol].classList.toggle("visible"); event.preventDefault(); if (self[settingsLayerElementSymbol].classList.contains("visible")) { self[popperInstanceSymbol].update(); queueMicrotask(() => { document.body.addEventListener( "click", self[settingsButtonEventHandlerSymbol], ); }); } } }); self[settingsLayerElementSymbol].addEventListener("change", function (event) { const control = event.target; const index = control.getAttribute("data-monster-index"); const columns = clone(self.getOption("columns")); const column = columns.find((col) => { return parseInt(col.index) === parseInt(index); }); column.visible = control.checked; self.setOption("columns", columns); }); } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <template id="column"> <div data-monster-role="column"> <label><input type="checkbox" data-monster-attributes=" data-monster-index path:column.index, checked path:column.visible | ?:checked:"><span data-monster-replace="path:column.name" ></span></label> </div> </template> <template id="dots"> <li data-monster-insert=""> <a href="#" data-monster-role="column" data-monster-attributes=" class path:dots.visible | ?:is-hidden:is-visible, title path:dots.name, data-monster-index path:dots.index"> </a> </li> </template> <div data-monster-role="control" part="control" data-monster-select-this="true" data-monster-attributes="class path:columns | has-entries | ?::hidden"> <ul data-monster-insert="dots path:columns" data-monster-role="dots"></ul> <a href="#" data-monster-role="settings-button">i18n{settings}</a> <div data-monster-role="settings-layer"> <div data-monster-insert="column path:columns" data-monster-role="settings-popup-list"> </div> </div> </div> `; } registerCustomElement(ColumnBar);