@schukai/monster 4.129.0 → 4.129.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.
@@ -21,6 +21,7 @@ import {
21
21
  import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
22
22
  import {
23
23
  assembleMethodSymbol,
24
+ attributeObserverSymbol,
24
25
  CustomElement,
25
26
  registerCustomElement,
26
27
  } from "../../dom/customelement.mjs";
@@ -79,6 +80,13 @@ const buttonElementSymbol = Symbol("buttonElement");
79
80
  */
80
81
  const popperElementSymbol = Symbol("popperElement");
81
82
 
83
+ /**
84
+ * Symbol for content element reference
85
+ * @private
86
+ * @type {symbol}
87
+ */
88
+ const contentElementSymbol = Symbol("contentElement");
89
+
82
90
  /**
83
91
  * Symbol for arrow element reference
84
92
  * @private
@@ -143,6 +151,7 @@ class Popper extends CustomElement {
143
151
  * @property {Object} popper - Positioning options
144
152
  * @property {string} popper.placement - Placement: top|bottom|left|right
145
153
  * @property {Array} popper.middleware - Positioning middleware functions
154
+ * @property {string} popper.contentOverflow - Content clipping mode: both|horizontal
146
155
  * @property {Object} features - Feature flags
147
156
  * @property {boolean} features.preventOpenEventSent - Prevent open event
148
157
  * @returns {Object} Default options merged with parent defaults
@@ -157,6 +166,7 @@ class Popper extends CustomElement {
157
166
  popper: {
158
167
  placement: "top",
159
168
  middleware: ["autoPlacement", "shift", "offset:15", "arrow"],
169
+ contentOverflow: "both",
160
170
  },
161
171
  features: {
162
172
  preventOpenEventSent: false,
@@ -172,9 +182,24 @@ class Popper extends CustomElement {
172
182
  [assembleMethodSymbol]() {
173
183
  super[assembleMethodSymbol]();
174
184
  initControlReferences.call(this);
185
+ initOverflowObserver.call(this);
186
+ applyContentOverflowMode.call(this);
175
187
  initEventHandler.call(this);
176
188
  }
177
189
 
190
+ /**
191
+ * @inheritdoc
192
+ */
193
+ setOption(path, value) {
194
+ super.setOption(path, value);
195
+
196
+ if (path === "popper.contentOverflow") {
197
+ applyContentOverflowMode.call(this);
198
+ }
199
+
200
+ return this;
201
+ }
202
+
178
203
  /**
179
204
  * Gets the custom element tag name
180
205
  * @return {string} The tag name
@@ -245,6 +270,16 @@ class Popper extends CustomElement {
245
270
  return this;
246
271
  }
247
272
 
273
+ /**
274
+ * Resolves the effective content overflow mode for the rendered wrapper.
275
+ * Subclasses can override this when the configured option is only an intermediate mode.
276
+ *
277
+ * @return {string}
278
+ */
279
+ resolveContentOverflowMode() {
280
+ return this.getOption("popper.contentOverflow", "both");
281
+ }
282
+
248
283
  /**
249
284
  * Hides the popper element
250
285
  * @return {Popper} The popper instance
@@ -485,6 +520,8 @@ function show() {
485
520
  self,
486
521
  });
487
522
 
523
+ applyContentOverflowMode.call(self);
524
+
488
525
  popperElement.style.visibility = "hidden";
489
526
  popperElement.style.display = STYLE_DISPLAY_MODE_BLOCK;
490
527
 
@@ -520,6 +557,8 @@ function updatePopper() {
520
557
  return;
521
558
  }
522
559
 
560
+ applyContentOverflowMode.call(this);
561
+
523
562
  positionPopper.call(
524
563
  this,
525
564
  this[controlElementSymbol],
@@ -596,9 +635,39 @@ function initControlReferences() {
596
635
  this[arrowElementSymbol] = this.shadowRoot.querySelector(
597
636
  `[${ATTRIBUTE_ROLE}=arrow]`,
598
637
  );
638
+ this[contentElementSymbol] = this.shadowRoot.querySelector(`[part="content"]`);
599
639
  return this;
600
640
  }
601
641
 
642
+ /**
643
+ * Keeps the rendered content wrapper in sync with the configured overflow mode
644
+ * @private
645
+ * @return {void}
646
+ */
647
+ function initOverflowObserver() {
648
+ this[attributeObserverSymbol]["data-monster-option-popper-content-overflow"] =
649
+ () => {
650
+ applyContentOverflowMode.call(this);
651
+ };
652
+ }
653
+
654
+ /**
655
+ * Applies the current content overflow mode to the rendered wrapper element
656
+ * @private
657
+ * @return {void}
658
+ */
659
+ function applyContentOverflowMode() {
660
+ const contentElement = this[contentElementSymbol];
661
+ if (!(contentElement instanceof HTMLElement)) {
662
+ return;
663
+ }
664
+
665
+ contentElement.setAttribute(
666
+ "data-monster-overflow-mode",
667
+ this.resolveContentOverflowMode(),
668
+ );
669
+ }
670
+
602
671
  /**
603
672
  * Gets the main template HTML
604
673
  * @private
@@ -612,7 +681,9 @@ function getTemplate() {
612
681
 
613
682
  <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
614
683
  <div data-monster-role="arrow"></div>
615
- <div part="content" class="flex" data-monster-replace="path:content">
684
+ <div part="content"
685
+ class="flex"
686
+ data-monster-replace="path:content">
616
687
  </div>
617
688
  </div>
618
689
  </div>
@@ -10,10 +10,10 @@
10
10
  * For more information about purchasing a commercial license, please contact Volker Schukai.
11
11
  */
12
12
 
13
- import { addAttributeToken } from "../../../dom/attributes.mjs";
14
- import { ATTRIBUTE_ERRORMESSAGE } from "../../../dom/constants.mjs";
13
+ import {addAttributeToken} from "../../../dom/attributes.mjs";
14
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs";
15
15
 
16
- export { PopperStyleSheet };
16
+ export {PopperStyleSheet}
17
17
 
18
18
  /**
19
19
  * @private
@@ -22,17 +22,10 @@ export { PopperStyleSheet };
22
22
  const PopperStyleSheet = new CSSStyleSheet();
23
23
 
24
24
  try {
25
- PopperStyleSheet.insertRule(
26
- `
25
+ PopperStyleSheet.insertRule(`
27
26
  @layer popper {
28
- [data-monster-role=control]{box-sizing:border-box;outline:none;width:100%}[data-monster-role=control].flex{align-items:center;display:flex;flex-direction:row}:host{box-sizing:border-box;display:block}div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}[data-monster-role=control]{display:flex;position:relative}
29
- }`,
30
- 0,
31
- );
27
+ [data-monster-role=control]{box-sizing:border-box;outline:none;width:100%}[data-monster-role=control].flex{align-items:center;display:flex;flex-direction:row}:host{box-sizing:border-box;display:block}div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:inset(calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0 calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0);overflow:visible}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}[data-monster-role=control]{display:flex;position:relative}
28
+ }`, 0);
32
29
  } catch (e) {
33
- addAttributeToken(
34
- document.getRootNode().querySelector("html"),
35
- ATTRIBUTE_ERRORMESSAGE,
36
- e + "",
37
- );
30
+ addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + "");
38
31
  }
@@ -1,50 +1,2 @@
1
1
  /** generated from floating-ui.pcss **/
2
- div[data-monster-role="popper"] {
3
- align-content: center;
4
- background: var(--monster-bg-color-primary-1);
5
- border-color: var(--monster-bg-color-primary-4);
6
- border-radius: var(--monster-border-radius);
7
- border-style: var(--monster-border-style);
8
- border-width: var(--monster-border-width);
9
- box-shadow: var(--monster-box-shadow-1);
10
- box-sizing: border-box;
11
- color: var(--monster-color-primary-1);
12
- display: none;
13
- justify-content: space-between;
14
- left: 0;
15
- max-height: var(--monster-popper-max-height, calc(100vh - 2rem));
16
- max-width: var(--monster-popper-max-width, calc(100vw - 2rem));
17
- padding: 1.1em;
18
- position: absolute;
19
- top: 0;
20
- width: -moz-max-content;
21
- width: max-content;
22
- z-index: var(--monster-z-index-modal);
23
- }
24
- div[data-monster-role="popper"] > [part="content"] {
25
- max-height: var(--monster-popper-content-max-height, calc(100vh - 4.2rem));
26
- max-width: 100%;
27
- overflow: auto;
28
- }
29
- div[data-monster-role="popper"] div[data-monster-role="arrow"] {
30
- background: var(--monster-bg-color-primary-1);
31
- height: calc(
32
- max(
33
- var(--monster-popper-witharrrow-distance),
34
- -1 *
35
- var(--monster-popper-witharrrow-distance)
36
- ) *
37
- 2
38
- );
39
- pointer-events: none;
40
- position: absolute;
41
- width: calc(
42
- max(
43
- var(--monster-popper-witharrrow-distance),
44
- -1 *
45
- var(--monster-popper-witharrrow-distance)
46
- ) *
47
- 2
48
- );
49
- z-index: -1;
50
- }
2
+ div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:inset(calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0 calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0);overflow:visible}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}
@@ -29,6 +29,16 @@ div[data-monster-role=popper] {
29
29
  max-height: var(--monster-popper-content-max-height, calc(100vh - 4.2rem));
30
30
  }
31
31
 
32
+ & > [part=content][data-monster-overflow-mode=horizontal] {
33
+ overflow: visible;
34
+ clip-path: inset(
35
+ calc(-1 * var(--monster-popper-content-block-overflow, 100vh))
36
+ 0
37
+ calc(-1 * var(--monster-popper-content-block-overflow, 100vh))
38
+ 0
39
+ );
40
+ }
41
+
32
42
  & div[data-monster-role=arrow] {
33
43
  position: absolute;
34
44
  width: calc(2 * max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance)));
@@ -10,10 +10,10 @@
10
10
  * For more information about purchasing a commercial license, please contact Volker Schukai.
11
11
  */
12
12
 
13
- import { addAttributeToken } from "../../dom/attributes.mjs";
14
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
13
+ import {addAttributeToken} from "../../dom/attributes.mjs";
14
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
15
15
 
16
- export { FloatingUiStyleSheet };
16
+ export {FloatingUiStyleSheet}
17
17
 
18
18
  /**
19
19
  * @private
@@ -22,17 +22,10 @@ export { FloatingUiStyleSheet };
22
22
  const FloatingUiStyleSheet = new CSSStyleSheet();
23
23
 
24
24
  try {
25
- FloatingUiStyleSheet.insertRule(
26
- `
25
+ FloatingUiStyleSheet.insertRule(`
27
26
  @layer floatingui {
28
- div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}
29
- }`,
30
- 0,
31
- );
27
+ div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:inset(calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0 calc(var(--monster-popper-content-block-overflow, 100vh)*-1) 0);overflow:visible}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}
28
+ }`, 0);
32
29
  } catch (e) {
33
- addAttributeToken(
34
- document.getRootNode().querySelector("html"),
35
- ATTRIBUTE_ERRORMESSAGE,
36
- e + "",
37
- );
30
+ addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + "");
38
31
  }
@@ -32,32 +32,68 @@ function extractKeys(
32
32
  ) {
33
33
  const resultMap = new Map();
34
34
 
35
- function helper(currentObj, currentKeyPrefix, currentValuePrefix) {
35
+ function normalizeKeySegment(value) {
36
+ return String(value).toLowerCase();
37
+ }
38
+
39
+ function toKebabCase(value) {
40
+ return String(value)
41
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
42
+ .toLowerCase();
43
+ }
44
+
45
+ function appendKeys(compactKey, kebabKey, valuePath) {
46
+ resultMap.set(compactKey, valuePath);
47
+ if (kebabKey !== compactKey) {
48
+ resultMap.set(kebabKey, valuePath);
49
+ }
50
+ }
51
+
52
+ function helper(
53
+ currentObj,
54
+ currentCompactKeyPrefix,
55
+ currentKebabKeyPrefix,
56
+ currentValuePrefix,
57
+ ) {
36
58
  for (const key in currentObj) {
59
+ const compactSegment = normalizeKeySegment(key);
60
+ const kebabSegment = toKebabCase(key);
61
+
37
62
  if (
38
63
  currentObj[key] !== null &&
39
64
  typeof currentObj[key] === "object" &&
40
65
  !Array.isArray(currentObj[key])
41
66
  ) {
42
- const newKeyPrefix = currentKeyPrefix
43
- ? currentKeyPrefix + keySeparator + key.toLowerCase()
44
- : key.toLowerCase();
67
+ const newCompactKeyPrefix = currentCompactKeyPrefix
68
+ ? currentCompactKeyPrefix + keySeparator + compactSegment
69
+ : compactSegment;
70
+ const newKebabKeyPrefix = currentKebabKeyPrefix
71
+ ? currentKebabKeyPrefix + keySeparator + kebabSegment
72
+ : kebabSegment;
45
73
  const newValuePrefix = currentValuePrefix
46
74
  ? currentValuePrefix + valueSeparator + key
47
75
  : key;
48
- helper(currentObj[key], newKeyPrefix, newValuePrefix);
76
+ helper(
77
+ currentObj[key],
78
+ newCompactKeyPrefix,
79
+ newKebabKeyPrefix,
80
+ newValuePrefix,
81
+ );
49
82
  } else {
50
- const finalKey = currentKeyPrefix
51
- ? currentKeyPrefix + keySeparator + key.toLowerCase()
52
- : key.toLowerCase();
83
+ const finalCompactKey = currentCompactKeyPrefix
84
+ ? currentCompactKeyPrefix + keySeparator + compactSegment
85
+ : compactSegment;
86
+ const finalKebabKey = currentKebabKeyPrefix
87
+ ? currentKebabKeyPrefix + keySeparator + kebabSegment
88
+ : kebabSegment;
53
89
  const finalValue = currentValuePrefix
54
90
  ? currentValuePrefix + valueSeparator + key
55
91
  : key;
56
- resultMap.set(finalKey, finalValue);
92
+ appendKeys(finalCompactKey, finalKebabKey, finalValue);
57
93
  }
58
94
  }
59
95
  }
60
96
 
61
- helper(obj, keyPrefix, keyPrefix);
97
+ helper(obj, keyPrefix, keyPrefix, keyPrefix);
62
98
  return resultMap;
63
99
  }
@@ -29,8 +29,10 @@ export { initOptionsFromAttributes };
29
29
  /**
30
30
  * Initializes the given options object based on the attributes of the current DOM element.
31
31
  * The function looks for attributes with the prefix 'data-monster-option-', and maps them to
32
- * properties in the options object. It replaces the dashes with dots to form the property path.
33
- * For example, the attribute 'data-monster-option-url' maps to the 'url' property in the options object.
32
+ * properties in the options object. It supports both the historic compact form and kebab-case aliases
33
+ * for camelCase option names.
34
+ * For example, the attributes 'data-monster-option-url' and
35
+ * 'data-monster-option-popper-content-overflow' map to 'url' and 'popper.contentOverflow'.
34
36
  *
35
37
  * With the mapping parameter, the attribute value can be mapped to a different value.
36
38
  * For example, the attribute 'data-monster-option-foo' maps to the 'bar' property in the options object.
@@ -30,8 +30,10 @@ export { setOptionFromAttribute };
30
30
  /**
31
31
  * Set the given options object based on the attributes of the current DOM element.
32
32
  * The function looks for attributes with the prefix 'data-monster-option-', and maps them to
33
- * properties in the options object. It replaces the dashes with dots to form the property path.
34
- * For example, the attribute 'data-monster-option-url' maps to the 'url' property in the options object.
33
+ * properties in the options object. It supports both the historic compact form and kebab-case aliases
34
+ * for camelCase option names.
35
+ * For example, the attributes 'data-monster-option-url' and
36
+ * 'data-monster-option-popper-content-overflow' map to 'url' and 'popper.contentOverflow'.
35
37
  *
36
38
  * With the mapping parameter, the attribute value can be mapped to a different value.
37
39
  * For example, the attribute 'data-monster-option-foo' maps to the 'bar' property in the options object.
@@ -216,4 +216,276 @@ describe("MessageStateButton", function () {
216
216
  }, 0);
217
217
  });
218
218
  });
219
+
220
+ describe("popper content presentation", function () {
221
+ afterEach(() => {
222
+ let mocks = document.getElementById("mocks");
223
+ mocks.innerHTML = "";
224
+ });
225
+
226
+ it("should resolve plain prose content to default popper clipping", function (done) {
227
+ let mocks = document.getElementById("mocks");
228
+ const button = document.createElement("monster-message-state-button");
229
+ button.innerHTML = "Save";
230
+ mocks.appendChild(button);
231
+
232
+ setTimeout(() => {
233
+ try {
234
+ button.setMessage("<div><strong>Saved</strong><p>plain html</p></div>");
235
+ button.showMessage();
236
+
237
+ setTimeout(() => {
238
+ try {
239
+ const content = button.shadowRoot.querySelector('[part="content"]');
240
+ const message = button.shadowRoot.querySelector(
241
+ '[data-monster-role="message"]',
242
+ );
243
+ expect(content).to.exist;
244
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
245
+ "both",
246
+ );
247
+ expect(
248
+ content.getAttribute("data-monster-message-layout"),
249
+ ).to.equal("prose");
250
+ expect(
251
+ message.getAttribute("data-monster-message-layout"),
252
+ ).to.equal("prose");
253
+ done();
254
+ } catch (e) {
255
+ done(e);
256
+ }
257
+ }, 0);
258
+ } catch (e) {
259
+ done(e);
260
+ }
261
+ }, 0);
262
+ });
263
+
264
+ it("should resolve nested select message content to horizontal clipping only", function (done) {
265
+ let mocks = document.getElementById("mocks");
266
+ const button = document.createElement("monster-message-state-button");
267
+ button.innerHTML = "Save";
268
+ mocks.appendChild(button);
269
+
270
+ setTimeout(() => {
271
+ try {
272
+ const wrapper = document.createElement("div");
273
+ wrapper.appendChild(document.createElement("monster-select"));
274
+ button.setMessage(wrapper);
275
+ button.showMessage();
276
+
277
+ setTimeout(() => {
278
+ try {
279
+ const content = button.shadowRoot.querySelector('[part="content"]');
280
+ const message = button.shadowRoot.querySelector(
281
+ '[data-monster-role="message"]',
282
+ );
283
+ expect(content).to.exist;
284
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
285
+ "horizontal",
286
+ );
287
+ expect(
288
+ content.getAttribute("data-monster-message-layout"),
289
+ ).to.equal("overlay");
290
+ expect(
291
+ message.getAttribute("data-monster-message-layout"),
292
+ ).to.equal("overlay");
293
+ done();
294
+ } catch (e) {
295
+ done(e);
296
+ }
297
+ }, 0);
298
+ } catch (e) {
299
+ done(e);
300
+ }
301
+ }, 0);
302
+ });
303
+
304
+ it("should resolve wide plain content to the wide layout", function (done) {
305
+ let mocks = document.getElementById("mocks");
306
+ const button = document.createElement("monster-message-state-button");
307
+ button.innerHTML = "Save";
308
+ mocks.appendChild(button);
309
+
310
+ setTimeout(() => {
311
+ try {
312
+ const wrapper = document.createElement("div");
313
+ const line = document.createElement("div");
314
+ line.setAttribute(
315
+ "style",
316
+ "white-space: nowrap; overflow-x: auto;",
317
+ );
318
+ line.textContent =
319
+ "this is intentionally a single long line to trigger wide layout";
320
+ wrapper.appendChild(line);
321
+
322
+ button.setMessage(wrapper);
323
+ button.showMessage();
324
+
325
+ setTimeout(() => {
326
+ try {
327
+ const content = button.shadowRoot.querySelector('[part="content"]');
328
+ const message = button.shadowRoot.querySelector(
329
+ '[data-monster-role="message"]',
330
+ );
331
+ expect(content).to.exist;
332
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
333
+ "both",
334
+ );
335
+ expect(
336
+ content.getAttribute("data-monster-message-layout"),
337
+ ).to.equal("wide");
338
+ expect(
339
+ message.getAttribute("data-monster-message-layout"),
340
+ ).to.equal("wide");
341
+ done();
342
+ } catch (e) {
343
+ done(e);
344
+ }
345
+ }, 0);
346
+ } catch (e) {
347
+ done(e);
348
+ }
349
+ }, 0);
350
+ });
351
+ });
352
+
353
+ describe("message width behavior", function () {
354
+ let originalInnerWidth;
355
+ let originalGetBoundingClientRect;
356
+
357
+ beforeEach(() => {
358
+ originalInnerWidth = window.innerWidth;
359
+ originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
360
+ });
361
+
362
+ afterEach(() => {
363
+ Object.defineProperty(window, "innerWidth", {
364
+ configurable: true,
365
+ writable: true,
366
+ value: originalInnerWidth,
367
+ });
368
+ HTMLElement.prototype.getBoundingClientRect = originalGetBoundingClientRect;
369
+ let mocks = document.getElementById("mocks");
370
+ mocks.innerHTML = "";
371
+ });
372
+
373
+ it("should keep prose content on a readable max width", function (done) {
374
+ let mocks = document.getElementById("mocks");
375
+ const button = document.createElement("monster-message-state-button");
376
+ button.innerHTML = "Save";
377
+ mocks.appendChild(button);
378
+
379
+ Object.defineProperty(window, "innerWidth", {
380
+ configurable: true,
381
+ writable: true,
382
+ value: 800,
383
+ });
384
+ HTMLElement.prototype.getBoundingClientRect = function () {
385
+ if (this?.getAttribute?.("data-measurement") === "true") {
386
+ return {
387
+ width: 900,
388
+ height: 120,
389
+ top: 0,
390
+ left: 0,
391
+ right: 900,
392
+ bottom: 120,
393
+ };
394
+ }
395
+
396
+ return {
397
+ width: 100,
398
+ height: 40,
399
+ top: 0,
400
+ left: 0,
401
+ right: 100,
402
+ bottom: 40,
403
+ };
404
+ };
405
+
406
+ setTimeout(() => {
407
+ try {
408
+ button.setMessage(
409
+ "<div><p>This long prose content should wrap instead of forcing the message popper to use the full viewport width.</p></div>",
410
+ );
411
+ button.showMessage();
412
+
413
+ setTimeout(() => {
414
+ try {
415
+ const popper = button.shadowRoot.querySelector(
416
+ '[data-monster-role="popper"]',
417
+ );
418
+ expect(popper.style.width).to.equal("512px");
419
+ expect(popper.style.maxWidth).to.equal("512px");
420
+ done();
421
+ } catch (e) {
422
+ done(e);
423
+ }
424
+ }, 0);
425
+ } catch (e) {
426
+ done(e);
427
+ }
428
+ }, 0);
429
+ });
430
+
431
+ it("should allow wide content to grow until the viewport limit", function (done) {
432
+ let mocks = document.getElementById("mocks");
433
+ const button = document.createElement("monster-message-state-button");
434
+ button.innerHTML = "Save";
435
+ mocks.appendChild(button);
436
+
437
+ Object.defineProperty(window, "innerWidth", {
438
+ configurable: true,
439
+ writable: true,
440
+ value: 800,
441
+ });
442
+ HTMLElement.prototype.getBoundingClientRect = function () {
443
+ if (this?.getAttribute?.("data-measurement") === "true") {
444
+ return {
445
+ width: 900,
446
+ height: 120,
447
+ top: 0,
448
+ left: 0,
449
+ right: 900,
450
+ bottom: 120,
451
+ };
452
+ }
453
+
454
+ return {
455
+ width: 100,
456
+ height: 40,
457
+ top: 0,
458
+ left: 0,
459
+ right: 100,
460
+ bottom: 40,
461
+ };
462
+ };
463
+
464
+ setTimeout(() => {
465
+ try {
466
+ const wrapper = document.createElement("div");
467
+ wrapper.setAttribute("data-monster-message-layout", "wide");
468
+ wrapper.textContent =
469
+ "wide content placeholder that should grow until the viewport edge";
470
+ button.setMessage(wrapper);
471
+ button.showMessage();
472
+
473
+ setTimeout(() => {
474
+ try {
475
+ const popper = button.shadowRoot.querySelector(
476
+ '[data-monster-role="popper"]',
477
+ );
478
+ expect(popper.style.width).to.equal("768px");
479
+ expect(popper.style.maxWidth).to.equal("768px");
480
+ done();
481
+ } catch (e) {
482
+ done(e);
483
+ }
484
+ }, 0);
485
+ } catch (e) {
486
+ done(e);
487
+ }
488
+ }, 0);
489
+ });
490
+ });
219
491
  });