@schukai/monster 4.127.1 → 4.128.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
@@ -1,6 +1,26 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [Unreleased]
5
+
6
+ ### Bug Fixes
7
+
8
+ - **updater:** add explicit disposal semantics for observer, queue, and event-processing teardown
9
+ - **updater:** dispose linked stateful subtrees before destructive replace/remove operations
10
+ - **updater:** add `data-monster-patch` as a lifecycle-safe alternative to destructive replace rendering
11
+ - **updater:** extend `data-monster-patch` with `DocumentFragment` and unkeyed array support
12
+ - **updater:** add initial `data-monster-patch-key` support for keyed array reorder and removal
13
+ - **updater:** add `data-monster-patch-render` for keyed object arrays with explicit single-node item rendering
14
+ - **message-state-button:** render `message.content` through `data-monster-patch` to preserve rich and stateful message content
15
+ - **customelement:** skip mutation-observer updater reruns for disconnected or disposed instances
16
+ - **message-state-button:** clear auto-hide timers on disconnect and ignore delayed hides after removal
17
+ - **popper:** guard show/hide/update flows against disconnected hosts and missing internal elements
18
+
19
+ ### Changes
20
+
21
+ - document lifecycle ownership rules for Updater-driven and stateful custom element implementations
22
+
23
+
4
24
 
5
25
  ## [4.127.1] - 2026-03-16
6
26
 
@@ -4658,4 +4678,3 @@
4658
4678
  ## 1.8.0 - 2021-08-15
4659
4679
 
4660
4680
  - Initial release
4661
-
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.5","@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.127.1"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6","@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.128.0"}
@@ -39,6 +39,7 @@ const innerDisabledObserverSymbol = Symbol("innerDisabledObserver");
39
39
  const popperElementSymbol = Symbol("popperElement");
40
40
  const messageElementSymbol = Symbol("messageElement");
41
41
  const measurementPopperSymbol = Symbol("measurementPopper");
42
+ const autoHideTimerSymbol = Symbol("autoHideTimer");
42
43
 
43
44
  /**
44
45
  * A specialized button component that combines state management with message display capabilities.
@@ -245,6 +246,7 @@ class MessageStateButton extends Popper {
245
246
  this.setOption("message.title", undefined);
246
247
  this.setOption("message.content", undefined);
247
248
  this.setOption("message.icon", undefined);
249
+ clearAutoHideTimer.call(this);
248
250
  return this;
249
251
  }
250
252
 
@@ -255,12 +257,17 @@ class MessageStateButton extends Popper {
255
257
  * @return {MessageStateButton} Returns the button instance for chaining
256
258
  */
257
259
  showMessage(timeout) {
260
+ clearAutoHideTimer.call(this);
258
261
  applyMeasuredMessageWidth.call(this);
259
262
  this.showDialog.call(this);
260
263
 
261
264
  if (timeout !== undefined) {
262
- setTimeout(() => {
263
- super.hideDialog();
265
+ this[autoHideTimerSymbol] = setTimeout(() => {
266
+ this[autoHideTimerSymbol] = undefined;
267
+ if (!this.isConnected) {
268
+ return;
269
+ }
270
+ this.hideMessage();
264
271
  }, timeout);
265
272
  }
266
273
 
@@ -285,10 +292,16 @@ class MessageStateButton extends Popper {
285
292
  * @return {MessageStateButton}
286
293
  */
287
294
  hideMessage() {
295
+ clearAutoHideTimer.call(this);
288
296
  super.hideDialog();
289
297
  return this;
290
298
  }
291
299
 
300
+ disconnectedCallback() {
301
+ clearAutoHideTimer.call(this);
302
+ super.disconnectedCallback();
303
+ }
304
+
292
305
  /**
293
306
  *
294
307
  * @return {MessageStateButton}
@@ -407,6 +420,13 @@ function initEventHandlerByMode(mode) {
407
420
  }
408
421
  }
409
422
 
423
+ function clearAutoHideTimer() {
424
+ if (this[autoHideTimerSymbol] !== undefined) {
425
+ clearTimeout(this[autoHideTimerSymbol]);
426
+ this[autoHideTimerSymbol] = undefined;
427
+ }
428
+ }
429
+
410
430
  /**
411
431
  * @private
412
432
  * @return {Select}
@@ -515,7 +535,7 @@ function getTemplate() {
515
535
  <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
516
536
  <div data-monster-role="arrow"></div>
517
537
  <div data-monster-role="message" part="message" class="flex"
518
- data-monster-replace="path:message.content"></div>
538
+ data-monster-patch="path:message.content"></div>
519
539
  </div>
520
540
  </div>
521
541
  </div>
@@ -427,13 +427,26 @@ function disconnectResizeObserver() {
427
427
  */
428
428
  function hide() {
429
429
  const self = this;
430
+ const popperElement = self[popperElementSymbol];
431
+ const controlElement = self[controlElementSymbol];
432
+
433
+ if (!self.isConnected) {
434
+ unregisterFromHost.call(self);
435
+ return;
436
+ }
430
437
 
431
438
  fireCustomEvent(self, "monster-popper-hide", {
432
439
  self,
433
440
  });
434
441
 
435
- self[popperElementSymbol].style.display = "none";
436
- removeAttributeToken(self[controlElementSymbol], "class", "open");
442
+ if (popperElement instanceof HTMLElement) {
443
+ popperElement.style.display = "none";
444
+ }
445
+
446
+ if (controlElement instanceof HTMLElement) {
447
+ removeAttributeToken(controlElement, "class", "open");
448
+ }
449
+
437
450
  unregisterFromHost.call(self);
438
451
 
439
452
  setTimeout(() => {
@@ -449,12 +462,22 @@ function hide() {
449
462
  */
450
463
  function show() {
451
464
  const self = this;
465
+ const popperElement = self[popperElementSymbol];
466
+ const controlElement = self[controlElementSymbol];
452
467
 
453
468
  if (self.getOption("disabled", false) === true) {
454
469
  return;
455
470
  }
456
471
 
457
- if (self[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
472
+ if (
473
+ !self.isConnected ||
474
+ !(popperElement instanceof HTMLElement) ||
475
+ !(controlElement instanceof HTMLElement)
476
+ ) {
477
+ return;
478
+ }
479
+
480
+ if (popperElement.style.display === STYLE_DISPLAY_MODE_BLOCK) {
458
481
  return;
459
482
  }
460
483
 
@@ -462,10 +485,10 @@ function show() {
462
485
  self,
463
486
  });
464
487
 
465
- self[popperElementSymbol].style.visibility = "hidden";
466
- self[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
488
+ popperElement.style.visibility = "hidden";
489
+ popperElement.style.display = STYLE_DISPLAY_MODE_BLOCK;
467
490
 
468
- addAttributeToken(self[controlElementSymbol], "class", "open");
491
+ addAttributeToken(controlElement, "class", "open");
469
492
  registerWithHost.call(self);
470
493
  updatePopper.call(self);
471
494
 
@@ -481,6 +504,14 @@ function show() {
481
504
  * @private
482
505
  */
483
506
  function updatePopper() {
507
+ if (
508
+ !this.isConnected ||
509
+ !(this[controlElementSymbol] instanceof HTMLElement) ||
510
+ !(this[popperElementSymbol] instanceof HTMLElement)
511
+ ) {
512
+ return;
513
+ }
514
+
484
515
  if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
485
516
  return;
486
517
  }
@@ -23,6 +23,9 @@ export {
23
23
  ATTRIBUTE_UPDATER_PROPERTIES,
24
24
  ATTRIBUTE_UPDATER_SELECT_THIS,
25
25
  ATTRIBUTE_UPDATER_REPLACE,
26
+ ATTRIBUTE_UPDATER_PATCH,
27
+ ATTRIBUTE_UPDATER_PATCH_KEY,
28
+ ATTRIBUTE_UPDATER_PATCH_RENDER,
26
29
  ATTRIBUTE_UPDATER_INSERT,
27
30
  ATTRIBUTE_UPDATER_INSERT_REFERENCE,
28
31
  ATTRIBUTE_UPDATER_REMOVE,
@@ -172,6 +175,9 @@ const ATTRIBUTE_UPDATER_SELECT_THIS = `${ATTRIBUTE_PREFIX}select-this`;
172
175
  * @since 1.8.0
173
176
  */
174
177
  const ATTRIBUTE_UPDATER_REPLACE = `${ATTRIBUTE_PREFIX}replace`;
178
+ const ATTRIBUTE_UPDATER_PATCH = `${ATTRIBUTE_PREFIX}patch`;
179
+ const ATTRIBUTE_UPDATER_PATCH_KEY = `${ATTRIBUTE_PREFIX}patch-key`;
180
+ const ATTRIBUTE_UPDATER_PATCH_RENDER = `${ATTRIBUTE_PREFIX}patch-render`;
175
181
 
176
182
  /**
177
183
  * @type {string}
@@ -932,18 +932,33 @@ function attachChildMutationObserver() {
932
932
  return;
933
933
  }
934
934
 
935
- self[childMutationTimerSymbol] = setTimeout(() => {
936
- self[childMutationTimerSymbol] = null;
937
- if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
938
- return;
939
- }
935
+ self[childMutationTimerSymbol] = setTimeout(() => {
936
+ self[childMutationTimerSymbol] = null;
937
+ if (!self.isConnected) {
938
+ return;
939
+ }
940
940
 
941
- const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
942
- for (const list of updaters) {
943
- for (const updater of list) {
944
- updater.run().catch((e) => {
945
- addErrorAttribute(self, e);
946
- });
941
+ if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
942
+ return;
943
+ }
944
+
945
+ const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
946
+ for (const list of updaters) {
947
+ for (const updater of list) {
948
+ if (
949
+ typeof updater?.isDisposed === "function" &&
950
+ updater.isDisposed() === true
951
+ ) {
952
+ continue;
953
+ }
954
+
955
+ if (!updater?.[internalSymbol]?.element?.isConnected) {
956
+ continue;
957
+ }
958
+
959
+ updater.run().catch((e) => {
960
+ addErrorAttribute(self, e);
961
+ });
947
962
  }
948
963
  }
949
964
  }, 50);
@@ -72,7 +72,12 @@ function fireEvent(element, type) {
72
72
  * @summary Construct and send and event
73
73
  */
74
74
  function fireCustomEvent(element, type, detail) {
75
- if (element instanceof HTMLElement) {
75
+ if (
76
+ element instanceof HTMLElement ||
77
+ (element &&
78
+ typeof element === "object" &&
79
+ typeof element.dispatchEvent === "function")
80
+ ) {
76
81
  if (!isObject(detail)) {
77
82
  detail = { detail };
78
83
  }