From 0bf0a0feac91945b774de690d1356b74e7366004 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Thu, 5 Jun 2025 18:55:24 +0200 Subject: [PATCH] feat: Update node and pnpx binaries; enhance datatable column bar and select components - Updated node binary path to version 24.1.0 and pnpx binary path to version 22.14.0 in import.mjs to reflect the latest dependency versions. - Improved the layout of `<monster-tabs>` in 312.html by adding a div for an active state and a button label. - Included the import of the tabs component in 312.mjs for modular usage. - Enhanced the ColumnBar class in columnbar.mjs with a close event listener for better usability and management of settings visibility. - Expanded translation capabilities in getTranslations() to support more languages (e.g., Mandarin and Hindi) in columnbar.mjs. - Updated lookup functionality in select.mjs to leverage IntersectionObserver for performance optimization, ensuring options are fetched only when needed and not more often than necessary. These changes aim to keep our dependencies fresh, improve user experience with the newly integrated features, and optimize the performance of component data fetching. --- development/config/import.mjs | 4 +- development/issues/closed/312.html | 11 ++ development/issues/closed/312.mjs | 1 + source/components/datatable/columnbar.mjs | 147 +++++++++++++++------- source/components/form/select.mjs | 84 +++++++------ 5 files changed, 167 insertions(+), 80 deletions(-) diff --git a/development/config/import.mjs b/development/config/import.mjs index da425fd2d..b7202242f 100644 --- a/development/config/import.mjs +++ b/development/config/import.mjs @@ -1,8 +1,8 @@ export const projectRoot = "/home/vs/workspaces/oss/monster/monster"; export const sourcePath = "/home/vs/workspaces/oss/monster/monster/source"; export const developmentPath = "/home/vs/workspaces/oss/monster/monster/development"; -export const pnpxBin = "/nix/store/rbdrkcs5kkwpalxcd7c6bnm33lk2955n-nodejs-20.19.0/bin/npx"; -export const nodeBin = "/nix/store/qsbvdw53nbhnzad466fvgczrw9kxc1vh-nodejs-23.11.0/bin/node"; +export const pnpxBin = "/nix/store/c8jxsih8yy2rnncdmx2hyraizf689nvp-nodejs-22.14.0/bin/npx"; +export const nodeBin = "/nix/store/wnayblhh0555nwfccnzcqkzph52y4yby-nodejs-24.1.0/bin/node"; export const license = "/**" + "\n" + " * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved." + "\n" + " * Node module: @schukai/monster" + "\n" + diff --git a/development/issues/closed/312.html b/development/issues/closed/312.html index f4a2f2421..b18ace65f 100644 --- a/development/issues/closed/312.html +++ b/development/issues/closed/312.html @@ -27,6 +27,13 @@ <main> <!-- data-monster-option-features-lazyload="false" --> + <monster-tabs style="width: 100%" + data-monster-option-features-opendelay="500"> + <div data-monster-button-label="Leer" data-monster-state="active" class="active"> + EMPTY, zum testen von intersection observer + </div> + <div data-monster-button-label="Select"> + <monster-select data-monster-option-features-lazyload="true" @@ -43,6 +50,10 @@ data-monster-option-url="/issue-312?q={filter}" data-monster-option-filter-defaultoptionsurl="/issue-312?q=1002"> </monster-select> + </div> + + </monster-tabs> + </main> </body> </html> diff --git a/development/issues/closed/312.mjs b/development/issues/closed/312.mjs index 31c551a96..030cf9d33 100644 --- a/development/issues/closed/312.mjs +++ b/development/issues/closed/312.mjs @@ -12,4 +12,5 @@ import "../../../source/components/style/theme.pcss"; import "../../../source/components/style/normalize.pcss"; import "../../../source/components/style/typography.pcss"; import "../../../source/components/form/select.mjs"; +import "../../../source/components/layout/tabs.mjs"; diff --git a/source/components/datatable/columnbar.mjs b/source/components/datatable/columnbar.mjs index ed5637906..e6a57b156 100644 --- a/source/components/datatable/columnbar.mjs +++ b/source/components/datatable/columnbar.mjs @@ -23,6 +23,9 @@ 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 }; @@ -56,6 +59,12 @@ const dotsContainerElementSymbol = Symbol("dotsContainerElement"); */ const popperInstanceSymbol = Symbol("popperInstance"); +/** + * @private + * @type {symbol} + */ +const closeEventHandlerSymbol = Symbol("closeEventHandler"); + /** * A column bar for a datatable * @@ -72,7 +81,18 @@ class ColumnBar extends CustomElement { * @return {symbol} */ static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/column-bar"); + 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], + ]); } /** @@ -83,24 +103,61 @@ class ColumnBar extends CustomElement { * * @property {Object} templates Template definitions * @property {string} templates.main Main template - * @property {object} datasource The datasource - * @property {boolean} autoLoad If true, the datasource is called immediately after the control is created. + * @property {object} labels Locale definitions + * @property {string} locale.settings The text for the settings button */ get defaults() { - const obj = Object.assign({}, super.defaults, { + return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, - locale: getTranslations(), + 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]); - return obj; } /** + * 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() { @@ -133,43 +190,47 @@ class ColumnBar extends CustomElement { function getTranslations() { const locale = getLocaleOfDocument(); switch (locale.language) { - case "de": - return { - settings: "Einstellungen", - }; - case "fr": - return { - settings: "Paramètres", - }; - case "sp": - return { - settings: "Configuración", - }; - case "it": - return { - settings: "Impostazioni", - }; - case "pl": - return { - settings: "Ustawienia", - }; - case "no": - return { - settings: "Innstillinger", - }; - case "dk": - return { - settings: "Indstillinger", - }; - case "sw": - return { - settings: "Inställningar", - }; - default: + 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", - }; + return { settings: "Settings" }; } } @@ -325,7 +386,7 @@ function getTemplate() { <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" data-monster-replace="path:locale.settings">Settings</a> + <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> diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs index f8060a565..f5c110e1a 100644 --- a/source/components/form/select.mjs +++ b/source/components/form/select.mjs @@ -390,6 +390,7 @@ class Select extends CustomControl { * @property {"radio"|"checkbox"} type Selection mode: "radio" for single, "checkbox" for multiple. * @property {string} name Name of the hidden form field for form submission. * @property {string|null} url URL to dynamically fetch options via HTTP when opening or filtering. + * @property {Object} lookup Configuration for lookup requests. * @property {string} lookup.url URL template with ${filter} placeholder to fetch only selected entries on init when `url` is set and either `features.lazyLoad` or `filter.mode==="remote"`. * @property {boolean} lookup.grouping Group lookup requests: true to fetch all selected values in one request, false to fetch each individually. * @property {string} fetch.redirect Fetch redirect mode (e.g. "error"). @@ -1354,51 +1355,64 @@ function getTranslations() { function lookupSelection() { const self = this; - setTimeout(() => { - const selection = self.getOption("selection"); - if (selection.length === 0) { - return; - } + const observer = new IntersectionObserver((entries, obs) => { + for (const entry of entries) { + if (entry.isIntersecting) { + console.log("IntersectionObserver: entry.target is visible"); + obs.disconnect(); // Only observe once - if (self[isLoadingSymbol] === true) { - return; - } + setTimeout(() => { + const selection = self.getOption("selection"); + if (selection.length === 0) { + return; + } - if (self[lazyLoadDoneSymbol] === true) { - return; - } + if (self[isLoadingSymbol] === true) { + return; + } - let url = self.getOption("url"); - const lookupUrl = self.getOption("lookup.url"); - if (lookupUrl !== null) { - url = lookupUrl; - } + if (self[lazyLoadDoneSymbol] === true) { + return; + } - this[cleanupOptionsListSymbol] = false; + let url = self.getOption("url"); + const lookupUrl = self.getOption("lookup.url"); + if (lookupUrl !== null) { + url = lookupUrl; + } - if (this.getOption("lookup.grouping") === true) { - filterFromRemoteByValue - .call( - self, - url, - selection.map((s) => s?.["value"]), - ) - .catch((e) => { - addErrorAttribute(self, e); - }); - return; - } + self[cleanupOptionsListSymbol] = false; + + if (self.getOption("lookup.grouping") === true) { + filterFromRemoteByValue + .call( + self, + url, + selection.map((s) => s?.["value"]), + ) + .catch((e) => { + addErrorAttribute(self, e); + }); + return; + } - for (const s of selection) { - if (s?.["value"]) { - filterFromRemoteByValue.call(self, url, s?.["value"]).catch((e) => { - addErrorAttribute(self, e); - }); + for (const s of selection) { + if (s?.["value"]) { + filterFromRemoteByValue.call(self, url, s["value"]).catch((e) => { + addErrorAttribute(self, e); + }); + } + } + }, 100); } } - }, 100); + }, { threshold: 0.1 }); + + // Beobachte das Element selbst (dieses Element muss im DOM sein) + observer.observe(self); } + /** * * @param url -- GitLab