@schukai/monster 4.134.0 → 4.136.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.
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.134.0"}
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.0"}
@@ -466,6 +466,11 @@ function initControlReferences() {
466
466
  this[messageElementSymbol] = this.shadowRoot.querySelector(
467
467
  `[${ATTRIBUTE_ROLE}=message]`,
468
468
  );
469
+ if (this[popperElementSymbol] instanceof HTMLElement) {
470
+ this[popperElementSymbol].monsterBeforeFloatingUpdate = () => {
471
+ applyMeasuredMessageWidth.call(this);
472
+ };
473
+ }
469
474
  applyResolvedMessagePresentation.call(this);
470
475
  }
471
476
 
@@ -868,6 +873,7 @@ function applyMeasuredMessageWidth() {
868
873
  Math.min(measuredWidth || minWidth, maxWidth),
869
874
  );
870
875
 
876
+ popper.dataset.monsterPreferredMaxWidth = `${maxWidth}`;
871
877
  popper.style.width = `${targetWidth}px`;
872
878
  popper.style.minWidth = `${minWidth}px`;
873
879
  popper.style.maxWidth = `${maxWidth}px`;
@@ -2453,6 +2453,7 @@ function getOptionElements() {
2453
2453
  function calcAndSetOptionsDimension() {
2454
2454
  const options = getOptionElements.call(this);
2455
2455
  const container = this[optionsElementSymbol];
2456
+ const content = this[popperElementSymbol]?.querySelector?.('[part="content"]');
2456
2457
  if (!(container instanceof HTMLElement && options instanceof NodeList)) {
2457
2458
  return;
2458
2459
  }
@@ -2541,8 +2542,17 @@ function calcAndSetOptionsDimension() {
2541
2542
 
2542
2543
  const domRect = this[controlElementSymbol].getBoundingClientRect();
2543
2544
 
2545
+ this[popperElementSymbol].dataset.monsterPreferredWidth = `${Math.ceil(
2546
+ domRect.width,
2547
+ )}`;
2544
2548
  this[popperElementSymbol].style.width = `${domRect.width}px`;
2549
+ this[popperElementSymbol].style.minWidth = `${domRect.width}px`;
2545
2550
  container.style.overflowX = "auto";
2551
+
2552
+ if (content instanceof HTMLElement) {
2553
+ content.style.overflowX = "hidden";
2554
+ content.style.overflowY = "hidden";
2555
+ }
2546
2556
  }
2547
2557
 
2548
2558
  /**
@@ -4429,6 +4439,9 @@ function initControlReferences() {
4429
4439
  this[popperElementSymbol] = this.shadowRoot.querySelector(
4430
4440
  `[${ATTRIBUTE_ROLE}=popper]`,
4431
4441
  );
4442
+ this[popperElementSymbol].monsterBeforeFloatingUpdate = () => {
4443
+ calcAndSetOptionsDimension.call(this);
4444
+ };
4432
4445
  this[inlineFilterElementSymbol] = this.shadowRoot.querySelector(
4433
4446
  `[${ATTRIBUTE_ROLE}=filter][name="inline-filter"]`,
4434
4447
  );
@@ -27,6 +27,7 @@ import { isArray, isFunction, isObject, isString } from "../../../types/is.mjs";
27
27
  import { Processing } from "../../../util/processing.mjs";
28
28
 
29
29
  export {
30
+ applyAdaptiveFloatingElementSize,
30
31
  closePositionedPopper,
31
32
  resolveClippingBoundaryElement,
32
33
  isPositionedPopperOpen,
@@ -35,6 +36,8 @@ export {
35
36
  };
36
37
 
37
38
  const autoUpdateCleanupMap = new WeakMap();
39
+ const settlingFrameMap = new WeakMap();
40
+ const floatingResizeObserverMap = new WeakMap();
38
41
 
39
42
  /**
40
43
  * @private
@@ -67,16 +70,26 @@ function enableFloatingPositioning(controlElement, popperElement, config) {
67
70
  popperElement.style.removeProperty("visibility");
68
71
  popperElement.style.display = "block";
69
72
 
73
+ startFloatingResizeObserver(controlElement, popperElement, config);
70
74
  startAutoUpdate(controlElement, popperElement, () => {
75
+ runFloatingUpdateHook(popperElement);
71
76
  syncFloatingPopover(controlElement, popperElement, config);
72
77
  });
78
+ runFloatingUpdateHook(popperElement);
73
79
  syncFloatingPopover(controlElement, popperElement, config);
74
80
  }
75
81
 
76
- function syncFloatingPopover(controlElement, popperElement, config) {
82
+ function syncFloatingPopover(
83
+ controlElement,
84
+ popperElement,
85
+ config,
86
+ allowSettlingPass = true,
87
+ ) {
77
88
  const arrowElement = popperElement.querySelector("[data-monster-role=arrow]");
78
89
  const floatingMiddleware = [...config.floatingMiddleware];
79
90
 
91
+ resetAdaptiveFloatingElementSize(popperElement);
92
+
80
93
  if (
81
94
  arrowElement instanceof HTMLElement &&
82
95
  config.middlewareTokens.includes("arrow")
@@ -101,6 +114,10 @@ function syncFloatingPopover(controlElement, popperElement, config) {
101
114
  if (middlewareData.arrow) {
102
115
  applyFloatingArrowStyles(arrowElement, placement, middlewareData.arrow);
103
116
  }
117
+
118
+ if (allowSettlingPass) {
119
+ scheduleSettlingPass(controlElement, popperElement, config);
120
+ }
104
121
  });
105
122
  }
106
123
 
@@ -253,30 +270,249 @@ function createAdaptiveSizeMiddleware(detectOverflowOptions, popperElement) {
253
270
  return;
254
271
  }
255
272
 
256
- const maxWidth = clampAvailableDimension(
273
+ applyAdaptiveFloatingElementSize(floatingElement, {
257
274
  availableWidth,
258
- readMaxDimension(floatingElement, "maxWidth"),
259
- );
260
- const maxHeight = clampAvailableDimension(
261
275
  availableHeight,
262
- readMaxDimension(floatingElement, "maxHeight"),
263
- );
264
- const nextStyle = {
265
- boxSizing: "border-box",
266
- };
276
+ });
277
+ },
278
+ }),
279
+ );
280
+ }
267
281
 
268
- if (Number.isFinite(maxWidth) && maxWidth > 0) {
269
- nextStyle.maxWidth = `${maxWidth}px`;
270
- }
282
+ function applyAdaptiveFloatingElementSize(
283
+ floatingElement,
284
+ { availableWidth, availableHeight },
285
+ ) {
286
+ const maxWidth = clampAvailableDimension(
287
+ availableWidth,
288
+ readMaxDimension(floatingElement, "maxWidth"),
289
+ );
290
+ const maxHeight = clampAvailableDimension(
291
+ availableHeight,
292
+ readMaxDimension(floatingElement, "maxHeight"),
293
+ );
294
+ const nextStyle = {
295
+ boxSizing: "border-box",
296
+ };
271
297
 
272
- if (Number.isFinite(maxHeight) && maxHeight > 0) {
273
- nextStyle.maxHeight = `${maxHeight}px`;
274
- }
298
+ if (Number.isFinite(maxWidth) && maxWidth > 0) {
299
+ nextStyle.maxWidth = `${maxWidth}px`;
300
+ } else {
301
+ nextStyle.maxWidth = "";
302
+ }
275
303
 
276
- Object.assign(floatingElement.style, nextStyle);
277
- },
278
- }),
304
+ if (Number.isFinite(maxHeight) && maxHeight > 0) {
305
+ nextStyle.maxHeight = `${maxHeight}px`;
306
+ } else {
307
+ nextStyle.maxHeight = "";
308
+ }
309
+
310
+ Object.assign(floatingElement.style, nextStyle);
311
+
312
+ syncPreferredFloatingWidth(floatingElement, maxWidth);
313
+ syncPreferredFloatingMaxWidth(floatingElement, maxWidth);
314
+ applyAdaptiveFloatingContentSize(floatingElement, maxHeight);
315
+ }
316
+
317
+ function applyAdaptiveFloatingContentSize(floatingElement, maxHeight) {
318
+ const contentElement = getFloatingContentElement(floatingElement);
319
+ if (!(contentElement instanceof HTMLElement)) {
320
+ return;
321
+ }
322
+
323
+ if (contentElement.dataset.monsterOverflowMode === "visible") {
324
+ return;
325
+ }
326
+
327
+ const reservedHeight = getReservedFloatingHeight(
328
+ floatingElement,
329
+ contentElement,
330
+ );
331
+ const contentMaxHeight = clampAvailableDimension(
332
+ Number.isFinite(maxHeight) ? maxHeight - reservedHeight : null,
333
+ readMaxDimension(contentElement, "maxHeight"),
279
334
  );
335
+
336
+ if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
337
+ contentElement.style.maxHeight = `${contentMaxHeight}px`;
338
+ } else {
339
+ contentElement.style.maxHeight = "";
340
+ }
341
+
342
+ syncNestedScrollContainerHeight(contentElement, contentMaxHeight);
343
+ }
344
+
345
+ function getFloatingContentElement(floatingElement) {
346
+ for (const child of floatingElement.children) {
347
+ if (!(child instanceof HTMLElement)) {
348
+ continue;
349
+ }
350
+
351
+ const part = child.getAttribute("part");
352
+ if (
353
+ isString(part) &&
354
+ part
355
+ .split(/\s+/)
356
+ .filter((token) => token.length > 0)
357
+ .includes("content")
358
+ ) {
359
+ return child;
360
+ }
361
+ }
362
+
363
+ return null;
364
+ }
365
+
366
+ function getReservedFloatingHeight(floatingElement, contentElement) {
367
+ const floatingStyle = getComputedStyle(floatingElement);
368
+ let reservedHeight = 0;
369
+
370
+ reservedHeight += readBoxDimension(floatingStyle.paddingTop);
371
+ reservedHeight += readBoxDimension(floatingStyle.paddingBottom);
372
+ reservedHeight += readBoxDimension(floatingStyle.borderTopWidth);
373
+ reservedHeight += readBoxDimension(floatingStyle.borderBottomWidth);
374
+
375
+ for (const child of floatingElement.children) {
376
+ if (!(child instanceof HTMLElement) || child === contentElement) {
377
+ continue;
378
+ }
379
+
380
+ const childStyle = getComputedStyle(child);
381
+ reservedHeight += child.getBoundingClientRect().height;
382
+ reservedHeight += readBoxDimension(childStyle.marginTop);
383
+ reservedHeight += readBoxDimension(childStyle.marginBottom);
384
+ }
385
+
386
+ return Math.max(0, reservedHeight);
387
+ }
388
+
389
+ function readBoxDimension(rawValue) {
390
+ const value = Number.parseFloat(rawValue);
391
+ return Number.isFinite(value) ? value : 0;
392
+ }
393
+
394
+ function syncNestedScrollContainerHeight(contentElement, contentMaxHeight) {
395
+ const nestedScrollableElement = getNestedScrollableElement(contentElement);
396
+ if (!(nestedScrollableElement instanceof HTMLElement)) {
397
+ return;
398
+ }
399
+
400
+ if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
401
+ nestedScrollableElement.style.height = `${contentMaxHeight}px`;
402
+ nestedScrollableElement.style.maxHeight = `${contentMaxHeight}px`;
403
+ return;
404
+ }
405
+
406
+ nestedScrollableElement.style.height = "";
407
+ nestedScrollableElement.style.maxHeight = "";
408
+ }
409
+
410
+ function syncPreferredFloatingWidth(floatingElement, maxWidth) {
411
+ const preferredWidth = Number.parseFloat(
412
+ floatingElement.dataset.monsterPreferredWidth || "",
413
+ );
414
+ if (!Number.isFinite(preferredWidth) || preferredWidth <= 0) {
415
+ return;
416
+ }
417
+
418
+ const contentElement = getFloatingContentElement(floatingElement);
419
+ if (!(contentElement instanceof HTMLElement)) {
420
+ floatingElement.style.width = `${preferredWidth}px`;
421
+ return;
422
+ }
423
+
424
+ const floatingRect = floatingElement.getBoundingClientRect();
425
+ const contentRect = contentElement.getBoundingClientRect();
426
+ const reservedWidth = Math.max(0, floatingRect.width - contentRect.width);
427
+ const contentRequiredWidth = getRequiredContentWidth(contentElement);
428
+ const preferredFloatingWidth = preferredWidth;
429
+ const requiredFloatingWidth = Math.max(
430
+ preferredFloatingWidth,
431
+ contentRequiredWidth + reservedWidth,
432
+ );
433
+ const nextFloatingWidth = clampAvailableDimension(
434
+ requiredFloatingWidth,
435
+ Number.isFinite(maxWidth) ? maxWidth : Infinity,
436
+ );
437
+
438
+ if (Number.isFinite(nextFloatingWidth) && nextFloatingWidth > 0) {
439
+ floatingElement.style.width = `${nextFloatingWidth}px`;
440
+ }
441
+ }
442
+
443
+ function syncPreferredFloatingMaxWidth(floatingElement, maxWidth) {
444
+ const preferredMaxWidth = Number.parseFloat(
445
+ floatingElement.dataset.monsterPreferredMaxWidth || "",
446
+ );
447
+ if (!Number.isFinite(preferredMaxWidth) || preferredMaxWidth <= 0) {
448
+ return;
449
+ }
450
+
451
+ const nextFloatingMaxWidth = clampAvailableDimension(
452
+ preferredMaxWidth,
453
+ Number.isFinite(maxWidth) ? maxWidth : Infinity,
454
+ );
455
+ if (Number.isFinite(nextFloatingMaxWidth) && nextFloatingMaxWidth > 0) {
456
+ floatingElement.style.maxWidth = `${nextFloatingMaxWidth}px`;
457
+ }
458
+ }
459
+
460
+ function resetAdaptiveFloatingElementSize(floatingElement) {
461
+ if (!(floatingElement instanceof HTMLElement)) {
462
+ return;
463
+ }
464
+
465
+ floatingElement.style.removeProperty("maxHeight");
466
+
467
+ const contentElement = getFloatingContentElement(floatingElement);
468
+ if (!(contentElement instanceof HTMLElement)) {
469
+ return;
470
+ }
471
+
472
+ contentElement.style.removeProperty("maxHeight");
473
+
474
+ const nestedScrollableElement = getNestedScrollableElement(contentElement);
475
+ if (!(nestedScrollableElement instanceof HTMLElement)) {
476
+ return;
477
+ }
478
+
479
+ nestedScrollableElement.style.removeProperty("height");
480
+ nestedScrollableElement.style.removeProperty("maxHeight");
481
+ }
482
+
483
+ function runFloatingUpdateHook(popperElement) {
484
+ const hook = popperElement?.monsterBeforeFloatingUpdate;
485
+ if (typeof hook === "function") {
486
+ hook();
487
+ }
488
+ }
489
+
490
+ function getRequiredContentWidth(contentElement) {
491
+ const nestedScrollableElement = getNestedScrollableElement(contentElement);
492
+ if (nestedScrollableElement instanceof HTMLElement) {
493
+ return Math.max(
494
+ contentElement.scrollWidth,
495
+ nestedScrollableElement.scrollWidth,
496
+ contentElement.clientWidth,
497
+ );
498
+ }
499
+
500
+ return Math.max(contentElement.scrollWidth, contentElement.clientWidth);
501
+ }
502
+
503
+ function getNestedScrollableElement(contentElement) {
504
+ for (const child of contentElement.children) {
505
+ if (!(child instanceof HTMLElement)) {
506
+ continue;
507
+ }
508
+
509
+ const style = getComputedStyle(child);
510
+ if (["auto", "scroll"].includes(style.overflowY)) {
511
+ return child;
512
+ }
513
+ }
514
+
515
+ return null;
280
516
  }
281
517
 
282
518
  function buildDetectOverflowOptions(boundaryElement) {
@@ -418,6 +654,8 @@ function startAutoUpdate(controlElement, popperElement, callback) {
418
654
  }
419
655
 
420
656
  function stopAutoUpdate(popperElement) {
657
+ cancelSettlingPass(popperElement);
658
+ stopFloatingResizeObserver(popperElement);
421
659
  const cleanup = autoUpdateCleanupMap.get(popperElement);
422
660
  if (typeof cleanup === "function") {
423
661
  cleanup();
@@ -425,6 +663,47 @@ function stopAutoUpdate(popperElement) {
425
663
  autoUpdateCleanupMap.delete(popperElement);
426
664
  }
427
665
 
666
+ function startFloatingResizeObserver(controlElement, popperElement, config) {
667
+ stopFloatingResizeObserver(popperElement);
668
+
669
+ const observer = new ResizeObserver(() => {
670
+ scheduleSettlingPass(controlElement, popperElement, config);
671
+ });
672
+
673
+ observer.observe(popperElement);
674
+ floatingResizeObserverMap.set(popperElement, observer);
675
+ }
676
+
677
+ function stopFloatingResizeObserver(popperElement) {
678
+ const observer = floatingResizeObserverMap.get(popperElement);
679
+ if (observer instanceof ResizeObserver) {
680
+ observer.disconnect();
681
+ }
682
+ floatingResizeObserverMap.delete(popperElement);
683
+ }
684
+
685
+ function scheduleSettlingPass(controlElement, popperElement, config) {
686
+ if (settlingFrameMap.has(popperElement)) {
687
+ return;
688
+ }
689
+
690
+ const frameId = requestAnimationFrame(() => {
691
+ settlingFrameMap.delete(popperElement);
692
+ runFloatingUpdateHook(popperElement);
693
+ syncFloatingPopover(controlElement, popperElement, config, false);
694
+ });
695
+
696
+ settlingFrameMap.set(popperElement, frameId);
697
+ }
698
+
699
+ function cancelSettlingPass(popperElement) {
700
+ const frameId = settlingFrameMap.get(popperElement);
701
+ if (typeof frameId === "number") {
702
+ cancelAnimationFrame(frameId);
703
+ }
704
+ settlingFrameMap.delete(popperElement);
705
+ }
706
+
428
707
  function applyFloatingArrowStyles(arrowElement, placement, arrowData) {
429
708
  if (!(arrowElement instanceof HTMLElement)) {
430
709
  return;
@@ -35,11 +35,17 @@ const rawDataSymbol = Symbol.for(
35
35
  /**
36
36
  * The RestAPI is a class that enables a REST API server.
37
37
  *
38
+ * @fragments /fragments/libraries/data/datasource/server/restapi/
39
+ *
38
40
  * @externalExample ../../../../example/data/datasource/server/restapi.mjs
41
+ * @example /examples/libraries/data/datasource/server/restapi/simple/ Configure read and write endpoints
42
+ * @example /examples/libraries/data/datasource/server/restapi/response-callback/ Replace the default read callback with a custom assignment flow
43
+ * @example /examples/libraries/data/datasource/server/restapi/partial-write/ Patch only changed fields before sending a write request
44
+ * @example /examples/libraries/data/datasource/server/restapi/report-path/ Read validation reports from a nested response path
39
45
  * @license AGPLv3
40
46
  * @since 1.22.0
41
47
  * @copyright Volker Schukai
42
- * @summary The RestAPI is a class that binds a REST API server.
48
+ * @summary A REST-backed datasource for reading and writing application state over HTTP.
43
49
  */
44
50
  class RestAPI extends Server {
45
51
  /**
@@ -31,11 +31,17 @@ const webConnectSymbol = Symbol("connection");
31
31
  /**
32
32
  * The RestAPI is a class that enables a REST API server.
33
33
  *
34
+ * @fragments /fragments/libraries/data/datasource/server/webconnect/
35
+ *
34
36
  * @externalExample ../../../../example/data/datasource/server/webconnect.mjs
37
+ * @example /examples/libraries/data/datasource/server/webconnect/simple/ Configure a realtime datasource bridge
38
+ * @example /examples/libraries/data/datasource/server/webconnect/message-queue/ Read queued realtime messages through the datasource API
39
+ * @example /examples/libraries/data/datasource/server/webconnect/transformed-read/ Apply read mapping to incoming socket payloads
40
+ * @example /examples/libraries/data/datasource/server/webconnect/write-envelope/ Prepare outgoing socket writes with sheathing options
35
41
  * @license AGPLv3
36
42
  * @since 3.1.0
37
43
  * @copyright Volker Schukai
38
- * @summary The LocalStorage class encapsulates the access to data objects.
44
+ * @summary A realtime datasource that bridges WebConnect messages into the datasource API.
39
45
  */
40
46
  class WebConnect extends Server {
41
47
  /**
@@ -30,10 +30,16 @@ const serverVersionSymbol = Symbol("serverVersion");
30
30
  /**
31
31
  * Base class for all server data sources
32
32
  *
33
+ * @fragments /fragments/libraries/data/datasource/server/
34
+ *
35
+ * @example /examples/libraries/data/datasource/server/simple/ Transform and prepare server payloads
36
+ * @example /examples/libraries/data/datasource/server/transformer-callbacks/ Combine transformer expressions with mapping callbacks
37
+ * @example /examples/libraries/data/datasource/server/partial-diff/ Reduce write payloads through the partial diff callback
38
+ * @example /examples/libraries/data/datasource/server/sheathing-object/ Wrap outgoing payloads into a server-side envelope
33
39
  * @license AGPLv3
34
40
  * @since 3.4.0
35
41
  * @copyright Volker Schukai
36
- * @summary The Server class encapsulates the access to a server datasource
42
+ * @summary A base datasource layer for reading, transforming and writing structured server payloads.
37
43
  */
38
44
  class Server extends Datasource {
39
45
  /**
@@ -29,6 +29,12 @@ const storageObjectSymbol = Symbol.for(
29
29
  /**
30
30
  * The class represents a record.
31
31
  *
32
+ * @fragments /fragments/libraries/data/datasource/storage/
33
+ *
34
+ * @example /examples/libraries/data/datasource/storage/simple/ Read and write JSON through Web Storage
35
+ * @example /examples/libraries/data/datasource/storage/session-draft/ Persist draft state in session storage
36
+ * @example /examples/libraries/data/datasource/storage/local-preferences/ Persist user preferences in local storage
37
+ * @example /examples/libraries/data/datasource/storage/remove-on-undefined/ Remove the storage entry when the datasource becomes undefined
32
38
  * @license AGPLv3
33
39
  * @since 1.22.0
34
40
  * @copyright Volker Schukai
@@ -48,6 +48,12 @@ const internalDataSymbol = Symbol.for(
48
48
  * The datasource class is the basis for dealing with different data sources.
49
49
  * It provides a unified interface for accessing data
50
50
  * @externalExample ../../example/data/datasource.mjs
51
+ * @fragments /fragments/libraries/data/datasource/
52
+ * @example /examples/libraries/data/datasource/basic-read-write/ Implement a small in memory datasource with read and write
53
+ * @example /examples/libraries/data/datasource/observer-log/ Attach observers and react to subject changes
54
+ * @example /examples/libraries/data/datasource/skip-noop-events/ Compare skipNoopEvents with repeated writes
55
+ * @example /examples/libraries/data/datasource/render-list/ Drive a small UI list from datasource updates
56
+ * @example /examples/libraries/data/datasource/async-options/ Use datasource options to control async reads
51
57
  * @license AGPLv3
52
58
  * @since 1.22.0
53
59
  * @copyright Volker Schukai
@@ -29,6 +29,9 @@ const ATTRIBUTEPREFIX = "data-monster-";
29
29
  /**
30
30
  * Assembler class
31
31
  *
32
+ * @fragments /fragments/libraries/dom/assembler/
33
+ *
34
+ * @example /examples/libraries/dom/assembler/simple/ Basic assembly flow
32
35
  * @license AGPLv3
33
36
  * @since 1.6.0
34
37
  * @copyright Volker Schukai
@@ -50,6 +50,11 @@ const attachedInternalSymbol = Symbol("attachedInternal");
50
50
  * Read the HTML specification for Custom Element Reactions: {@link https://html.spec.whatwg.org/dev/custom-elements.html#custom-element-reactions|Custom Element Reactions}.
51
51
  *
52
52
  * @fragments /fragments/libraries/dom/customcontrol/
53
+ * @example /examples/libraries/dom/customcontrol/text-input/ Build a minimal form associated text control
54
+ * @example /examples/libraries/dom/customcontrol/validation/ Add validation and reportValidity behaviour
55
+ * @example /examples/libraries/dom/customcontrol/formdata-submit/ Submit structured FormData from a custom control
56
+ * @example /examples/libraries/dom/customcontrol/reset-behaviour/ Restore the control state on form reset
57
+ * @example /examples/libraries/dom/customcontrol/disabled-sync/ Sync disabled state from form containers into the control
53
58
  *
54
59
  * @summary A form-oriented base class for Monster controls with value handling, validation and control semantics.
55
60
  * @license AGPLv3
@@ -190,6 +190,11 @@ let hostVisibilityStyleSheet = null;
190
190
  * And in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
191
191
  *
192
192
  * @fragments /fragments/libraries/dom/customelement/
193
+ * @example /examples/libraries/dom/customelement/simple-card/ Register a minimal element with inline template and stylesheet
194
+ * @example /examples/libraries/dom/customelement/options-from-attributes/ Read declarative options from data-monster-options
195
+ * @example /examples/libraries/dom/customelement/runtime-option-updates/ Update component state via setOption and setOptions
196
+ * @example /examples/libraries/dom/customelement/document-template/ Resolve the template from the surrounding document
197
+ * @example /examples/libraries/dom/customelement/visibility-events/ React to show hide and visibility events
193
198
  *
194
199
  * @license AGPLv3
195
200
  * @since 1.7.0
@@ -42,6 +42,9 @@ const stackSymbol = Symbol("stack");
42
42
  /**
43
43
  * With the focus manager the focus can be stored in a document, recalled and moved.
44
44
  *
45
+ * @fragments /fragments/libraries/dom/focusmanager/
46
+ *
47
+ * @example /examples/libraries/dom/focusmanager/simple/ Store and restore focus inside a dialog flow
45
48
  * @license AGPLv3
46
49
  * @since 1.25.0
47
50
  * @copyright Volker Schukai
@@ -20,10 +20,13 @@ export { Stylesheet };
20
20
  /**
21
21
  * This class is used by the resource manager to embed external resources.
22
22
  *
23
+ * @fragments /fragments/libraries/dom/resource/link/stylesheet/
24
+ *
25
+ * @example /examples/libraries/dom/resource/link/stylesheet/simple/ Configure a stylesheet resource
23
26
  * @license AGPLv3
24
27
  * @since 1.25.0
25
28
  * @copyright Volker Schukai
26
- * @summary A Resource class
29
+ * @summary A link resource specialized for loading external stylesheets.
27
30
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
28
31
  */
29
32
  class Stylesheet extends Link {
@@ -30,10 +30,13 @@ export { Link };
30
30
  /**
31
31
  * This class is used by the resource manager to embed external resources.
32
32
  *
33
+ * @fragments /fragments/libraries/dom/resource/link/
34
+ *
35
+ * @example /examples/libraries/dom/resource/link/simple/ Create and connect a link resource
33
36
  * @license AGPLv3
34
37
  * @since 1.25.0
35
38
  * @copyright Volker Schukai
36
- * @summary A Resource class
39
+ * @summary A link resource for attaching external link-based assets such as stylesheets or preloads.
37
40
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
38
41
  */
39
42
  class Link extends Resource {
@@ -29,10 +29,13 @@ export { Script };
29
29
  /**
30
30
  * This class is used by the resource manager to embed scripts.
31
31
  *
32
+ * @fragments /fragments/libraries/dom/resource/script/
33
+ *
34
+ * @example /examples/libraries/dom/resource/script/simple/ Create and connect a script resource
32
35
  * @license AGPLv3
33
36
  * @since 1.25.0
34
37
  * @copyright Volker Schukai
35
- * @summary A Resource class
38
+ * @summary A script resource for inserting external JavaScript files into the document.
36
39
  */
37
40
  class Script extends Resource {
38
41
  /**
@@ -56,10 +56,13 @@ const referenceSymbol = Symbol("reference");
56
56
  /**
57
57
  * This class is the base class for all resources to be loaded.
58
58
  *
59
+ * @fragments /fragments/libraries/dom/resource/
60
+ *
61
+ * @example /examples/libraries/dom/resource/simple/ Create and connect a DOM resource
59
62
  * @license AGPLv3
60
63
  * @since 1.25.0
61
64
  * @copyright Volker Schukai
62
- * @summary A Resource class
65
+ * @summary A base resource abstraction for creating, connecting and observing DOM-loaded assets.
63
66
  */
64
67
  class Resource extends BaseWithOptions {
65
68
  /**
@@ -28,10 +28,16 @@ export { ResourceManager };
28
28
  /**
29
29
  * The ResourceManager is a singleton that manages all resources.
30
30
  *
31
+ * @fragments /fragments/libraries/dom/resourcemanager/
32
+ *
33
+ * @example /examples/libraries/dom/resourcemanager/simple/ Register and connect multiple resources
34
+ * @example /examples/libraries/dom/resourcemanager/data-url-bundle/ Connect script stylesheet and data resources from inline urls
35
+ * @example /examples/libraries/dom/resourcemanager/separate-registration/ Register resources in separate steps and inspect the internal groups
36
+ * @example /examples/libraries/dom/resourcemanager/availability-check/ Wait for registered resources to report availability
31
37
  * @license AGPLv3
32
38
  * @since 1.25.0
33
39
  * @copyright Volker Schukai
34
- * @summary A Resource class
40
+ * @summary A resource coordinator for registering and connecting scripts, stylesheets and data assets.
35
41
  */
36
42
  class ResourceManager extends Base {
37
43
  /**
@@ -25,6 +25,12 @@ export { Template };
25
25
  /**
26
26
  * The template class provides methods for creating templates.
27
27
  *
28
+ * @fragments /fragments/libraries/dom/template/
29
+ *
30
+ * @example /examples/libraries/dom/template/simple/ Clone a named template fragment
31
+ * @example /examples/libraries/dom/template/theme-specific/ Resolve theme specific templates by id suffix
32
+ * @example /examples/libraries/dom/template/prefixed-lookup/ Resolve prefixed templates near the current node
33
+ * @example /examples/libraries/dom/template/repeated-clones/ Clone the same template multiple times without mutating the source
28
34
  * @license AGPLv3
29
35
  * @since 1.6.0
30
36
  * @copyright Volker Schukai
@@ -105,6 +105,15 @@ const queuedSnapshotSymbol = Symbol("queuedSnapshot");
105
105
  * @fragments /fragments/libraries/dom/updater/
106
106
  *
107
107
  * @example /examples/libraries/dom/updater/simple/ Simple example
108
+ * @example /examples/libraries/dom/updater/properties-and-patch/ Properties, patching and keyed list rendering
109
+ * @example /examples/libraries/dom/updater/attributes-and-aria/ Attribute and ARIA mapping
110
+ * @example /examples/libraries/dom/updater/bind-types/ Bound value casting
111
+ * @example /examples/libraries/dom/updater/retrieve-once/ One-time retrieval without live events
112
+ * @example /examples/libraries/dom/updater/insert-template-list/ Template insertion for iterable data
113
+ * @example /examples/libraries/dom/updater/custom-callbacks/ Custom pipe callbacks
114
+ * @example /examples/libraries/dom/updater/batch-updates/ Batched update processing
115
+ * @example /examples/libraries/dom/updater/control-properties/ Property updates on native controls
116
+ * @example /examples/libraries/dom/updater/select-multiple-array/ Multi-select array binding
108
117
  *
109
118
  * @license AGPLv3
110
119
  * @since 1.8.0
@@ -31,6 +31,9 @@ const internalTranslationSymbol = Symbol("internalTranslation");
31
31
  * The Formatter extends the Text.Formatter with the possibility to replace the key by a translation.
32
32
  *
33
33
  * @fragments /fragments/libraries/i18n/formatter/
34
+ * @example /examples/libraries/i18n/formatter/basic-translation/ Resolve one translation key through the i18n marker
35
+ * @example /examples/libraries/i18n/formatter/parameterized-text/ Combine translated text with formatter parameters
36
+ * @example /examples/libraries/i18n/formatter/custom-markers/ Configure custom formatter markers for translated strings
34
37
  *
35
38
  * @license AGPLv3
36
39
  * @since 1.26.0
@@ -38,6 +38,12 @@ const translationsLinkSymbol = Symbol.for(
38
38
  /**
39
39
  * A provider makes a translation object available.
40
40
  *
41
+ * @fragments /fragments/libraries/i18n/provider/
42
+ *
43
+ * @example /examples/libraries/i18n/provider/simple/ Assign translations to a document
44
+ * @example /examples/libraries/i18n/provider/merge-existing/ Merge a second translation payload into an already linked element
45
+ * @example /examples/libraries/i18n/provider/subtree-assignment/ Attach translations to a dedicated subtree instead of the whole document
46
+ * @example /examples/libraries/i18n/provider/locale-switch/ Resolve different translation objects for different locales
41
47
  * @license AGPLv3
42
48
  * @since 1.13.0
43
49
  * @copyright Volker Schukai
@@ -36,6 +36,10 @@ export { Translations, getDocumentTranslations };
36
36
  * @fragments /fragments/libraries/i18n/translations/
37
37
  *
38
38
  * @externalExample ../../example/i18n/translations.mjs
39
+ * @example /examples/libraries/i18n/translations/simple/ Resolve singular and plural translations
40
+ * @example /examples/libraries/i18n/translations/bulk-assignment/ Load multiple translation keys from plain objects
41
+ * @example /examples/libraries/i18n/translations/fallbacks/ Use explicit default text when a key is missing
42
+ * @example /examples/libraries/i18n/translations/plural-keywords/ Resolve plural texts with explicit plural rule keywords
39
43
  * @license AGPLv3
40
44
  * @since 1.13.0
41
45
  * @copyright Volker Schukai
@@ -22,6 +22,12 @@ export { Handler };
22
22
  /**
23
23
  * The log handler is the interface between the log entries and the log listeners.
24
24
  *
25
+ * @fragments /fragments/libraries/logging/handler/
26
+ *
27
+ * @example /examples/libraries/logging/handler/simple/ Filter log entries before forwarding them
28
+ * @example /examples/libraries/logging/handler/level-switch/ Switch handler thresholds with the convenience methods
29
+ * @example /examples/libraries/logging/handler/structured-output/ Forward structured log entry data into a custom buffer
30
+ * @example /examples/libraries/logging/handler/off-state/ Disable a handler completely with the off level
25
31
  * @license AGPLv3
26
32
  * @since 1.5.0
27
33
  * @copyright Volker Schukai
@@ -68,6 +68,12 @@ const OFF = 0;
68
68
  /**
69
69
  * The logger is a class that takes care of logging.
70
70
  *
71
+ * @fragments /fragments/libraries/logging/logger/
72
+ *
73
+ * @example /examples/libraries/logging/logger/simple/ Write entries through a console handler
74
+ * @example /examples/libraries/logging/logger/multiple-handlers/ Route the same log entry through multiple handlers
75
+ * @example /examples/libraries/logging/logger/level-filtering/ Filter log output through handler log levels
76
+ * @example /examples/libraries/logging/logger/remove-handler/ Remove a handler and verify that it no longer receives entries
71
77
  * @license AGPLv3
72
78
  * @since 1.5.0
73
79
  * @copyright Volker Schukai
@@ -22,6 +22,12 @@ const dataSymbol = Symbol("@@data");
22
22
  /**
23
23
  * This class represents a WebSocket message.
24
24
  *
25
+ * @fragments /fragments/libraries/net/webconnect/message/
26
+ *
27
+ * @example /examples/libraries/net/webconnect/message/simple/ Serialize and restore a structured message
28
+ * @example /examples/libraries/net/webconnect/message/nested-payload/ Keep nested payload data intact through serialization
29
+ * @example /examples/libraries/net/webconnect/message/queue-ready-shape/ Use a message shape that is ready for queue inspection and logging
30
+ * @example /examples/libraries/net/webconnect/message/object-access/ Access raw message data through getData and toJSON
25
31
  * @license AGPLv3
26
32
  * @since 3.4.0
27
33
  * @copyright Volker Schukai
@@ -209,7 +209,13 @@ function connectServer(resolve, reject) {
209
209
  /**
210
210
  * The RestAPI is a class that enables a REST API server.
211
211
  *
212
+ * @fragments /fragments/libraries/net/webconnect/
213
+ *
212
214
  * @externalExample ../../example/net/webconnect.mjs
215
+ * @example /examples/libraries/net/webconnect/simple/ Connect and consume structured messages
216
+ * @example /examples/libraries/net/webconnect/send-and-close/ Send structured messages and close the connection manually
217
+ * @example /examples/libraries/net/webconnect/observer-queue/ Observe incoming queue updates through the receive queue observer API
218
+ * @example /examples/libraries/net/webconnect/reconnect-options/ Configure reconnect and timeout options for resilient connections
213
219
  * @license AGPLv3
214
220
  * @since 3.1.0
215
221
  * @copyright Volker Schukai
@@ -23,9 +23,12 @@ export { ObservableQueue };
23
23
  *
24
24
  * `Queue.add()` and `Queue.clear()` notify all observers.
25
25
  *
26
+ * @fragments /fragments/libraries/types/observablequeue/
27
+ *
28
+ * @example /examples/libraries/types/observablequeue/simple/ Observe queue changes while items are added
26
29
  * @since 3.3.0
27
30
  * @copyright Volker Schukai
28
- * @summary An observable Queue (Fifo)
31
+ * @summary A FIFO queue that notifies observers whenever its contents change.
29
32
  */
30
33
  class ObservableQueue extends Queue {
31
34
  /**
@@ -40,8 +40,12 @@ export { Observer };
40
40
  * observer.update(mySubject);
41
41
  * greeter.update(mySubject);
42
42
  *
43
+ * @fragments /fragments/libraries/types/observer/
44
+ *
45
+ * @example /examples/libraries/types/observer/simple/ Schedule a deduplicated observer update
43
46
  * @license AGPLv3
44
47
  * @since 1.0.0
48
+ * @summary An asynchronous observer that batches updates per subject and callback.
45
49
  */
46
50
  class Observer extends Base {
47
51
  /**
@@ -4,6 +4,7 @@ import { initJSDOM } from "../../../util/jsdom.mjs";
4
4
  let expect = chai.expect;
5
5
 
6
6
  let resolveClippingBoundaryElement;
7
+ let applyAdaptiveFloatingElementSize;
7
8
 
8
9
  describe("form floating-ui boundary resolution", function () {
9
10
  before(function (done) {
@@ -15,6 +16,7 @@ describe("form floating-ui boundary resolution", function () {
15
16
  })
16
17
  .then((m) => {
17
18
  resolveClippingBoundaryElement = m.resolveClippingBoundaryElement;
19
+ applyAdaptiveFloatingElementSize = m.applyAdaptiveFloatingElementSize;
18
20
  done();
19
21
  })
20
22
  .catch((e) => done(e));
@@ -58,4 +60,79 @@ describe("form floating-ui boundary resolution", function () {
58
60
 
59
61
  expect(resolveClippingBoundaryElement(control, popper)).to.equal(wrapper);
60
62
  });
63
+
64
+ it("should adapt the content max height to the available popper height", function () {
65
+ const mocks = document.getElementById("mocks");
66
+ const popper = document.createElement("div");
67
+ const header = document.createElement("div");
68
+ const content = document.createElement("div");
69
+ const footer = document.createElement("div");
70
+
71
+ popper.style.maxHeight = "300px";
72
+ content.style.maxHeight = "240px";
73
+ content.setAttribute("part", "content");
74
+
75
+ popper.appendChild(header);
76
+ popper.appendChild(content);
77
+ popper.appendChild(footer);
78
+ mocks.appendChild(popper);
79
+
80
+ header.getBoundingClientRect = () => {
81
+ return {
82
+ width: 180,
83
+ height: 30,
84
+ top: 0,
85
+ left: 0,
86
+ right: 180,
87
+ bottom: 30,
88
+ x: 0,
89
+ y: 0,
90
+ };
91
+ };
92
+ popper.getBoundingClientRect = () => {
93
+ return {
94
+ width: 220,
95
+ height: 200,
96
+ top: 0,
97
+ left: 0,
98
+ right: 220,
99
+ bottom: 200,
100
+ x: 0,
101
+ y: 0,
102
+ };
103
+ };
104
+ footer.getBoundingClientRect = () => {
105
+ return {
106
+ width: 180,
107
+ height: 30,
108
+ top: 0,
109
+ left: 0,
110
+ right: 180,
111
+ bottom: 30,
112
+ x: 0,
113
+ y: 0,
114
+ };
115
+ };
116
+ content.getBoundingClientRect = () => {
117
+ return {
118
+ width: 180,
119
+ height: 140,
120
+ top: 0,
121
+ left: 0,
122
+ right: 180,
123
+ bottom: 140,
124
+ x: 0,
125
+ y: 0,
126
+ };
127
+ };
128
+
129
+ applyAdaptiveFloatingElementSize(popper, {
130
+ availableWidth: 220,
131
+ availableHeight: 160,
132
+ });
133
+
134
+ expect(popper.style.maxHeight).to.equal("160px");
135
+ expect(content.style.maxWidth).to.equal("");
136
+ expect(content.style.maxHeight).to.equal("100px");
137
+ });
61
138
  });