@schukai/monster 4.150.3 → 4.150.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.150.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.150.5"}
@@ -560,9 +560,9 @@ monster-state[data-monster-role=empty-without-action]::part(action) {
560
560
  & :is(monster-button-bar, .monster-button-group) {
561
561
  --monster-button-bar-gap: 0;
562
562
  --monster-button-bar-item-spacing: 0;
563
- --monster-control-bar-button-inline-size: 100%;
563
+ --monster-control-bar-button-inline-size: max-content;
564
564
  --monster-control-bar-height: max(2.75rem, 44px);
565
- --monster-control-bar-item-flex: 1 1 0;
565
+ --monster-control-bar-item-flex: 0 0 auto;
566
566
  --monster-control-bar-slot-flex: 1 1 auto;
567
567
  box-sizing: border-box;
568
568
  display: flex;
@@ -99,7 +99,7 @@ try {
99
99
  --monster-theme-control-bg-color,var(--monster-bg-color-primary-1)
100
100
  );border:0;border-bottom:1px solid var(--monster-theme-control-border-color);border-left:1px solid var(--monster-theme-control-border-color);border-right:1px solid var(--monster-theme-control-border-color);display:grid;gap:.35rem .75rem;grid-template-columns:minmax(5.5rem,.35fr) minmax(0,1fr);justify-content:stretch;min-height:2.75rem;overflow:visible;padding:.625rem .75rem;white-space:normal;word-break:break-word}.center:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div),.end:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div),.start:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){justify-content:stretch}[data-monster-datatable-row-start]:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){border-radius:var(--monster-theme-control-border-radius) var(--monster-theme-control-border-radius) 0 0;border-top:1px solid var(--monster-theme-control-border-color);margin-top:.75rem}[data-monster-datatable-row-end]:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){border-radius:0 0 var(--monster-theme-control-border-radius) var(--monster-theme-control-border-radius);margin-bottom:.75rem}[data-monster-datatable-row-start][data-monster-datatable-row-end]:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){border-radius:var(--monster-theme-control-border-radius)}[data-monster-features~=select]:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){align-items:center;background-color:var(
101
101
  --monster-theme-surface-subtle,var(--monster-bg-color-primary-2)
102
- );display:flex;min-height:max(2.75rem,var(--monster-control-min-block-size,44px))}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div):has(:is(monster-button,monster-button-bar,monster-action-button,monster-select,monster-filter-select,select[multiple])){grid-template-columns:minmax(0,1fr)}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-select,monster-filter-select,select[multiple]){box-sizing:border-box;display:block;max-width:100%;min-width:0;width:100%}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-button-bar,.monster-button-group){--monster-button-bar-gap:0;--monster-button-bar-item-spacing:0;--monster-control-bar-button-inline-size:100%;--monster-control-bar-height:max(2.75rem,44px);--monster-control-bar-item-flex:1 1 0;--monster-control-bar-slot-flex:1 1 auto;box-sizing:border-box;display:flex;flex-wrap:wrap;gap:0;max-width:100%;min-width:0;width:100%}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-button,monster-action-button,button,[role=button]){box-sizing:border-box;flex:1 1 8rem;min-height:max(2.75rem,var(--monster-control-min-block-size,44px));min-width:0}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div)>:is(monster-button,monster-action-button){display:block;width:100%}.selected:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){background-color:var(
102
+ );display:flex;min-height:max(2.75rem,var(--monster-control-min-block-size,44px))}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div):has(:is(monster-button,monster-button-bar,monster-action-button,monster-select,monster-filter-select,select[multiple])){grid-template-columns:minmax(0,1fr)}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-select,monster-filter-select,select[multiple]){box-sizing:border-box;display:block;max-width:100%;min-width:0;width:100%}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-button-bar,.monster-button-group){--monster-button-bar-gap:0;--monster-button-bar-item-spacing:0;--monster-control-bar-button-inline-size:max-content;--monster-control-bar-height:max(2.75rem,44px);--monster-control-bar-item-flex:0 0 auto;--monster-control-bar-slot-flex:1 1 auto;box-sizing:border-box;display:flex;flex-wrap:wrap;gap:0;max-width:100%;min-width:0;width:100%}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div) :is(monster-button,monster-action-button,button,[role=button]){box-sizing:border-box;flex:1 1 8rem;min-height:max(2.75rem,var(--monster-control-min-block-size,44px));min-width:0}:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div)>:is(monster-button,monster-action-button){display:block;width:100%}.selected:is(:is(.small[data-monster-role=control] [data-monster-role=datatable])>div){background-color:var(
103
103
  --monster-theme-accent-subtle,var(--monster-bg-color-selection-2)
104
104
  );color:var(
105
105
  --monster-theme-accent,var(--monster-color-selection-2)
@@ -30,6 +30,7 @@ import { isArray, isObject, isString } from "../../types/is.mjs";
30
30
  import { ID } from "../../types/id.mjs";
31
31
  import { clone } from "../../util/clone.mjs";
32
32
 
33
+ import "./button.mjs";
33
34
  import { SheetStyleSheet } from "./stylesheet/sheet.mjs";
34
35
 
35
36
  export { Sheet };
@@ -391,13 +392,17 @@ function syncToolbarState() {
391
392
 
392
393
  this[addRowButtonSymbol].hidden = !addRow;
393
394
  this[addColumnButtonSymbol].hidden = !addCol;
394
- this[addRowButtonSymbol].disabled =
395
- this.getOption("disabled", false) || !addRow;
396
- this[addColumnButtonSymbol].disabled =
397
- this.getOption("disabled", false) || !addCol;
395
+ this[addRowButtonSymbol].setOption(
396
+ "disabled",
397
+ this.getOption("disabled", false) || !addRow,
398
+ );
399
+ this[addColumnButtonSymbol].setOption(
400
+ "disabled",
401
+ this.getOption("disabled", false) || !addCol,
402
+ );
398
403
 
399
- this[addRowButtonSymbol].className = buttonClass;
400
- this[addColumnButtonSymbol].className = buttonClass;
404
+ this[addRowButtonSymbol].setOption("classes.button", buttonClass);
405
+ this[addColumnButtonSymbol].setOption("classes.button", buttonClass);
401
406
  if (isString(labels.addRow)) {
402
407
  this[addRowButtonSymbol].textContent = labels.addRow;
403
408
  }
@@ -2427,8 +2432,8 @@ function getTemplate() {
2427
2432
  return `
2428
2433
  <div data-monster-role="control" part="control">
2429
2434
  <div data-monster-role="toolbar" part="toolbar">
2430
- <button type="button" data-monster-role="add-row" part="add-row"></button>
2431
- <button type="button" data-monster-role="add-column" part="add-column"></button>
2435
+ <monster-button data-monster-role="add-row" part="add-row"></monster-button>
2436
+ <monster-button data-monster-role="add-column" part="add-column"></monster-button>
2432
2437
  </div>
2433
2438
  <div data-monster-role="grid-wrapper" part="grid-wrapper">
2434
2439
  <div data-monster-role="grid" part="grid"></div>
@@ -45,6 +45,7 @@ const currentPanelIndexSymbol = Symbol("currentPanelIndex");
45
45
  const optionObserverSymbol = Symbol("optionObserver");
46
46
  const idsSymbol = Symbol("ids");
47
47
  const finalRecordSymbol = Symbol("finalRecord");
48
+ const slottedElementsSymbol = Symbol("slottedElements");
48
49
 
49
50
  /**
50
51
  * A reusable wizard container that combines content panels with `monster-wizard-navigation`.
@@ -151,6 +152,7 @@ class Wizard extends CustomElement {
151
152
  panel: 0,
152
153
  group: 0,
153
154
  };
155
+ this[slottedElementsSymbol] = [];
154
156
 
155
157
  initControlReferences.call(this);
156
158
  initEventHandler.call(this);
@@ -170,6 +172,9 @@ class Wizard extends CustomElement {
170
172
  * @return {Wizard}
171
173
  */
172
174
  refresh() {
175
+ this[slottedElementsSymbol] = Array.from(
176
+ getSlottedElements.call(this) || [],
177
+ );
173
178
  const records = collectPanels.call(this);
174
179
  const groups = buildGroups.call(this, records);
175
180
 
@@ -388,6 +393,10 @@ function initControlReferences() {
388
393
 
389
394
  function initEventHandler() {
390
395
  this[slotElementSymbol]?.addEventListener("slotchange", () => {
396
+ const elements = Array.from(getSlottedElements.call(this) || []);
397
+ if (haveSameElements(this[slottedElementsSymbol], elements)) {
398
+ return;
399
+ }
391
400
  this.refresh();
392
401
  });
393
402
 
@@ -449,6 +458,13 @@ function initEventHandler() {
449
458
  });
450
459
  }
451
460
 
461
+ function haveSameElements(currentElements, nextElements) {
462
+ return (
463
+ currentElements.length === nextElements.length &&
464
+ currentElements.every((element, index) => element === nextElements[index])
465
+ );
466
+ }
467
+
452
468
  function initOptionObserver() {
453
469
  if (this[optionObserverSymbol]) {
454
470
  return;
@@ -19,6 +19,7 @@ const currentStepIndexSymbol = Symbol("currentStepIndex");
19
19
  const stepsSymbol = Symbol("steps");
20
20
  const isTransitioningSymbol = Symbol("isTransitioning");
21
21
  const currentSubStepIndexSymbol = Symbol("currentSubStepIndex");
22
+ const activationRequestSymbol = Symbol("activationRequest");
22
23
 
23
24
  /**
24
25
  * A WizardNavigation component to display progress and allow navigation through a series of steps,
@@ -45,6 +46,7 @@ class WizardNavigation extends CustomElement {
45
46
  this[isTransitioningSymbol] = false;
46
47
  this[currentStepIndexSymbol] = -1;
47
48
  this[currentSubStepIndexSymbol] = -1;
49
+ this[activationRequestSymbol] = 0;
48
50
 
49
51
  initControlReferences.call(this);
50
52
  initEventHandler.call(this);
@@ -186,7 +188,11 @@ class WizardNavigation extends CustomElement {
186
188
  return;
187
189
  }
188
190
 
191
+ const request = ++this[activationRequestSymbol];
189
192
  await this.goToStep(mainIndex, options);
193
+ if (request !== this[activationRequestSymbol]) {
194
+ return;
195
+ }
190
196
 
191
197
  const stepsData = this[stepsSymbol];
192
198
  if (!stepsData || !stepsData[mainIndex]) return;
@@ -80,6 +80,24 @@ describe("Datatable mobile layout", () => {
80
80
  expect(generated).not.to.include("--monster-button-bar-gap:0.5rem");
81
81
  });
82
82
 
83
+ it("measures responsive row actions at their compact width", () => {
84
+ expect(source).to.include(
85
+ "--monster-control-bar-button-inline-size: max-content",
86
+ );
87
+ expect(source).to.include(
88
+ "--monster-control-bar-item-flex: 0 0 auto",
89
+ );
90
+ expect(source).not.to.include(
91
+ "--monster-control-bar-button-inline-size: 100%",
92
+ );
93
+ expect(generated).to.include(
94
+ "--monster-control-bar-button-inline-size:max-content",
95
+ );
96
+ expect(generated).to.include(
97
+ "--monster-control-bar-item-flex:0 0 auto",
98
+ );
99
+ });
100
+
83
101
  it("keeps icon-only row overflow menus compact", () => {
84
102
  expect(source).to.include("& > monster-button-bar");
85
103
  expect(source).to.include("--monster-action-menu-min-inline-size: auto");
@@ -27,6 +27,46 @@ describe("Sheet", function () {
27
27
  expect(document.createElement("monster-sheet")).to.be.instanceof(Sheet);
28
28
  });
29
29
 
30
+ it("should use Monster controls for visible toolbar actions", async function () {
31
+ const sheet = document.createElement("monster-sheet");
32
+ sheet.setOptions({
33
+ classes: { button: "monster-button-outline-secondary" },
34
+ features: { addRows: true, addColumns: true },
35
+ labels: { addRow: "Append row", addColumn: "Append column" },
36
+ });
37
+ document.getElementById("mocks").appendChild(sheet);
38
+
39
+ const addRow = sheet.shadowRoot.querySelector(
40
+ '[data-monster-role="add-row"]',
41
+ );
42
+ const addColumn = sheet.shadowRoot.querySelector(
43
+ '[data-monster-role="add-column"]',
44
+ );
45
+ const menuItems = sheet.shadowRoot.querySelectorAll(
46
+ '[data-monster-role="context-menu"] [role="menuitem"]',
47
+ );
48
+
49
+ expect(addRow.tagName).to.equal("MONSTER-BUTTON");
50
+ expect(addColumn.tagName).to.equal("MONSTER-BUTTON");
51
+ expect(addRow.textContent).to.equal("Append row");
52
+ expect(addColumn.textContent).to.equal("Append column");
53
+ expect(addRow.getOption("classes.button")).to.equal(
54
+ "monster-button-outline-secondary",
55
+ );
56
+ expect(addRow.getOption("disabled")).to.equal(false);
57
+ expect(Array.from(menuItems).every((item) => item.tagName === "BUTTON")).to
58
+ .equal(true);
59
+
60
+ const rowCount = sheet.value.rows.length;
61
+ addRow.click();
62
+ expect(sheet.value.rows).to.have.length(rowCount + 1);
63
+
64
+ sheet.setOption("disabled", true);
65
+ await new Promise((resolve) => setTimeout(resolve, 0));
66
+ expect(addRow.getOption("disabled")).to.equal(true);
67
+ expect(addColumn.getOption("disabled")).to.equal(true);
68
+ });
69
+
30
70
  it("should name editable cells from their row and column labels", function () {
31
71
  const sheet = document.createElement("monster-sheet");
32
72
  sheet.setOptions({
@@ -98,6 +98,24 @@ describe("Wizard", function () {
98
98
  ).to.equal(true);
99
99
  });
100
100
 
101
+ it("should ignore slot changes when the assigned panels are unchanged", async () => {
102
+ const mocks = document.getElementById("mocks");
103
+ mocks.innerHTML = html;
104
+
105
+ await new Promise((resolve) => setTimeout(resolve, 30));
106
+
107
+ const wizard = document.getElementById("wizard-under-test");
108
+ const slot = wizard.shadowRoot.querySelector("slot");
109
+ let refreshCount = 0;
110
+ wizard.addEventListener("monster-wizard-refresh", () => {
111
+ refreshCount++;
112
+ });
113
+
114
+ slot.dispatchEvent(new Event("slotchange"));
115
+
116
+ expect(refreshCount).to.equal(0);
117
+ });
118
+
101
119
  it("should use panel-specific validation callbacks before moving forward", async function () {
102
120
  const mocks = document.getElementById("mocks");
103
121
  mocks.innerHTML = html;
@@ -0,0 +1,49 @@
1
+ import * as chai from "chai";
2
+ import { initJSDOM } from "../../../util/jsdom.mjs";
3
+
4
+ const expect = chai.expect;
5
+
6
+ describe("WizardNavigation", () => {
7
+ before(async () => {
8
+ await initJSDOM();
9
+ await import(
10
+ "../../../../source/components/navigation/wizard-navigation.mjs"
11
+ );
12
+ });
13
+
14
+ afterEach(() => {
15
+ document.getElementById("mocks").innerHTML = "";
16
+ });
17
+
18
+ it("should only dispatch the latest concurrent substep activation", async () => {
19
+ const mocks = document.getElementById("mocks");
20
+ mocks.innerHTML = `
21
+ <monster-wizard-navigation id="wizard-navigation-under-test">
22
+ <ol class="wizard-steps">
23
+ <li class="step">
24
+ <span>Account</span>
25
+ <ul>
26
+ <li>Email</li>
27
+ <li>Password</li>
28
+ </ul>
29
+ </li>
30
+ </ol>
31
+ </monster-wizard-navigation>
32
+ `;
33
+
34
+ await new Promise((resolve) => setTimeout(resolve, 20));
35
+
36
+ const navigation = document.getElementById("wizard-navigation-under-test");
37
+ const substeps = [];
38
+ navigation.addEventListener("monster-wizard-substep-changed", (event) => {
39
+ substeps.push(event.detail.subIndex);
40
+ });
41
+
42
+ await Promise.all([
43
+ navigation.activate(0, 0, { force: true }),
44
+ navigation.activate(0, 1, { force: true }),
45
+ ]);
46
+
47
+ expect(substeps).to.deep.equal([1]);
48
+ });
49
+ });