@schukai/monster 4.148.9 → 4.149.1

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.
Files changed (207) hide show
  1. package/package.json +1 -1
  2. package/source/components/accessibility/stylesheet/locale-picker.mjs +61 -1
  3. package/source/components/content/image-editor.mjs +79 -11
  4. package/source/components/content/style/copy.pcss +1 -1
  5. package/source/components/content/stylesheet/camera-capture.mjs +1 -1
  6. package/source/components/content/stylesheet/copy.mjs +1 -1
  7. package/source/components/content/stylesheet/fetch-box.mjs +1 -1
  8. package/source/components/content/stylesheet/viewer.mjs +61 -1
  9. package/source/components/data/kpi-tile.mjs +5 -3
  10. package/source/components/data/metric-graph.mjs +22 -20
  11. package/source/components/data/metric.mjs +20 -18
  12. package/source/components/data/style/kpi-tile.pcss +65 -43
  13. package/source/components/data/style/metric-graph.pcss +35 -18
  14. package/source/components/data/style/metric.pcss +35 -28
  15. package/source/components/data/stylesheet/kpi-tile.mjs +61 -1
  16. package/source/components/data/stylesheet/metric-graph.mjs +61 -1
  17. package/source/components/data/stylesheet/metric.mjs +61 -1
  18. package/source/components/datatable/columnbar.mjs +4 -2
  19. package/source/components/datatable/datatable.mjs +73 -15
  20. package/source/components/datatable/filter/date-range.mjs +61 -61
  21. package/source/components/datatable/filter/date-time.mjs +5 -1
  22. package/source/components/datatable/filter/range.mjs +3 -3
  23. package/source/components/datatable/filter/select-operator.mjs +7 -1
  24. package/source/components/datatable/filter/text-operator.mjs +11 -3
  25. package/source/components/datatable/filter.mjs +16 -0
  26. package/source/components/datatable/pagination.mjs +122 -23
  27. package/source/components/datatable/style/column-bar.pcss +115 -23
  28. package/source/components/datatable/style/datatable.pcss +257 -29
  29. package/source/components/datatable/style/embedded-pagination.pcss +93 -43
  30. package/source/components/datatable/style/filter-button.pcss +1 -1
  31. package/source/components/datatable/style/filter-controls-defaults.pcss +49 -3
  32. package/source/components/datatable/style/filter-select.pcss +4 -1
  33. package/source/components/datatable/style/filter.pcss +18 -1
  34. package/source/components/datatable/stylesheet/change-button.mjs +61 -1
  35. package/source/components/datatable/stylesheet/column-bar.mjs +69 -1
  36. package/source/components/datatable/stylesheet/dataset.mjs +61 -1
  37. package/source/components/datatable/stylesheet/datatable.mjs +71 -1
  38. package/source/components/datatable/stylesheet/embedded-pagination.mjs +15 -1
  39. package/source/components/datatable/stylesheet/filter-button.mjs +1 -1
  40. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +1 -1
  41. package/source/components/datatable/stylesheet/filter-select.mjs +61 -1
  42. package/source/components/datatable/stylesheet/filter.mjs +61 -1
  43. package/source/components/datatable/stylesheet/pagination.mjs +67 -1
  44. package/source/components/datatable/stylesheet/save-button.mjs +61 -1
  45. package/source/components/datatable/stylesheet/status.mjs +61 -1
  46. package/source/components/form/action-button.mjs +2 -1
  47. package/source/components/form/button-bar.mjs +7 -2
  48. package/source/components/form/context-error.mjs +4 -3
  49. package/source/components/form/context-help.mjs +10 -4
  50. package/source/components/form/context-hint.mjs +1 -1
  51. package/source/components/form/context-info.mjs +1 -1
  52. package/source/components/form/context-note.mjs +1 -1
  53. package/source/components/form/context-success.mjs +1 -1
  54. package/source/components/form/context-warning.mjs +1 -1
  55. package/source/components/form/control-bar.mjs +300 -14
  56. package/source/components/form/credential-button.mjs +4 -2
  57. package/source/components/form/digits.mjs +28 -1
  58. package/source/components/form/field-set.mjs +30 -0
  59. package/source/components/form/login.mjs +13 -10
  60. package/source/components/form/password.mjs +6 -0
  61. package/source/components/form/popper-button.mjs +1 -1
  62. package/source/components/form/quantity.mjs +14 -6
  63. package/source/components/form/register-wizard.mjs +46 -26
  64. package/source/components/form/select.mjs +17 -10
  65. package/source/components/form/sheet.mjs +13 -2
  66. package/source/components/form/style/action-button.pcss +131 -5
  67. package/source/components/form/style/button-bar.pcss +204 -3
  68. package/source/components/form/style/control-bar.pcss +254 -26
  69. package/source/components/form/style/digits.pcss +7 -6
  70. package/source/components/form/style/input-group.pcss +65 -1
  71. package/source/components/form/style/login.pcss +1 -1
  72. package/source/components/form/style/mixin/field-set-layout.pcss +8 -0
  73. package/source/components/form/style/password.pcss +25 -1
  74. package/source/components/form/style/popper-button.pcss +1 -0
  75. package/source/components/form/style/quantity.pcss +11 -20
  76. package/source/components/form/style/select.pcss +52 -7
  77. package/source/components/form/style/sheet.pcss +70 -1
  78. package/source/components/form/style/state-button.pcss +5 -2
  79. package/source/components/form/style/toggle-switch.pcss +7 -4
  80. package/source/components/form/stylesheet/action-button.mjs +63 -1
  81. package/source/components/form/stylesheet/button-bar.mjs +153 -1
  82. package/source/components/form/stylesheet/button.mjs +67 -1
  83. package/source/components/form/stylesheet/control-bar.mjs +163 -1
  84. package/source/components/form/stylesheet/digits.mjs +1 -1
  85. package/source/components/form/stylesheet/field-layout.mjs +1 -1
  86. package/source/components/form/stylesheet/field-set.mjs +1 -1
  87. package/source/components/form/stylesheet/form.mjs +1 -1
  88. package/source/components/form/stylesheet/input-group.mjs +15 -1
  89. package/source/components/form/stylesheet/login.mjs +1 -1
  90. package/source/components/form/stylesheet/password.mjs +7 -1
  91. package/source/components/form/stylesheet/popper-button.mjs +61 -1
  92. package/source/components/form/stylesheet/quantity.mjs +1 -1
  93. package/source/components/form/stylesheet/register-wizard.mjs +1 -1
  94. package/source/components/form/stylesheet/select.mjs +3 -1
  95. package/source/components/form/stylesheet/sheet.mjs +1 -1
  96. package/source/components/form/stylesheet/state-button.mjs +3 -1
  97. package/source/components/form/stylesheet/toggle-switch.mjs +1 -1
  98. package/source/components/form/stylesheet/wizard.mjs +1 -1
  99. package/source/components/form/util/floating-ui.mjs +35 -7
  100. package/source/components/host/call-button.mjs +3 -1
  101. package/source/components/host/stylesheet/call-button.mjs +61 -1
  102. package/source/components/host/stylesheet/host.mjs +1 -1
  103. package/source/components/host/stylesheet/toggle-button.mjs +61 -1
  104. package/source/components/layout/full-screen.mjs +31 -23
  105. package/source/components/layout/iframe.mjs +5 -1
  106. package/source/components/layout/popper.mjs +34 -4
  107. package/source/components/layout/style/full-screen.pcss +94 -23
  108. package/source/components/layout/style/split-panel.pcss +57 -5
  109. package/source/components/layout/style/tabs.pcss +277 -47
  110. package/source/components/layout/style/vertical-tabs.pcss +147 -0
  111. package/source/components/layout/stylesheet/collapse.mjs +61 -1
  112. package/source/components/layout/stylesheet/details.mjs +61 -1
  113. package/source/components/layout/stylesheet/full-screen.mjs +23 -1
  114. package/source/components/layout/stylesheet/overlay.mjs +1 -1
  115. package/source/components/layout/stylesheet/panel.mjs +1 -1
  116. package/source/components/layout/stylesheet/slider.mjs +1 -1
  117. package/source/components/layout/stylesheet/split-panel.mjs +17 -1
  118. package/source/components/layout/stylesheet/tabs.mjs +97 -1
  119. package/source/components/layout/stylesheet/vertical-tabs.mjs +33 -95
  120. package/source/components/layout/tabs.mjs +63 -0
  121. package/source/components/layout/vertical-tabs.mjs +63 -0
  122. package/source/components/navigation/stylesheet/site-navigation.mjs +1 -1
  123. package/source/components/navigation/stylesheet/table-of-content.mjs +1 -1
  124. package/source/components/navigation/stylesheet/wizard-navigation.mjs +1 -1
  125. package/source/components/navigation/table-of-content.mjs +13 -0
  126. package/source/components/notify/style/notify.pcss +1 -2
  127. package/source/components/notify/stylesheet/message.mjs +1 -1
  128. package/source/components/notify/stylesheet/notify.mjs +1 -1
  129. package/source/components/state/style/log.pcss +1 -0
  130. package/source/components/state/style/state.pcss +5 -1
  131. package/source/components/state/stylesheet/log.mjs +1 -1
  132. package/source/components/state/stylesheet/state.mjs +7 -1
  133. package/source/components/style/button.css +61 -205
  134. package/source/components/style/button.pcss +78 -34
  135. package/source/components/style/common.css +1 -170
  136. package/source/components/style/common.pcss +5 -9
  137. package/source/components/style/form.css +1 -49
  138. package/source/components/style/form.pcss +34 -9
  139. package/source/components/style/link.css +1 -39
  140. package/source/components/style/link.pcss +8 -16
  141. package/source/components/style/mixin/button.pcss +21 -11
  142. package/source/components/style/mixin/property.pcss +8 -0
  143. package/source/components/style/mixin/skeleton.pcss +11 -5
  144. package/source/components/style/normalize.css +1 -144
  145. package/source/components/style/normalize.pcss +5 -5
  146. package/source/components/style/property.css +1 -387
  147. package/source/components/style/skeleton.css +1 -198
  148. package/source/components/style/skeleton.pcss +8 -2
  149. package/source/components/style/theme-modern.css +74 -0
  150. package/source/components/style/theme-modern.pcss +498 -0
  151. package/source/components/stylesheet/button.mjs +61 -1
  152. package/source/components/stylesheet/common.mjs +1 -1
  153. package/source/components/stylesheet/form.mjs +1 -1
  154. package/source/components/stylesheet/link.mjs +1 -1
  155. package/source/components/stylesheet/normalize.mjs +1 -1
  156. package/source/components/stylesheet/property.mjs +1 -1
  157. package/source/components/stylesheet/skeleton.mjs +1 -1
  158. package/source/components/stylesheet/theme-modern.mjs +110 -0
  159. package/source/components/time/stylesheet/day.mjs +61 -1
  160. package/source/components/time/stylesheet/month-calendar.mjs +61 -1
  161. package/source/components/time/timeline/stylesheet/appointment.mjs +61 -1
  162. package/source/components/time/timeline/stylesheet/segment.mjs +61 -1
  163. package/source/components/tree-menu/style/tree-menu.pcss +26 -37
  164. package/source/components/tree-menu/stylesheet/tree-menu.mjs +1 -1
  165. package/test/cases/components/content/image-editor.mjs +19 -0
  166. package/test/cases/components/datatable/accessibility-defaults.mjs +112 -0
  167. package/test/cases/components/datatable/filter-availability.mjs +37 -0
  168. package/test/cases/components/datatable/filter-date-range-layout.mjs +40 -0
  169. package/test/cases/components/datatable/filter-select-operator.mjs +118 -0
  170. package/test/cases/components/datatable/mobile-layout.mjs +254 -0
  171. package/test/cases/components/datatable/pagination.mjs +79 -0
  172. package/test/cases/components/datatable/responsive-breakpoint.mjs +41 -0
  173. package/test/cases/components/datatable/selection.mjs +149 -0
  174. package/test/cases/components/form/action-button-style.mjs +85 -0
  175. package/test/cases/components/form/button-bar.mjs +222 -3
  176. package/test/cases/components/form/context-theme-colors.mjs +59 -0
  177. package/test/cases/components/form/control-bar.mjs +228 -3
  178. package/test/cases/components/form/control-heights.mjs +103 -0
  179. package/test/cases/components/form/digits-accessibility.mjs +65 -0
  180. package/test/cases/components/form/field-set.mjs +54 -0
  181. package/test/cases/components/form/floating-ui.mjs +74 -0
  182. package/test/cases/components/form/input-group.mjs +61 -28
  183. package/test/cases/components/form/login.mjs +47 -0
  184. package/test/cases/components/form/password.mjs +63 -0
  185. package/test/cases/components/form/popper-button.mjs +30 -15
  186. package/test/cases/components/form/quantity-accessibility.mjs +46 -0
  187. package/test/cases/components/form/register-wizard.mjs +79 -0
  188. package/test/cases/components/form/select.mjs +94 -2
  189. package/test/cases/components/form/sheet.mjs +56 -2
  190. package/test/cases/components/host/toggle-button.mjs +50 -0
  191. package/test/cases/components/layout/full-screen.mjs +60 -0
  192. package/test/cases/components/layout/iframe-accessibility.mjs +44 -0
  193. package/test/cases/components/layout/popper.mjs +128 -0
  194. package/test/cases/components/layout/slit-panel.mjs +30 -1
  195. package/test/cases/components/layout/tabs.mjs +93 -0
  196. package/test/cases/components/navigation/table-of-content.mjs +18 -0
  197. package/test/cases/components/state/state-style.mjs +31 -0
  198. package/test/cases/components/style/action-menu.mjs +74 -0
  199. package/test/cases/components/style/button-sizing.mjs +31 -0
  200. package/test/cases/components/style/form-controls.mjs +50 -0
  201. package/test/cases/components/style/kpi-tile.mjs +60 -0
  202. package/test/cases/components/style/legacy-theme-compatibility.mjs +137 -0
  203. package/test/cases/components/style/metric-layout.mjs +94 -0
  204. package/test/cases/components/style/skeleton.mjs +69 -0
  205. package/test/cases/components/style/theme-component-catalog.mjs +853 -0
  206. package/test/cases/components/style/theme-modern.mjs +439 -0
  207. package/test/cases/components/style/theme-review-regressions.mjs +41 -0
@@ -0,0 +1,41 @@
1
+ import { expect } from "chai";
2
+ import { initJSDOM } from "../../../util/jsdom.mjs";
3
+ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
4
+
5
+ const nextTask = () => new Promise((resolve) => setTimeout(resolve, 0));
6
+ let declarativeBreakpoint;
7
+
8
+ describe("Datatable responsive breakpoint", function () {
9
+ before(async function () {
10
+ await initJSDOM();
11
+ await import("element-internals-polyfill").catch(() => {});
12
+ globalThis.ResizeObserver ??= ResizeObserverMock;
13
+
14
+ document.getElementById("mocks").innerHTML = `
15
+ <monster-datatable
16
+ data-monster-responsive-breakpoint="720"
17
+ ></monster-datatable>
18
+ `;
19
+ await import("../../../../source/components/datatable/datatable.mjs");
20
+ await nextTask();
21
+
22
+ declarativeBreakpoint = document
23
+ .querySelector("monster-datatable")
24
+ .getOption("responsive.breakpoint");
25
+ document.getElementById("mocks").replaceChildren();
26
+ });
27
+
28
+ afterEach(function () {
29
+ document.getElementById("mocks").replaceChildren();
30
+ });
31
+
32
+ it("uses 960px as the default responsive threshold", function () {
33
+ const datatable = document.createElement("monster-datatable");
34
+
35
+ expect(datatable.getOption("responsive.breakpoint")).to.equal(960);
36
+ });
37
+
38
+ it("keeps the responsive breakpoint attribute as an explicit override", function () {
39
+ expect(declarativeBreakpoint).to.equal(720);
40
+ });
41
+ });
@@ -0,0 +1,149 @@
1
+ import { expect } from "chai";
2
+ import { initJSDOM } from "../../../util/jsdom.mjs";
3
+
4
+ const waitForUpdates = () => new Promise((resolve) => setTimeout(resolve, 250));
5
+
6
+ function createFixture() {
7
+ const datasource = document.createElement("monster-datasource-dom");
8
+ datasource.id = "selection-datasource";
9
+ datasource.innerHTML = `
10
+ <script type="application/json">
11
+ {
12
+ "dataset": [
13
+ { "id": "first", "name": "Alpha" },
14
+ { "id": "second", "name": "Beta" },
15
+ { "id": "third", "name": "Gamma" }
16
+ ]
17
+ }
18
+ </script>
19
+ `;
20
+
21
+ const datatable = document.createElement("monster-datatable");
22
+ datatable.id = "selection-table";
23
+ datatable.setAttribute(
24
+ "data-monster-datasource-selector",
25
+ "#selection-datasource",
26
+ );
27
+ datatable.setOption("datasource.selector", "#selection-datasource");
28
+ datatable.innerHTML = `
29
+ <template id="selection-table-row">
30
+ <div
31
+ data-monster-features="select"
32
+ data-monster-mode="fixed"
33
+ ></div>
34
+ <div
35
+ data-monster-head="Name"
36
+ data-monster-replace="path:selection-table-row.name"
37
+ ></div>
38
+ </template>
39
+ `;
40
+
41
+ document.getElementById("mocks").append(datasource, datatable);
42
+ return datatable;
43
+ }
44
+
45
+ describe("Datatable row selection", function () {
46
+ before(async function () {
47
+ await initJSDOM();
48
+ await import("element-internals-polyfill").catch(() => {});
49
+ await import("../../../../source/components/datatable/datatable.mjs");
50
+ });
51
+
52
+ beforeEach(() => {
53
+ document.getElementById("mocks").innerHTML = "";
54
+ });
55
+
56
+ it("renders selectable rows from a populated DOM datasource", async function () {
57
+ const datatable = createFixture();
58
+ await waitForUpdates();
59
+
60
+ const rowCheckboxes = datatable.shadowRoot.querySelectorAll(
61
+ '[data-monster-role="select-row"]',
62
+ );
63
+ const selectAll = datatable.shadowRoot.querySelector(
64
+ '[data-monster-role="select-all"]',
65
+ );
66
+ const firstRowCells = datatable.shadowRoot.querySelectorAll(
67
+ '[data-monster-insert-reference="selection-table-row-0"]',
68
+ );
69
+ expect(rowCheckboxes).to.have.length(3);
70
+ expect(rowCheckboxes[0].getAttribute("aria-label")).to.equal("Select row");
71
+ expect(selectAll.getAttribute("aria-label")).to.equal("Select all rows");
72
+ expect(
73
+ firstRowCells[0].hasAttribute("data-monster-datatable-row-start"),
74
+ ).to.equal(true);
75
+ expect(
76
+ firstRowCells[firstRowCells.length - 1].hasAttribute(
77
+ "data-monster-datatable-row-end",
78
+ ),
79
+ ).to.equal(true);
80
+ expect(datatable.getSelectedRows()).to.deep.equal([]);
81
+ });
82
+
83
+ it("synchronizes row, select-all, and mixed selection states", async function () {
84
+ const datatable = createFixture();
85
+ const events = [];
86
+ for (const type of [
87
+ "monster-datatable-row-selected",
88
+ "monster-datatable-all-rows-selected",
89
+ "monster-datatable-all-rows-deselected",
90
+ "monster-datatable-selection-changed",
91
+ ]) {
92
+ datatable.addEventListener(type, () => events.push(type));
93
+ }
94
+
95
+ await waitForUpdates();
96
+
97
+ const rowCheckboxes = [
98
+ ...datatable.shadowRoot.querySelectorAll(
99
+ '[data-monster-role="select-row"]',
100
+ ),
101
+ ];
102
+ const selectAll = datatable.shadowRoot.querySelector(
103
+ '[data-monster-role="select-all"]',
104
+ );
105
+
106
+ rowCheckboxes[0].click();
107
+
108
+ expect(datatable.getSelectedRows()).to.deep.equal([0]);
109
+ expect(selectAll.checked).to.equal(false);
110
+ expect(selectAll.indeterminate).to.equal(true);
111
+ expect(
112
+ datatable.shadowRoot.querySelectorAll(
113
+ '[data-monster-role="datatable"] > .selected',
114
+ ),
115
+ ).to.have.length(2);
116
+
117
+ selectAll.click();
118
+
119
+ expect(datatable.getSelectedRows()).to.deep.equal([0, 1, 2]);
120
+ expect(rowCheckboxes.every((checkbox) => checkbox.checked)).to.equal(true);
121
+ expect(selectAll.checked).to.equal(true);
122
+ expect(selectAll.indeterminate).to.equal(false);
123
+ expect(
124
+ datatable.shadowRoot.querySelectorAll(
125
+ '[data-monster-role="datatable"] > .selected',
126
+ ),
127
+ ).to.have.length(6);
128
+
129
+ selectAll.click();
130
+
131
+ expect(datatable.getSelectedRows()).to.deep.equal([]);
132
+ expect(rowCheckboxes.every((checkbox) => !checkbox.checked)).to.equal(true);
133
+ expect(selectAll.checked).to.equal(false);
134
+ expect(selectAll.indeterminate).to.equal(false);
135
+ expect(
136
+ datatable.shadowRoot.querySelectorAll(
137
+ '[data-monster-role="datatable"] > .selected',
138
+ ),
139
+ ).to.have.length(0);
140
+ expect(events).to.deep.equal([
141
+ "monster-datatable-row-selected",
142
+ "monster-datatable-selection-changed",
143
+ "monster-datatable-all-rows-selected",
144
+ "monster-datatable-selection-changed",
145
+ "monster-datatable-all-rows-deselected",
146
+ "monster-datatable-selection-changed",
147
+ ]);
148
+ });
149
+ });
@@ -0,0 +1,85 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { expect } from "chai";
3
+
4
+ const actionButtonSource = readFileSync(
5
+ new URL(
6
+ "../../../../source/components/form/style/action-button.pcss",
7
+ import.meta.url,
8
+ ),
9
+ "utf8",
10
+ );
11
+ const actionButtonStyleSheet = readFileSync(
12
+ new URL(
13
+ "../../../../source/components/form/stylesheet/action-button.mjs",
14
+ import.meta.url,
15
+ ),
16
+ "utf8",
17
+ );
18
+ const actionButtonComponent = readFileSync(
19
+ new URL(
20
+ "../../../../source/components/form/action-button.mjs",
21
+ import.meta.url,
22
+ ),
23
+ "utf8",
24
+ );
25
+ const stateButtonSource = readFileSync(
26
+ new URL(
27
+ "../../../../source/components/form/style/state-button.pcss",
28
+ import.meta.url,
29
+ ),
30
+ "utf8",
31
+ );
32
+ const stateButtonStyleSheet = readFileSync(
33
+ new URL(
34
+ "../../../../source/components/form/stylesheet/state-button.mjs",
35
+ import.meta.url,
36
+ ),
37
+ "utf8",
38
+ );
39
+
40
+ describe("ActionButton styles", () => {
41
+ it("provides theme hooks for a coherent action menu", () => {
42
+ for (const token of [
43
+ "--monster-action-menu-padding",
44
+ "--monster-action-menu-item-padding",
45
+ "--monster-action-button-popper-padding",
46
+ "--monster-action-button-popper-border-color",
47
+ "--monster-action-button-menu-min-inline-size",
48
+ "--monster-action-button-item-background-color",
49
+ "--monster-action-button-item-hover-background-color",
50
+ "--monster-action-button-item-focus-outline-offset",
51
+ ]) {
52
+ expect(actionButtonSource, token).to.include(token);
53
+ expect(actionButtonStyleSheet, token).to.include(token);
54
+ }
55
+ });
56
+
57
+ it("keeps the existing action appearance as the token fallback", () => {
58
+ expect(actionButtonSource).to.include(
59
+ "--monster-action-menu-item-background-color",
60
+ );
61
+ expect(actionButtonSource).to.include(
62
+ "--monster-action-button-item-margin, 2px",
63
+ );
64
+ });
65
+
66
+ it("allows menu labels to align independently from regular state buttons", () => {
67
+ const declaration =
68
+ "var(--monster-state-button-label-justify-content, center)";
69
+ const buttonDeclaration = "--monster-state-button-justify-content";
70
+
71
+ expect(stateButtonSource).to.include(declaration);
72
+ expect(stateButtonSource).to.include(buttonDeclaration);
73
+ expect(stateButtonStyleSheet).to.include(
74
+ "var(--monster-state-button-label-justify-content,center)",
75
+ );
76
+ expect(stateButtonStyleSheet).to.include(buttonDeclaration);
77
+ });
78
+
79
+ it("forwards the public accessible name to the native menu trigger", () => {
80
+ expect(actionButtonComponent).to.include(
81
+ 'data-monster-attributes="aria-label path:aria.label, class path:classes.button"',
82
+ );
83
+ expect(actionButtonComponent).to.include('aria-haspopup="menu"');
84
+ });
85
+ });
@@ -85,6 +85,38 @@ describe("ButtonBar", function () {
85
85
  expect(bar.toggleDialog).to.be.a("function");
86
86
  });
87
87
 
88
+ it("should name and expose the state of the overflow switch", async function () {
89
+ const bar = document.getElementById("bar");
90
+ await new Promise((resolve) => setTimeout(resolve, 0));
91
+ const switchButton = bar.shadowRoot.querySelector(
92
+ '[data-monster-role="switch"]',
93
+ );
94
+
95
+ expect(switchButton.getAttribute("aria-label")).to.equal("More actions");
96
+ expect(switchButton.getAttribute("aria-expanded")).to.equal("false");
97
+ expect(switchButton.getAttribute("aria-controls")).to.equal(
98
+ "monster-button-bar-popper",
99
+ );
100
+
101
+ bar.setOption("labels.moreActions", "Weitere Aktionen");
102
+ await new Promise((resolve) => setTimeout(resolve, 0));
103
+ expect(switchButton.getAttribute("aria-label")).to.equal(
104
+ "Weitere Aktionen",
105
+ );
106
+
107
+ bar.showDialog();
108
+ expect(switchButton.getAttribute("aria-expanded")).to.equal("true");
109
+ switchButton.dispatchEvent(
110
+ new KeyboardEvent("keydown", {
111
+ key: "Escape",
112
+ bubbles: true,
113
+ composed: true,
114
+ }),
115
+ );
116
+ expect(switchButton.getAttribute("aria-expanded")).to.equal("false");
117
+ expect(bar.shadowRoot.activeElement).to.equal(switchButton);
118
+ });
119
+
88
120
  it("should keep the popper switch hidden when no buttons are slotted", function (done) {
89
121
  const bar = document.getElementById("bar");
90
122
 
@@ -245,6 +277,99 @@ describe("ButtonBar", function () {
245
277
  }
246
278
  });
247
279
 
280
+ it("should use the host content box when reserving overflow switch space", async function () {
281
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
282
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
283
+ const scheduledCallbacks = [];
284
+ const flushFrames = async () => {
285
+ while (scheduledCallbacks.length > 0) {
286
+ scheduledCallbacks.shift()();
287
+ await new Promise((resolve) => setTimeout(resolve, 0));
288
+ }
289
+ };
290
+
291
+ try {
292
+ window.requestAnimationFrame = (callback) => {
293
+ scheduledCallbacks.push(callback);
294
+ return scheduledCallbacks.length;
295
+ };
296
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
297
+
298
+ const mocks = document.getElementById("mocks");
299
+ mocks.innerHTML = `
300
+ <div id="content-box-wrapper">
301
+ <monster-button-bar
302
+ id="content-box-button-bar"
303
+ style="box-sizing: border-box; padding-left: 20px; padding-right: 20px"
304
+ >
305
+ <button id="content-box-a" type="button">One</button>
306
+ <button id="content-box-b" type="button">Two</button>
307
+ </monster-button-bar>
308
+ </div>
309
+ `;
310
+ const wrapper = document.getElementById("content-box-wrapper");
311
+ const bar = document.getElementById("content-box-button-bar");
312
+ const buttons = [
313
+ document.getElementById("content-box-a"),
314
+ document.getElementById("content-box-b"),
315
+ ];
316
+ const switchButton = bar.shadowRoot.querySelector(
317
+ '[data-monster-role="switch"]',
318
+ );
319
+
320
+ wrapper.style.boxSizing = "border-box";
321
+ Object.defineProperty(wrapper, "clientWidth", {
322
+ configurable: true,
323
+ value: 120,
324
+ });
325
+ Object.defineProperty(bar, "clientWidth", {
326
+ configurable: true,
327
+ value: 60,
328
+ });
329
+ Object.defineProperty(switchButton, "offsetWidth", {
330
+ configurable: true,
331
+ value: 20,
332
+ });
333
+
334
+ for (const button of buttons) {
335
+ Object.defineProperty(button, "offsetWidth", {
336
+ configurable: true,
337
+ value: 48,
338
+ });
339
+ Object.defineProperty(button, "offsetHeight", {
340
+ configurable: true,
341
+ value: 30,
342
+ });
343
+ button.getBoundingClientRect = () => ({
344
+ width: 48,
345
+ height: 30,
346
+ top: 0,
347
+ right: 48,
348
+ bottom: 30,
349
+ left: 0,
350
+ x: 0,
351
+ y: 0,
352
+ toJSON: () => {},
353
+ });
354
+ }
355
+
356
+ await flushFrames();
357
+ await waitForCondition(() => {
358
+ return buttons.every(
359
+ (button) => button.getAttribute("slot") === "popper",
360
+ );
361
+ });
362
+
363
+ expect(
364
+ buttons.every((button) => button.getAttribute("slot") === "popper"),
365
+ ).to.be.true;
366
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
367
+ } finally {
368
+ window.requestAnimationFrame = originalRequestAnimationFrame;
369
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
370
+ }
371
+ });
372
+
248
373
  it("should default the button bar layout alignment to left", function () {
249
374
  const bar = document.getElementById("bar");
250
375
  const buttonBar = bar.shadowRoot.querySelector(
@@ -275,7 +400,7 @@ describe("ButtonBar", function () {
275
400
  }, 50);
276
401
  });
277
402
 
278
- it("should render overflow popper buttons as a joined vertical group", function () {
403
+ it("should allow themes to space main and overflow button groups", function () {
279
404
  const cssText = ButtonBar.getCSSStyleSheet()
280
405
  .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
281
406
  .map((rule) => rule.cssText)
@@ -283,9 +408,103 @@ describe("ButtonBar", function () {
283
408
 
284
409
  expect(cssText).to.contain("slot[name=popper]");
285
410
  expect(cssText).to.contain("align-items: stretch");
286
- expect(cssText).to.contain("gap: 0");
411
+ expect(cssText).to.contain("var(--monster-button-bar-gap,0)");
412
+ expect(cssText).to.contain("--monster-button-bar-popper-gap");
413
+ expect(cssText).to.contain("--monster-action-menu-gap");
287
414
  expect(cssText).to.contain("margin-top");
288
- expect(cssText).to.contain("var(--monster-border-width)");
415
+ expect(cssText).to.contain("--monster-button-bar-item-spacing");
416
+ expect(cssText).to.contain("--monster-button-bar-popper-item-spacing");
417
+ expect(cssText).to.match(/--monster-button-bar-item-spacing,\s*0/u);
418
+ expect(cssText).to.match(/--monster-button-bar-popper-item-spacing,\s*0/u);
419
+ });
420
+
421
+ it("should clear joined-border offsets when a button gap is configured", async function () {
422
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
423
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
424
+ const scheduledCallbacks = [];
425
+ const flushFrames = async () => {
426
+ while (scheduledCallbacks.length > 0) {
427
+ scheduledCallbacks.shift()();
428
+ await new Promise((resolve) => setTimeout(resolve, 0));
429
+ }
430
+ };
431
+
432
+ try {
433
+ window.requestAnimationFrame = (callback) => {
434
+ scheduledCallbacks.push(callback);
435
+ return scheduledCallbacks.length;
436
+ };
437
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
438
+
439
+ const mocks = document.getElementById("mocks");
440
+ mocks.innerHTML = `
441
+ <div id="spaced-button-bar-wrapper">
442
+ <monster-button-bar
443
+ id="spaced-button-bar"
444
+ style="--monster-button-bar-gap: 8px"
445
+ >
446
+ <button id="spaced-button-a" type="button">One</button>
447
+ <button id="spaced-button-b" type="button">Two</button>
448
+ </monster-button-bar>
449
+ </div>
450
+ `;
451
+ const wrapper = document.getElementById("spaced-button-bar-wrapper");
452
+ const bar = document.getElementById("spaced-button-bar");
453
+ const buttons = [
454
+ document.getElementById("spaced-button-a"),
455
+ document.getElementById("spaced-button-b"),
456
+ ];
457
+
458
+ Object.defineProperty(wrapper, "clientWidth", {
459
+ configurable: true,
460
+ value: 200,
461
+ });
462
+ for (const button of buttons) {
463
+ button.style.border = "1px solid";
464
+ button.style.borderRadius = "8px";
465
+ Object.defineProperty(button, "offsetWidth", {
466
+ configurable: true,
467
+ value: 48,
468
+ });
469
+ Object.defineProperty(button, "offsetHeight", {
470
+ configurable: true,
471
+ value: 40,
472
+ });
473
+ button.getBoundingClientRect = () => ({
474
+ width: 48,
475
+ height: 40,
476
+ top: 0,
477
+ right: 48,
478
+ bottom: 40,
479
+ left: 0,
480
+ x: 0,
481
+ y: 0,
482
+ toJSON: () => {},
483
+ });
484
+ }
485
+ buttons[1].style.marginLeft = "-1px";
486
+
487
+ const switchButton = bar.shadowRoot.querySelector(
488
+ '[data-monster-role="switch"]',
489
+ );
490
+ Object.defineProperty(switchButton, "offsetWidth", {
491
+ configurable: true,
492
+ value: 20,
493
+ });
494
+
495
+ await flushFrames();
496
+
497
+ expect(buttons[1].style.marginLeft).to.equal("");
498
+ expect(buttons[0].style.borderRadius).to.equal("8px");
499
+ expect(buttons[0].style.borderTopRightRadius).to.equal("");
500
+ expect(buttons[0].style.borderBottomRightRadius).to.equal("");
501
+ expect(buttons[1].style.borderRadius).to.equal("8px");
502
+ expect(buttons[1].style.borderTopLeftRadius).to.equal("");
503
+ expect(buttons[1].style.borderBottomLeftRadius).to.equal("");
504
+ } finally {
505
+ window.requestAnimationFrame = originalRequestAnimationFrame;
506
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
507
+ }
289
508
  });
290
509
 
291
510
  it("should coalesce resize layouts and avoid unchanged layout writes", async function () {
@@ -0,0 +1,59 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { getGlobal } from "../../../../source/types/global.mjs";
3
+ import { initJSDOM } from "../../../util/jsdom.mjs";
4
+ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
5
+
6
+ const contextThemeClasses = new Map([
7
+ ["monster-context-help", "monster-theme-primary-1"],
8
+ ["monster-context-hint", "monster-theme-tertiary-2"],
9
+ ["monster-context-info", "monster-theme-primary-2"],
10
+ ["monster-context-note", "monster-theme-secondary-2"],
11
+ ["monster-context-success", "monster-theme-success-2"],
12
+ ["monster-context-warning", "monster-theme-warning-2"],
13
+ ["monster-context-error", "monster-theme-error-2"],
14
+ ]);
15
+
16
+ describe("Context component theme colors", () => {
17
+ before(async () => {
18
+ await initJSDOM();
19
+ await import("element-internals-polyfill");
20
+
21
+ const global = getGlobal();
22
+ if (!global.ResizeObserver) {
23
+ global.ResizeObserver = ResizeObserverMock;
24
+ }
25
+
26
+ await Promise.all([
27
+ import("../../../../source/components/form/context-help.mjs"),
28
+ import("../../../../source/components/form/context-hint.mjs"),
29
+ import("../../../../source/components/form/context-info.mjs"),
30
+ import("../../../../source/components/form/context-note.mjs"),
31
+ import("../../../../source/components/form/context-success.mjs"),
32
+ import("../../../../source/components/form/context-warning.mjs"),
33
+ import("../../../../source/components/form/context-error.mjs"),
34
+ ]);
35
+ });
36
+
37
+ afterEach(() => {
38
+ document.getElementById("mocks").replaceChildren();
39
+ });
40
+
41
+ for (const [tag, themeClass] of contextThemeClasses) {
42
+ it(`${tag} keeps its semantic pair while inheriting the control background`, async () => {
43
+ const element = document.createElement(tag);
44
+ element.textContent = "Accessible context";
45
+ document.getElementById("mocks").append(element);
46
+ await new Promise((resolve) => setTimeout(resolve, 0));
47
+
48
+ const button = element.shadowRoot.querySelector(
49
+ '[data-monster-role="button"]',
50
+ );
51
+ const icon = button.querySelector("svg");
52
+
53
+ assert.ok(button.classList.contains(themeClass));
54
+ assert.ok(button.classList.contains("monster-theme-background-inherit"));
55
+ assert.equal(icon.getAttribute("fill"), "currentColor");
56
+ assert.equal(icon.querySelector("[fill]"), null);
57
+ });
58
+ }
59
+ });