@schukai/monster 4.47.0 → 4.48.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.48.0] - 2025-11-28
6
+
7
+ ### Add Features
8
+
9
+ - Make autocomplete configurable in login form
10
+ ### Changes
11
+
12
+ - nachtrag issue nummer f+r commit 18c2348b4 ist [#344](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/344)
13
+
14
+
15
+
16
+ ## [4.47.1] - 2025-11-25
17
+
18
+ ### Bug Fixes
19
+
20
+ - rollback updater [#343](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/343)
21
+ - **select:** return if selection no array
22
+
23
+
24
+
5
25
  ## [4.47.0] - 2025-11-25
6
26
 
7
27
  ### Add Features
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.47.0"}
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.48.0"}
@@ -232,6 +232,10 @@ class Login extends CustomElement {
232
232
  * @property {string} placeholder.username Placeholder for the username field
233
233
  * @property {string} placeholder.password Placeholder for the password field
234
234
  * @property {string} placeholder.email Placeholder for the email field
235
+ * @property {Object} autocomplete Autocomplete settings for the form fields
236
+ * @property {string} autocomplete.username Autocomplete value for the username field
237
+ * @property {string} autocomplete.password Autocomplete value for the password field
238
+ * @property {string} autocomplete.email Autocomplete value for the email field
235
239
  * @property {Object} fetch.login Fetch config for login request
236
240
  * @property {string} fetch.login.url Endpoint to post login credentials to
237
241
  * @property {string} fetch.login.method HTTP method for login (e.g., "POST")
@@ -317,6 +321,12 @@ class Login extends CustomElement {
317
321
  email: "",
318
322
  },
319
323
 
324
+ autocomplete: {
325
+ username: "off",
326
+ password: "off",
327
+ email: "off",
328
+ },
329
+
320
330
  fetch: {
321
331
  login: {
322
332
  url: "",
@@ -1740,12 +1750,12 @@ function getTemplate() {
1740
1750
  <slot name="login-header"></slot>
1741
1751
  <label part="login-label" data-monster-replace="path:labels.username"></label>
1742
1752
  <input part="login-username"
1743
- data-monster-attributes="placeholder path:placeholder.username"
1744
- type="text" name="username" autofocus autocomplete="off">
1753
+ data-monster-attributes="placeholder path:placeholder.username, autocomplete path:autocomplete.username"
1754
+ type="text" name="username" autofocus>
1745
1755
  <div data-monster-attributes="class path:classes.usernameInvalid"></div>
1746
1756
  <label part="login-password-label" data-monster-replace="path:labels.password"></label>
1747
- <monster-password
1748
- data-monster-attributes="data-monster-option-placeholder path:placeholder.password"
1757
+ <monster-password
1758
+ data-monster-attributes="data-monster-option-placeholder path:placeholder.password, data-monster-option-autocomplete path:autocomplete.password"
1749
1759
  exportparts="input-group:input-group-password,
1750
1760
  control:input-group-password-control,
1751
1761
  input:input-group-password-input" part="login-password"
@@ -1789,8 +1799,9 @@ function getTemplate() {
1789
1799
  <input type="email" name="email"
1790
1800
  part="field-set-forgot-password-email"
1791
1801
  data-monster-attributes="accesskey path:accessKeys.username,
1792
- placeholder path:placeholder.email"
1793
- autocomplete="off">
1802
+ placeholder path:placeholder.email,
1803
+ autocomplete path:autocomplete.email"
1804
+ >
1794
1805
  <div data-monster-attributes="class path:classes.emailInvalid"></div>
1795
1806
  <monster-message-state-button id="requestLinkButton"
1796
1807
  part="request-link-button"
@@ -3056,11 +3056,8 @@ function setSelection(selection) {
3056
3056
  }
3057
3057
 
3058
3058
  if (!isArray(selection)) {
3059
- console.warn(
3060
- "selection is not an array, resetting to empty array",
3061
- selection,
3062
- );
3063
- selection = [];
3059
+ addErrorAttribute(this, "selection is not an array");
3060
+ return Promise.reject(new Error("selection is not an array"));
3064
3061
  }
3065
3062
 
3066
3063
  let resultSelection = [];
@@ -72,24 +72,6 @@ const pendingDiffsSymbol = Symbol("pendingDiffs");
72
72
  */
73
73
  const processingSymbol = Symbol("processing");
74
74
 
75
- /**
76
- * @private
77
- * @type {symbol}
78
- */
79
- const pipeCacheSymbol = Symbol("pipeCache");
80
-
81
- /**
82
- * @private
83
- * @type {symbol}
84
- */
85
- const processingScheduledSymbol = Symbol("processingScheduled");
86
-
87
- /**
88
- * @private
89
- * Performance optimization: static Set for boolean checks
90
- */
91
- const TRUE_VALUES = new Set(["true", "1", "on"]);
92
-
93
75
  /**
94
76
  * The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be
95
77
  * programmatically adapted via attributes.
@@ -149,9 +131,6 @@ class Updater extends Base {
149
131
 
150
132
  this[pendingDiffsSymbol] = [];
151
133
  this[processingSymbol] = false;
152
- this[processingScheduledSymbol] = false;
153
- this[pipeCacheSymbol] = new Map();
154
- this[timerElementEventHandlerSymbol] = new WeakMap();
155
134
 
156
135
  this[internalSymbol].subject.attachObserver(
157
136
  new Observer(() => {
@@ -159,19 +138,7 @@ class Updater extends Base {
159
138
  const diffResult = diff(this[internalSymbol].last, real);
160
139
  this[internalSymbol].last = clone(real);
161
140
  this[pendingDiffsSymbol].push(diffResult);
162
-
163
- if (!this[processingScheduledSymbol]) {
164
- this[processingScheduledSymbol] = true;
165
-
166
- return new Promise((resolve) => {
167
- queueMicrotask(() => {
168
- this[processingScheduledSymbol] = false;
169
- this.#processQueue().finally(resolve);
170
- });
171
- });
172
- }
173
-
174
- return Promise.resolve();
141
+ return this.#processQueue();
175
142
  }),
176
143
  );
177
144
  }
@@ -227,7 +194,7 @@ class Updater extends Base {
227
194
  *
228
195
  * ```js
229
196
  * updater.run().then(() => {
230
- * updater.enableEventProcessing();
197
+ * updater.enableEventProcessing();
231
198
  * });
232
199
  * ```
233
200
  *
@@ -276,7 +243,7 @@ class Updater extends Base {
276
243
  *
277
244
  * ```js
278
245
  * updater.run().then(() => {
279
- * updater.enableEventProcessing();
246
+ * updater.enableEventProcessing();
280
247
  * });
281
248
  * ```
282
249
  *
@@ -328,20 +295,6 @@ class Updater extends Base {
328
295
  this[internalSymbol].callbacks.set(name, callback);
329
296
  return this;
330
297
  }
331
-
332
- /**
333
- * @private
334
- * @param {string} cmd
335
- * @returns {Pipe}
336
- */
337
- getPipe(cmd) {
338
- let pipe = this[pipeCacheSymbol].get(cmd);
339
- if (!pipe) {
340
- pipe = new Pipe(cmd);
341
- this[pipeCacheSymbol].set(cmd, pipe);
342
- }
343
- return pipe;
344
- }
345
298
  }
346
299
 
347
300
  /**
@@ -355,9 +308,8 @@ function getCheckStateCallback() {
355
308
  return function (current) {
356
309
  // this is a reference to the current object (therefore no array function here)
357
310
  if (this instanceof HTMLInputElement) {
358
- if (["radio", "checkbox"].includes(this.type)) {
359
- if (current == null) return undefined;
360
- return String(this.value) === String(current) ? "true" : undefined;
311
+ if (["radio", "checkbox"].indexOf(this.type) !== -1) {
312
+ return `${this.value}` === `${current}` ? "true" : undefined;
361
313
  }
362
314
  } else if (this instanceof HTMLOptionElement) {
363
315
  if (isArray(current) && current.indexOf(this.value) !== -1) {
@@ -397,26 +349,22 @@ function getControlEventHandler() {
397
349
  return;
398
350
  }
399
351
 
400
- const switches = this[timerElementEventHandlerSymbol];
401
- let dms = switches.get(element);
402
-
403
- if (dms instanceof DeadMansSwitch) {
352
+ if (this[timerElementEventHandlerSymbol] instanceof DeadMansSwitch) {
404
353
  try {
405
- dms.touch();
354
+ this[timerElementEventHandlerSymbol].touch();
406
355
  return;
407
356
  } catch (e) {
408
- switches.delete(element);
357
+ delete this[timerElementEventHandlerSymbol];
409
358
  }
410
359
  }
411
360
 
412
- dms = new DeadMansSwitch(50, () => {
361
+ this[timerElementEventHandlerSymbol] = new DeadMansSwitch(50, () => {
413
362
  try {
414
363
  retrieveAndSetValue.call(this, element);
415
364
  } catch (e) {
416
365
  addErrorAttribute(element, e);
417
366
  }
418
367
  });
419
- switches.set(element, dms);
420
368
  };
421
369
 
422
370
  return this[symbol];
@@ -499,21 +447,18 @@ function retrieveAndSetValue(element) {
499
447
  case "int":
500
448
  case "float":
501
449
  case "integer":
502
- const num = Number(value);
503
- if (value === "" || value === null || value === undefined) {
504
- value = undefined;
505
- } else if (Number.isNaN(num)) {
506
- value = undefined;
507
- } else {
508
- value = num;
450
+ value = Number(value);
451
+ if (isNaN(value)) {
452
+ value = 0;
509
453
  }
510
454
  break;
511
455
  case "boolean":
512
456
  case "bool":
513
457
  case "checkbox":
514
- // Use static set (Performance fix)
515
- value = TRUE_VALUES.has(String(value).toLowerCase()) || value === true;
458
+ value =
459
+ value === "true" || value === "1" || value === "on" || value === true;
516
460
  break;
461
+
517
462
  case "string[]":
518
463
  case "[]string":
519
464
  if (isString(value)) {
@@ -564,14 +509,11 @@ function retrieveAndSetValue(element) {
564
509
  case "object":
565
510
  case "json":
566
511
  if (isString(value)) {
567
- try {
568
- value = JSON.parse(value);
569
- } catch (e) {
570
- throw new Error("unsupported value for object");
571
- }
512
+ value = JSON.parse(value);
572
513
  } else {
573
514
  throw new Error("unsupported value for object");
574
515
  }
516
+
575
517
  break;
576
518
  default:
577
519
  break;
@@ -593,11 +535,21 @@ function retrieveAndSetValue(element) {
593
535
  * @private
594
536
  */
595
537
  function parseIntArray(val) {
596
- if (val === undefined || val === null) return [];
597
-
598
- const list = isArray(val) ? val : String(val).split(",");
599
-
600
- return list.map((v) => parseInt(v, 10)).filter((v) => !Number.isNaN(v));
538
+ if (isString(val)) {
539
+ return val.trim() === ""
540
+ ? []
541
+ : val
542
+ .split(",")
543
+ .map((v) => parseInt(v, 10))
544
+ .filter((v) => !isNaN(v));
545
+ } else if (isInteger(val)) {
546
+ return [val];
547
+ } else if (val === undefined || val === null) {
548
+ return [];
549
+ } else if (isArray(val)) {
550
+ return val.map((v) => parseInt(v, 10)).filter((v) => !isNaN(v));
551
+ }
552
+ throw new Error("unsupported value for int array");
601
553
  }
602
554
 
603
555
  /**
@@ -698,7 +650,7 @@ function insertElement(change) {
698
650
  throw new Error("pipes are not allowed when cloning a node.");
699
651
  }
700
652
 
701
- const pipe = this.getPipe(cmd);
653
+ const pipe = new Pipe(cmd);
702
654
  this[internalSymbol].callbacks.forEach((f, n) => {
703
655
  pipe.setCallback(n, f);
704
656
  });
@@ -878,18 +830,25 @@ function runUpdateContent(container, parts, subject) {
878
830
 
879
831
  // Unfortunately, static data is always changed as well, since it is not possible to react to changes here.
880
832
  const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`;
833
+ const e = container.querySelectorAll(`${query}`);
881
834
 
882
- // Performance optimization: avoid new Set([...NodeList])
883
- const elements = container.querySelectorAll(`${query}`);
835
+ const iterator = new Set([...e]);
884
836
 
885
- const process = (element) => {
837
+ if (container.matches(query)) {
838
+ iterator.add(container);
839
+ }
840
+
841
+ /**
842
+ * @type {HTMLElement}
843
+ */
844
+ for (const [element] of iterator.entries()) {
886
845
  if (mem.has(element)) return;
887
846
  mem.add(element);
888
847
 
889
848
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
890
849
  const cmd = trimSpaces(attributes);
891
850
 
892
- const pipe = this.getPipe(cmd);
851
+ const pipe = new Pipe(cmd);
893
852
  this[internalSymbol].callbacks.forEach((f, n) => {
894
853
  pipe.setCallback(n, f);
895
854
  });
@@ -915,16 +874,6 @@ function runUpdateContent(container, parts, subject) {
915
874
  } else {
916
875
  element.innerHTML = value;
917
876
  }
918
- };
919
-
920
- // Iterate NodeList directly
921
- for (const element of elements) {
922
- process(element);
923
- }
924
-
925
- // Check container
926
- if (container.matches(query)) {
927
- process(container);
928
877
  }
929
878
  }
930
879
  }
@@ -959,18 +908,27 @@ function runUpdateAttributes(container, parts, subject) {
959
908
  const current = parts.join(".");
960
909
  parts.pop();
961
910
 
911
+ let iterator = new Set();
912
+
962
913
  const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`;
963
914
 
964
- // Performance optimization: avoid new Set([...NodeList])
965
- const elements = container.querySelectorAll(query);
915
+ const e = container.querySelectorAll(query);
916
+
917
+ if (e.length > 0) {
918
+ iterator = new Set([...e]);
919
+ }
966
920
 
967
- const process = (element) => {
921
+ if (container.matches(query)) {
922
+ iterator.add(container);
923
+ }
924
+
925
+ for (const [element] of iterator.entries()) {
968
926
  if (mem.has(element)) return;
969
927
  mem.add(element);
970
928
 
971
929
  // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
972
930
  if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) {
973
- return;
931
+ continue;
974
932
  }
975
933
 
976
934
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES);
@@ -981,7 +939,7 @@ function runUpdateAttributes(container, parts, subject) {
981
939
  const name = trimSpaces(def.substr(0, i));
982
940
  const cmd = trimSpaces(def.substr(i));
983
941
 
984
- const pipe = this.getPipe(cmd);
942
+ const pipe = new Pipe(cmd);
985
943
 
986
944
  this[internalSymbol].callbacks.forEach((f, n) => {
987
945
  pipe.setCallback(n, f, element);
@@ -995,30 +953,14 @@ function runUpdateAttributes(container, parts, subject) {
995
953
  addErrorAttribute(element, e);
996
954
  }
997
955
 
998
- const shouldRemove =
999
- value === undefined || value === null || Number.isNaN(value);
1000
-
1001
- if (shouldRemove) {
956
+ if (value === undefined) {
1002
957
  element.removeAttribute(name);
1003
- } else {
1004
- const strValue = String(value);
1005
-
1006
- if (element.getAttribute(name) !== strValue) {
1007
- element.setAttribute(name, strValue);
1008
- }
958
+ } else if (element.getAttribute(name) !== value) {
959
+ element.setAttribute(name, value);
1009
960
  }
961
+
1010
962
  handleInputControlAttributeUpdate.call(this, element, name, value);
1011
963
  }
1012
- };
1013
-
1014
- // Iterate NodeList directly
1015
- for (const element of elements) {
1016
- process(element);
1017
- }
1018
-
1019
- // Check container
1020
- if (container.matches(query)) {
1021
- process(container);
1022
964
  }
1023
965
  }
1024
966
  }
@@ -1031,59 +973,52 @@ function runUpdateAttributes(container, parts, subject) {
1031
973
  * @return {void}
1032
974
  * @this Updater
1033
975
  */
1034
- function handleInputControlAttributeUpdate(element, name, value) {
1035
- // Prevent NaN warnings by normalizing invalid numbers to undefined
1036
- if (typeof value === "number" && isNaN(value)) {
1037
- value = undefined;
1038
- }
1039
976
 
977
+ function handleInputControlAttributeUpdate(element, name, value) {
1040
978
  if (element instanceof HTMLSelectElement) {
1041
979
  switch (element.type) {
1042
980
  case "select-multiple":
1043
- // Ensure value is an array before calling indexOf to avoid errors
1044
- if (Array.isArray(value) || typeof value === "string") {
1045
- for (const [index, opt] of Object.entries(element.options)) {
1046
- opt.selected = value.indexOf(opt.value) !== -1;
1047
- }
981
+ for (const [index, opt] of Object.entries(element.options)) {
982
+ opt.selected = value.indexOf(opt.value) !== -1;
1048
983
  }
984
+
1049
985
  break;
1050
986
  case "select-one":
1051
987
  // Only one value may be selected
988
+
1052
989
  for (const [index, opt] of Object.entries(element.options)) {
1053
- if (opt.value == value) {
1054
- // Loose equality to match string numbers
990
+ if (opt.value === value) {
1055
991
  element.selectedIndex = index;
1056
992
  break;
1057
993
  }
1058
994
  }
995
+
1059
996
  break;
1060
997
  }
1061
998
  } else if (element instanceof HTMLInputElement) {
1062
999
  switch (element.type) {
1063
1000
  case "radio":
1064
1001
  if (name === "checked") {
1065
- element.checked = value !== undefined && value !== null;
1002
+ element.checked = value !== undefined;
1066
1003
  }
1067
1004
  break;
1068
1005
 
1069
1006
  case "checkbox":
1070
1007
  if (name === "checked") {
1071
- element.checked = value !== undefined && value !== null;
1008
+ element.checked = value !== undefined;
1072
1009
  }
1073
1010
  break;
1074
1011
 
1075
1012
  case "text":
1076
1013
  default:
1077
1014
  if (name === "value") {
1078
- // Check for undefined, null, or NaN
1079
- element.value = value === undefined || value === null ? "" : value;
1015
+ element.value = value === undefined ? "" : value;
1080
1016
  }
1081
1017
  break;
1082
1018
  }
1083
1019
  } else if (element instanceof HTMLTextAreaElement) {
1084
1020
  if (name === "value") {
1085
- // Check for undefined, null, or NaN
1086
- element.value = value === undefined || value === null ? "" : value;
1021
+ element.value = value === undefined ? "" : value;
1087
1022
  }
1088
1023
  }
1089
1024
  }