@schukai/monster 4.38.10 → 4.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.39.0] - 2025-09-08
6
+
7
+ ### Add Features
8
+
9
+ - new local-select component
10
+
11
+
12
+
5
13
  ## [4.38.10] - 2025-09-08
6
14
 
7
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.38.10"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.39.0"}
@@ -40,30 +40,35 @@ export { LocalePicker };
40
40
  /**
41
41
  * @private
42
42
  * @type {symbol}
43
+ * Reference to the main picker element
43
44
  */
44
45
  const localePickerElementSymbol = Symbol("localePickerElement");
45
46
 
46
47
  /**
47
48
  * @private
48
49
  * @type {symbol}
50
+ * Reference to the other languages select element
49
51
  */
50
52
  const otherLanguagesElementSymbol = Symbol("otherLanguagesElement");
51
53
 
52
54
  /**
53
55
  * @private
54
56
  * @type {symbol}
57
+ * Reference to the language button element
55
58
  */
56
59
  const buttonLanguageElementSymbol = Symbol("buttonLanguageElement");
57
60
 
58
61
  /**
59
62
  * @private
60
63
  * @type {symbol}
64
+ * Reference to the 'no thanks' button element
61
65
  */
62
66
  const buttonNoThanksElementSymbol = Symbol("buttonNoThanksElement");
63
67
 
64
68
  /**
65
69
  * @private
66
70
  * @type {symbol}
71
+ * Stores detected languages
67
72
  */
68
73
  const detectedLanguagesSymbol = Symbol("detectedLanguages");
69
74
 
@@ -93,7 +98,7 @@ class LocalePicker extends CustomElement {
93
98
  }
94
99
 
95
100
  /**
96
- *
101
+ * Initializes the component.
97
102
  * @return {LocalePicker}
98
103
  */
99
104
  [assembleMethodSymbol]() {
@@ -168,7 +173,7 @@ class LocalePicker extends CustomElement {
168
173
  /**
169
174
  * Lifecycle method that is called when the custom element is appended into a document-connected element.
170
175
  * Invokes the parent class's connectedCallback method and retrieves the user's preferred language.
171
- * Logs the preferred language to the console.
176
+ * Hides or shows the picker depending on the user's language and stored session.
172
177
  *
173
178
  * @return {void}
174
179
  */
@@ -197,7 +202,7 @@ class LocalePicker extends CustomElement {
197
202
  };
198
203
  }
199
204
 
200
- const stored = localStorage.getItem(buildStorageKey.call(this));
205
+ const stored = sessionStorage.getItem(buildStorageKey.call(this));
201
206
  if (stored && this.getOption("features.showAlways") !== true) {
202
207
  if (this.getOption("features.removeOnSelected")) {
203
208
  this.remove();
@@ -253,7 +258,7 @@ class LocalePicker extends CustomElement {
253
258
  * @returns {LocalePicker}
254
259
  */
255
260
  reset() {
256
- localStorage.removeItem(buildStorageKey.call(this));
261
+ sessionStorage.removeItem(buildStorageKey.call(this));
257
262
  this.show();
258
263
  return this;
259
264
  }
@@ -304,9 +309,33 @@ class LocalePicker extends CustomElement {
304
309
  }
305
310
  }
306
311
 
312
+ /**
313
+ * @private
314
+ * @return {void}
315
+ * Initializes references to all relevant elements in the picker.
316
+ */
317
+ function initControlReferences() {
318
+ this[localePickerElementSymbol] = this.shadowRoot.querySelector(
319
+ `[${ATTRIBUTE_ROLE}="control"]`,
320
+ );
321
+
322
+ this[otherLanguagesElementSymbol] = this.shadowRoot.querySelector(
323
+ `[${ATTRIBUTE_ROLE}="other-languages"]`,
324
+ );
325
+
326
+ this[buttonNoThanksElementSymbol] = this.shadowRoot.querySelector(
327
+ `[${ATTRIBUTE_ROLE}="button-no-thanks"]`,
328
+ );
329
+
330
+ this[buttonLanguageElementSymbol] = this.shadowRoot.querySelector(
331
+ `[${ATTRIBUTE_ROLE}="button-language"]`,
332
+ );
333
+ }
334
+
307
335
  /**
308
336
  * @private
309
337
  * @return {initEventHandler}
338
+ * Initializes all event handlers for the picker.
310
339
  */
311
340
  function initEventHandler() {
312
341
  const self = this;
@@ -339,7 +368,7 @@ function initEventHandler() {
339
368
  });
340
369
 
341
370
  this[buttonNoThanksElementSymbol].setOption("actions.click", () => {
342
- localStorage.setItem(buildStorageKey.call(this), "1");
371
+ sessionStorage.setItem(buildStorageKey.call(this), "1");
343
372
  this.hide();
344
373
  if (this.getOption("features.removeOnSelected")) {
345
374
  this.remove();
@@ -347,7 +376,7 @@ function initEventHandler() {
347
376
  });
348
377
 
349
378
  this[buttonLanguageElementSymbol].setOption("actions.click", () => {
350
- localStorage.setItem(buildStorageKey.call(this), "1");
379
+ sessionStorage.setItem(buildStorageKey.call(this), "1");
351
380
  window.location.href = this[detectedLanguagesSymbol].offerable?.[0]?.href;
352
381
  });
353
382
 
@@ -361,7 +390,7 @@ function initEventHandler() {
361
390
  if (element) {
362
391
  const selected = element?.value;
363
392
  if (selected) {
364
- localStorage.setItem(buildStorageKey.call(this), "1");
393
+ sessionStorage.setItem(buildStorageKey.call(this), "1");
365
394
  window.location.href = selected;
366
395
  }
367
396
  }
@@ -373,6 +402,7 @@ function initEventHandler() {
373
402
  /**
374
403
  * @private
375
404
  * @returns {string}
405
+ * Builds the session storage key for the picker.
376
406
  */
377
407
  function buildStorageKey() {
378
408
  return "locale-picker-" + this[detectedLanguagesSymbol].current;
@@ -382,6 +412,7 @@ function buildStorageKey() {
382
412
  * @private
383
413
  * @param lang
384
414
  * @returns {Object}
415
+ * Returns translations for the given language.
385
416
  */
386
417
  function getTranslations(lang) {
387
418
  const map = {
@@ -624,31 +655,10 @@ function getTranslations(lang) {
624
655
  return result;
625
656
  }
626
657
 
627
- /**
628
- * @private
629
- * @return {void}
630
- */
631
- function initControlReferences() {
632
- this[localePickerElementSymbol] = this.shadowRoot.querySelector(
633
- `[${ATTRIBUTE_ROLE}="control"]`,
634
- );
635
-
636
- this[otherLanguagesElementSymbol] = this.shadowRoot.querySelector(
637
- `[${ATTRIBUTE_ROLE}="other-languages"]`,
638
- );
639
-
640
- this[buttonNoThanksElementSymbol] = this.shadowRoot.querySelector(
641
- `[${ATTRIBUTE_ROLE}="button-no-thanks"]`,
642
- );
643
-
644
- this[buttonLanguageElementSymbol] = this.shadowRoot.querySelector(
645
- `[${ATTRIBUTE_ROLE}="button-language"]`,
646
- );
647
- }
648
-
649
658
  /**
650
659
  * @private
651
660
  * @return {string}
661
+ * Returns the template for the component.
652
662
  */
653
663
  function getTemplate() {
654
664
  // language=HTML
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License Version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.de.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
10
+ */
11
+
12
+ import { instanceSymbol } from "../../constants.mjs";
13
+ import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
14
+ import { CustomElement } from "../../dom/customelement.mjs";
15
+ import {
16
+ assembleMethodSymbol,
17
+ registerCustomElement,
18
+ } from "../../dom/customelement.mjs";
19
+ import { isFunction, isObject } from "../../types/is.mjs";
20
+ import { LocalePickerStyleSheet } from "./stylesheet/locale-picker.mjs";
21
+ import { detectUserLanguagePreference } from "../../i18n/util.mjs";
22
+
23
+ import "../form/select.mjs";
24
+
25
+ export { LocaleSelect as LocalSelector };
26
+
27
+ /**
28
+ * @private
29
+ * @type {symbol}
30
+ * Reference to the select element
31
+ */
32
+ const selectElementSymbol = Symbol("selectElement");
33
+
34
+ /**
35
+ * @private
36
+ * @type {symbol}
37
+ * Stores detected languages
38
+ */
39
+ const detectedLanguagesSymbol = Symbol("detectedLanguages");
40
+
41
+ /**
42
+ * Returns the localized label for the language selection.
43
+ * @return {string}
44
+ */
45
+ function getLocalizedLabel() {
46
+ const lang = document.documentElement.lang || navigator.language || "en";
47
+ switch (lang.split("-")[0]) {
48
+ case "de":
49
+ return "Sprache wählen";
50
+ case "fr":
51
+ return "Sélectionnez une langue";
52
+ case "es":
53
+ return "Seleccione un idioma";
54
+ case "it":
55
+ return "Seleziona una lingua";
56
+ case "pt":
57
+ return "Selecione um idioma";
58
+ case "nl":
59
+ return "Selecteer een taal";
60
+ case "pl":
61
+ return "Wybierz język";
62
+ case "ru":
63
+ return "Выберите язык";
64
+ case "cs":
65
+ return "Vyberte jazyk";
66
+ case "sk":
67
+ return "Vyberte jazyk";
68
+ case "bg":
69
+ return "Изберете език";
70
+ case "hr":
71
+ return "Odaberite jezik";
72
+ case "fi":
73
+ return "Valitse kieli";
74
+ case "sv":
75
+ return "Välj ett språk";
76
+ case "el":
77
+ return "Επιλέξτε γλώσσα";
78
+ case "hu":
79
+ return "Válasszon egy nyelvet";
80
+ case "ro":
81
+ return "Selectați o limbă";
82
+ case "da":
83
+ return "Vælg et sprog";
84
+ case "no":
85
+ return "Velg et språk";
86
+ case "hi":
87
+ return "एक भाषा चुनें";
88
+ case "bn":
89
+ return "একটি ভাষা নির্বাচন করুন";
90
+ case "ta":
91
+ return "ஒரு மொழியைத் தேர்ந்தெடுக்கவும்";
92
+ case "te":
93
+ return "భాషను ఎంచుకోండి";
94
+ case "mr":
95
+ return "एक भाषा निवडा";
96
+ case "zh":
97
+ return "选择一种语言";
98
+ case "ja":
99
+ return "言語を選択してください";
100
+ default:
101
+ return "Select a language";
102
+ }
103
+ }
104
+
105
+ /**
106
+ * LocaleSelect component
107
+ *
108
+ * Displays a monster-select with all available languages except the current document language.
109
+ *
110
+ * @since 3.97.0
111
+ * @copyright schukai GmbH
112
+ * @summary A simple language switcher as a select.
113
+ */
114
+ class LocaleSelect extends CustomElement {
115
+ /**
116
+ * Used by the instanceof operator.
117
+ * @returns {symbol}
118
+ */
119
+ static get [instanceSymbol]() {
120
+ return Symbol.for(
121
+ "@schukai/monster/components/accessibility/local-selector@@instance",
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Initializes the component.
127
+ * @return {LocaleSelect}
128
+ */
129
+ [assembleMethodSymbol]() {
130
+ super[assembleMethodSymbol]();
131
+ initControlReferences.call(this);
132
+ initSelectOptions.call(this);
133
+ initEventHandler.call(this);
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Default options
139
+ */
140
+ get defaults() {
141
+ return Object.assign({}, super.defaults, {
142
+ templates: {
143
+ main: getTemplate(),
144
+ },
145
+ labels: {
146
+ "select-an-option": getLocalizedLabel(),
147
+ },
148
+ callbacks: {},
149
+ disabled: false,
150
+ });
151
+ }
152
+
153
+ /**
154
+ * connectedCallback
155
+ */
156
+ connectedCallback() {
157
+ super.connectedCallback();
158
+ this[detectedLanguagesSymbol] = detectUserLanguagePreference();
159
+ initSelectOptions.call(this);
160
+ }
161
+
162
+ /**
163
+ * @return {string}
164
+ */
165
+ static getTag() {
166
+ return "monster-locale-select";
167
+ }
168
+
169
+ /**
170
+ * @return {CSSStyleSheet[]}
171
+ */
172
+ static getCSSStyleSheet() {
173
+ return [LocalePickerStyleSheet];
174
+ }
175
+
176
+ /**
177
+ * Export parts from monster-select to make them available for styling outside.
178
+ */
179
+ static get exportparts() {
180
+ // The parts from monster-select, as defined in source/components/form/select.mjs:
181
+ return [
182
+ "control",
183
+ "container",
184
+ "popper",
185
+ "option",
186
+ "option-label",
187
+ "option-control",
188
+ "badge",
189
+ "badge-label",
190
+ "remove-badge",
191
+ "summary",
192
+ "status-or-remove-badges",
193
+ "remote-info",
194
+ "no-options",
195
+ "selection",
196
+ "inline-filter",
197
+ "popper-filter",
198
+ "content"
199
+ ].join(",");
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Initializes the reference to the select element.
205
+ * @private
206
+ */
207
+ function initControlReferences() {
208
+ this[selectElementSymbol] = this.shadowRoot.querySelector(
209
+ `[${ATTRIBUTE_ROLE}="select"]`,
210
+ );
211
+ }
212
+
213
+ /**
214
+ * Initializes the options of the select element.
215
+ * @private
216
+ */
217
+ function initSelectOptions() {
218
+ const detected =
219
+ this[detectedLanguagesSymbol] || detectUserLanguagePreference();
220
+
221
+ let options = [];
222
+ if (Array.isArray(detected.available)) {
223
+ const currentLang = detected.current;
224
+ options = detected.available.filter(
225
+ (lang) => lang.baseLang !== currentLang && lang.fullLang !== currentLang,
226
+ );
227
+ } else if (Array.isArray(detected.allOfferable)) {
228
+ options = detected.allOfferable;
229
+ }
230
+
231
+ options = detected.allOfferable;
232
+ if (this[selectElementSymbol]) {
233
+ this[selectElementSymbol].setOption("mapping.labelTemplate", "${label}");
234
+ this[selectElementSymbol].setOption("mapping.valueTemplate", "${href}");
235
+ this[selectElementSymbol].setOption(
236
+ "labels.select-an-option",
237
+ this.getOption("labels.select-an-option"),
238
+ );
239
+ this[selectElementSymbol].importOptions(options);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Initializes the event handler for the select element.
245
+ * @private
246
+ */
247
+ function initEventHandler() {
248
+ if (!this[selectElementSymbol]) return;
249
+
250
+ this[selectElementSymbol].addEventListener("change", (event) => {
251
+ const selected = event.target?.value;
252
+ if (selected) {
253
+ window.location.href = selected;
254
+ }
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Returns the template for the component.
260
+ * @private
261
+ * @return {string}
262
+ */
263
+ function getTemplate() {
264
+ // language=HTML
265
+ return `
266
+ <monster-select exportparts="control,container,popper,option,option-label,option-control,badge,badge-label,remove-badge,summary,status-or-remove-badges,remote-info,no-options,selection,inline-filter,popper-filter,content" data-monster-role="select"></monster-select>
267
+ `;
268
+ }
269
+
270
+ registerCustomElement(LocaleSelect);