@schukai/monster 4.145.1 → 4.145.2

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.145.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.145.2"}
@@ -219,6 +219,35 @@ const EVENT_LAYOUT_CHANGED = "monster-control-bar-layout-changed";
219
219
  const LAYOUT_CHANGED_EVENT_DELAY = 20;
220
220
  const LAYOUT_SWITCH_HYSTERESIS = 16;
221
221
 
222
+ /**
223
+ * @private
224
+ * @type {string}
225
+ */
226
+ const CONTAINED_CONTROL_SELECTOR = [
227
+ "monster-input-group",
228
+ "monster-select",
229
+ "monster-button",
230
+ "monster-state-button",
231
+ "monster-message-state-button",
232
+ "monster-action-button",
233
+ "monster-api-button",
234
+ "monster-confirm-button",
235
+ "monster-popper-button",
236
+ ].join(",");
237
+
238
+ /**
239
+ * @private
240
+ * @type {string}
241
+ */
242
+ const VISUAL_BORDER_CONTROL_SELECTOR = [
243
+ "button",
244
+ "input",
245
+ "select",
246
+ "textarea",
247
+ CONTAINED_CONTROL_SELECTOR,
248
+ "[data-monster-role=control]",
249
+ ].join(",");
250
+
222
251
  /**
223
252
  * A control bar control.
224
253
  *
@@ -1213,24 +1242,36 @@ function isWrapperElement(element) {
1213
1242
  * @return {HTMLElement[]}
1214
1243
  */
1215
1244
  function getContainedControlElements(element) {
1216
- if (!isWrapperElement(element)) {
1245
+ if (element.matches(CONTAINED_CONTROL_SELECTOR)) {
1217
1246
  return [];
1218
1247
  }
1219
1248
 
1220
1249
  return Array.from(
1221
- element.querySelectorAll(
1222
- [
1223
- "monster-input-group",
1224
- "monster-select",
1225
- "monster-button",
1226
- "monster-state-button",
1227
- "monster-message-state-button",
1228
- "monster-action-button",
1229
- "monster-api-button",
1230
- "monster-confirm-button",
1231
- ].join(","),
1232
- ),
1233
- ).filter((control) => control instanceof HTMLElement);
1250
+ element.querySelectorAll(CONTAINED_CONTROL_SELECTOR),
1251
+ ).filter((control) => {
1252
+ return (
1253
+ control instanceof HTMLElement &&
1254
+ isTopLevelContainedControl(element, control)
1255
+ );
1256
+ });
1257
+ }
1258
+
1259
+ /**
1260
+ * @private
1261
+ * @param {HTMLElement} container
1262
+ * @param {HTMLElement} control
1263
+ * @return {boolean}
1264
+ */
1265
+ function isTopLevelContainedControl(container, control) {
1266
+ let parent = control.parentElement;
1267
+ while (parent instanceof HTMLElement && parent !== container) {
1268
+ if (parent.matches(CONTAINED_CONTROL_SELECTOR)) {
1269
+ return false;
1270
+ }
1271
+ parent = parent.parentElement;
1272
+ }
1273
+
1274
+ return true;
1234
1275
  }
1235
1276
 
1236
1277
  /**
@@ -1253,6 +1294,13 @@ function updateJoinedBorders(layout, shouldShowSwitch) {
1253
1294
  const marginTopByElement = new Map();
1254
1295
 
1255
1296
  collectInlineJoinedBorders.call(this, mainItems, marginLeftByElement);
1297
+ for (const item of [...mainItems, ...layout.itemsToMoveToPopper]) {
1298
+ collectInlineJoinedBorders.call(
1299
+ this,
1300
+ getContainedControlElements(item),
1301
+ marginLeftByElement,
1302
+ );
1303
+ }
1256
1304
  collectBlockJoinedBorders.call(
1257
1305
  this,
1258
1306
  layout.itemsToMoveToPopper,
@@ -1392,6 +1440,9 @@ function getJoinedBorderOffsetElements() {
1392
1440
  const elements = Array.from(this.children).filter(
1393
1441
  (element) => element instanceof HTMLElement,
1394
1442
  );
1443
+ for (const element of [...elements]) {
1444
+ elements.push(...getContainedControlElements(element));
1445
+ }
1395
1446
  if (this[popperNavElementSymbol] instanceof HTMLElement) {
1396
1447
  elements.push(this[popperNavElementSymbol]);
1397
1448
  }
@@ -1458,22 +1509,6 @@ function getVisualBorderElement(element, side) {
1458
1509
  return this[switchElementSymbol] || element;
1459
1510
  }
1460
1511
 
1461
- const selector = [
1462
- "button",
1463
- "input",
1464
- "select",
1465
- "textarea",
1466
- "monster-input-group",
1467
- "monster-select",
1468
- "monster-button",
1469
- "monster-state-button",
1470
- "monster-message-state-button",
1471
- "monster-action-button",
1472
- "monster-api-button",
1473
- "monster-confirm-button",
1474
- "monster-popper-button",
1475
- "[data-monster-role=control]",
1476
- ].join(",");
1477
1512
  if (element.shadowRoot instanceof ShadowRoot) {
1478
1513
  const primaryControl = element.shadowRoot.querySelector(
1479
1514
  "[data-monster-role=button],button,input,select,textarea",
@@ -1482,7 +1517,9 @@ function getVisualBorderElement(element, side) {
1482
1517
  return getVisualBorderElement.call(this, primaryControl, side);
1483
1518
  }
1484
1519
 
1485
- const control = element.shadowRoot.querySelector(selector);
1520
+ const control = element.shadowRoot.querySelector(
1521
+ VISUAL_BORDER_CONTROL_SELECTOR,
1522
+ );
1486
1523
  if (control instanceof HTMLElement && control !== element) {
1487
1524
  return getVisualBorderElement.call(this, control, side);
1488
1525
  }
@@ -1490,9 +1527,9 @@ function getVisualBorderElement(element, side) {
1490
1527
  return element;
1491
1528
  }
1492
1529
 
1493
- const candidates = Array.from(element.querySelectorAll(selector)).filter(
1494
- (candidate) => candidate instanceof HTMLElement,
1495
- );
1530
+ const candidates = Array.from(
1531
+ element.querySelectorAll(VISUAL_BORDER_CONTROL_SELECTOR),
1532
+ ).filter((candidate) => candidate instanceof HTMLElement);
1496
1533
  if (candidates.length === 0) {
1497
1534
  return element;
1498
1535
  }
@@ -140,15 +140,17 @@ describe("ButtonBar", function () {
140
140
  const bar = document.getElementById("dynamic-auto-hidden-button-bar");
141
141
  const button = document.getElementById("dynamic-auto-hidden-button");
142
142
 
143
- await waitForLayout();
144
-
145
- expect(bar.hasAttribute("hidden")).to.be.true;
143
+ await waitForCondition(() => {
144
+ return bar.hasAttribute("hidden");
145
+ });
146
146
 
147
147
  button.removeAttribute("hidden");
148
- await waitForLayout();
149
-
150
- expect(bar.hasAttribute("hidden")).to.be.false;
151
- expect(bar.hasAttribute("data-monster-empty-hidden")).to.be.false;
148
+ await waitForCondition(() => {
149
+ return (
150
+ !bar.hasAttribute("hidden") &&
151
+ !bar.hasAttribute("data-monster-empty-hidden")
152
+ );
153
+ });
152
154
  });
153
155
 
154
156
  it("should keep an auto-hidden button bar visible when buttons overflow into the popper", async function () {
@@ -633,6 +633,123 @@ describe("ControlBar", function () {
633
633
  }
634
634
  });
635
635
 
636
+ it("should size and join popper buttons inside custom control wrappers", async function () {
637
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
638
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
639
+
640
+ const scheduledCallbacks = [];
641
+ const flushFrames = async () => {
642
+ while (scheduledCallbacks.length > 0) {
643
+ scheduledCallbacks.shift()();
644
+ await new Promise((resolve) => setTimeout(resolve, 0));
645
+ }
646
+ };
647
+
648
+ try {
649
+ window.requestAnimationFrame = (callback) => {
650
+ scheduledCallbacks.push(callback);
651
+ return scheduledCallbacks.length;
652
+ };
653
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
654
+
655
+ const mocks = document.getElementById("mocks");
656
+ mocks.innerHTML = `
657
+ <div id="custom-wrapper-border-bar-wrapper">
658
+ <monster-control-bar id="custom-wrapper-border-bar">
659
+ <nucleus-workflow-transition-control id="custom-wrapper-workflow">
660
+ <monster-popper-button id="custom-wrapper-release">
661
+ <span slot="button">Release order</span>
662
+ </monster-popper-button>
663
+ <monster-popper-button id="custom-wrapper-cancel">
664
+ <span slot="button">Cancel order</span>
665
+ </monster-popper-button>
666
+ </nucleus-workflow-transition-control>
667
+ <nucleus-follow-ups-action id="custom-wrapper-follow-up">
668
+ <monster-popper-button id="custom-wrapper-follow-up-button">
669
+ <span slot="button">Create follow-up</span>
670
+ <monster-message-state-button id="custom-wrapper-follow-up-submit">
671
+ Create follow-up
672
+ </monster-message-state-button>
673
+ </monster-popper-button>
674
+ </nucleus-follow-ups-action>
675
+ </monster-control-bar>
676
+ </div>
677
+ `;
678
+
679
+ const wrapper = document.getElementById(
680
+ "custom-wrapper-border-bar-wrapper",
681
+ );
682
+ const workflow = document.getElementById("custom-wrapper-workflow");
683
+ const followUp = document.getElementById("custom-wrapper-follow-up");
684
+ const release = document.getElementById("custom-wrapper-release");
685
+ const cancel = document.getElementById("custom-wrapper-cancel");
686
+ const followUpButton = document.getElementById(
687
+ "custom-wrapper-follow-up-button",
688
+ );
689
+ const followUpSubmit = document.getElementById(
690
+ "custom-wrapper-follow-up-submit",
691
+ );
692
+
693
+ wrapper.style.boxSizing = "border-box";
694
+ wrapper.style.width = "500px";
695
+ Object.defineProperty(wrapper, "clientWidth", {
696
+ configurable: true,
697
+ value: 500,
698
+ });
699
+
700
+ for (const control of [workflow, followUp]) {
701
+ Object.defineProperty(control, "offsetWidth", {
702
+ configurable: true,
703
+ value: 160,
704
+ });
705
+ Object.defineProperty(control, "offsetHeight", {
706
+ configurable: true,
707
+ value: 30,
708
+ });
709
+ control.getBoundingClientRect = () => ({
710
+ width: 160,
711
+ height: 30,
712
+ top: 0,
713
+ right: 160,
714
+ bottom: 30,
715
+ left: 0,
716
+ x: 0,
717
+ y: 0,
718
+ toJSON: () => {},
719
+ });
720
+ }
721
+
722
+ const releaseInnerButton = release.shadowRoot?.querySelector(
723
+ '[data-monster-role="button"]',
724
+ );
725
+ const cancelInnerButton = cancel.shadowRoot?.querySelector(
726
+ '[data-monster-role="button"]',
727
+ );
728
+ const followUpInnerButton = followUpButton.shadowRoot?.querySelector(
729
+ '[data-monster-role="button"]',
730
+ );
731
+
732
+ releaseInnerButton.style.borderRightWidth = "3px";
733
+ cancelInnerButton.style.borderLeftWidth = "3px";
734
+ cancelInnerButton.style.borderRightWidth = "2px";
735
+ followUpInnerButton.style.borderLeftWidth = "2px";
736
+
737
+ await flushFrames();
738
+ await new Promise((resolve) => setTimeout(resolve, 0));
739
+ await new Promise((resolve) => setTimeout(resolve, 0));
740
+
741
+ expect(release.style.height).to.equal("100%");
742
+ expect(cancel.style.height).to.equal("100%");
743
+ expect(followUpButton.style.height).to.equal("100%");
744
+ expect(cancel.style.marginLeft).to.equal("-3px");
745
+ expect(followUp.style.marginLeft).to.equal("-2px");
746
+ expect(followUpSubmit.style.height).to.equal("");
747
+ } finally {
748
+ window.requestAnimationFrame = originalRequestAnimationFrame;
749
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
750
+ }
751
+ });
752
+
636
753
  it("should join borders between message state and confirm buttons", async function () {
637
754
  const originalRequestAnimationFrame = window.requestAnimationFrame;
638
755
  const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;