@schukai/monster 4.54.1 → 4.55.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.55.0] - 2025-12-29
6
+
7
+ ### Add Features
8
+
9
+ - Introduce updater root symbol and enhance event handling for nested structures
10
+
11
+
12
+
5
13
  ## [4.54.1] - 2025-12-28
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.54.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.55.0"}
@@ -71,6 +71,7 @@ const pendingDiffsSymbol = Symbol("pendingDiffs");
71
71
  * @type {symbol}
72
72
  */
73
73
  const processingSymbol = Symbol("processing");
74
+ const updaterRootSymbol = Symbol.for("@schukai/monster/dom/@@updater-root");
74
75
 
75
76
  /**
76
77
  * The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be
@@ -123,6 +124,7 @@ class Updater extends Base {
123
124
  eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"],
124
125
  subject: subject,
125
126
  };
127
+ this[internalSymbol].element[updaterRootSymbol] = true;
126
128
 
127
129
  this[internalSymbol].callbacks.set(
128
130
  "checkstate",
@@ -137,6 +139,9 @@ class Updater extends Base {
137
139
  const real = this[internalSymbol].subject.getRealSubject();
138
140
  const diffResult = diff(this[internalSymbol].last, real);
139
141
  this[internalSymbol].last = clone(real);
142
+ if (diffResult.length === 0) {
143
+ return Promise.resolve();
144
+ }
140
145
  this[pendingDiffsSymbol].push(diffResult);
141
146
  return this.#processQueue();
142
147
  }),
@@ -343,6 +348,10 @@ function getControlEventHandler() {
343
348
  * @param {Event} event
344
349
  */
345
350
  this[symbol] = (event) => {
351
+ const root = findClosestUpdaterRootFromEvent(event);
352
+ if (root !== this[internalSymbol].element) {
353
+ return;
354
+ }
346
355
  const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND);
347
356
 
348
357
  if (element === undefined) {
@@ -370,6 +379,27 @@ function getControlEventHandler() {
370
379
  return this[symbol];
371
380
  }
372
381
 
382
+ /**
383
+ * @private
384
+ * @param {Event} event
385
+ * @return {HTMLElement|undefined}
386
+ */
387
+ function findClosestUpdaterRootFromEvent(event) {
388
+ if (typeof event?.composedPath !== "function") {
389
+ return undefined;
390
+ }
391
+
392
+ const path = event.composedPath();
393
+ for (let i = 0; i < path.length; i++) {
394
+ const node = path[i];
395
+ if (node instanceof HTMLElement && node[updaterRootSymbol] === true) {
396
+ return node;
397
+ }
398
+ }
399
+
400
+ return undefined;
401
+ }
402
+
373
403
  /**
374
404
  * @throws {Error} the bind argument must start as a value with a path
375
405
  * @param {HTMLElement} element
@@ -247,7 +247,7 @@ function getHandler() {
247
247
 
248
248
  // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
249
249
  setPrototypeOf: function (target, key) {
250
- const result = Reflect.setPrototypeOf(object1, key);
250
+ const result = Reflect.setPrototypeOf(target, key);
251
251
 
252
252
  if (typeof key !== "symbol") {
253
253
  proxy.observers.notify(proxy);
@@ -109,6 +109,15 @@ let html4 = `
109
109
 
110
110
  `;
111
111
 
112
+ let htmlNested = `
113
+ <div id="parent">
114
+ <input id="parent-input" type="text" data-monster-bind="path:parentVal">
115
+ <div id="child">
116
+ <input id="child-input" type="text" data-monster-bind="path:childVal">
117
+ </div>
118
+ </div>
119
+ `;
120
+
112
121
  describe("DOM", function () {
113
122
  let Updater = null;
114
123
 
@@ -525,6 +534,117 @@ describe("DOM", function () {
525
534
  });
526
535
  });
527
536
 
537
+ describe("Updater()", function () {
538
+ beforeEach(() => {
539
+ let mocks = document.getElementById("mocks");
540
+ mocks.innerHTML = htmlNested;
541
+ });
542
+
543
+ describe("Nested Eventhandling", function () {
544
+ it("should not propagate child events to parent updater", function (done) {
545
+ const parentObserver = new ProxyObserver({});
546
+ const childObserver = new ProxyObserver({});
547
+ const parentUpdater = new Updater(
548
+ document.getElementById("parent"),
549
+ parentObserver,
550
+ );
551
+ const childUpdater = new Updater(
552
+ document.getElementById("child"),
553
+ childObserver,
554
+ );
555
+
556
+ parentUpdater.enableEventProcessing();
557
+ childUpdater.enableEventProcessing();
558
+
559
+ let parentNotified = false;
560
+ parentObserver.attachObserver(
561
+ new Observer(function () {
562
+ parentNotified = true;
563
+ }),
564
+ );
565
+
566
+ childObserver.attachObserver(
567
+ new Observer(function () {
568
+ try {
569
+ expect(childUpdater.getSubject()["childVal"]).to.equal("child");
570
+ expect(parentUpdater.getSubject()).to.not.have.property(
571
+ "childVal",
572
+ );
573
+ setTimeout(() => {
574
+ if (parentNotified) {
575
+ done(
576
+ new Error(
577
+ "parent updater should not react to child bind events",
578
+ ),
579
+ );
580
+ } else {
581
+ done();
582
+ }
583
+ }, 10);
584
+ } catch (e) {
585
+ done(e);
586
+ }
587
+ }),
588
+ );
589
+
590
+ const childInput = document.getElementById("child-input");
591
+ childInput.value = "child";
592
+ childInput.click();
593
+ });
594
+
595
+ it("should handle parent events without triggering child updater", function (done) {
596
+ const parentObserver = new ProxyObserver({});
597
+ const childObserver = new ProxyObserver({});
598
+ const parentUpdater = new Updater(
599
+ document.getElementById("parent"),
600
+ parentObserver,
601
+ );
602
+ const childUpdater = new Updater(
603
+ document.getElementById("child"),
604
+ childObserver,
605
+ );
606
+
607
+ parentUpdater.enableEventProcessing();
608
+ childUpdater.enableEventProcessing();
609
+
610
+ let childNotified = false;
611
+ childObserver.attachObserver(
612
+ new Observer(function () {
613
+ childNotified = true;
614
+ }),
615
+ );
616
+
617
+ parentObserver.attachObserver(
618
+ new Observer(function () {
619
+ try {
620
+ expect(parentUpdater.getSubject()["parentVal"]).to.equal("parent");
621
+ expect(childUpdater.getSubject()).to.not.have.property(
622
+ "parentVal",
623
+ );
624
+ setTimeout(() => {
625
+ if (childNotified) {
626
+ done(
627
+ new Error(
628
+ "child updater should not react to parent bind events",
629
+ ),
630
+ );
631
+ } else {
632
+ done();
633
+ }
634
+ }, 10);
635
+ } catch (e) {
636
+ done(e);
637
+ }
638
+ }),
639
+ );
640
+
641
+ const parentInput = document.getElementById("parent-input");
642
+ parentInput.value = "parent";
643
+ parentInput.click();
644
+ });
645
+ });
646
+ });
647
+
528
648
  describe("Updater()", function () {
529
649
  beforeEach(() => {
530
650
  let mocks = document.getElementById("mocks");
@@ -216,5 +216,18 @@ describe('ProxyObserver', function () {
216
216
  });
217
217
  });
218
218
 
219
- })
219
+ describe('setPrototypeOf', function () {
220
+ it('should allow setPrototypeOf and notify observer', function (done) {
221
+ const proxy = new ProxyObserver({});
222
+ const observer = new Observer(function () {
223
+ done();
224
+ });
225
+ proxy.attachObserver(observer);
226
+
227
+ const proto = {flag: true};
228
+ Object.setPrototypeOf(proxy.getSubject(), proto);
229
+ expect(Object.getPrototypeOf(proxy.getSubject())).to.equal(proto);
230
+ });
231
+ });
220
232
 
233
+ })