diff --git a/development/config/import.mjs b/development/config/import.mjs index da425fd2db8c6daa89207471d98fe9d04363eb5a..b7202242f58c0a00aac7a1667ab67374d937c814 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 f4a2f24218ca4f4156b6435fe33b64e2484e76fc..b18ace65f4f66017ad5f1baf97bd1f211acc0e13 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 31c551a96a25c0044e82eaf93433b1d706178d9d..030cf9d33abfbcbcfed6f915a6ec5f72e1f106a1 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 ed5637906907be257613f7bff9698de068b26af2..e6a57b156eb8420312d1152fc62c3e2b3f4234c1 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 f8060a565f8b64172fcca87225d47f65b17830df..f5c110e1adb0fe80d9e937e661d66ffb94914733 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