@schukai/monster 4.85.1 → 4.86.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,25 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.86.0] - 2026-01-10
6
+
7
+ ### Add Features
8
+
9
+ - Add investigation for form writeback and control ([#370](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/370))
10
+ ### Changes
11
+
12
+ - close issue
13
+
14
+
15
+
16
+ ## [4.85.2] - 2026-01-08
17
+
18
+ ### Bug Fixes
19
+
20
+ - Rename issue [#368](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/368) to closed and add issue [#369](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/369)
21
+
22
+
23
+
5
24
  ## [4.85.1] - 2026-01-08
6
25
 
7
26
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","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.85.1"}
1
+ {"author":"Volker Schukai","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.86.0"}
@@ -166,7 +166,8 @@ function initControlReferences() {
166
166
  this[errorElementSymbol] = this.shadowRoot.querySelector(
167
167
  "monster-context-error",
168
168
  );
169
- this[spinnerElementSymbol] = this.shadowRoot.querySelector(".monster-spinner");
169
+ this[spinnerElementSymbol] =
170
+ this.shadowRoot.querySelector(".monster-spinner");
170
171
  }
171
172
 
172
173
  /**
@@ -189,6 +189,12 @@ function initEventHandler() {
189
189
  const events = this.getOption("writeBack.events");
190
190
  for (const event of events) {
191
191
  this.addEventListener(event, (e) => {
192
+ const target = e?.target;
193
+ const targetTag = target?.tagName?.toLowerCase?.();
194
+ if (targetTag === "monster-select" && e.type !== "change") {
195
+ return;
196
+ }
197
+
192
198
  if (e?.target && typeof e.target.setCustomValidity === "function") {
193
199
  e.target.setCustomValidity("");
194
200
  e.target.removeAttribute("data-monster-validation-error");
@@ -3241,6 +3241,40 @@ function isValueIsEmptyThenGetNormalize(value) {
3241
3241
  return value;
3242
3242
  }
3243
3243
 
3244
+ /**
3245
+ * @private
3246
+ * @param {Array} current
3247
+ * @param {Array} next
3248
+ * @returns {boolean}
3249
+ */
3250
+ function areSelectionValuesEqual(current, next) {
3251
+ if (!isArray(current) || !isArray(next)) {
3252
+ return false;
3253
+ }
3254
+
3255
+ if (current.length !== next.length) {
3256
+ return false;
3257
+ }
3258
+
3259
+ const toValue = (item) => {
3260
+ if (isObject(item) && Object.prototype.hasOwnProperty.call(item, "value")) {
3261
+ return `${item.value}`;
3262
+ }
3263
+ return `${item}`;
3264
+ };
3265
+
3266
+ const currentValues = current.map(toValue).sort();
3267
+ const nextValues = next.map(toValue).sort();
3268
+
3269
+ for (let i = 0; i < currentValues.length; i++) {
3270
+ if (currentValues[i] !== nextValues[i]) {
3271
+ return false;
3272
+ }
3273
+ }
3274
+
3275
+ return true;
3276
+ }
3277
+
3244
3278
  /**
3245
3279
  * @private
3246
3280
  * @param selection
@@ -3280,27 +3314,34 @@ function setSelection(selection) {
3280
3314
 
3281
3315
  selection = resultSelection;
3282
3316
 
3317
+ const previousSelection = this.getOption("selection", []);
3318
+ const valuesChanged = !areSelectionValuesEqual(previousSelection, selection);
3319
+
3283
3320
  if (!isInteger(this[selectionVersionSymbol])) {
3284
3321
  this[selectionVersionSymbol] = 0;
3285
3322
  }
3286
- this[selectionVersionSymbol] += 1;
3323
+ if (valuesChanged) {
3324
+ this[selectionVersionSymbol] += 1;
3325
+ }
3287
3326
 
3288
3327
  this.setOption("selection", selection);
3289
3328
 
3290
3329
  checkOptionState.call(this);
3291
3330
  setSummaryAndControlText.call(this);
3292
3331
 
3293
- try {
3294
- this?.setFormValue(this.value);
3295
- } catch (e) {
3296
- addErrorAttribute(this, e);
3297
- }
3332
+ if (valuesChanged) {
3333
+ try {
3334
+ this?.setFormValue(this.value);
3335
+ } catch (e) {
3336
+ addErrorAttribute(this, e);
3337
+ }
3298
3338
 
3299
- fireCustomEvent(this, "monster-selected", {
3300
- selection,
3301
- });
3339
+ fireCustomEvent(this, "monster-selected", {
3340
+ selection,
3341
+ });
3302
3342
 
3303
- fireEvent(this, "change"); // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/291
3343
+ fireEvent(this, "change"); // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/291
3344
+ }
3304
3345
 
3305
3346
  if (this[runLookupOnceSymbol] !== true && selection.length > 0) {
3306
3347
  this[runLookupOnceSymbol] = true;
@@ -68,7 +68,16 @@ function doDiff(a, b, path, diff) {
68
68
  const currDiff = diff || [];
69
69
 
70
70
  if (typeA === typeB && (typeA === "object" || typeA === "array")) {
71
- getKeys(a, b, typeA).forEach((v) => {
71
+ const keys = getKeys(a, b, typeA);
72
+ if (keys.size === 0) {
73
+ const o = getOperator(a, b, typeA, typeB);
74
+ if (o !== undefined) {
75
+ currDiff.push(buildResult(a, b, o, currPath));
76
+ }
77
+ return currDiff;
78
+ }
79
+
80
+ keys.forEach((v) => {
72
81
  if (!Object.prototype.hasOwnProperty.call(a, v)) {
73
82
  currDiff.push(buildResult(a[v], b[v], "add", currPath.concat(v)));
74
83
  } else if (!Object.prototype.hasOwnProperty.call(b, v)) {
@@ -147,8 +147,7 @@ class ControlFlow {
147
147
  */
148
148
  static onState(events, run) {
149
149
  return {
150
- on: (ctx) =>
151
- ctx.source === "state" && matchEvent(ctx.event, events),
150
+ on: (ctx) => ctx.source === "state" && matchEvent(ctx.event, events),
152
151
  run,
153
152
  };
154
153
  }
@@ -1101,6 +1101,11 @@ function syncUpdaterSubject(target, source) {
1101
1101
  }
1102
1102
 
1103
1103
  if (isObject(value)) {
1104
+ const proto = Object.getPrototypeOf(value);
1105
+ if (proto && proto !== Object.prototype) {
1106
+ target[key] = value;
1107
+ continue;
1108
+ }
1104
1109
  if (!isObject(target?.[key]) || isArray(target?.[key])) {
1105
1110
  target[key] = {};
1106
1111
  }
@@ -80,6 +80,29 @@ function clone(obj) {
80
80
  return new URL(obj.toString());
81
81
  }
82
82
 
83
+ // Handle RegExp
84
+ if (obj instanceof RegExp) {
85
+ return new RegExp(obj.source, obj.flags);
86
+ }
87
+
88
+ // Handle Map
89
+ if (obj instanceof Map) {
90
+ const copy = new Map();
91
+ for (const [key, value] of obj.entries()) {
92
+ copy.set(clone(key), clone(value));
93
+ }
94
+ return copy;
95
+ }
96
+
97
+ // Handle Set
98
+ if (obj instanceof Set) {
99
+ const copy = new Set();
100
+ for (const value of obj.values()) {
101
+ copy.add(clone(value));
102
+ }
103
+ return copy;
104
+ }
105
+
83
106
  /** Do not clone DOM nodes */
84
107
  if (typeof Element !== "undefined" && obj instanceof Element) return obj;
85
108
  if (typeof Document !== "undefined" && obj instanceof Document) return obj;
@@ -65,7 +65,7 @@ describe('Diff', function () {
65
65
 
66
66
  it('Diff value with null ', function () {
67
67
  let d = diff(obj1, obj2);
68
- expect(JSON.stringify(d)).is.equal('[{"operator":"update","path":["count"],"first":{"value":1,"type":"number"},"second":{"value":2,"type":"number"}},{"operator":"delete","path":["info"],"first":{"value":"test","type":"string"}}]');
68
+ expect(JSON.stringify(d)).is.equal('[{"operator":"update","path":["count"],"first":{"value":1,"type":"number"},"second":{"value":2,"type":"number"}},{"operator":"delete","path":["info"],"first":{"value":"test","type":"string"}},{"operator":"update","path":["exchange"],"first":{"value":[],"type":"object"},"second":{"value":[],"type":"object"}}]');
69
69
  });
70
70
 
71
71
  it('Diff identical value with null ', function () {
@@ -86,6 +86,21 @@ describe('Diff', function () {
86
86
  const date5 = new Date(Date.parse('04 Dec 1995 00:12:01 GMT')); // Date
87
87
  const date6 = new Date(Date.parse('04 Dec 1995 00:12:02 GMT')); // Date
88
88
 
89
+ const hiddenKey = Symbol("hidden");
90
+ class HiddenState {
91
+ constructor(value) {
92
+ this[hiddenKey] = value;
93
+ }
94
+
95
+ get value() {
96
+ return this[hiddenKey];
97
+ }
98
+
99
+ getClone() {
100
+ return new HiddenState(this.value);
101
+ }
102
+ }
103
+
89
104
  [
90
105
 
91
106
  [
@@ -146,6 +161,15 @@ describe('Diff', function () {
146
161
  },
147
162
  '[]'
148
163
  ],
164
+ [
165
+ {
166
+ a: new HiddenState("a")
167
+ },
168
+ {
169
+ a: new HiddenState("b")
170
+ },
171
+ '[{"operator":"update","path":["a"],"first":{"value":{},"type":"object","instance":"HiddenState"},"second":{"value":{},"type":"object","instance":"HiddenState"}}]'
172
+ ],
149
173
  [
150
174
  {},
151
175
  {
@@ -238,4 +262,4 @@ describe('Diff', function () {
238
262
 
239
263
  });
240
264
 
241
- })
265
+ })
@@ -27,7 +27,7 @@ const updaterSymbolSymbol = Symbol.for(updaterSymbolKey);
27
27
 
28
28
  describe('DOM', function () {
29
29
 
30
- let CustomElement, registerCustomElement, TestComponent, document, TestComponent2, assignUpdaterToElement,
30
+ let CustomElement, registerCustomElement, TestComponent, document, TestComponent2, TestStateComponent, HiddenStateClass, assignUpdaterToElement,
31
31
  addObjectWithUpdaterToElement;
32
32
 
33
33
  describe("assignUpdaterToElement", function () {
@@ -184,6 +184,40 @@ describe('DOM', function () {
184
184
 
185
185
  registerCustomElement(TestComponent2)
186
186
 
187
+ const hiddenKey = Symbol("hidden");
188
+ class HiddenState {
189
+ constructor(presentation) {
190
+ this[hiddenKey] = presentation;
191
+ }
192
+
193
+ get presentation() {
194
+ return this[hiddenKey];
195
+ }
196
+
197
+ getClone() {
198
+ return new HiddenState(this.presentation);
199
+ }
200
+ }
201
+
202
+ HiddenStateClass = HiddenState;
203
+
204
+ TestStateComponent = class extends CustomElement {
205
+ static getTag() {
206
+ return "monster-testclass-state"
207
+ }
208
+
209
+ get defaults() {
210
+ return Object.assign({}, super.defaults, {
211
+ current: new HiddenStateClass(""),
212
+ templates: {
213
+ main: '<div id="state" data-monster-replace="path:current.presentation"></div>'
214
+ }
215
+ })
216
+ }
217
+ }
218
+
219
+ registerCustomElement(TestStateComponent)
220
+
187
221
  document = getDocument();
188
222
  done()
189
223
  } catch (e) {
@@ -292,6 +326,26 @@ describe('DOM', function () {
292
326
 
293
327
  })
294
328
 
329
+ describe('Updater sync for instance values', function () {
330
+ it('should keep class instances and update presentation', function (done) {
331
+ let d = document.createElement('monster-testclass-state');
332
+ document.getElementById('test1').appendChild(d);
333
+
334
+ setTimeout(function () {
335
+ d.setOption('current', new HiddenStateClass('OK'));
336
+
337
+ setTimeout(function () {
338
+ try {
339
+ expect(d.shadowRoot.querySelector('#state').textContent).to.equal('OK');
340
+ done();
341
+ } catch (e) {
342
+ done(e);
343
+ }
344
+ }, 20);
345
+ }, 10);
346
+ });
347
+ })
348
+
295
349
  describe('setOptions()', function () {
296
350
  [
297
351
  ['shadowMode', 'x1'],
@@ -119,6 +119,41 @@ describe('Clone', function () {
119
119
 
120
120
  })
121
121
 
122
+ describe('.clone(Map)', function () {
123
+ it('.clone(Map) should preserve entries', function () {
124
+ let a = new Map([
125
+ ['x', 1],
126
+ ['y', {z: 2}],
127
+ ]);
128
+ let b = clone(a);
129
+ expect(b).to.be.instanceOf(Map);
130
+ expect(b).not.equal(a);
131
+ expect(b.get('x')).to.equal(1);
132
+ expect(JSON.stringify(b.get('y'))).to.equal(JSON.stringify({z: 2}));
133
+ });
134
+ })
135
+
136
+ describe('.clone(Set)', function () {
137
+ it('.clone(Set) should preserve values', function () {
138
+ let a = new Set([1, 2, 3]);
139
+ let b = clone(a);
140
+ expect(b).to.be.instanceOf(Set);
141
+ expect(b).not.equal(a);
142
+ expect([...b]).to.deep.equal([1, 2, 3]);
143
+ });
144
+ })
145
+
146
+ describe('.clone(RegExp)', function () {
147
+ it('.clone(RegExp) should preserve pattern and flags', function () {
148
+ let a = /test/gi;
149
+ let b = clone(a);
150
+ expect(b).to.be.instanceOf(RegExp);
151
+ expect(b).not.equal(a);
152
+ expect(b.source).to.equal(a.source);
153
+ expect(b.flags).to.equal(a.flags);
154
+ });
155
+ })
156
+
122
157
  describe('.clone()', function () {
123
158
 
124
159
  [
@@ -152,4 +187,3 @@ describe('Clone', function () {
152
187
  });
153
188
 
154
189
  });
155
-