@schukai/monster 4.1.2 → 4.3.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,26 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.3.0] - 2025-05-12
6
+
7
+ ### Add Features
8
+
9
+ - Add support for array-list filter serialization in Datatable
10
+ ### Changes
11
+
12
+ - Enhance documentation for login component properties
13
+
14
+
15
+
16
+ ## [4.2.0] - 2025-05-12
17
+
18
+ ### Add Features
19
+
20
+ - Update Node binary paths and introduce username callback customization
21
+ - Update event naming and clean up spacing for clarity
22
+
23
+
24
+
5
25
  ## [4.1.2] - 2025-05-11
6
26
 
7
27
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.0","@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.1.2"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.0","@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.3.0"}
@@ -75,7 +75,7 @@ const emptyHistoryStateElementSymbol = Symbol("emptyHistoryStateElement");
75
75
  * @since 3.111.0
76
76
  * @copyright schukai GmbH
77
77
  * @summary A simple but powerful camera capture component. It can be used to capture images from the camera.
78
- * @fires monster-camera-capture-take-picture
78
+ * @fires monster-camera-capture-captured
79
79
  */
80
80
  class CameraCapture extends CustomElement {
81
81
  /**
@@ -373,17 +373,17 @@ function getTranslations() {
373
373
  /**
374
374
  * @private
375
375
  * @return {initEventHandler}
376
- * @fires monster-camera-capture-take-picture
376
+ * @fires monster-camera-capture-captured
377
377
  */
378
378
  function initEventHandler() {
379
379
  const self = this;
380
380
 
381
381
  this[takePictureButtonElementSymbol].setOption("actions.click", function () {
382
- fireCustomEvent(self, "monster-camera-capture-take-picture", {
383
- element: self,
384
- });
385
-
386
382
  self.capture();
383
+
384
+ fireCustomEvent(self, "monster-camera-capture-captured", {
385
+ element: self,
386
+ })
387
387
  });
388
388
 
389
389
  return this;
@@ -13,18 +13,18 @@
13
13
  */
14
14
 
15
15
  import {
16
- assembleMethodSymbol,
17
- CustomElement,
18
- registerCustomElement,
16
+ assembleMethodSymbol,
17
+ CustomElement,
18
+ registerCustomElement,
19
19
  } from "../../dom/customelement.mjs";
20
20
  import "../notify/notify.mjs";
21
- import { ViewerStyleSheet } from "./stylesheet/viewer.mjs";
22
- import { instanceSymbol } from "../../constants.mjs";
23
- import { isString } from "../../types/is.mjs";
24
- import { getGlobal } from "../../types/global.mjs";
25
- import { MediaType, parseMediaType } from "../../types/mediatype.mjs";
21
+ import {ViewerStyleSheet} from "./stylesheet/viewer.mjs";
22
+ import {instanceSymbol} from "../../constants.mjs";
23
+ import {isString} from "../../types/is.mjs";
24
+ import {getGlobal} from "../../types/global.mjs";
25
+ import {MediaType, parseMediaType} from "../../types/mediatype.mjs";
26
26
 
27
- export { Viewer };
27
+ export {Viewer};
28
28
 
29
29
  /**
30
30
  * @private
@@ -45,278 +45,286 @@ const viewerElementSymbol = Symbol("viewerElement");
45
45
  * @summary A simple viewer component for PDF, HTML and images.
46
46
  */
47
47
  class Viewer extends CustomElement {
48
- /**
49
- * This method is called by the `instanceof` operator.
50
- * @return {symbol}
51
- */
52
- static get [instanceSymbol]() {
53
- return Symbol.for("@schukai/monster/components/content/viewer@@instance");
54
- }
55
-
56
- /**
57
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
58
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
59
- *
60
- * The individual configuration values can be found in the table.
61
- *
62
- * @property {Object} templates Template definitions
63
- * @property {string} templates.main Main template
64
- * @property {string} content Content to be displayed in the viewer
65
- * @property {Object} classes Css classes
66
- * @property {string} classes.viewer Css class for the viewer
67
- */
68
- get defaults() {
69
- return Object.assign({}, super.defaults, {
70
- templates: {
71
- main: getTemplate(),
72
- },
73
- content: "<slot></slot>",
74
- classes: {
75
- viewer: "",
76
- },
77
- });
78
- }
79
-
80
- /**
81
- * Sets the content of an element based on the provided content and media type.
82
- *
83
- * @param {string} content - The content to be set.
84
- * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
85
- * @return {void} This method does not return a value.
86
- * @throws {Error} Throws an error if shadowRoot is not defined.
87
- */
88
- setContent(content, mediaType = "text/plain") {
89
- if (!this.shadowRoot) {
90
- throw new Error("no shadow-root is defined");
91
- }
92
-
93
- let type;
94
- try {
95
- const m = new parseMediaType(mediaType);
96
- switch (m.type) {
97
- case "image":
98
- return this.setImage(content);
99
- }
100
-
101
- mediaType = m.toString();
102
- } catch (error) {
103
- type = null;
104
- }
105
-
106
- if (mediaType === undefined || mediaType === null || mediaType === "") {
107
- mediaType = "text/plain";
108
- }
109
-
110
- switch (mediaType) {
111
- case "text/html":
112
- this.setHTML(content);
113
- break;
114
- case "text/plain":
115
- this.setPlainText(content);
116
- break;
117
- case "application/pdf":
118
- this.setPDF(content);
119
- break;
120
- case "image/png":
121
- case "image/jpeg":
122
- case "image/gif":
123
- this.setImage(content);
124
- break;
125
- default:
126
- this.setOption("content", content);
127
- }
128
- }
129
-
130
- /**
131
- * Configures and embeds a PDF document into the application with customizable display settings.
132
- *
133
- * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL or base64 string.
134
- * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
135
- * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
136
- * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
137
- * @return {void} This method returns nothing but sets the embedded PDF as the content.
138
- */
139
- setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
140
- const hashes =
141
- "#toolbar=" +
142
- (toolbar ? "1" : "0") +
143
- "&navpanes=" +
144
- (navigation ? "1" : "0") +
145
- "&scrollbar=" +
146
- (scrollbar ? "1" : "0");
147
-
148
- let pdfURL = "";
149
- if (isBlob(data)) {
150
- pdfURL = URL.createObjectURL(data);
151
- pdfURL += hashes;
152
- } else if (isURL(data)) {
153
- // check if the url already contains the hashes
154
- if (data?.hash?.indexOf("#") === -1) {
155
- pdfURL = data.toString() + hashes;
156
- } else {
157
- pdfURL = data.toString();
158
- }
159
- } else if (isString(data)) {
160
- //URL.createObjectURL(data);
161
- const blobObj = new Blob([atob(data)], { type: "application/pdf" });
162
- const url = window.URL.createObjectURL(blobObj);
163
-
164
- pdfURL = data;
165
- } else {
166
- throw new Error("Blob or URL expected");
167
- }
168
-
169
- const html =
170
- '<object part="pdf" data="' +
171
- pdfURL +
172
- '" width="100%" height="100%" type="application/pdf"></object>';
173
-
174
- this.setOption("content", html);
175
- }
176
-
177
- /**
178
- * Sets an image for the target by accepting a blob, URL, or string representation of the image.
179
- *
180
- * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
181
- * @return {void} Does not return a value.
182
- */
183
- setImage(data) {
184
- if (isBlob(data)) {
185
- data = URL.createObjectURL(data);
186
- } else if (isURL(data)) {
187
- // nothing to do
188
- } else if (isString(data)) {
189
- // nothing to do
190
- } else {
191
- throw new Error("Blob or URL expected");
192
- }
193
-
194
- this.setOption("content", '<img src="' + data + '" alt="image" />');
195
- }
196
-
197
- /**
198
- *
199
- * if the data is a string, it is interpreted as HTML.
200
- * if the data is an url, the HTML is loaded from the url and set as content.
201
- * if the data is an HTMLElement, the outerHTML is used as content.
202
- *
203
- * @param {HTMLElement|URL|string|Blob} data
204
- */
205
- setHTML(data) {
206
- if (data instanceof Blob) {
207
- blobToText(data)
208
- .then((html) => {
209
- this.setOption("content", html);
210
- })
211
- .catch((error) => {
212
- throw new Error(error);
213
- });
214
-
215
- return;
216
- } else if (data instanceof HTMLElement) {
217
- data = data.outerHTML;
218
- } else if (isString(data)) {
219
- // nothing to do
220
- } else if (isURL(data)) {
221
- // fetch element
222
- getGlobal()
223
- .fetch(data)
224
- .then((response) => {
225
- return response.text();
226
- })
227
- .then((html) => {
228
- this.setOption("content", html);
229
- })
230
- .catch((error) => {
231
- throw new Error(error);
232
- });
233
- } else {
234
- throw new Error("HTMLElement or string expected");
235
- }
236
-
237
- this.setOption("content", data);
238
- }
239
-
240
- /**
241
- * Sets the plain text content by processing the input data, which can be of various types, including Blob,
242
- * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
243
- *
244
- * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
245
- * a plain string, or a string formatted as a valid URL. The method determines
246
- * the data type and processes it accordingly.
247
- * @return {void} - This method does not return any value. It processes the content and updates the relevant option
248
- * property.
249
- */
250
- setPlainText(data) {
251
- if (data instanceof Blob) {
252
- blobToText(data)
253
- .then((html) => {
254
- const div = document.createElement("div");
255
- div.innerHTML = html;
256
- html = div.innerText;
257
-
258
- this.setOption("content", html);
259
- })
260
- .catch((error) => {
261
- throw new Error(error);
262
- });
263
-
264
- return;
265
- } else if (data instanceof HTMLElement) {
266
- data = data.outerText;
267
- } else if (isString(data)) {
268
- const div = document.createElement("div");
269
- div.innerHTML = data;
270
- data = div.innerText;
271
- } else if (isURL(data)) {
272
- // fetch element
273
- getGlobal()
274
- .fetch(data)
275
- .then((response) => {
276
- return response.text();
277
- })
278
- .then((text) => {
279
- const div = document.createElement("div");
280
- div.innerHTML = text;
281
- text = div.innerText;
282
-
283
- this.setOption("content", text);
284
- })
285
- .catch((error) => {
286
- throw new Error(error);
287
- });
288
- } else {
289
- throw new Error("HTMLElement or string expected");
290
- }
291
-
292
- this.setOption("content", data);
293
- }
294
-
295
- /**
296
- *
297
- * @return {Viewer}
298
- */
299
- [assembleMethodSymbol]() {
300
- super[assembleMethodSymbol]();
301
-
302
- initControlReferences.call(this);
303
- initEventHandler.call(this);
304
- }
305
-
306
- /**
307
- *
308
- * @return {string}
309
- */
310
- static getTag() {
311
- return "monster-viewer";
312
- }
313
-
314
- /**
315
- * @return {CSSStyleSheet[]}
316
- */
317
- static getCSSStyleSheet() {
318
- return [ViewerStyleSheet];
319
- }
48
+ /**
49
+ * This method is called by the `instanceof` operator.
50
+ * @return {symbol}
51
+ */
52
+ static get [instanceSymbol]() {
53
+ return Symbol.for("@schukai/monster/components/content/viewer@@instance");
54
+ }
55
+
56
+ /**
57
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
58
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
59
+ *
60
+ * The individual configuration values can be found in the table.
61
+ *
62
+ * @property {Object} templates Template definitions
63
+ * @property {string} templates.main Main template
64
+ * @property {string} content Content to be displayed in the viewer
65
+ * @property {Object} classes Css classes
66
+ * @property {string} classes.viewer Css class for the viewer
67
+ */
68
+ get defaults() {
69
+ return Object.assign({}, super.defaults, {
70
+ templates: {
71
+ main: getTemplate(),
72
+ },
73
+ content: "<slot></slot>",
74
+ classes: {
75
+ viewer: "",
76
+ },
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Sets the content of an element based on the provided content and media type.
82
+ *
83
+ * @param {string} content - The content to be set.
84
+ * @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
85
+ * @return {void} This method does not return a value.
86
+ * @throws {Error} Throws an error if shadowRoot is not defined.
87
+ */
88
+ setContent(content, mediaType = "text/plain") {
89
+ if (!this.shadowRoot) {
90
+ throw new Error("no shadow-root is defined");
91
+ }
92
+
93
+ let type;
94
+ try {
95
+ const m = new parseMediaType(mediaType);
96
+ switch (m.type) {
97
+ case "image":
98
+ return this.setImage(content);
99
+ }
100
+
101
+ mediaType = m.toString();
102
+ } catch (error) {
103
+ type = null;
104
+ }
105
+
106
+ if (mediaType === undefined || mediaType === null || mediaType === "") {
107
+ mediaType = "text/plain";
108
+ }
109
+
110
+ switch (mediaType) {
111
+ case "text/html":
112
+ this.setHTML(content);
113
+ break;
114
+ case "text/plain":
115
+ this.setPlainText(content);
116
+ break;
117
+ case "application/pdf":
118
+ this.setPDF(content);
119
+ break;
120
+ case "image/png":
121
+ case "image/jpeg":
122
+ case "image/gif":
123
+ this.setImage(content);
124
+ break;
125
+ default:
126
+ this.setOption("content", content);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Configures and embeds a PDF document into the application with customizable display settings.
132
+ *
133
+ * @param {Blob|URL|string} data The PDF data to be embedded. Can be provided as a Blob, URL or base64 string.
134
+ * @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
135
+ * @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
136
+ * @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
137
+ * @return {void} This method returns nothing but sets the embedded PDF as the content.
138
+ */
139
+ setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
140
+ const hashes =
141
+ "#toolbar=" +
142
+ (toolbar ? "1" : "0") +
143
+ "&navpanes=" +
144
+ (navigation ? "1" : "0") +
145
+ "&scrollbar=" +
146
+ (scrollbar ? "1" : "0");
147
+
148
+ let pdfURL = "";
149
+ if (isBlob(data)) {
150
+ pdfURL = URL.createObjectURL(data);
151
+ pdfURL += hashes;
152
+ } else if (isURL(data)) {
153
+ // check if the url already contains the hashes
154
+ if (data?.hash?.indexOf("#") === -1) {
155
+ pdfURL = data.toString() + hashes;
156
+ } else {
157
+ pdfURL = data.toString();
158
+ }
159
+ } else if (isString(data)) {
160
+ //URL.createObjectURL(data);
161
+ const blobObj = new Blob([atob(data)], {type: "application/pdf"});
162
+ const url = window.URL.createObjectURL(blobObj);
163
+
164
+ pdfURL = data;
165
+ } else {
166
+ throw new Error("Blob or URL expected");
167
+ }
168
+
169
+ const html =
170
+ '<object part="pdf" data="' +
171
+ pdfURL +
172
+ '" width="100%" height="100%" type="application/pdf"></object>';
173
+
174
+ this.setOption("content", html);
175
+ }
176
+
177
+ /**
178
+ * Sets an image for the target by accepting a blob, URL, or string representation of the image.
179
+ *
180
+ * @param {(Blob|string)} data - The image data, which can be a Blob, a valid URL, or a string representation of the image.
181
+ * @return {void} Does not return a value.
182
+ */
183
+ setImage(data) {
184
+ if (isBlob(data)) {
185
+ data = URL.createObjectURL(data);
186
+ } else if (isURL(data)) {
187
+ // nothing to do
188
+ } else if (isString(data)) {
189
+ // nothing to do
190
+ } else {
191
+ throw new Error("Blob or URL expected");
192
+ }
193
+
194
+ this.setOption("content", '<img src="' + data + '" alt="image" />');
195
+ }
196
+
197
+ /**
198
+ *
199
+ * if the data is a string, it is interpreted as HTML.
200
+ * if the data is an url, the HTML is loaded from the url and set as content.
201
+ * if the data is an HTMLElement, the outerHTML is used as content.
202
+ *
203
+ * @param {HTMLElement|URL|string|Blob} data
204
+ */
205
+ setHTML(data) {
206
+ if (data instanceof Blob) {
207
+ blobToText(data)
208
+ .then((html) => {
209
+ this.setOption("content", html);
210
+ })
211
+ .catch((error) => {
212
+ throw new Error(error);
213
+ });
214
+
215
+ return;
216
+ } else if (data instanceof HTMLElement) {
217
+ data = data.outerHTML;
218
+ } else if (isString(data)) {
219
+ // nothing to do
220
+ } else if (isURL(data)) {
221
+ // fetch element
222
+ getGlobal()
223
+ .fetch(data)
224
+ .then((response) => {
225
+ return response.text();
226
+ })
227
+ .then((html) => {
228
+ this.setOption("content", html);
229
+ })
230
+ .catch((error) => {
231
+ throw new Error(error);
232
+ });
233
+ } else {
234
+ throw new Error("HTMLElement or string expected");
235
+ }
236
+
237
+ this.setOption("content", data);
238
+ }
239
+
240
+ /**
241
+ * Sets the plain text content by processing the input data, which can be of various types, including Blob,
242
+ * HTMLElement, string, or a valid URL. The method extracts and sets the raw text content into a predefined option.
243
+ *
244
+ * @param {Blob|HTMLElement|string} data - The input data to be processed. It can be a Blob object, an HTMLElement,
245
+ * a plain string, or a string formatted as a valid URL. The method determines
246
+ * the data type and processes it accordingly.
247
+ * @return {void} - This method does not return any value. It processes the content and updates the relevant option
248
+ * property.
249
+ */
250
+ setPlainText(data) {
251
+
252
+ const mkPreSpan = (text) => {
253
+ const pre = document.createElement("pre");
254
+ pre.innerText = text;
255
+ pre.setAttribute("part", "text");
256
+ return pre.outerHTML;
257
+ }
258
+
259
+ if (data instanceof Blob) {
260
+ blobToText(data)
261
+ .then((text) => {
262
+ const div = document.createElement("div");
263
+ div.innerHTML = test;
264
+ text = div.innerText;
265
+
266
+ this.setOption("content", mkPreSpan(text));
267
+ })
268
+ .catch((error) => {
269
+ throw new Error(error);
270
+ });
271
+
272
+ return;
273
+ } else if (data instanceof HTMLElement) {
274
+ data = data.outerText;
275
+ } else if (isString(data)) {
276
+ const div = document.createElement("div");
277
+ div.innerHTML = data;
278
+ data = div.innerText;
279
+ } else if (isURL(data)) {
280
+
281
+ getGlobal()
282
+ .fetch(data)
283
+ .then((response) => {
284
+ return response.text();
285
+ })
286
+ .then((text) => {
287
+ const div = document.createElement("div");
288
+ div.innerHTML = text;
289
+ text = div.innerText;
290
+
291
+ this.setOption("content", mkPreSpan(text));
292
+ })
293
+ .catch((error) => {
294
+ throw new Error(error);
295
+ });
296
+ } else {
297
+ throw new Error("HTMLElement or string expected");
298
+ }
299
+
300
+ this.setOption("content", mkPreSpan(data));
301
+ }
302
+
303
+ /**
304
+ *
305
+ * @return {Viewer}
306
+ */
307
+ [assembleMethodSymbol]() {
308
+ super[assembleMethodSymbol]();
309
+
310
+ initControlReferences.call(this);
311
+ initEventHandler.call(this);
312
+ }
313
+
314
+ /**
315
+ *
316
+ * @return {string}
317
+ */
318
+ static getTag() {
319
+ return "monster-viewer";
320
+ }
321
+
322
+ /**
323
+ * @return {CSSStyleSheet[]}
324
+ */
325
+ static getCSSStyleSheet() {
326
+ return [ViewerStyleSheet];
327
+ }
320
328
  }
321
329
 
322
330
  /**
@@ -325,12 +333,12 @@ class Viewer extends CustomElement {
325
333
  * @return {boolean}
326
334
  */
327
335
  function isURL(variable) {
328
- try {
329
- new URL(variable);
330
- return true;
331
- } catch (error) {
332
- return false;
333
- }
336
+ try {
337
+ new URL(variable);
338
+ return true;
339
+ } catch (error) {
340
+ return false;
341
+ }
334
342
  }
335
343
 
336
344
  /**
@@ -339,7 +347,7 @@ function isURL(variable) {
339
347
  * @return {boolean}
340
348
  */
341
349
  function isBlob(variable) {
342
- return variable instanceof Blob;
350
+ return variable instanceof Blob;
343
351
  }
344
352
 
345
353
  /**
@@ -348,12 +356,12 @@ function isBlob(variable) {
348
356
  * @return {Promise<unknown>}
349
357
  */
350
358
  function blobToText(blob) {
351
- return new Promise((resolve, reject) => {
352
- const reader = new FileReader();
353
- reader.onloadend = () => resolve(reader.result);
354
- reader.onerror = reject;
355
- reader.readAsText(blob);
356
- });
359
+ return new Promise((resolve, reject) => {
360
+ const reader = new FileReader();
361
+ reader.onloadend = () => resolve(reader.result);
362
+ reader.onerror = reject;
363
+ reader.readAsText(blob);
364
+ });
357
365
  }
358
366
 
359
367
  /**
@@ -362,18 +370,18 @@ function blobToText(blob) {
362
370
  * @throws {Error} no shadow-root is defined
363
371
  */
364
372
  function initControlReferences() {
365
- if (!this.shadowRoot) {
366
- throw new Error("no shadow-root is defined");
367
- }
373
+ if (!this.shadowRoot) {
374
+ throw new Error("no shadow-root is defined");
375
+ }
368
376
 
369
- this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
377
+ this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer");
370
378
  }
371
379
 
372
380
  /**
373
381
  * @private
374
382
  */
375
383
  function initEventHandler() {
376
- return this;
384
+ return this;
377
385
  }
378
386
 
379
387
  /**
@@ -381,8 +389,8 @@ function initEventHandler() {
381
389
  * @return {string}
382
390
  */
383
391
  function getTemplate() {
384
- // language=HTML
385
- return `
392
+ // language=HTML
393
+ return `
386
394
  <div id="viewer" data-monster-role="viewer" part="viewer"
387
395
  data-monster-replace="path:content"
388
396
  data-monster-attributes="class path:classes.viewer">
@@ -1231,6 +1231,25 @@ function collectSearchQueries() {
1231
1231
  .join(",")
1232
1232
  );
1233
1233
  },
1234
+ "array-list": (value, key) => {
1235
+ if (isString(value)) {
1236
+ value = value.split(",");
1237
+ }
1238
+
1239
+ if (!isArray(value)) {
1240
+ return "";
1241
+ }
1242
+
1243
+ return (
1244
+ key +
1245
+ "=" +
1246
+ value
1247
+ .map((v) => {
1248
+ return `"${encodeURIComponent(v)}"`;
1249
+ })
1250
+ .join(",")
1251
+ );
1252
+ },
1234
1253
  "date-range": (value, key) => {
1235
1254
  const query = parseDateInput(value, key);
1236
1255
  if (!query || query === "false") {
@@ -166,56 +166,88 @@ class Login extends CustomElement {
166
166
  * The individual configuration values can be found in the table.
167
167
  *
168
168
  * @property {Object} templates Template definitions
169
- * @property {string} templates.main Main template
170
- * @property {Object} labels Label definitions
171
- * @property {Object} classes Class definitions
172
- * @property {string} classes.usernameInvalid Class for invalid
173
- * @property {string} classes.passwordInvalid Class for invalid
174
- * @property {string} classes.emailInvalid Class for invalid
175
- * @property {boolean} disabled Disabled
176
- * @property {Object} features Feature definitions
177
- * @property {boolean} features.forgotPassword Forgot Password enabled
178
- * @property {boolean} features.redirectToFirstSuccessUrl Redirect to first success URL
179
- * @property {Object} actions Action definitions
180
- * @property {number} digits Digits
181
- * @property {Object[]} successUrls Success URLs
182
- * @property {string} successUrls.label Label
183
- * @property {string} successUrls.url URL
184
- * @property {number} timeoutForMessage Timeout for message
185
- * @property {number} timeoutForSuccess Timeout for success
186
- * @property {Object} accessKeys Access keys
187
- * @property {string} accessKeys.loginLink Login link
188
- * @property {string} accessKeys.username Username
189
- * @property {string} accessKeys.password Password
190
- * @property {Object} fetch Fetch definitions
191
- * @property {Object} fetch.login Login fetch
192
- * @property {string} fetch.login.url URL
193
- * @property {string} fetch.login.method Method
194
- * @property {string} fetch.login.mode Mode
195
- * @property {Object} fetch.login.headers Headers
196
- * @property {string} fetch.login.headers.accept Accept
197
- * @property {string} fetch.login.credentials Credentials
198
- * @property {Object} fetch.forgotPassword Forgot Password fetch
199
- * @property {string} fetch.forgotPassword.url URL
200
- * @property {string} fetch.forgotPassword.method Method
201
- * @property {string} fetch.forgotPassword.mode Mode
169
+ * @property {string} templates.main The main HTML template used for rendering the login form
170
+ * @property {Object} labels Label definitions used for localization and form messages
171
+ * @property {string} labels.username Label for the username or email field
172
+ * @property {string} labels.password Label for the password field
173
+ * @property {string} labels.login Label for the login button
174
+ * @property {string} labels.forgotPasswordLink Text for the "forgot password" link
175
+ * @property {string} labels.mailAddress Label for the email input in password reset flow
176
+ * @property {string} labels.requestLink Label for the button that sends a password reset code
177
+ * @property {string} labels.digits Label for the digits input field
178
+ * @property {string} labels.loginLink Label for the back-to-login link
179
+ * @property {string} labels.secondFactor Label for the second factor authentication input
180
+ * @property {string} labels.sendDigits Label for the button that submits the digits input
181
+ * @property {string} labels.sendSecondFactorDigits Label for the button that submits the second factor code
182
+ * @property {string} labels.resetLoginProcess Label for the link to return to the login form
183
+ * @property {string} labels.messageEmptyUserName Message shown when username is empty
184
+ * @property {string} labels.messageEmptyPassword Message shown when password is empty
185
+ * @property {string} labels.messageEmptyBoth Message shown when both username and password are empty
186
+ * @property {string} labels.messageEmptyEmail Message shown when email field is empty
187
+ * @property {string} labels.messageInvalidEmail Message shown when an invalid email address is entered
188
+ * @property {string} labels.digitsEmpty Message shown when digits field is empty
189
+ * @property {string} labels.digitsInvalid Message shown when digits input is invalid
190
+ * @property {string} labels.messageLoginFailed Message shown on failed login attempt
191
+ * @property {string} labels.messageForbidden Message shown on successful login with insufficient permissions
192
+ * @property {string} labels.messageSomethingWentWrong Fallback error message
193
+ * @property {string} labels.messageThisFormIsNotConfigured Message shown if no backend URL is configured
194
+ * @property {string} labels.messagePasswordResetDisabled Message shown if password reset is disabled due to 2FA
195
+ * @property {Object} classes Class definitions for visual styling
196
+ * @property {string} classes.usernameInvalid CSS class applied when username is invalid
197
+ * @property {string} classes.passwordInvalid CSS class applied when password is invalid
198
+ * @property {string} classes.emailInvalid CSS class applied when email input is invalid
199
+ * @property {string} classes.button CSS class applied to all form buttons
200
+ * @property {boolean} disabled If true, disables interaction with the component
201
+ * @property {Object} features Feature flags to toggle optional behavior
202
+ * @property {boolean} features.forgotPassword Enables the forgot password flow
203
+ * @property {boolean} features.redirectToFirstSuccessUrl If true, redirects to the first success URL after login
204
+ * @property {Object} actions Action definitions for custom event handling
205
+ * @property {Function} actions.click Callback function for generic click actions within the login component
206
+ * @property {Object} callbacks Optional callback hooks for modifying internal behavior
207
+ * @property {Function} callbacks.username A function that receives and can transform the entered username before submission
208
+ * @property {number} digits Number of digits required for second factor or password reset code input
209
+ * @property {Object[]} successUrls List of URLs shown after successful login (e.g., home or logout)
210
+ * @property {string} successUrls.label Label for the success URL (displayed)
211
+ * @property {string} successUrls.url Actual target URL
212
+ * @property {number} timeoutForMessage Duration in milliseconds to show error messages
213
+ * @property {number} timeoutForSuccess Duration in milliseconds before triggering the post-login success state
214
+ * @property {Object} accessKeys Keyboard access keys for accessibility and shortcuts
215
+ * @property {string} accessKeys.loginLink Access key for switching to login form
216
+ * @property {string} accessKeys.username Access key for focusing the username field
217
+ * @property {string} accessKeys.password Access key for focusing the password field
218
+ * @property {Object} fetch Definitions for backend requests to support login workflows
219
+ * @property {Object} fetch.login Fetch config for login request
220
+ * @property {string} fetch.login.url Endpoint to post login credentials to
221
+ * @property {string} fetch.login.method HTTP method for login (e.g., "POST")
222
+ * @property {string} fetch.login.mode Fetch mode (e.g., "same-origin")
223
+ * @property {Object} fetch.login.headers HTTP headers to be sent with the login request
224
+ * @property {string} fetch.login.headers.accept Accept header value
225
+ * @property {string} fetch.login.headers.Content-Type Content-Type header value
226
+ * @property {string} fetch.login.credentials Credential mode (e.g., "same-origin")
227
+ * @property {Object} fetch.forgotPassword Fetch config for password reset code request
228
+ * @property {string} fetch.forgotPassword.url Endpoint to request a reset link/code
229
+ * @property {string} fetch.forgotPassword.method HTTP method
230
+ * @property {string} fetch.forgotPassword.mode Fetch mode
202
231
  * @property {Object} fetch.forgotPassword.headers Headers
203
- * @property {string} fetch.forgotPassword.headers.accept Accept
204
- * @property {string} fetch.forgotPassword.credentials Credentials
205
- * @property {Object} fetch.digits Digits fetch
206
- * @property {string} fetch.digits.url URL
207
- * @property {string} fetch.digits.method Method
208
- * @property {string} fetch.digits.mode Mode
232
+ * @property {string} fetch.forgotPassword.headers.accept Accept header
233
+ * @property {string} fetch.forgotPassword.headers.Content-Type Content-Type header
234
+ * @property {string} fetch.forgotPassword.credentials Credential mode
235
+ * @property {Object} fetch.digits Fetch config for submitting password reset code
236
+ * @property {string} fetch.digits.url Endpoint for validating the code
237
+ * @property {string} fetch.digits.method HTTP method
238
+ * @property {string} fetch.digits.mode Fetch mode
209
239
  * @property {Object} fetch.digits.headers Headers
210
- * @property {string} fetch.digits.headers.accept Accept
211
- * @property {string} fetch.digits.credentials Credentials
212
- * @property {Object} fetch.secondFactor Second Factor fetch
213
- * @property {string} fetch.secondFactor.url URL
214
- * @property {string} fetch.secondFactor.method Method
215
- * @property {string} fetch.secondFactor.mode Mode
240
+ * @property {string} fetch.digits.headers.accept Accept header
241
+ * @property {string} fetch.digits.headers.Content-Type Content-Type header
242
+ * @property {string} fetch.digits.credentials Credential mode
243
+ * @property {Object} fetch.secondFactor Fetch config for submitting second factor code
244
+ * @property {string} fetch.secondFactor.url Endpoint to validate second factor code
245
+ * @property {string} fetch.secondFactor.method HTTP method
246
+ * @property {string} fetch.secondFactor.mode Fetch mode
216
247
  * @property {Object} fetch.secondFactor.headers Headers
217
- * @property {string} fetch.secondFactor.headers.accept Accept
218
- * @property {string} fetch.secondFactor.credentials Credentials
248
+ * @property {string} fetch.secondFactor.headers.accept Accept header
249
+ * @property {string} fetch.secondFactor.headers.Content-Type Content-Type header
250
+ * @property {string} fetch.secondFactor.credentials Credential mode
219
251
  */
220
252
  get defaults() {
221
253
  return Object.assign({}, super.defaults, {
@@ -236,6 +268,10 @@ class Login extends CustomElement {
236
268
  },
237
269
  actions: {},
238
270
 
271
+ callbacks : {
272
+ username : null,
273
+ },
274
+
239
275
  digits: 6,
240
276
 
241
277
  successUrls: [
@@ -1076,11 +1112,19 @@ function initEventHandler() {
1076
1112
  });
1077
1113
 
1078
1114
  this[loginButtonSymbol].setOption("actions.click", (event) => {
1079
- // get username and password
1080
- const username = this.shadowRoot.querySelector(
1115
+
1116
+ let username = this.shadowRoot.querySelector(
1081
1117
  "input[name='username']",
1082
1118
  ).value;
1083
1119
 
1120
+ const userCallback = this.getOption("callbacks.username");
1121
+ if (isFunction(userCallback)) {
1122
+ const userCallbackResult = userCallback.call(this, username);
1123
+ if (userCallbackResult !== undefined) {
1124
+ username = userCallbackResult;
1125
+ }
1126
+ }
1127
+
1084
1128
  const password = this.shadowRoot.querySelector("monster-password").value;
1085
1129
 
1086
1130
  let missingBits = 0;