@schukai/monster 4.143.3 → 4.143.5

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/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.143.3"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.143.5"}
@@ -1089,6 +1089,10 @@ function initTabEvents() {
1089
1089
  return host.getConfig(configKey);
1090
1090
  })
1091
1091
  .then((config) => {
1092
+ if (!isObject(config)) {
1093
+ return;
1094
+ }
1095
+
1092
1096
  for (const [name, query] of Object.entries(config)) {
1093
1097
  if (labels.includes(name)) {
1094
1098
  continue;
@@ -1130,6 +1134,10 @@ function updateFilterTabs() {
1130
1134
  return host.getConfig(configKey);
1131
1135
  })
1132
1136
  .then((config) => {
1137
+ if (!isObject(config)) {
1138
+ return;
1139
+ }
1140
+
1133
1141
  for (const [name, query] of Object.entries(config)) {
1134
1142
  const found = element.querySelector(
1135
1143
  `[data-monster-button-label="${name}"]`,
@@ -1396,7 +1396,13 @@ function getVisualBorderElement(element, side) {
1396
1396
  "[data-monster-role=control]",
1397
1397
  ].join(",");
1398
1398
  if (element.shadowRoot instanceof ShadowRoot) {
1399
- return element.shadowRoot.querySelector(selector) || element;
1399
+ return (
1400
+ element.shadowRoot.querySelector("button,input,select,textarea") ||
1401
+ element.shadowRoot.querySelector(
1402
+ "monster-input-group,monster-select,[data-monster-role=control]",
1403
+ ) ||
1404
+ element
1405
+ );
1400
1406
  }
1401
1407
 
1402
1408
  const candidates = Array.from(element.querySelectorAll(selector)).filter(
@@ -82,6 +82,8 @@ function parseBracketedKeyValueHash(hashString) {
82
82
  let inSelector = true;
83
83
  let escaped = false;
84
84
  let quotedValueStartChar = "";
85
+ let valueParenthesisDepth = 0;
86
+ let valueClosedByParenthesis = false;
85
87
 
86
88
  for (let i = 0; i < cleanedHashString.length; i++) {
87
89
  const c = cleanedHashString[i];
@@ -166,6 +168,19 @@ function parseBracketedKeyValueHash(hashString) {
166
168
  continue;
167
169
  }
168
170
 
171
+ if (c === "(") {
172
+ valueParenthesisDepth++;
173
+ currentValue += c;
174
+ continue;
175
+ }
176
+
177
+ if (c === ")" && valueParenthesisDepth > 0) {
178
+ valueParenthesisDepth--;
179
+ currentValue += c;
180
+ valueClosedByParenthesis = valueParenthesisDepth === 0;
181
+ continue;
182
+ }
183
+
169
184
  if (c === ",") {
170
185
  inValue = false;
171
186
  inKey = true;
@@ -173,6 +188,8 @@ function parseBracketedKeyValueHash(hashString) {
173
188
  addToResult(currentKey, decodedCurrentValue);
174
189
  currentKey = "";
175
190
  currentValue = "";
191
+ valueParenthesisDepth = 0;
192
+ valueClosedByParenthesis = false;
176
193
  continue;
177
194
  }
178
195
 
@@ -186,6 +203,8 @@ function parseBracketedKeyValueHash(hashString) {
186
203
  currentKey = "";
187
204
  currentValue = "";
188
205
  currentSelector = "";
206
+ valueParenthesisDepth = 0;
207
+ valueClosedByParenthesis = false;
189
208
  continue;
190
209
  }
191
210
 
@@ -199,6 +218,12 @@ function parseBracketedKeyValueHash(hashString) {
199
218
  return selectors;
200
219
  }
201
220
 
221
+ if (inValue && valueParenthesisDepth === 0 && valueClosedByParenthesis) {
222
+ const decodedCurrentValue = decodeURIComponent(currentValue);
223
+ addToResult(currentKey, decodedCurrentValue);
224
+ return selectors;
225
+ }
226
+
202
227
  return {};
203
228
  }
204
229
 
@@ -30,6 +30,8 @@ describe("ControlBar", function () {
30
30
  Promise.all([
31
31
  import("../../../../source/components/form/control-bar.mjs"),
32
32
  import("../../../../source/components/form/control-bar-spacer.mjs"),
33
+ import("../../../../source/components/form/button.mjs"),
34
+ import("../../../../source/components/form/popper-button.mjs"),
33
35
  ])
34
36
  .then((m) => {
35
37
  ControlBar = m[0].ControlBar;
@@ -380,6 +382,92 @@ describe("ControlBar", function () {
380
382
  }
381
383
  });
382
384
 
385
+ it("should join borders between direct monster button controls", async function () {
386
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
387
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
388
+
389
+ const scheduledCallbacks = [];
390
+ const flushFrames = async () => {
391
+ while (scheduledCallbacks.length > 0) {
392
+ scheduledCallbacks.shift()();
393
+ await new Promise((resolve) => setTimeout(resolve, 0));
394
+ }
395
+ };
396
+
397
+ try {
398
+ window.requestAnimationFrame = (callback) => {
399
+ scheduledCallbacks.push(callback);
400
+ return scheduledCallbacks.length;
401
+ };
402
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
403
+
404
+ const mocks = document.getElementById("mocks");
405
+ mocks.innerHTML = `
406
+ <div id="monster-button-border-bar-wrapper">
407
+ <monster-control-bar id="monster-button-border-bar">
408
+ <monster-popper-button id="monster-button-border-create">
409
+ <span slot="button">Create</span>
410
+ </monster-popper-button>
411
+ <monster-button id="monster-button-border-tags">Manage tags</monster-button>
412
+ <monster-button id="monster-button-border-import">Import</monster-button>
413
+ </monster-control-bar>
414
+ </div>
415
+ `;
416
+
417
+ const wrapper = document.getElementById(
418
+ "monster-button-border-bar-wrapper",
419
+ );
420
+ const controls = [
421
+ document.getElementById("monster-button-border-create"),
422
+ document.getElementById("monster-button-border-tags"),
423
+ document.getElementById("monster-button-border-import"),
424
+ ];
425
+
426
+ wrapper.style.boxSizing = "border-box";
427
+ wrapper.style.width = "400px";
428
+ Object.defineProperty(wrapper, "clientWidth", {
429
+ configurable: true,
430
+ value: 400,
431
+ });
432
+
433
+ for (const control of controls) {
434
+ Object.defineProperty(control, "offsetWidth", {
435
+ configurable: true,
436
+ value: 80,
437
+ });
438
+ Object.defineProperty(control, "offsetHeight", {
439
+ configurable: true,
440
+ value: 30,
441
+ });
442
+ control.getBoundingClientRect = () => ({
443
+ width: 80,
444
+ height: 30,
445
+ top: 0,
446
+ right: 80,
447
+ bottom: 30,
448
+ left: 0,
449
+ x: 0,
450
+ y: 0,
451
+ toJSON: () => {},
452
+ });
453
+
454
+ const innerButton = control.shadowRoot?.querySelector("button");
455
+ innerButton.style.borderLeftWidth = "3px";
456
+ innerButton.style.borderRightWidth = "3px";
457
+ }
458
+
459
+ await flushFrames();
460
+ await new Promise((resolve) => setTimeout(resolve, 0));
461
+ await new Promise((resolve) => setTimeout(resolve, 0));
462
+
463
+ expect(controls[1].style.marginLeft).to.equal("-3px");
464
+ expect(controls[2].style.marginLeft).to.equal("-3px");
465
+ } finally {
466
+ window.requestAnimationFrame = originalRequestAnimationFrame;
467
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
468
+ }
469
+ });
470
+
383
471
  it("should size inline spacer lines from adjacent border widths", async function () {
384
472
  const originalRequestAnimationFrame = window.requestAnimationFrame;
385
473
  const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
@@ -106,6 +106,16 @@ describe("parseBracketedKeyValueHash", () => {
106
106
  expect(result).to.deep.equal({selector: {key1: 'value,1', key2: 'value,2'}});
107
107
  });
108
108
 
109
+ it('should keep unencoded parentheses inside a percent-encoded filter expression', () => {
110
+ const hashString = '#list-filter-nucleus-pim-product-variants-list(tags=tags%20IN%20(%22gustav-varianten%22)';
111
+ const result = parseBracketedKeyValueHash(hashString);
112
+ expect(result).to.deep.equal({
113
+ 'list-filter-nucleus-pim-product-variants-list': {
114
+ tags: 'tags IN ("gustav-varianten")'
115
+ }
116
+ });
117
+ });
118
+
109
119
  it('should ignore leading hash symbol (#)', () => {
110
120
  const hashString = 'selector(key=value)';
111
121
  const result = parseBracketedKeyValueHash(hashString);
@@ -195,6 +205,18 @@ describe("parseBracketedKeyValueHash", () => {
195
205
  const result = createBracketedKeyValueHash(input, true);
196
206
  expect(result).to.deep.equal("#.example(color=r%22ed,font-size=14px);.other(background=blue)");
197
207
  });
208
+
209
+ it('should round-trip a filter expression with quotes and parentheses', () => {
210
+ const input = {
211
+ 'list-filter-nucleus-pim-product-variants-list': {
212
+ tags: 'tags IN ("gustav-varianten")'
213
+ }
214
+ };
215
+
216
+ const hashString = createBracketedKeyValueHash(input, true);
217
+ const result = parseBracketedKeyValueHash(hashString);
218
+ expect(result).to.deep.equal(input);
219
+ });
198
220
 
199
221
  it('should return an empty string for an empty object', () => {
200
222
  const input = {};