@schukai/monster 4.148.8 → 4.149.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.
Files changed (209) 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 +2 -2
  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 +63 -34
  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 +1 -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 +61 -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 +24 -6
  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 +19 -10
  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/source/dom/customelement.mjs +105 -40
  166. package/source/dom/updater.mjs +381 -119
  167. package/test/cases/components/content/image-editor.mjs +19 -0
  168. package/test/cases/components/datatable/accessibility-defaults.mjs +112 -0
  169. package/test/cases/components/datatable/filter-availability.mjs +37 -0
  170. package/test/cases/components/datatable/filter-date-range-layout.mjs +40 -0
  171. package/test/cases/components/datatable/filter-select-operator.mjs +118 -0
  172. package/test/cases/components/datatable/mobile-layout.mjs +203 -0
  173. package/test/cases/components/datatable/responsive-breakpoint.mjs +41 -0
  174. package/test/cases/components/datatable/selection.mjs +149 -0
  175. package/test/cases/components/form/action-button-style.mjs +85 -0
  176. package/test/cases/components/form/button-bar.mjs +222 -3
  177. package/test/cases/components/form/context-theme-colors.mjs +59 -0
  178. package/test/cases/components/form/control-bar.mjs +228 -3
  179. package/test/cases/components/form/control-heights.mjs +103 -0
  180. package/test/cases/components/form/digits-accessibility.mjs +65 -0
  181. package/test/cases/components/form/field-set.mjs +54 -0
  182. package/test/cases/components/form/floating-ui.mjs +74 -0
  183. package/test/cases/components/form/input-group.mjs +61 -28
  184. package/test/cases/components/form/login.mjs +47 -0
  185. package/test/cases/components/form/password.mjs +63 -0
  186. package/test/cases/components/form/popper-button.mjs +30 -15
  187. package/test/cases/components/form/quantity-accessibility.mjs +46 -0
  188. package/test/cases/components/form/register-wizard.mjs +79 -0
  189. package/test/cases/components/form/select.mjs +94 -2
  190. package/test/cases/components/form/sheet.mjs +56 -2
  191. package/test/cases/components/host/toggle-button.mjs +50 -0
  192. package/test/cases/components/layout/full-screen.mjs +60 -0
  193. package/test/cases/components/layout/iframe-accessibility.mjs +44 -0
  194. package/test/cases/components/layout/popper.mjs +128 -0
  195. package/test/cases/components/layout/slit-panel.mjs +30 -1
  196. package/test/cases/components/layout/tabs.mjs +93 -0
  197. package/test/cases/components/navigation/table-of-content.mjs +18 -0
  198. package/test/cases/components/state/state-style.mjs +31 -0
  199. package/test/cases/components/style/action-menu.mjs +74 -0
  200. package/test/cases/components/style/form-controls.mjs +37 -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
  208. package/test/cases/dom/customelement.mjs +136 -1
  209. package/test/cases/dom/updater.mjs +174 -0
@@ -2,36 +2,69 @@ import * as chai from "chai";
2
2
  import { chaiDom } from "../../../util/chai-dom.mjs";
3
3
  import { initJSDOM } from "../../../util/jsdom.mjs";
4
4
 
5
- let expect = chai.expect;
5
+ const expect = chai.expect;
6
6
  chai.use(chaiDom);
7
7
 
8
8
  let InputGroup;
9
9
 
10
- describe("InputGroup", function () {
11
- before(function (done) {
12
- initJSDOM()
13
- .then(() => import("../../../../source/components/form/input-group.mjs"))
14
- .then((m) => {
15
- InputGroup = m.InputGroup;
16
- done();
17
- })
18
- .catch((e) => done(e));
19
- });
20
-
21
- it("should create an input group element", function () {
22
- const inputGroup = document.createElement("monster-input-group");
23
-
24
- expect(inputGroup).to.be.instanceof(InputGroup);
25
- });
26
-
27
- it("should allow the input group to stretch within constrained controls", function () {
28
- const cssText = InputGroup.getCSSStyleSheet()
29
- .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
30
- .map((rule) => rule.cssText)
31
- .join("\n");
32
-
33
- expect(cssText).to.contain("display: flex");
34
- expect(cssText).to.contain("box-sizing: border-box");
35
- expect(cssText).to.contain("height: 100%");
36
- });
10
+ describe("InputGroup", () => {
11
+ before((done) => {
12
+ initJSDOM()
13
+ .then(() => import("../../../../source/components/form/input-group.mjs"))
14
+ .then((m) => {
15
+ InputGroup = m.InputGroup;
16
+ done();
17
+ })
18
+ .catch((e) => done(e));
19
+ });
20
+
21
+ it("should create an input group element", () => {
22
+ const inputGroup = document.createElement("monster-input-group");
23
+
24
+ expect(inputGroup).to.be.instanceof(InputGroup);
25
+ });
26
+
27
+ it("should allow the input group to stretch within constrained controls", () => {
28
+ const cssText = InputGroup.getCSSStyleSheet()
29
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
30
+ .map((rule) => rule.cssText)
31
+ .join("\n");
32
+
33
+ expect(cssText).to.contain("display: flex");
34
+ expect(cssText).to.contain("box-sizing: border-box");
35
+ expect(cssText).to.contain("height: 100%");
36
+ expect(cssText).to.contain(
37
+ "min-block-size: var(--monster-control-min-block-size",
38
+ );
39
+ expect(cssText).to.contain("min-block-size: 0");
40
+ });
41
+
42
+ it("should draw one focus ring around the complete composed control", () => {
43
+ const cssText = InputGroup.getCSSStyleSheet()
44
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
45
+ .map((rule) => rule.cssText)
46
+ .join("\n");
47
+
48
+ expect(cssText).to.contain(":host(:focus-within)");
49
+ expect(cssText).to.contain("var(--monster-focus-ring-width");
50
+ expect(cssText).to.contain("var(--monster-focus-ring-offset");
51
+ expect(cssText).to.contain("::slotted(input:focus-visible)");
52
+ expect(cssText).to.contain("outline: none !important");
53
+ expect(cssText).to.contain("box-sizing: content-box !important");
54
+ expect(cssText).to.contain("height: 1.1rem");
55
+ });
56
+
57
+ it("should keep hover feedback on the complete composed control", () => {
58
+ const cssText = InputGroup.getCSSStyleSheet()
59
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
60
+ .map((rule) => rule.cssText)
61
+ .join("\n");
62
+
63
+ expect(cssText).to.contain(":host(:hover)");
64
+ expect(cssText).to.contain("--monster-input-group-hover-background-color");
65
+ expect(cssText).to.contain("--monster-input-group-hover-border-color");
66
+ expect(cssText).to.contain("::slotted(input:hover)");
67
+ expect(cssText).to.contain("box-shadow: none !important");
68
+ expect(cssText).to.contain("transition: none !important");
69
+ });
37
70
  });
@@ -34,6 +34,53 @@ describe("Login", function () {
34
34
  expect(document.createElement("monster-login")).is.instanceof(Login);
35
35
  });
36
36
 
37
+ it("should associate visible labels and pass them to nested controls", async function () {
38
+ const login = document.createElement("monster-login");
39
+ document.getElementById("mocks").appendChild(login);
40
+ await new Promise((resolve) => setTimeout(resolve, 0));
41
+
42
+ const username = login.shadowRoot.querySelector("input[name='username']");
43
+ const usernameLabel = login.shadowRoot.querySelector(
44
+ 'label[for="monster-login-username"]',
45
+ );
46
+ const email = login.shadowRoot.querySelector("input[name='email']");
47
+ const emailLabel = login.shadowRoot.querySelector(
48
+ 'label[for="monster-login-email"]',
49
+ );
50
+ const password = login.shadowRoot.querySelector("monster-password");
51
+ const digits = login.shadowRoot.querySelector(
52
+ '[data-monster-role="digits-collapse"] monster-digits',
53
+ );
54
+
55
+ expect(usernameLabel).to.not.equal(null);
56
+ expect(usernameLabel.htmlFor).to.equal(username.id);
57
+ expect(emailLabel).to.not.equal(null);
58
+ expect(emailLabel.htmlFor).to.equal(email.id);
59
+ expect(password.getOption("labels.input")).to.equal(
60
+ login.getOption("labels.password"),
61
+ );
62
+ expect(digits.getOption("labels.group")).to.equal(
63
+ login.getOption("labels.digits"),
64
+ );
65
+ });
66
+
67
+ it("keeps validation markers from shifting later grid rows", function () {
68
+ const login = document.createElement("monster-login");
69
+ document.getElementById("mocks").appendChild(login);
70
+
71
+ const validationMarkers = [
72
+ ...login.shadowRoot.querySelectorAll(
73
+ 'div[style*="grid-column: 1 / -1"]',
74
+ ),
75
+ ];
76
+ expect(validationMarkers).to.have.length.greaterThan(0);
77
+ expect(
78
+ validationMarkers.every(
79
+ (element) => element.style.gridColumn === "1 / -1",
80
+ ),
81
+ ).to.be.true;
82
+ });
83
+
37
84
  it("should respect the native hidden attribute on the host", function () {
38
85
  const login = document.createElement("monster-login");
39
86
  login.setAttribute("hidden", "");
@@ -0,0 +1,63 @@
1
+ import * as chai from "chai";
2
+ import { chaiDom } from "../../../util/chai-dom.mjs";
3
+ import { initJSDOM } from "../../../util/jsdom.mjs";
4
+
5
+ const { expect } = chai;
6
+ chai.use(chaiDom);
7
+ const nextTask = () => new Promise((resolve) => setTimeout(resolve, 0));
8
+
9
+ let Password;
10
+
11
+ describe("Password", function () {
12
+ before(function (done) {
13
+ initJSDOM()
14
+ .then(() => import("../../../../source/components/form/password.mjs"))
15
+ .then((module) => {
16
+ Password = module.Password;
17
+ done();
18
+ })
19
+ .catch((error) => done(error));
20
+ });
21
+
22
+ afterEach(() => {
23
+ document.getElementById("mocks").replaceChildren();
24
+ });
25
+
26
+ it("should name its native input from the public label option", async function () {
27
+ const password = document.createElement("monster-password");
28
+ document.getElementById("mocks").append(password);
29
+ await nextTask();
30
+ const input = password.shadowRoot.querySelector(
31
+ '[data-monster-role="input"]',
32
+ );
33
+
34
+ expect(input.getAttribute("aria-label")).to.equal("Password");
35
+ password.setOption("labels.input", "Passwort");
36
+ await nextTask();
37
+ expect(input.getAttribute("aria-label")).to.equal("Passwort");
38
+ });
39
+
40
+ it("should preserve the input-group radius around the reveal control", function () {
41
+ const cssText = Password.getCSSStyleSheet()
42
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
43
+ .map((rule) => rule.cssText)
44
+ .join("\n");
45
+
46
+ expect(cssText).to.contain("monster-input-group");
47
+ expect(cssText).to.contain("--monster-theme-control-bg-color");
48
+ expect(cssText).to.contain("overflow: hidden");
49
+ });
50
+
51
+ it("should vertically center the input and reveal icon in the shared control height", function () {
52
+ const cssText = Password.getCSSStyleSheet()
53
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
54
+ .map((rule) => rule.cssText)
55
+ .join("\n");
56
+
57
+ expect(cssText).to.contain("--monster-password-control-block-size");
58
+ expect(cssText).to.contain("var(--monster-control-min-block-size,2.5rem)");
59
+ expect(cssText).to.contain("[data-monster-role=control]");
60
+ expect(cssText).to.contain("monster-input-group");
61
+ expect(cssText).to.contain("block-size: 100%");
62
+ });
63
+ });
@@ -31,6 +31,23 @@ describe("PopperButton", function () {
31
31
  );
32
32
  });
33
33
 
34
+ it("should style native form controls inside popper content", function () {
35
+ const cssText = PopperButton.getCSSStyleSheet()
36
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules))
37
+ .map((rule) => rule.cssText)
38
+ .join("\n")
39
+ .replaceAll(/\s+/g, "");
40
+
41
+ expect(cssText).to.contain(
42
+ "input:not([type=checkbox]):not([type=radio]),select,textarea{min-height:var(--monster-control-min-block-size,auto)",
43
+ );
44
+ expect(cssText).to.contain(
45
+ "input:is([type=checkbox],[type=radio]){block-size:1rem;inline-size:1rem",
46
+ );
47
+ expect(cssText).to.contain("font-size:100%");
48
+ expect(cssText).to.contain("textarea{resize:vertical;min-height:6rem");
49
+ });
50
+
34
51
  it("should apply content overflow mode to the rendered content wrapper", function (done) {
35
52
  let mocks = document.getElementById("mocks");
36
53
  const button = document.createElement("monster-popper-button");
@@ -48,9 +65,9 @@ describe("PopperButton", function () {
48
65
 
49
66
  setTimeout(() => {
50
67
  try {
51
- expect(
52
- content.getAttribute("data-monster-overflow-mode"),
53
- ).to.equal("horizontal");
68
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
69
+ "horizontal",
70
+ );
54
71
  done();
55
72
  } catch (e) {
56
73
  done(e);
@@ -79,9 +96,9 @@ describe("PopperButton", function () {
79
96
 
80
97
  setTimeout(() => {
81
98
  try {
82
- expect(
83
- content.getAttribute("data-monster-overflow-mode"),
84
- ).to.equal("horizontal");
99
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
100
+ "horizontal",
101
+ );
85
102
  done();
86
103
  } catch (e) {
87
104
  done(e);
@@ -107,12 +124,12 @@ describe("PopperButton", function () {
107
124
 
108
125
  setTimeout(() => {
109
126
  try {
110
- expect(
111
- button.getOption("popper.contentOverflow"),
112
- ).to.equal("visible");
113
- expect(
114
- content.getAttribute("data-monster-overflow-mode"),
115
- ).to.equal("visible");
127
+ expect(button.getOption("popper.contentOverflow")).to.equal(
128
+ "visible",
129
+ );
130
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
131
+ "visible",
132
+ );
116
133
  done();
117
134
  } catch (e) {
118
135
  done(e);
@@ -161,9 +178,7 @@ describe("PopperButton", function () {
161
178
  try {
162
179
  const button = mocks.querySelector("monster-popper-button");
163
180
  const content = button.shadowRoot.querySelector('[part="content"]');
164
- expect(button.getOption("popper.contentOverflow")).to.equal(
165
- "visible",
166
- );
181
+ expect(button.getOption("popper.contentOverflow")).to.equal("visible");
167
182
  expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
168
183
  "visible",
169
184
  );
@@ -0,0 +1,46 @@
1
+ import * as chai from "chai";
2
+ import { initJSDOM } from "../../../util/jsdom.mjs";
3
+
4
+ const { expect } = chai;
5
+ const nextTask = () => new Promise((resolve) => setTimeout(resolve, 0));
6
+
7
+ describe("Quantity accessibility", function () {
8
+ before(async () => {
9
+ await initJSDOM();
10
+ await import("element-internals-polyfill");
11
+ await import("../../../../source/components/form/quantity.mjs");
12
+ });
13
+
14
+ afterEach(() => {
15
+ document.getElementById("mocks").replaceChildren();
16
+ });
17
+
18
+ it("should name every native control from configurable labels", async function () {
19
+ const quantity = document.createElement("monster-quantity");
20
+ quantity.setOptions({
21
+ labels: {
22
+ input: "Seats",
23
+ decrement: "Remove one seat",
24
+ increment: "Add one seat",
25
+ },
26
+ });
27
+ document.getElementById("mocks").append(quantity);
28
+ await nextTask();
29
+
30
+ expect(
31
+ quantity.shadowRoot
32
+ .querySelector('[data-monster-role="input"]')
33
+ .getAttribute("aria-label"),
34
+ ).to.equal("Seats");
35
+ expect(
36
+ quantity.shadowRoot
37
+ .querySelector('[data-monster-role="decrement"]')
38
+ .getAttribute("aria-label"),
39
+ ).to.equal("Remove one seat");
40
+ expect(
41
+ quantity.shadowRoot
42
+ .querySelector('[data-monster-role="increment"]')
43
+ .getAttribute("aria-label"),
44
+ ).to.equal("Add one seat");
45
+ });
46
+ });
@@ -0,0 +1,79 @@
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
+ describe("RegisterWizard", () => {
7
+ before(async () => {
8
+ await initJSDOM();
9
+ await import("element-internals-polyfill");
10
+
11
+ const global = getGlobal();
12
+ if (!global.ResizeObserver) {
13
+ global.ResizeObserver = ResizeObserverMock;
14
+ }
15
+
16
+ await import("../../../../source/components/form/register-wizard.mjs");
17
+ });
18
+
19
+ afterEach(() => {
20
+ document.getElementById("mocks").replaceChildren();
21
+ });
22
+
23
+ it("keeps explicit labels inside nested field sets", async () => {
24
+ const element = document.createElement("monster-register-wizard");
25
+ element.setOptions({
26
+ labels: {
27
+ email: "Email address",
28
+ password: "Password",
29
+ passwordConfirm: "Confirm password",
30
+ firstName: "First name",
31
+ lastName: "Last name",
32
+ consentsTitle: "Consents",
33
+ },
34
+ });
35
+ document.getElementById("mocks").append(element);
36
+ await new Promise((resolve) => setTimeout(resolve, 0));
37
+
38
+ const labels = new Map([
39
+ ["email-label", "Email address"],
40
+ ["password-label", "Password"],
41
+ ["password-confirm-label", "Confirm password"],
42
+ ["first-name-label", "First name"],
43
+ ["last-name-label", "Last name"],
44
+ ["consents-title", "Consents"],
45
+ ]);
46
+ for (const [role, expected] of labels) {
47
+ const label = element.shadowRoot.querySelector(
48
+ `[data-monster-role~="${role}"]`,
49
+ );
50
+ assert.equal(label.textContent.trim(), expected, role);
51
+ }
52
+ const email = element.shadowRoot.querySelector(
53
+ '[data-monster-role="email-input"]',
54
+ );
55
+ assert.equal(
56
+ email.getAttribute("aria-label"),
57
+ element.getOption("labels.emailAria"),
58
+ );
59
+ assert.equal(
60
+ email.getAttribute("placeholder"),
61
+ element.getOption("labels.emailPlaceholder"),
62
+ );
63
+ assert.equal(
64
+ email.getAttribute("autocomplete"),
65
+ element.getOption("autocomplete.email"),
66
+ );
67
+ const password = element.shadowRoot.querySelector(
68
+ '[data-monster-role="password-input"]',
69
+ );
70
+ assert.equal(password.hasAttribute("aria-label"), false);
71
+ assert.equal(
72
+ password.shadowRoot
73
+ .querySelector('[data-monster-role="input"]')
74
+ .getAttribute("aria-label"),
75
+ element.getOption("labels.passwordAria"),
76
+ );
77
+ assert.equal(element.shadowRoot.textContent.includes("undefined"), false);
78
+ });
79
+ });
@@ -187,9 +187,29 @@ describe('Select', function () {
187
187
  expect(cssText).to.contain("--monster-select-container-align-self");
188
188
  expect(cssText).to.contain("--monster-select-container-block-size");
189
189
  expect(cssText).to.contain("--monster-select-control-padding");
190
+ expect(cssText).to.contain("--monster-select-control-min-block-size");
191
+ expect(cssText).to.contain("--monster-select-option-padding");
192
+ expect(cssText).to.contain("--monster-select-option-control-size");
190
193
  expect(cssText).to.contain("--monster-select-selection-margin");
191
194
  expect(cssText).to.contain("--monster-select-selection-flex-wrap");
192
- expect(cssText.replace(/\s+/g, '')).to.contain('z-index:var(--monster-z-index-popover)');
195
+ expect(cssText.replace(/\s+/g, '')).to.contain(
196
+ 'div[data-monster-role=popper]{z-index:var(--monster-z-index-popover);}'
197
+ );
198
+ expect(cssText.replace(/\s+/g, '')).to.contain(
199
+ 'min-block-size:var(--monster-select-control-min-block-size,var(--monster-control-min-block-size,2.5rem))'
200
+ );
201
+ expect(cssText.replace(/\s+/g, '')).to.contain(
202
+ 'margin:var(--monster-select-selection-margin,05px)'
203
+ );
204
+ expect(cssText.replace(/\s+/g, '')).to.contain(
205
+ 'padding:var(--monster-select-option-padding,2px6px)'
206
+ );
207
+ expect(cssText.replace(/\s+/g, '')).to.contain(
208
+ 'min-block-size:var(--monster-select-option-control-size,1rem)'
209
+ );
210
+ expect(cssText.replace(/\s+/g, '')).to.match(
211
+ /@media\(prefers-color-scheme:light\)\{\[data-monster-role=control\]\{[^}]*border-color:var\(--monster-theme-control-border-color\)/u
212
+ );
193
213
  });
194
214
 
195
215
  it('should render select pagination in the compact arrow mode immediately', async function () {
@@ -1532,6 +1552,7 @@ describe('Select', function () {
1532
1552
  const result = getDefaultSelectPopperPositionProfile();
1533
1553
 
1534
1554
  expect(result.placement).to.equal('bottom-start');
1555
+ expect(result.strategy).to.equal('fixed');
1535
1556
  expect(result.middleware).to.deep.equal([
1536
1557
  'flip',
1537
1558
  'offset:4',
@@ -2122,6 +2143,71 @@ describe('Select', function () {
2122
2143
  mocks.innerHTML = "";
2123
2144
  })
2124
2145
 
2146
+ it('should prevent automatic popper filter focus from scrolling the page', async function () {
2147
+ const mocks = document.getElementById('mocks');
2148
+ const select = document.createElement('monster-select');
2149
+ select.setOption('filter.mode', 'options');
2150
+ select.setOption('filter.position', 'popper');
2151
+ select.setOption('options', [
2152
+ {label: 'Alpha', value: 'alpha'},
2153
+ {label: 'Beta', value: 'beta'}
2154
+ ]);
2155
+ mocks.appendChild(select);
2156
+
2157
+ await waitForCondition(() => {
2158
+ return select.shadowRoot.querySelector(
2159
+ '[data-monster-role=filter][name="popper-filter"]'
2160
+ ) instanceof HTMLInputElement;
2161
+ });
2162
+ await new Promise(resolve => setTimeout(resolve, 150));
2163
+
2164
+ const filterInput = select.shadowRoot.querySelector(
2165
+ '[data-monster-role=filter][name="popper-filter"]'
2166
+ );
2167
+ const focusCalls = [];
2168
+ filterInput.focus = (...args) => {
2169
+ focusCalls.push(args);
2170
+ };
2171
+
2172
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
2173
+
2174
+ await waitForCondition(() => focusCalls.length > 0);
2175
+
2176
+ expect(focusCalls.every(([options]) => {
2177
+ return options?.preventScroll === true;
2178
+ })).to.equal(true);
2179
+ });
2180
+
2181
+ it('should reveal a searchable popper after opening', async function () {
2182
+ this.timeout(5000);
2183
+
2184
+ const mocks = document.getElementById('mocks');
2185
+ const select = document.createElement('monster-select');
2186
+ select.setOption('filter.mode', 'options');
2187
+ select.setOption('filter.position', 'popper');
2188
+ select.setOption('options', [
2189
+ {label: 'Alpha', value: 'alpha'},
2190
+ {label: 'Beta', value: 'beta'}
2191
+ ]);
2192
+ mocks.appendChild(select);
2193
+
2194
+ await waitForCondition(() => {
2195
+ return select.shadowRoot.querySelector(
2196
+ '[data-monster-role=container]'
2197
+ ) instanceof HTMLElement;
2198
+ });
2199
+
2200
+ const popper = select.shadowRoot.querySelector(
2201
+ '[data-monster-role=popper]'
2202
+ );
2203
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
2204
+
2205
+ await waitForCondition(() => popper.style.display === 'block');
2206
+ await new Promise(resolve => setTimeout(resolve, 300));
2207
+
2208
+ expect(popper.style.visibility).to.equal('');
2209
+ });
2210
+
2125
2211
  it('should have no options', function (done) {
2126
2212
 
2127
2213
  let mocks = document.getElementById('mocks');
@@ -2147,7 +2233,7 @@ describe('Select', function () {
2147
2233
 
2148
2234
  });
2149
2235
 
2150
- it('should expose styling parts for container, filter messages and pagination', function (done) {
2236
+ it('should expose styling parts for container, filter messages and the compact pagination footer', function (done) {
2151
2237
 
2152
2238
  let mocks = document.getElementById('mocks');
2153
2239
  const select = document.createElement('monster-select');
@@ -2158,10 +2244,16 @@ describe('Select', function () {
2158
2244
  try {
2159
2245
  const container = select.shadowRoot.querySelector('[data-monster-role=container]');
2160
2246
  const selectionMessages = select.shadowRoot.querySelector('[data-monster-role=selection-messages]');
2247
+ const paginationFooter = select.shadowRoot.querySelector('[data-monster-role=pagination-footer]');
2248
+ const remoteInfo = select.shadowRoot.querySelector('[data-monster-role=remote-info]');
2161
2249
  const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');
2162
2250
 
2163
2251
  expect(container.getAttribute('part')).to.equal('container');
2164
2252
  expect(selectionMessages.getAttribute('part')).to.equal('selection-messages');
2253
+ expect(paginationFooter.getAttribute('part')).to.equal('pagination-footer');
2254
+ expect(paginationFooter.firstElementChild).to.equal(remoteInfo);
2255
+ expect(remoteInfo.getAttribute('part')).to.equal('remote-info');
2256
+ expect(remoteInfo.nextElementSibling).to.equal(pagination);
2165
2257
  expect(pagination.getAttribute('part')).to.equal('pagination');
2166
2258
  } catch (e) {
2167
2259
  return done(e);
@@ -27,9 +27,62 @@ describe("Sheet", function () {
27
27
  expect(document.createElement("monster-sheet")).to.be.instanceof(Sheet);
28
28
  });
29
29
 
30
+ it("should name editable cells from their row and column labels", function () {
31
+ const sheet = document.createElement("monster-sheet");
32
+ sheet.setOptions({
33
+ columns: [{ id: "project", label: "Project" }],
34
+ rows: [{ id: "first", label: "First row" }],
35
+ labels: { cell: "{column} / {row}" },
36
+ });
37
+ document.getElementById("mocks").appendChild(sheet);
38
+
39
+ const input = sheet.shadowRoot.querySelector(
40
+ '[data-monster-role="cell-input"]',
41
+ );
42
+ expect(input.getAttribute("aria-label")).to.equal("Project / First row");
43
+ });
44
+
45
+ it("should position and expose its context menu on right click", function () {
46
+ const sheet = document.createElement("monster-sheet");
47
+ document.getElementById("mocks").appendChild(sheet);
48
+
49
+ const input = sheet.shadowRoot.querySelector(
50
+ '[data-monster-role="cell-input"]',
51
+ );
52
+ const menu = sheet.shadowRoot.querySelector(
53
+ '[data-monster-role="context-menu"]',
54
+ );
55
+ const event = new window.MouseEvent("contextmenu", {
56
+ bubbles: true,
57
+ cancelable: true,
58
+ clientX: 120,
59
+ clientY: 80,
60
+ });
61
+
62
+ input.dispatchEvent(event);
63
+
64
+ expect(event.defaultPrevented).to.equal(true);
65
+ expect(menu.hidden).to.equal(false);
66
+ expect(menu.style.display).to.equal("flex");
67
+ expect(menu.style.left).to.equal("0px");
68
+ expect(menu.style.top).to.equal("0px");
69
+ expect(menu.querySelectorAll('[role="menuitem"]')).to.have.length(3);
70
+
71
+ const cssText = Sheet.getCSSStyleSheet()
72
+ .flatMap((styleSheet) => Array.from(styleSheet.cssRules || []))
73
+ .map((rule) => rule.cssText)
74
+ .join("\n")
75
+ .replace(/\s+/g, "");
76
+ expect(cssText).to.contain("[part=context-menu]{position:absolute");
77
+ expect(cssText).to.contain(
78
+ "min-block-size:var(--monster-action-menu-item-min-block-size,44px)",
79
+ );
80
+ });
81
+
30
82
  it("should coalesce column resize pointer moves", function (done) {
31
83
  const originalRequestAnimationFrame = window.requestAnimationFrame;
32
- const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
84
+ const originalGlobalRequestAnimationFrame =
85
+ globalThis.requestAnimationFrame;
33
86
  const originalCancelAnimationFrame = window.cancelAnimationFrame;
34
87
  const originalGlobalCancelAnimationFrame = globalThis.cancelAnimationFrame;
35
88
  const scheduledCallbacks = [];
@@ -110,7 +163,8 @@ describe("Sheet", function () {
110
163
  done(e);
111
164
  } finally {
112
165
  window.requestAnimationFrame = originalRequestAnimationFrame;
113
- globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
166
+ globalThis.requestAnimationFrame =
167
+ originalGlobalRequestAnimationFrame;
114
168
  window.cancelAnimationFrame = originalCancelAnimationFrame;
115
169
  globalThis.cancelAnimationFrame = originalGlobalCancelAnimationFrame;
116
170
  }