@schukai/monster 4.136.2 → 4.136.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.2"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.3"}
@@ -13,7 +13,10 @@
13
13
  */
14
14
 
15
15
  import { assembleMethodSymbol } from "../../dom/customelement.mjs";
16
- import { resolveClippingBoundaryElement } from "./util/floating-ui.mjs";
16
+ import {
17
+ resolveClippingBoundaryElement,
18
+ resolveParentPopperContentBoundary,
19
+ } from "./util/floating-ui.mjs";
17
20
  import { Popper } from "../layout/popper.mjs";
18
21
 
19
22
  export { ContextBase };
@@ -85,17 +88,40 @@ class ContextBase extends Popper {
85
88
  */
86
89
  resolvePopperOptions() {
87
90
  const options = super.resolvePopperOptions();
88
- const controlElement =
89
- this.shadowRoot?.querySelector('[data-monster-role="control"]') || null;
90
- const popperElement =
91
- this.shadowRoot?.querySelector('[data-monster-role="popper"]') || null;
91
+ const { controlElement, popperElement } = getPopperElements.call(this);
92
+ const parentPopperBoundary = resolveParentPopperContentBoundary(
93
+ controlElement,
94
+ popperElement,
95
+ );
92
96
 
93
- if (resolveClippingBoundaryElement(controlElement, popperElement)) {
97
+ if (
98
+ resolveClippingBoundaryElement(controlElement, popperElement) ||
99
+ parentPopperBoundary
100
+ ) {
94
101
  options.strategy = "fixed";
95
102
  }
96
103
 
97
104
  return options;
98
105
  }
106
+
107
+ /**
108
+ * Nested context poppers inside another popper need bounded height.
109
+ *
110
+ * @return {string}
111
+ */
112
+ resolveContentOverflowMode() {
113
+ const configuredMode = super.resolveContentOverflowMode();
114
+ if (configuredMode !== "visible") {
115
+ return configuredMode;
116
+ }
117
+
118
+ const { controlElement, popperElement } = getPopperElements.call(this);
119
+ if (resolveParentPopperContentBoundary(controlElement, popperElement)) {
120
+ return "both";
121
+ }
122
+
123
+ return configuredMode;
124
+ }
99
125
  }
100
126
 
101
127
  /**
@@ -126,6 +152,15 @@ function initContentObserver() {
126
152
  });
127
153
  }
128
154
 
155
+ function getPopperElements() {
156
+ return {
157
+ controlElement:
158
+ this.shadowRoot?.querySelector('[data-monster-role="control"]') || null,
159
+ popperElement:
160
+ this.shadowRoot?.querySelector('[data-monster-role="popper"]') || null,
161
+ };
162
+ }
163
+
129
164
  /**
130
165
  * @private
131
166
  * @return {void}
@@ -38,8 +38,6 @@ div[data-monster-role="popper"] {
38
38
  & > [part="content"] {
39
39
  display: block;
40
40
  max-width: 100%;
41
- max-height: none;
42
- overflow: visible;
43
41
  white-space: normal;
44
42
  text-wrap: pretty;
45
43
  word-break: break-word;
@@ -31,8 +31,6 @@ div[data-monster-role="popper"] {
31
31
  & > [part="content"] {
32
32
  display: block;
33
33
  max-width: 100%;
34
- max-height: none;
35
- overflow: visible;
36
34
  white-space: normal;
37
35
  text-wrap: pretty;
38
36
  word-break: break-word;
@@ -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 { ContextErrorStyleSheet };
16
+ export {ContextErrorStyleSheet}
17
17
 
18
18
  /**
19
19
  * @private
@@ -22,17 +22,10 @@ export { ContextErrorStyleSheet };
22
22
  const ContextErrorStyleSheet = new CSSStyleSheet();
23
23
 
24
24
  try {
25
- ContextErrorStyleSheet.insertRule(
26
- `
25
+ ContextErrorStyleSheet.insertRule(`
27
26
  @layer contexterror {
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));overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;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]{line-height:1em;margin:0;padding:0;position:relative}[data-monster-role=control] [data-monster-role=button]{display:inline-block;position:relative}:is([data-monster-role=control] [data-monster-role=button]) svg{cursor:pointer}:is([data-monster-role=control] [data-monster-role=button]) svg.hidden{cursor:default;pointer-events:none;visibility:hidden}:host{display:inline-block;margin:0 .2em;padding:0;position:relative;vertical-align:bottom}div[data-monster-role=popper]{max-width:min(var(--monster-popper-max-width,calc(100vw - 2rem)),32rem)}div[data-monster-role=popper]>[part=content]{display:block;max-height:none;max-width:100%;overflow:visible;overflow-wrap:anywhere;text-wrap:pretty;white-space:normal;word-break:break-word}:host([disabled]) [data-monster-role=button] svg{cursor:default;pointer-events:none}
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));overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;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]{line-height:1em;margin:0;padding:0;position:relative}[data-monster-role=control] [data-monster-role=button]{display:inline-block;position:relative}:is([data-monster-role=control] [data-monster-role=button]) svg{cursor:pointer}:is([data-monster-role=control] [data-monster-role=button]) svg.hidden{cursor:default;pointer-events:none;visibility:hidden}:host{display:inline-block;margin:0 .2em;padding:0;position:relative;vertical-align:bottom}div[data-monster-role=popper]{max-width:min(var(--monster-popper-max-width,calc(100vw - 2rem)),32rem)}div[data-monster-role=popper]>[part=content]{display:block;max-width:100%;overflow-wrap:anywhere;text-wrap:pretty;white-space:normal;word-break:break-word}:host([disabled]) [data-monster-role=button] svg{cursor:default;pointer-events:none}
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
  }
@@ -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 { ContextHelpStyleSheet };
16
+ export {ContextHelpStyleSheet}
17
17
 
18
18
  /**
19
19
  * @private
@@ -22,17 +22,10 @@ export { ContextHelpStyleSheet };
22
22
  const ContextHelpStyleSheet = new CSSStyleSheet();
23
23
 
24
24
  try {
25
- ContextHelpStyleSheet.insertRule(
26
- `
25
+ ContextHelpStyleSheet.insertRule(`
27
26
  @layer contexthelp {
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));overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;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]{line-height:1em;margin:0;padding:0;position:relative}[data-monster-role=control] [data-monster-role=button]{display:inline-block;position:relative}:is([data-monster-role=control] [data-monster-role=button]) svg{cursor:pointer}:is([data-monster-role=control] [data-monster-role=button]) svg.hidden{cursor:default;pointer-events:none;visibility:hidden}div[data-monster-role=popper]{max-width:min(var(--monster-popper-max-width,calc(100vw - 2rem)),32rem);z-index:var(--monster-z-index-tooltip-overlay)}div[data-monster-role=popper]>[part=content]{display:block;max-height:none;max-width:100%;overflow:visible;overflow-wrap:anywhere;text-wrap:pretty;white-space:normal;word-break:break-word}:host{display:inline-block;margin:0 .2em;padding:0;position:relative;vertical-align:bottom}:host([disabled]) [data-monster-role=button] svg{cursor:default;pointer-events:none}
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));overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;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]{line-height:1em;margin:0;padding:0;position:relative}[data-monster-role=control] [data-monster-role=button]{display:inline-block;position:relative}:is([data-monster-role=control] [data-monster-role=button]) svg{cursor:pointer}:is([data-monster-role=control] [data-monster-role=button]) svg.hidden{cursor:default;pointer-events:none;visibility:hidden}div[data-monster-role=popper]{max-width:min(var(--monster-popper-max-width,calc(100vw - 2rem)),32rem);z-index:var(--monster-z-index-tooltip-overlay)}div[data-monster-role=popper]>[part=content]{display:block;max-width:100%;overflow-wrap:anywhere;text-wrap:pretty;white-space:normal;word-break:break-word}:host{display:inline-block;margin:0 .2em;padding:0;position:relative;vertical-align:bottom}:host([disabled]) [data-monster-role=button] svg{cursor:default;pointer-events:none}
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
  }
@@ -30,6 +30,7 @@ export {
30
30
  applyAdaptiveFloatingElementSize,
31
31
  closePositionedPopper,
32
32
  resolveClippingBoundaryElement,
33
+ resolveParentPopperContentBoundary,
33
34
  isPositionedPopperOpen,
34
35
  openPositionedPopper,
35
36
  positionPopper,
@@ -144,10 +145,12 @@ function normalizePopperConfig(options, controlElement, popperElement) {
144
145
  options,
145
146
  );
146
147
 
147
- config.boundaryElement = resolveClippingBoundaryElement(
148
- controlElement,
149
- popperElement,
150
- );
148
+ if (!(config.boundaryElement instanceof HTMLElement)) {
149
+ config.boundaryElement = resolveClippingBoundaryElement(
150
+ controlElement,
151
+ popperElement,
152
+ );
153
+ }
151
154
  config.detectOverflowOptions = buildDetectOverflowOptions(
152
155
  config.boundaryElement,
153
156
  );
@@ -283,6 +286,10 @@ function applyAdaptiveFloatingElementSize(
283
286
  floatingElement,
284
287
  { availableWidth, availableHeight },
285
288
  ) {
289
+ const contentElement = getFloatingContentElement(floatingElement);
290
+ const usesVisibleOverflow =
291
+ contentElement instanceof HTMLElement &&
292
+ contentElement.dataset.monsterOverflowMode === "visible";
286
293
  const maxWidth = clampAvailableDimension(
287
294
  availableWidth,
288
295
  readMaxDimension(floatingElement, "maxWidth"),
@@ -301,7 +308,7 @@ function applyAdaptiveFloatingElementSize(
301
308
  nextStyle.maxWidth = "";
302
309
  }
303
310
 
304
- if (Number.isFinite(maxHeight) && maxHeight > 0) {
311
+ if (!usesVisibleOverflow && Number.isFinite(maxHeight) && maxHeight > 0) {
305
312
  nextStyle.maxHeight = `${maxHeight}px`;
306
313
  } else {
307
314
  nextStyle.maxHeight = "";
@@ -332,14 +339,19 @@ function applyAdaptiveFloatingContentSize(floatingElement, maxHeight) {
332
339
  Number.isFinite(maxHeight) ? maxHeight - reservedHeight : null,
333
340
  readMaxDimension(contentElement, "maxHeight"),
334
341
  );
335
-
336
- if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
337
- contentElement.style.maxHeight = `${contentMaxHeight}px`;
342
+ const minimumReadableHeight = getMinimumReadableContentHeight(contentElement);
343
+ const nextContentMaxHeight =
344
+ Number.isFinite(contentMaxHeight) && contentMaxHeight > 0
345
+ ? Math.max(contentMaxHeight, minimumReadableHeight)
346
+ : contentMaxHeight;
347
+
348
+ if (Number.isFinite(nextContentMaxHeight) && nextContentMaxHeight > 0) {
349
+ contentElement.style.maxHeight = `${nextContentMaxHeight}px`;
338
350
  } else {
339
351
  contentElement.style.maxHeight = "";
340
352
  }
341
353
 
342
- syncNestedScrollContainerHeight(contentElement, contentMaxHeight);
354
+ syncNestedScrollContainerHeight(contentElement, nextContentMaxHeight);
343
355
  }
344
356
 
345
357
  function getFloatingContentElement(floatingElement) {
@@ -391,6 +403,63 @@ function readBoxDimension(rawValue) {
391
403
  return Number.isFinite(value) ? value : 0;
392
404
  }
393
405
 
406
+ function getMinimumReadableContentHeight(contentElement) {
407
+ if (!(contentElement instanceof HTMLElement)) {
408
+ return 0;
409
+ }
410
+
411
+ const measurementTarget =
412
+ getPrimaryReadableContentElement(contentElement) || contentElement;
413
+ const style = getComputedStyle(measurementTarget);
414
+ const lineHeight = readLineHeight(style);
415
+ const paddingHeight =
416
+ readBoxDimension(getComputedStyle(contentElement).paddingTop) +
417
+ readBoxDimension(getComputedStyle(contentElement).paddingBottom);
418
+
419
+ return Math.max(0, lineHeight + paddingHeight);
420
+ }
421
+
422
+ function readLineHeight(style) {
423
+ if (!style) {
424
+ return 0;
425
+ }
426
+
427
+ const lineHeight = Number.parseFloat(style.lineHeight);
428
+ if (Number.isFinite(lineHeight) && lineHeight > 0) {
429
+ return lineHeight;
430
+ }
431
+
432
+ const fontSize = Number.parseFloat(style.fontSize);
433
+ if (Number.isFinite(fontSize) && fontSize > 0) {
434
+ return fontSize * 1.4;
435
+ }
436
+
437
+ return 0;
438
+ }
439
+
440
+ function getPrimaryReadableContentElement(contentElement) {
441
+ if (!(contentElement instanceof HTMLElement)) {
442
+ return null;
443
+ }
444
+
445
+ const slotElement = contentElement.querySelector("slot");
446
+ if (slotElement?.assignedElements instanceof Function) {
447
+ for (const element of slotElement.assignedElements({ flatten: true })) {
448
+ if (element instanceof HTMLElement) {
449
+ return element;
450
+ }
451
+ }
452
+ }
453
+
454
+ for (const child of contentElement.children) {
455
+ if (child instanceof HTMLElement) {
456
+ return child;
457
+ }
458
+ }
459
+
460
+ return null;
461
+ }
462
+
394
463
  function syncNestedScrollContainerHeight(contentElement, contentMaxHeight) {
395
464
  const nestedScrollableElement = getNestedScrollableElement(contentElement);
396
465
  if (!(nestedScrollableElement instanceof HTMLElement)) {
@@ -539,6 +608,17 @@ function resolveClippingBoundaryElement(...elements) {
539
608
  return null;
540
609
  }
541
610
 
611
+ function resolveParentPopperContentBoundary(...elements) {
612
+ for (const element of elements) {
613
+ const clippingBoundary = findNearestIgnoredClippingContainer(element);
614
+ if (clippingBoundary instanceof HTMLElement) {
615
+ return clippingBoundary;
616
+ }
617
+ }
618
+
619
+ return null;
620
+ }
621
+
542
622
  function findNearestClippingContainer(element) {
543
623
  let current = getComposedParent(element);
544
624
 
@@ -557,11 +637,46 @@ function findNearestClippingContainer(element) {
557
637
  return null;
558
638
  }
559
639
 
640
+ function findNearestIgnoredClippingContainer(element) {
641
+ let current = getComposedParent(element);
642
+
643
+ while (current) {
644
+ if (
645
+ current instanceof HTMLElement &&
646
+ shouldIgnoreClippingContainer(current) &&
647
+ shouldEscapeParentPopperContentWrapper(current)
648
+ ) {
649
+ return current;
650
+ }
651
+
652
+ current = getComposedParent(current);
653
+ }
654
+
655
+ return null;
656
+ }
657
+
658
+ function shouldEscapeParentPopperContentWrapper(element) {
659
+ if (!(element instanceof HTMLElement) || !isPopperContentWrapper(element)) {
660
+ return false;
661
+ }
662
+
663
+ const overflowMode = element.getAttribute("data-monster-overflow-mode");
664
+ if (overflowMode === "both") {
665
+ return true;
666
+ }
667
+
668
+ return isClippingContainer(getComputedStyle(element));
669
+ }
670
+
560
671
  function getComposedParent(node) {
561
672
  if (!node) {
562
673
  return null;
563
674
  }
564
675
 
676
+ if (node instanceof Element && node.assignedSlot) {
677
+ return node.assignedSlot;
678
+ }
679
+
565
680
  if (node instanceof ShadowRoot) {
566
681
  return node.host || null;
567
682
  }
@@ -11,6 +11,7 @@ const global = getGlobal();
11
11
 
12
12
  let ContextHelp;
13
13
  let resolveClippingBoundaryElement;
14
+ let resolveParentPopperContentBoundary;
14
15
 
15
16
  describe("ContextHelp", function () {
16
17
  before(function (done) {
@@ -29,6 +30,8 @@ describe("ContextHelp", function () {
29
30
  ContextHelp = contextHelpModule.ContextHelp;
30
31
  resolveClippingBoundaryElement =
31
32
  floatingUiModule.resolveClippingBoundaryElement;
33
+ resolveParentPopperContentBoundary =
34
+ floatingUiModule.resolveParentPopperContentBoundary;
32
35
  done();
33
36
  })
34
37
  .catch((e) => done(e));
@@ -114,4 +117,48 @@ describe("ContextHelp", function () {
114
117
  }
115
118
  }, 0);
116
119
  });
120
+
121
+ it("should switch to fixed positioning inside a parent popper content wrapper with overflow both", function (done) {
122
+ let mocks = document.getElementById("mocks");
123
+ const host = document.createElement("div");
124
+ const help = document.createElement("monster-context-help");
125
+
126
+ mocks.appendChild(host);
127
+ const shadowRoot = host.attachShadow({ mode: "open" });
128
+ shadowRoot.innerHTML = `
129
+ <div data-monster-role="popper">
130
+ <div part="content"
131
+ data-monster-overflow-mode="both">
132
+ </div>
133
+ </div>
134
+ `;
135
+ shadowRoot.querySelector('[part="content"]').appendChild(help);
136
+ help.innerHTML = "<p>Nested help</p>";
137
+
138
+ setTimeout(() => {
139
+ try {
140
+ const control = help.shadowRoot.querySelector(
141
+ '[data-monster-role="control"]',
142
+ );
143
+ const popper = help.shadowRoot.querySelector(
144
+ '[data-monster-role="popper"]',
145
+ );
146
+
147
+ expect(
148
+ resolveParentPopperContentBoundary(control, popper),
149
+ ).to.equal(shadowRoot.querySelector('[part="content"]'));
150
+
151
+ const content = help.shadowRoot.querySelector('[part="content"]');
152
+ expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
153
+ "both",
154
+ );
155
+
156
+ help.showDialog();
157
+ expect(popper.style.position).to.equal("fixed");
158
+ done();
159
+ } catch (e) {
160
+ done(e);
161
+ }
162
+ }, 0);
163
+ });
117
164
  });
@@ -135,4 +135,76 @@ describe("form floating-ui boundary resolution", function () {
135
135
  expect(content.style.maxWidth).to.equal("");
136
136
  expect(content.style.maxHeight).to.equal("100px");
137
137
  });
138
+
139
+ it("should not clamp the floating element height when content overflow is visible", function () {
140
+ const mocks = document.getElementById("mocks");
141
+ const popper = document.createElement("div");
142
+ const content = document.createElement("div");
143
+
144
+ popper.style.maxHeight = "300px";
145
+ content.style.maxHeight = "240px";
146
+ content.setAttribute("part", "content");
147
+ content.setAttribute("data-monster-overflow-mode", "visible");
148
+
149
+ popper.appendChild(content);
150
+ mocks.appendChild(popper);
151
+
152
+ applyAdaptiveFloatingElementSize(popper, {
153
+ availableWidth: 220,
154
+ availableHeight: 160,
155
+ });
156
+
157
+ expect(popper.style.maxHeight).to.equal("");
158
+ expect(content.style.maxHeight).to.equal("240px");
159
+ });
160
+
161
+ it("should keep at least one readable line for scrollable content", function () {
162
+ const mocks = document.getElementById("mocks");
163
+ const popper = document.createElement("div");
164
+ const content = document.createElement("div");
165
+
166
+ popper.style.maxHeight = "300px";
167
+ content.setAttribute("part", "content");
168
+ content.textContent = "A long help text that still needs one readable line.";
169
+ content.style.fontSize = "16px";
170
+ content.style.lineHeight = "24px";
171
+ popper.appendChild(content);
172
+ mocks.appendChild(popper);
173
+
174
+ applyAdaptiveFloatingElementSize(popper, {
175
+ availableWidth: 220,
176
+ availableHeight: 10,
177
+ });
178
+
179
+ expect(content.style.maxHeight).to.equal("24px");
180
+ });
181
+
182
+ it("should use the first slotted element line height for the minimum readable size", function () {
183
+ const mocks = document.getElementById("mocks");
184
+ const popperHost = document.createElement("div");
185
+ const slottedParagraph = document.createElement("p");
186
+ slottedParagraph.textContent = "Readable help line";
187
+ slottedParagraph.style.lineHeight = "26px";
188
+
189
+ mocks.appendChild(popperHost);
190
+ const shadowRoot = popperHost.attachShadow({ mode: "open" });
191
+ shadowRoot.innerHTML = `
192
+ <div data-monster-role="popper">
193
+ <div part="content">
194
+ <slot></slot>
195
+ </div>
196
+ </div>
197
+ `;
198
+
199
+ const popper = shadowRoot.querySelector('[data-monster-role="popper"]');
200
+ const content = shadowRoot.querySelector('[part="content"]');
201
+ popperHost.appendChild(slottedParagraph);
202
+
203
+ applyAdaptiveFloatingElementSize(popper, {
204
+ availableWidth: 220,
205
+ availableHeight: 10,
206
+ });
207
+
208
+ expect(content.style.maxHeight).to.equal("26px");
209
+ });
138
210
  });