@schukai/monster 4.92.0 → 4.94.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,22 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.94.0] - 2026-01-13
6
+
7
+ ### Add Features
8
+
9
+ - Add demo page for "column-bar dots too long" issue [#373](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/373)
10
+
11
+
12
+
13
+ ## [4.93.0] - 2026-01-13
14
+
15
+ ### Add Features
16
+
17
+ - Enhance formatter with recursion detection and cycle handling
18
+
19
+
20
+
5
21
  ## [4.92.0] - 2026-01-12
6
22
 
7
23
  ### Add Features
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.92.0"}
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.94.0"}
@@ -65,6 +65,23 @@ const popperInstanceSymbol = Symbol("popperInstance");
65
65
  */
66
66
  const closeEventHandlerSymbol = Symbol("closeEventHandler");
67
67
 
68
+ /**
69
+ * @private
70
+ * @type {symbol}
71
+ */
72
+ const resizeObserverSymbol = Symbol("resizeObserver");
73
+
74
+ /**
75
+ * @private
76
+ * @type {symbol}
77
+ */
78
+ const dotsMutationObserverSymbol = Symbol("dotsMutationObserver");
79
+ /**
80
+ * @private
81
+ * @type {symbol}
82
+ */
83
+ const dotsUpdateInProgressSymbol = Symbol("dotsUpdateInProgress");
84
+
68
85
  /**
69
86
  * A column bar for a datatable
70
87
  *
@@ -102,6 +119,8 @@ class ColumnBar extends CustomElement {
102
119
  * @property {string} templates.main Main template
103
120
  * @property {object} labels Locale definitions
104
121
  * @property {string} locale.settings The text for the settings button
122
+ * @property {object} dots Dots configuration
123
+ * @property {number} dots.maxVisible Max dots to show (0 disables limit)
105
124
  */
106
125
  get defaults() {
107
126
  return Object.assign({}, super.defaults, {
@@ -109,6 +128,9 @@ class ColumnBar extends CustomElement {
109
128
  main: getTemplate(),
110
129
  },
111
130
  labels: getTranslations(),
131
+ dots: {
132
+ maxVisible: 15,
133
+ },
112
134
 
113
135
  columns: [],
114
136
  });
@@ -144,6 +166,11 @@ class ColumnBar extends CustomElement {
144
166
  "touch",
145
167
  this[closeEventHandlerSymbol],
146
168
  );
169
+
170
+ queueMicrotask(() => {
171
+ initDotsObservers.call(this);
172
+ updateDotsVisibility.call(this);
173
+ });
147
174
  }
148
175
 
149
176
  /**
@@ -155,6 +182,16 @@ class ColumnBar extends CustomElement {
155
182
  disconnectedCallback() {
156
183
  super.disconnectedCallback();
157
184
 
185
+ if (this[resizeObserverSymbol] instanceof ResizeObserver) {
186
+ this[resizeObserverSymbol].disconnect();
187
+ this[resizeObserverSymbol] = null;
188
+ }
189
+
190
+ if (this[dotsMutationObserverSymbol] instanceof MutationObserver) {
191
+ this[dotsMutationObserverSymbol].disconnect();
192
+ this[dotsMutationObserverSymbol] = null;
193
+ }
194
+
158
195
  if (this[closeEventHandlerSymbol]) {
159
196
  getGlobalObject("document").removeEventListener(
160
197
  "click",
@@ -367,6 +404,173 @@ function initEventHandler() {
367
404
  });
368
405
  }
369
406
 
407
+ /**
408
+ * @private
409
+ */
410
+ function initDotsObservers() {
411
+ const controlElement = this.shadowRoot?.querySelector(
412
+ "[data-monster-role=control]",
413
+ );
414
+ const parentElement = this.parentElement;
415
+
416
+ if (controlElement && !this[resizeObserverSymbol]) {
417
+ this[resizeObserverSymbol] = new ResizeObserver(() => {
418
+ updateDotsVisibility.call(this);
419
+ });
420
+ this[resizeObserverSymbol].observe(controlElement);
421
+ if (parentElement) {
422
+ this[resizeObserverSymbol].observe(parentElement);
423
+ }
424
+ }
425
+
426
+ if (this[dotsContainerElementSymbol] && !this[dotsMutationObserverSymbol]) {
427
+ this[dotsMutationObserverSymbol] = new MutationObserver(() => {
428
+ if (this[dotsUpdateInProgressSymbol]) {
429
+ return;
430
+ }
431
+ requestAnimationFrame(() => updateDotsVisibility.call(this));
432
+ });
433
+ this[dotsMutationObserverSymbol].observe(this[dotsContainerElementSymbol], {
434
+ childList: true,
435
+ });
436
+ }
437
+ }
438
+
439
+ /**
440
+ * @private
441
+ */
442
+ function updateDotsVisibility() {
443
+ if (this[dotsUpdateInProgressSymbol]) {
444
+ return;
445
+ }
446
+
447
+ if (!this.shadowRoot || !this[dotsContainerElementSymbol]) {
448
+ return;
449
+ }
450
+
451
+ const controlElement = this.shadowRoot.querySelector(
452
+ "[data-monster-role=control]",
453
+ );
454
+ const settingsButton = this[settingsButtonElementSymbol];
455
+ const parentElement = this.parentElement;
456
+
457
+ if (!controlElement || !settingsButton) {
458
+ return;
459
+ }
460
+
461
+ const dotsContainer = this[dotsContainerElementSymbol];
462
+ dotsContainer.classList.remove("dots-hidden");
463
+
464
+ this[dotsUpdateInProgressSymbol] = true;
465
+ try {
466
+ const dots = Array.from(dotsContainer.querySelectorAll("li"));
467
+ if (dots.length === 0) {
468
+ return;
469
+ }
470
+
471
+ for (const dot of dots) {
472
+ dot.classList.remove("dots-overflow-hidden");
473
+ }
474
+
475
+ const indicator = dotsContainer.querySelector(".dots-overflow-indicator");
476
+ if (indicator) {
477
+ indicator.remove();
478
+ }
479
+
480
+ const controlWidth = controlElement.getBoundingClientRect().width;
481
+ const settingsWidth = settingsButton.getBoundingClientRect().width;
482
+ const availableWidth = Math.max(
483
+ 0,
484
+ getAvailableWidth(parentElement, settingsWidth, this) ??
485
+ controlWidth - settingsWidth - 12,
486
+ );
487
+
488
+ const dotSlotWidth = getDotSlotWidth(dots[0]);
489
+ const maxDots =
490
+ dotSlotWidth > 0
491
+ ? Math.floor(availableWidth / dotSlotWidth)
492
+ : dots.length;
493
+ const configuredMaxVisible = parseInt(
494
+ this.getOption("dots.maxVisible"),
495
+ 10,
496
+ );
497
+ const enforceMaxVisible =
498
+ Number.isFinite(configuredMaxVisible) && configuredMaxVisible > 0;
499
+
500
+ if (maxDots <= 1) {
501
+ dotsContainer.classList.add("dots-hidden");
502
+ return;
503
+ }
504
+
505
+ if (maxDots >= dots.length) {
506
+ if (!enforceMaxVisible) {
507
+ return;
508
+ }
509
+ }
510
+
511
+ let visibleDots = Math.max(1, maxDots - 1);
512
+ if (enforceMaxVisible) {
513
+ visibleDots = Math.min(visibleDots, configuredMaxVisible);
514
+ }
515
+ for (let i = visibleDots; i < dots.length; i++) {
516
+ dots[i].classList.add("dots-overflow-hidden");
517
+ }
518
+
519
+ const hiddenCount = dots.length - visibleDots;
520
+ const overflowIndicator = document.createElement("li");
521
+ overflowIndicator.className = "dots-overflow-indicator";
522
+ overflowIndicator.textContent = `+${hiddenCount}`;
523
+ dotsContainer.appendChild(overflowIndicator);
524
+ } finally {
525
+ this[dotsUpdateInProgressSymbol] = false;
526
+ }
527
+ }
528
+
529
+ /**
530
+ * @private
531
+ * @param {HTMLElement} dot
532
+ * @return {number}
533
+ */
534
+ function getDotSlotWidth(dot) {
535
+ if (!dot) return 0;
536
+ const styles = getComputedStyle(dot);
537
+ const marginRight = parseFloat(styles.marginRight || "0");
538
+ const marginLeft = parseFloat(styles.marginLeft || "0");
539
+ return dot.getBoundingClientRect().width + marginLeft + marginRight;
540
+ }
541
+
542
+ /**
543
+ * @private
544
+ * @param {HTMLElement|null} parent
545
+ * @param {number} settingsWidth
546
+ * @return {number|null}
547
+ */
548
+ function getAvailableWidth(parent, settingsWidth, hostElement) {
549
+ if (!parent) return null;
550
+
551
+ const parentWidth = parent.getBoundingClientRect().width;
552
+ if (!parentWidth) return null;
553
+
554
+ const styles = getComputedStyle(parent);
555
+ const gapValue = styles.columnGap || styles.gap || "0";
556
+ const gap = parseFloat(gapValue) || 0;
557
+
558
+ const siblings = Array.from(parent.children).filter(
559
+ (el) => el !== hostElement,
560
+ );
561
+ let siblingsWidth = 0;
562
+ for (const sibling of siblings) {
563
+ const siblingStyles = getComputedStyle(sibling);
564
+ if (siblingStyles.display === "none") {
565
+ continue;
566
+ }
567
+ siblingsWidth += sibling.getBoundingClientRect().width;
568
+ }
569
+
570
+ const gaps = siblings.length > 0 ? siblings.length : 0;
571
+ return Math.max(0, parentWidth - siblingsWidth - gap * gaps - settingsWidth);
572
+ }
573
+
370
574
  /**
371
575
  * @private
372
576
  * @return {string}