@schukai/monster 4.51.3 → 4.53.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.53.0] - 2025-12-28
6
+
7
+ ### Add Features
8
+
9
+ - Improve button visibility handling and optimize element width calculations
10
+ ### Bug Fixes
11
+
12
+ - Preserve scroll position when updating hash for tabs
13
+
14
+
15
+
16
+ ## [4.52.0] - 2025-12-28
17
+
18
+ ### Add Features
19
+
20
+ - Enhance copy functionality for data table cells
21
+
22
+
23
+
5
24
  ## [4.51.3] - 2025-12-28
6
25
 
7
26
  ### 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.51.3"}
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.53.0"}
@@ -753,6 +753,18 @@ function updateConfigColumnBar() {
753
753
  }
754
754
  }
755
755
 
756
+ /**
757
+ * @private
758
+ * @param {HTMLElement} cell
759
+ * @return {string}
760
+ */
761
+ function getCellValueForCopy(cell) {
762
+ if (cell.hasAttribute("data-monster-raw-value")) {
763
+ return cell.getAttribute("data-monster-raw-value");
764
+ }
765
+ return cell.textContent.trim();
766
+ }
767
+
756
768
  /**
757
769
  * @private
758
770
  */
@@ -903,10 +915,11 @@ function initEventHandler() {
903
915
  ) {
904
916
  continue;
905
917
  }
906
-
907
- if (col.textContent) {
918
+
919
+ const cellValue = getCellValueForCopy(col);
920
+ if (cellValue) {
908
921
  colTexts.push(
909
- quoteOpenChar + col.textContent.trim() + quoteCloseChar,
922
+ quoteOpenChar + cellValue + quoteCloseChar,
910
923
  );
911
924
  }
912
925
  }
@@ -920,7 +933,7 @@ function initEventHandler() {
920
933
  ) {
921
934
  return;
922
935
  }
923
- text = headCell.textContent.trim();
936
+ text = getCellValueForCopy(headCell);
924
937
  }
925
938
 
926
939
  if (getWindow().navigator.clipboard && text) {
@@ -970,9 +983,10 @@ function initEventHandler() {
970
983
  continue;
971
984
  }
972
985
 
973
- if (col.textContent) {
986
+ const cellValue = getCellValueForCopy(col);
987
+ if (cellValue) {
974
988
  currentRow.push(
975
- quoteOpenChar + col.textContent.trim() + quoteCloseChar,
989
+ quoteOpenChar + cellValue + quoteCloseChar,
976
990
  );
977
991
  }
978
992
  }
@@ -300,6 +300,19 @@ function initDefaultsFromAttributes(obj) {
300
300
  return obj;
301
301
  }
302
302
 
303
+ /**
304
+ * @private
305
+ * @param {HTMLElement} element
306
+ * @return {boolean}
307
+ */
308
+ function isElementTrulyVisible(element) {
309
+ if (!(element instanceof HTMLElement)) {
310
+ return false;
311
+ }
312
+ const computedStyle = getComputedStyle(element);
313
+ return computedStyle.display !== 'none' && computedStyle.visibility !== 'hidden' && computedStyle.opacity !== '0' && element.offsetWidth > 0 && element.offsetHeight > 0;
314
+ }
315
+
303
316
  /**
304
317
  * @private
305
318
  */
@@ -389,16 +402,21 @@ function checkAndRearrangeButtons() {
389
402
  * @return {Object}
390
403
  */
391
404
  function rearrangeButtons() {
392
- let sum = this[switchElementSymbol].offsetWidth;
405
+ let sum = 0;
406
+ // Only add the popper switch's width if it's currently visible.
407
+ if (isElementTrulyVisible(this[switchElementSymbol])) {
408
+ sum += this[switchElementSymbol].offsetWidth;
409
+ }
393
410
  const space = this[dimensionsSymbol].getVia("data.space");
394
411
 
395
412
  const buttonReferences = this[dimensionsSymbol].getVia(
396
413
  "data.buttonReferences",
397
414
  );
398
415
 
399
- for (const ref of buttonReferences) {
400
- sum += this[dimensionsSymbol].getVia(`data.button.${ref}`);
416
+ const visibleButtonsInMainSlot = [];
417
+ const buttonsToMoveToPopper = [];
401
418
 
419
+ for (const ref of buttonReferences) {
402
420
  let elements = getSlottedElements.call(
403
421
  this,
404
422
  '[data-monster-reference="' + ref + '"]',
@@ -422,15 +440,33 @@ function rearrangeButtons() {
422
440
  continue;
423
441
  }
424
442
 
425
- if (sum > space) {
426
- element.setAttribute("slot", "popper");
443
+ if (isElementTrulyVisible(element)) {
444
+ const buttonWidth = this[dimensionsSymbol].getVia(`data.button.${ref}`);
445
+ if (sum + buttonWidth > space) {
446
+ buttonsToMoveToPopper.push(element);
447
+ } else {
448
+ sum += buttonWidth;
449
+ visibleButtonsInMainSlot.push(element);
450
+ }
427
451
  } else {
428
- element.removeAttribute("slot");
452
+ // If a button is not truly visible, force it into the popper slot
453
+ buttonsToMoveToPopper.push(element);
429
454
  }
430
455
  }
431
456
 
432
- const inVisibleButtons = getSlottedElements.call(this, ":scope", "popper"); // null ↦ o
433
- if (inVisibleButtons.size > 0) {
457
+ for (const button of buttonsToMoveToPopper) {
458
+ button.setAttribute("slot", "popper");
459
+ }
460
+
461
+ for (const button of visibleButtonsInMainSlot) {
462
+ button.removeAttribute("slot");
463
+ }
464
+
465
+ const trulyVisibleButtonsInPopper = Array.from(
466
+ getSlottedElements.call(this, ":scope", "popper"),
467
+ ).filter(isElementTrulyVisible);
468
+
469
+ if (trulyVisibleButtonsInPopper.length > 0) {
434
470
  this[switchElementSymbol].classList.remove("hidden");
435
471
  } else {
436
472
  this[switchElementSymbol].classList.add("hidden");
@@ -483,9 +519,8 @@ function calculateButtonBarDimensions() {
483
519
  try {
484
520
  pixel = convertToPixels(width);
485
521
  } catch (e) {
486
- addAttributeToken(
522
+ addErrorAttribute(
487
523
  this,
488
- ATTRIBUTE_ERRORMESSAGE,
489
524
  e?.message || "An error occurred while calculating the dimensions",
490
525
  );
491
526
  }
@@ -503,9 +538,8 @@ function calculateButtonBarDimensions() {
503
538
  try {
504
539
  borderWidthWithoutUnit = convertToPixels(borderWidth);
505
540
  } catch (e) {
506
- addAttributeToken(
541
+ addErrorAttribute(
507
542
  this,
508
- ATTRIBUTE_ERRORMESSAGE,
509
543
  e?.message || "An error occurred while calculating the dimensions",
510
544
  );
511
545
  }
@@ -521,34 +555,34 @@ function calculateButtonBarDimensions() {
521
555
 
522
556
  const buttonReferences = [];
523
557
 
524
- const visibleButtons = getSlottedElements.call(this, ":scope", null); // null ↦ o
525
-
526
- for (const [, button] of visibleButtons.entries()) {
527
- if (!button.hasAttribute("data-monster-reference")) {
528
- button.setAttribute("data-monster-reference", new ID("vb").toString());
529
- }
558
+ // Get all buttons, regardless of their current slot
559
+ const allButtons = Array.from(getSlottedElements.call(this, ":scope", null));
560
+ const popperButtons = Array.from(getSlottedElements.call(this, ":scope", "popper"));
530
561
 
531
- const ref = button.getAttribute("data-monster-reference");
532
- if (ref === null) continue;
562
+ const combinedButtons = [...allButtons, ...popperButtons].filter((button, index, self) => {
563
+ // Filter out duplicates based on data-monster-reference if present, or element itself
564
+ return self.findIndex(b => b.dataset.monsterReference === button.dataset.monsterReference || b === button) === index;
565
+ });
533
566
 
534
- buttonReferences.push(ref);
535
- this[dimensionsSymbol].setVia(
536
- `data.button.${ref}`,
537
- calcBoxWidth.call(this, button),
538
- );
539
- }
567
+ for (const button of combinedButtons) {
568
+ if (!(button instanceof HTMLElement)) {
569
+ continue;
570
+ }
540
571
 
541
- const invisibleButtons = getSlottedElements.call(this, ":scope", "popper"); // null ↦ o
542
- for (const [, button] of invisibleButtons.entries()) {
543
572
  if (!button.hasAttribute("data-monster-reference")) {
544
- button.setAttribute("data-monster-reference", new ID("ib").toString());
573
+ button.setAttribute("data-monster-reference", new ID("btn").toString());
545
574
  }
546
575
 
547
576
  const ref = button.getAttribute("data-monster-reference");
548
577
  if (ref === null) continue;
549
578
 
550
- if (ref.indexOf("ib") !== 0) {
579
+ // Only consider truly visible buttons for width calculation
580
+ if (isElementTrulyVisible(button)) {
551
581
  buttonReferences.push(ref);
582
+ this[dimensionsSymbol].setVia(
583
+ `data.button.${ref}`,
584
+ calcBoxWidth.call(this, button),
585
+ );
552
586
  }
553
587
  }
554
588
 
@@ -17,9 +17,9 @@ import "../form/field-set.mjs";
17
17
  import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
18
18
  import { DataSet } from "../datatable/dataset.mjs";
19
19
  import {
20
- assembleMethodSymbol,
21
- registerCustomElement,
22
- getSlottedElements,
20
+ assembleMethodSymbol,
21
+ registerCustomElement,
22
+ getSlottedElements,
23
23
  } from "../../dom/customelement.mjs";
24
24
  import { datasourceLinkedElementSymbol } from "../datatable/util.mjs";
25
25
  import { FormStyleSheet } from "./stylesheet/form.mjs";
@@ -60,116 +60,116 @@ const debounceBindSymbol = Symbol("debounceBind");
60
60
  * @fires monster-changed
61
61
  */
62
62
  class Form extends DataSet {
63
- /**
64
- * @property {Object} templates Template definitions
65
- * @property {string} templates.main Main template
66
- * @property {Object} classes Class definitions
67
- * @property {string} classes.form Form class
68
- * @property {Object} writeBack Write back definitions
69
- * @property {string[]} writeBack.events Write back events
70
- * @property {Object} bind Bind definitions
71
- * @property {Object} reportValidity Report validity definitions
72
- * @property {string} reportValidity.selector Report validity selector
73
- * @property {boolean} features.mutationObserver Mutation observer feature
74
- * @property {boolean} features.writeBack Write back feature
75
- * @property {boolean} features.bind Bind feature
76
- */
77
- get defaults() {
78
- const obj = Object.assign({}, super.defaults, {
79
- templates: {
80
- main: getTemplate(),
81
- },
82
-
83
- classes: {
84
- form: "",
85
- },
86
-
87
- writeBack: {
88
- events: ["keyup", "click", "change", "drop", "touchend", "input"],
89
- },
90
-
91
- reportValidity: {
92
- selector:
93
- "input,select,textarea,monster-select,monster-toggle-switch,monster-password",
94
- },
95
-
96
- eventProcessing: true,
97
- });
98
-
99
- obj["features"]["mutationObserver"] = false;
100
- obj["features"]["writeBack"] = true;
101
-
102
- return obj;
103
- }
104
-
105
- /**
106
- *
107
- * @return {string}
108
- */
109
- static getTag() {
110
- return "monster-form";
111
- }
112
-
113
- /**
114
- * @return {CSSStyleSheet[]}
115
- */
116
- static getCSSStyleSheet() {
117
- return [FormStyleSheet, InvalidStyleSheet];
118
- }
119
-
120
- /**
121
- *
122
- */
123
- [assembleMethodSymbol]() {
124
- const selector = this.getOption("datasource.selector");
125
-
126
- if (!selector) {
127
- this[datasourceLinkedElementSymbol] = getDocument().createElement(
128
- "monster-datasource-dom",
129
- );
130
- }
131
-
132
- super[assembleMethodSymbol]();
133
-
134
- initControlReferences.call(this);
135
- initEventHandler.call(this);
136
- initDataSourceHandler.call(this);
137
- }
138
-
139
- /**
140
- * This method is called when the component is created.
141
- * @since 3.70.0
142
- * @return {Promise}
143
- */
144
- refresh() {
145
- return this.write().then(() => {
146
- super.refresh();
147
- return this;
148
- });
149
- }
150
-
151
- /**
152
- * Run reportValidation on all child html form controls.
153
- *
154
- * @since 2.10.0
155
- * @return {boolean}
156
- */
157
- reportValidity() {
158
- let valid = true;
159
-
160
- const selector = this.getOption("reportValidity.selector");
161
- const nodes = getSlottedElements.call(this, selector);
162
-
163
- nodes.forEach((node) => {
164
- if (typeof node.reportValidity === "function") {
165
- if (node.reportValidity() === false) {
166
- valid = false;
167
- }
168
- }
169
- });
170
-
171
- return valid;
172
- }
63
+ /**
64
+ * @property {Object} templates Template definitions
65
+ * @property {string} templates.main Main template
66
+ * @property {Object} classes Class definitions
67
+ * @property {string} classes.form Form class
68
+ * @property {Object} writeBack Write back definitions
69
+ * @property {string[]} writeBack.events Write back events
70
+ * @property {Object} bind Bind definitions
71
+ * @property {Object} reportValidity Report validity definitions
72
+ * @property {string} reportValidity.selector Report validity selector
73
+ * @property {boolean} features.mutationObserver Mutation observer feature
74
+ * @property {boolean} features.writeBack Write back feature
75
+ * @property {boolean} features.bind Bind feature
76
+ */
77
+ get defaults() {
78
+ const obj = Object.assign({}, super.defaults, {
79
+ templates: {
80
+ main: getTemplate(),
81
+ },
82
+
83
+ classes: {
84
+ form: "",
85
+ },
86
+
87
+ writeBack: {
88
+ events: ["keyup", "click", "change", "drop", "touchend", "input"],
89
+ },
90
+
91
+ reportValidity: {
92
+ selector:
93
+ "input,select,textarea,monster-select,monster-toggle-switch,monster-password",
94
+ },
95
+
96
+ eventProcessing: true,
97
+ });
98
+
99
+ obj["features"]["mutationObserver"] = false;
100
+ obj["features"]["writeBack"] = true;
101
+
102
+ return obj;
103
+ }
104
+
105
+ /**
106
+ *
107
+ * @return {string}
108
+ */
109
+ static getTag() {
110
+ return "monster-form";
111
+ }
112
+
113
+ /**
114
+ * @return {CSSStyleSheet[]}
115
+ */
116
+ static getCSSStyleSheet() {
117
+ return [FormStyleSheet, InvalidStyleSheet];
118
+ }
119
+
120
+ /**
121
+ *
122
+ */
123
+ [assembleMethodSymbol]() {
124
+ const selector = this.getOption("datasource.selector");
125
+
126
+ if (!selector) {
127
+ this[datasourceLinkedElementSymbol] = getDocument().createElement(
128
+ "monster-datasource-dom",
129
+ );
130
+ }
131
+
132
+ super[assembleMethodSymbol]();
133
+
134
+ initControlReferences.call(this);
135
+ initEventHandler.call(this);
136
+ initDataSourceHandler.call(this);
137
+ }
138
+
139
+ /**
140
+ * This method is called when the component is created.
141
+ * @since 3.70.0
142
+ * @return {Promise}
143
+ */
144
+ refresh() {
145
+ return this.write().then(() => {
146
+ super.refresh();
147
+ return this;
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Run reportValidation on all child html form controls.
153
+ *
154
+ * @since 2.10.0
155
+ * @return {boolean}
156
+ */
157
+ reportValidity() {
158
+ let valid = true;
159
+
160
+ const selector = this.getOption("reportValidity.selector");
161
+ const nodes = getSlottedElements.call(this, selector);
162
+
163
+ nodes.forEach((node) => {
164
+ if (typeof node.reportValidity === "function") {
165
+ if (node.reportValidity() === false) {
166
+ valid = false;
167
+ }
168
+ }
169
+ });
170
+
171
+ return valid;
172
+ }
173
173
  }
174
174
 
175
175
  function initDataSourceHandler() {}
@@ -179,47 +179,47 @@ function initDataSourceHandler() {}
179
179
  * @return {initEventHandler}
180
180
  */
181
181
  function initEventHandler() {
182
- this[debounceBindSymbol] = {};
183
-
184
- if (this.getOption("features.writeBack") === true) {
185
- setTimeout(() => {
186
- const events = this.getOption("writeBack.events");
187
- for (const event of events) {
188
- this.addEventListener(event, (e) => {
189
- if (!this.reportValidity()) {
190
- this.classList.add("invalid");
191
- setTimeout(() => {
192
- this.classList.remove("invalid");
193
- }, 1000);
194
-
195
- return;
196
- }
197
-
198
- if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
199
- try {
200
- this[debounceWriteBackSymbol].touch();
201
- return;
202
- } catch (e) {
203
- if (e.message !== "has already run") {
204
- throw e;
205
- }
206
- delete this[debounceWriteBackSymbol];
207
- }
208
- }
209
-
210
- this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
211
- setTimeout(() => {
212
- this.write().catch((e) => {
213
- addAttributeToken(this, "error", e.message || `${e}`);
214
- });
215
- }, 0);
216
- });
217
- });
218
- }
219
- }, 0);
220
- }
221
-
222
- return this;
182
+ this[debounceBindSymbol] = {};
183
+
184
+ if (this.getOption("features.writeBack") === true) {
185
+ setTimeout(() => {
186
+ const events = this.getOption("writeBack.events");
187
+ for (const event of events) {
188
+ this.addEventListener(event, (e) => {
189
+ if (!this.reportValidity()) {
190
+ this.classList.add("invalid");
191
+ setTimeout(() => {
192
+ this.classList.remove("invalid");
193
+ }, 1000);
194
+
195
+ return;
196
+ }
197
+
198
+ if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) {
199
+ try {
200
+ this[debounceWriteBackSymbol].touch();
201
+ return;
202
+ } catch (e) {
203
+ if (e.message !== "has already run") {
204
+ throw e;
205
+ }
206
+ delete this[debounceWriteBackSymbol];
207
+ }
208
+ }
209
+
210
+ this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => {
211
+ setTimeout(() => {
212
+ this.write().catch((e) => {
213
+ addAttributeToken(this, "error", e.message || `${e}`);
214
+ });
215
+ }, 0);
216
+ });
217
+ });
218
+ }
219
+ }, 0);
220
+ }
221
+
222
+ return this;
223
223
  }
224
224
 
225
225
  /**
@@ -227,10 +227,10 @@ function initEventHandler() {
227
227
  * @return {FilterButton}
228
228
  */
229
229
  function initControlReferences() {
230
- if (!this.shadowRoot) {
231
- throw new Error("no shadow-root is defined");
232
- }
233
- return this;
230
+ if (!this.shadowRoot) {
231
+ throw new Error("no shadow-root is defined");
232
+ }
233
+ return this;
234
234
  }
235
235
 
236
236
  /**
@@ -238,8 +238,8 @@ function initControlReferences() {
238
238
  * @return {string}
239
239
  */
240
240
  function getTemplate() {
241
- // language=HTML
242
- return `
241
+ // language=HTML
242
+ return `
243
243
  <div data-monster-role="control" part="control">
244
244
  <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form"
245
245
  data-monster-role="form"
@@ -108,7 +108,10 @@ export function attachTabsHashSync(
108
108
 
109
109
  const newHash = createBracketedKeyValueHash(hashObj);
110
110
  if (location.hash !== newHash) {
111
+ const scrollX = window.scrollX;
112
+ const scrollY = window.scrollY;
111
113
  history.replaceState(null, "", newHash);
114
+ window.scrollTo(scrollX, scrollY);
112
115
  }
113
116
  lastKnownActiveTabId = activeTabId;
114
117
  lastKnownAllTabIds = allTabIds;