@schukai/monster 4.137.9 → 4.139.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.
@@ -0,0 +1,1437 @@
1
+ /**
2
+ * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact Volker Schukai.
11
+ *
12
+ * SPDX-License-Identifier: AGPL-3.0
13
+ */
14
+
15
+ import { instanceSymbol } from "../../constants.mjs";
16
+ import { Pathfinder } from "../../data/pathfinder.mjs";
17
+ import {
18
+ addAttributeToken,
19
+ removeAttributeToken,
20
+ } from "../../dom/attributes.mjs";
21
+ import {
22
+ ATTRIBUTE_ERRORMESSAGE,
23
+ ATTRIBUTE_ROLE,
24
+ } from "../../dom/constants.mjs";
25
+ import {
26
+ assembleMethodSymbol,
27
+ getSlottedElements,
28
+ registerCustomElement,
29
+ } from "../../dom/customelement.mjs";
30
+ import {
31
+ CustomElement,
32
+ attributeObserverSymbol,
33
+ } from "../../dom/customelement.mjs";
34
+ import { findTargetElementFromEvent } from "../../dom/events.mjs";
35
+ import { getDocument } from "../../dom/util.mjs";
36
+ import { getGlobal } from "../../types/global.mjs";
37
+ import { ID } from "../../types/id.mjs";
38
+ import { Observer } from "../../types/observer.mjs";
39
+ import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
40
+ import { ControlBarStyleSheet } from "./stylesheet/control-bar.mjs";
41
+ import { positionPopper } from "./util/floating-ui.mjs";
42
+ import { convertToPixels } from "../../dom/dimension.mjs";
43
+ import { addErrorAttribute } from "../../dom/error.mjs";
44
+ import { Processing } from "../../util/processing.mjs";
45
+ export { ControlBar };
46
+
47
+ /**
48
+ * @private
49
+ * @type {symbol}
50
+ */
51
+ /**
52
+ * local symbol
53
+ * @private
54
+ * @type {symbol}
55
+ */
56
+ const resizeObserverSymbol = Symbol("windowResizeObserver");
57
+
58
+ /**
59
+ * @private
60
+ * @type {symbol}
61
+ */
62
+ const dimensionsSymbol = Symbol("dimensions");
63
+
64
+ /**
65
+ * @private
66
+ * @type {symbol}
67
+ */
68
+ const controlElementSymbol = Symbol("controlElement");
69
+
70
+ /**
71
+ * @private
72
+ * @type {symbol}
73
+ */
74
+ const controlBarSlotElementSymbol = Symbol("controlBarSlotElement");
75
+
76
+ /**
77
+ * @private
78
+ * @type {symbol}
79
+ */
80
+ const popperSlotElementSymbol = Symbol("popperSlotElement");
81
+
82
+ /**
83
+ * @private
84
+ * @type {symbol}
85
+ */
86
+ const controlBarElementSymbol = Symbol("controlBarElement");
87
+
88
+ /**
89
+ * @private
90
+ * @type {symbol}
91
+ */
92
+ const popperElementSymbol = Symbol("popperElement");
93
+
94
+ /**
95
+ * local symbol
96
+ * @private
97
+ * @type {symbol}
98
+ */
99
+ const closeEventHandler = Symbol("closeEventHandler");
100
+
101
+ /**
102
+ * @private
103
+ * @type {symbol}
104
+ */
105
+ const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
106
+
107
+ /**
108
+ * @private
109
+ * @type {symbol}
110
+ */
111
+ const popperNavElementSymbol = Symbol("popperNavElement");
112
+
113
+ /**
114
+ * @private
115
+ * @type {symbol}
116
+ */
117
+ const mutationObserverSymbol = Symbol("mutationObserver");
118
+
119
+ /**
120
+ * @private
121
+ * @type {symbol}
122
+ */
123
+ const switchElementSymbol = Symbol("switchElement");
124
+
125
+ /**
126
+ * @private
127
+ * @type {symbol}
128
+ */
129
+ const layoutStateSymbol = Symbol("layoutState");
130
+ const layoutFrameSymbol = Symbol("layoutFrame");
131
+ const layoutTokenSymbol = Symbol("layoutToken");
132
+ const observedLayoutNodesSignatureSymbol = Symbol(
133
+ "observedLayoutNodesSignature",
134
+ );
135
+
136
+ /**
137
+ * @private
138
+ * @type {WeakMap<HTMLElement, number>}
139
+ */
140
+ const layoutNodeIds = new WeakMap();
141
+ let layoutNodeId = 0;
142
+
143
+ /**
144
+ * @private
145
+ * @type {string}
146
+ */
147
+ const ATTRIBUTE_POPPER_POSITION = "data-monster-popper-position";
148
+
149
+ /**
150
+ * @private
151
+ * @type {string}
152
+ */
153
+ const ATTRIBUTE_LAYOUT_ALIGNMENT = "data-monster-layout-alignment";
154
+
155
+ /**
156
+ * A control bar control.
157
+ *
158
+ * @fragments /fragments/components/form/control-bar/
159
+ *
160
+ * @example /examples/components/form/control-bar-simple Control bar
161
+ *
162
+ * @copyright Volker Schukai
163
+ * @summary A responsive control bar that groups multiple controls and moves overflowed controls into a menu.
164
+ * @fires monster-fetched
165
+ */
166
+ class ControlBar extends CustomElement {
167
+ /**
168
+ * This method is called by the `instanceof` operator.
169
+ * @return {symbol}
170
+ */
171
+ static get [instanceSymbol]() {
172
+ return Symbol.for("@schukai/monster/components/form/control-bar@@instance");
173
+ }
174
+
175
+ /**
176
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
177
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
178
+ *
179
+ * The individual configuration values can be found in the table.
180
+ *
181
+ * @property {Object} templates Template definitions
182
+ * @property {string} templates.main Main template
183
+ * @property {Object} labels
184
+ * @property {Object} popper FloatingUI popper configuration
185
+ * @property {string} popper.placement=top Placement of the popper
186
+ * @property {Array<string>} popper.middleware Middleware for the popper
187
+ */
188
+ get defaults() {
189
+ const obj = Object.assign({}, super.defaults, {
190
+ templates: {
191
+ main: getTemplate(),
192
+ },
193
+ labels: {},
194
+ layout: {
195
+ alignment: "left",
196
+ },
197
+ popper: {
198
+ placement: "left",
199
+ middleware: ["flip", "shift", "offset:5"],
200
+ },
201
+ });
202
+
203
+ initDefaultsFromAttributes.call(this, obj);
204
+
205
+ return obj;
206
+ }
207
+
208
+ /**
209
+ * This method is called internal and should not be called directly.
210
+ */
211
+ [assembleMethodSymbol]() {
212
+ super[assembleMethodSymbol]();
213
+
214
+ this[dimensionsSymbol] = new Pathfinder({ data: {} });
215
+ this[layoutStateSymbol] = {
216
+ scheduled: false,
217
+ running: false,
218
+ needsMeasure: true,
219
+ needsLayout: true,
220
+ needsObserve: true,
221
+ suppressSlotChange: false,
222
+ suppressMutation: false,
223
+ };
224
+
225
+ initControlReferences.call(this);
226
+ initEventHandler.call(this);
227
+
228
+ // setup structure
229
+ initControlBar.call(this);
230
+ initPopperSwitch.call(this);
231
+ applyLayoutAlignment.call(this);
232
+ this.attachObserver(
233
+ new Observer(() => {
234
+ applyLayoutAlignment.call(this);
235
+ }),
236
+ );
237
+ }
238
+
239
+ /**
240
+ * This method is called internal and should not be called directly.
241
+ *
242
+ * @return {CSSStyleSheet[]}
243
+ */
244
+ static getCSSStyleSheet() {
245
+ return [ControlBarStyleSheet];
246
+ }
247
+
248
+ /**
249
+ * This method is called internal and should not be called directly.
250
+ *
251
+ * @return {string}
252
+ */
253
+ static getTag() {
254
+ return "monster-control-bar";
255
+ }
256
+
257
+ /**
258
+ * This method is called by the dom and should not be called directly.
259
+ *
260
+ * @return {void}
261
+ */
262
+ connectedCallback() {
263
+ super.connectedCallback();
264
+
265
+ const document = getDocument();
266
+
267
+ for (const [, type] of Object.entries(["click", "touch"])) {
268
+ // close on outside ui-events
269
+ document.addEventListener(type, this[closeEventHandler]);
270
+ }
271
+
272
+ scheduleLayout.call(this, { measure: true, layout: true, observe: true });
273
+ }
274
+
275
+ /**
276
+ * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
277
+ *
278
+ * @return {string[]}
279
+ */
280
+ static get observedAttributes() {
281
+ const attributes = super.observedAttributes;
282
+ attributes.push(ATTRIBUTE_POPPER_POSITION);
283
+ return attributes;
284
+ }
285
+
286
+ /**
287
+ * This method is called by the dom and should not be called directly.
288
+ *
289
+ * @return {void}
290
+ */
291
+ disconnectedCallback() {
292
+ super.disconnectedCallback();
293
+
294
+ const document = getDocument();
295
+
296
+ // close on outside ui-events
297
+ for (const [, type] of Object.entries(["click", "touch"])) {
298
+ document.removeEventListener(type, this[closeEventHandler]);
299
+ }
300
+
301
+ if (typeof this[layoutFrameSymbol] === "number") {
302
+ cancelAnimationFrame(this[layoutFrameSymbol]);
303
+ }
304
+ delete this[layoutFrameSymbol];
305
+ this[layoutTokenSymbol] = (this[layoutTokenSymbol] || 0) + 1;
306
+
307
+ disconnectResizeObserver.call(this);
308
+ if (this[mutationObserverSymbol]) {
309
+ this[mutationObserverSymbol].disconnect();
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Close the slotted dialog.
315
+ * @return {ControlBar}
316
+ */
317
+ hideDialog() {
318
+ hide.call(this);
319
+ return this;
320
+ }
321
+
322
+ /**
323
+ * Open the slotted dialog.
324
+ * @return {ControlBar}
325
+ */
326
+ showDialog() {
327
+ show.call(this);
328
+ return this;
329
+ }
330
+
331
+ /**
332
+ * Toggle the slotted dialog.
333
+ * @return {ControlBar}
334
+ */
335
+ toggleDialog() {
336
+ toggle.call(this);
337
+ return this;
338
+ }
339
+ }
340
+
341
+ /**
342
+ * @private
343
+ * @param obj
344
+ * @return {*}
345
+ */
346
+ function initDefaultsFromAttributes(obj) {
347
+ if (this.hasAttribute(ATTRIBUTE_POPPER_POSITION)) {
348
+ obj.popper.placement = this.getAttribute(ATTRIBUTE_POPPER_POSITION);
349
+ }
350
+
351
+ return obj;
352
+ }
353
+
354
+ /**
355
+ * @private
356
+ * @param {HTMLElement} element
357
+ * @return {boolean}
358
+ */
359
+ function isElementTrulyVisible(element) {
360
+ if (!(element instanceof HTMLElement)) {
361
+ return false;
362
+ }
363
+ const computedStyle = getComputedStyle(element);
364
+ return (
365
+ computedStyle.display !== "none" &&
366
+ computedStyle.visibility !== "hidden" &&
367
+ computedStyle.opacity !== "0" &&
368
+ element.offsetWidth > 0 &&
369
+ element.offsetHeight > 0
370
+ );
371
+ }
372
+
373
+ /**
374
+ * @private
375
+ * @param {HTMLElement} element
376
+ * @return {boolean}
377
+ */
378
+ function isElementSelfHidden(element) {
379
+ if (!(element instanceof HTMLElement)) {
380
+ return true;
381
+ }
382
+
383
+ const computedStyle = getComputedStyle(element);
384
+ return (
385
+ element.hidden === true ||
386
+ element.hasAttribute("hidden") ||
387
+ computedStyle.display === "none" ||
388
+ computedStyle.visibility === "hidden"
389
+ );
390
+ }
391
+
392
+ /**
393
+ * @private
394
+ */
395
+ function initEventHandler() {
396
+ const self = this;
397
+
398
+ const mutationCallback = (mutationList) => {
399
+ if (self[layoutStateSymbol]?.suppressMutation) {
400
+ return;
401
+ }
402
+
403
+ let needsRecalc = false;
404
+ for (const mutation of mutationList) {
405
+ if (mutation.type === "attributes") {
406
+ const target = mutation.target;
407
+ if (target instanceof HTMLElement) {
408
+ const ref = target.getAttribute("data-monster-reference");
409
+ if (ref) {
410
+ if (!isElementTrulyVisible(target)) {
411
+ self[dimensionsSymbol].setVia(`data.item.${ref}`, 0);
412
+ }
413
+ needsRecalc = true;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ if (needsRecalc) {
419
+ scheduleLayout.call(self, { measure: true, layout: true });
420
+ }
421
+ };
422
+
423
+ /**
424
+ * @param {Event} event
425
+ */
426
+ self[closeEventHandler] = (event) => {
427
+ const path = event.composedPath();
428
+ for (const element of path) {
429
+ if (element === self) return;
430
+ }
431
+ hide.call(self);
432
+ };
433
+
434
+ if (self[controlBarSlotElementSymbol]) {
435
+ self[controlBarSlotElementSymbol].addEventListener("slotchange", () => {
436
+ if (self[layoutStateSymbol]?.suppressSlotChange) {
437
+ return;
438
+ }
439
+ scheduleLayout.call(self, {
440
+ measure: true,
441
+ layout: true,
442
+ observe: true,
443
+ });
444
+ });
445
+ }
446
+
447
+ if (self[popperElementSymbol]) {
448
+ self[popperElementSymbol].addEventListener("slotchange", () => {
449
+ if (self[layoutStateSymbol]?.suppressSlotChange) {
450
+ return;
451
+ }
452
+ scheduleLayout.call(self, {
453
+ measure: true,
454
+ layout: true,
455
+ observe: true,
456
+ });
457
+ });
458
+ }
459
+
460
+ self[attributeObserverSymbol][ATTRIBUTE_POPPER_POSITION] = function (value) {
461
+ self.setOption("popper.placement", value);
462
+ updatePopper.call(self);
463
+ };
464
+
465
+ self[resizeObserverSymbol] = new ResizeObserver(() => {
466
+ scheduleLayout.call(self, { measure: true, layout: true });
467
+ });
468
+ self[mutationObserverSymbol] = new MutationObserver(mutationCallback);
469
+
470
+ initSlotChangedHandler.call(self);
471
+ }
472
+
473
+ function initSlotChangedHandler() {
474
+ this[controlBarElementSymbol].addEventListener("slotchange", () => {
475
+ if (this[layoutStateSymbol]?.suppressSlotChange) {
476
+ return;
477
+ }
478
+ scheduleLayout.call(this, { observe: true });
479
+ });
480
+ }
481
+
482
+ function scheduleLayout(options = {}) {
483
+ if (!this[layoutStateSymbol]) {
484
+ return;
485
+ }
486
+
487
+ const state = this[layoutStateSymbol];
488
+ state.needsMeasure = state.needsMeasure || options.measure === true;
489
+ state.needsLayout = state.needsLayout || options.layout === true;
490
+ state.needsObserve = state.needsObserve || options.observe === true;
491
+
492
+ if (state.scheduled || state.running) {
493
+ return;
494
+ }
495
+
496
+ scheduleLayoutFrame.call(this);
497
+ }
498
+
499
+ function scheduleLayoutFrame() {
500
+ const state = this[layoutStateSymbol];
501
+ if (!state || state.scheduled || state.running) {
502
+ return;
503
+ }
504
+
505
+ state.scheduled = true;
506
+ const token = (this[layoutTokenSymbol] || 0) + 1;
507
+ this[layoutTokenSymbol] = token;
508
+ this[layoutFrameSymbol] = requestAnimationFrame(() => {
509
+ if (this[layoutTokenSymbol] !== token) {
510
+ return;
511
+ }
512
+ delete this[layoutFrameSymbol];
513
+ runLayout.call(this);
514
+ });
515
+ }
516
+
517
+ function runLayout() {
518
+ const state = this[layoutStateSymbol];
519
+ if (!state) {
520
+ return;
521
+ }
522
+
523
+ state.scheduled = false;
524
+ if (!this.isConnected) {
525
+ return;
526
+ }
527
+
528
+ if (state.running) {
529
+ return;
530
+ }
531
+
532
+ const needsObserve = state.needsObserve;
533
+ const needsMeasure = state.needsMeasure;
534
+ const needsLayout = state.needsLayout;
535
+
536
+ state.needsObserve = false;
537
+ state.needsMeasure = false;
538
+ state.needsLayout = false;
539
+ state.running = true;
540
+
541
+ new Processing(() => {
542
+ if (needsObserve) {
543
+ updateResizeObserverObservation.call(this);
544
+ }
545
+
546
+ if (needsMeasure) {
547
+ try {
548
+ calculateControlBarDimensions.call(this);
549
+ } catch (error) {
550
+ addErrorAttribute(
551
+ this,
552
+ error?.message || "An error occurred while calculating dimensions",
553
+ );
554
+ }
555
+ }
556
+
557
+ if (needsLayout) {
558
+ try {
559
+ rearrangeItems.call(this);
560
+ } catch (error) {
561
+ addErrorAttribute(
562
+ this,
563
+ error?.message || "An error occurred while rearranging the items",
564
+ );
565
+ }
566
+ }
567
+
568
+ return updatePopper.call(this);
569
+ })
570
+ .run()
571
+ .catch((error) => {
572
+ addErrorAttribute(
573
+ this,
574
+ error?.message ||
575
+ "An error occurred while running the control bar layout",
576
+ );
577
+ })
578
+ .finally(() => {
579
+ state.running = false;
580
+ if (state.needsObserve || state.needsMeasure || state.needsLayout) {
581
+ scheduleLayoutFrame.call(this);
582
+ }
583
+ });
584
+ }
585
+
586
+ /**
587
+ * @private
588
+ * @return {Object}
589
+ */
590
+ function rearrangeItems() {
591
+ let space = 0;
592
+ try {
593
+ space = this[dimensionsSymbol].getVia("data.space");
594
+ } catch {}
595
+
596
+ const itemReferences = this[dimensionsSymbol].getVia(
597
+ "data.itemReferences",
598
+ [],
599
+ );
600
+ const hasItems = itemReferences.length > 0;
601
+
602
+ const itemEntries = [];
603
+
604
+ for (const ref of itemReferences) {
605
+ let elements = getSlottedElements.call(
606
+ this,
607
+ '[data-monster-reference="' + ref + '"]',
608
+ null,
609
+ ); // null ↦ o
610
+ if (elements.size === 0) {
611
+ elements = getSlottedElements.call(
612
+ this,
613
+ '[data-monster-reference="' + ref + '"]',
614
+ "popper",
615
+ ); // null ↦ o
616
+ }
617
+
618
+ const nextValue = elements.values().next();
619
+ if (!nextValue) {
620
+ continue;
621
+ }
622
+
623
+ const element = nextValue?.value;
624
+ if (!(element instanceof HTMLElement)) {
625
+ continue;
626
+ }
627
+
628
+ let itemWidth = 0;
629
+ try {
630
+ itemWidth = this[dimensionsSymbol].getVia(`data.item.${ref}`);
631
+ } catch (e) {
632
+ // If the path does not exist, pathfinder throws an error.
633
+ // In this case, we assume the width is 0.
634
+ // This can happen for items that have never been visible.
635
+ }
636
+ itemEntries.push({
637
+ element,
638
+ width: itemWidth,
639
+ hidden: isElementSelfHidden(element),
640
+ });
641
+ }
642
+
643
+ const switchWidth = this[dimensionsSymbol].getVia("data.switchWidth") || 2;
644
+
645
+ const layoutItems = (availableSpace) => {
646
+ if (availableSpace < 0) {
647
+ availableSpace = 0;
648
+ }
649
+ let sum = 0;
650
+ const visibleItemsInMainSlot = [];
651
+ const itemsToMoveToPopper = [];
652
+
653
+ for (const entry of itemEntries) {
654
+ if (entry.hidden) {
655
+ visibleItemsInMainSlot.push(entry.element);
656
+ continue;
657
+ }
658
+
659
+ if (sum + entry.width > availableSpace) {
660
+ itemsToMoveToPopper.push(entry.element);
661
+ } else {
662
+ sum += entry.width;
663
+ visibleItemsInMainSlot.push(entry.element);
664
+ }
665
+ }
666
+
667
+ return { visibleItemsInMainSlot, itemsToMoveToPopper };
668
+ };
669
+
670
+ let layout = layoutItems(space);
671
+ if (layout.itemsToMoveToPopper.length > 0) {
672
+ layout = layoutItems(space - switchWidth);
673
+ }
674
+
675
+ const shouldShowSwitch =
676
+ layout.itemsToMoveToPopper.length > 0 && hasItems;
677
+
678
+ suppressLayoutFeedback.call(this);
679
+
680
+ for (const item of layout.itemsToMoveToPopper) {
681
+ if (item.getAttribute("slot") !== "popper") {
682
+ item.setAttribute("slot", "popper");
683
+ }
684
+ }
685
+
686
+ for (const item of layout.visibleItemsInMainSlot) {
687
+ if (item.hasAttribute("slot")) {
688
+ item.removeAttribute("slot");
689
+ }
690
+ }
691
+
692
+ setSwitchVisible.call(this, shouldShowSwitch);
693
+ updateControlSizing.call(this, layout, shouldShowSwitch);
694
+ updateJoinedBorders.call(this, layout, shouldShowSwitch);
695
+ if (!shouldShowSwitch) {
696
+ hide.call(this);
697
+ }
698
+ }
699
+
700
+ /**
701
+ * @private
702
+ * @param {HTMLElement} node
703
+ * @return {number}
704
+ */
705
+ function calcBoxWidth(node) {
706
+ const dim = getGlobal()?.getComputedStyle(node);
707
+ if (dim === null) {
708
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "no computed style");
709
+ throw new Error("no computed style");
710
+ }
711
+ const bounding = node.getBoundingClientRect();
712
+
713
+ return (
714
+ getComputedCssPixels(dim["border-left-width"]) +
715
+ getComputedCssPixels(dim["padding-left"]) +
716
+ getComputedCssPixels(dim["margin-left"]) +
717
+ bounding["width"] +
718
+ getComputedCssPixels(dim["border-right-width"]) +
719
+ getComputedCssPixels(dim["margin-right"]) +
720
+ getComputedCssPixels(dim["padding-right"])
721
+ );
722
+ }
723
+
724
+ /**
725
+ * @private
726
+ * @param {string} value
727
+ * @return {number}
728
+ */
729
+ function getComputedCssPixels(value) {
730
+ if (typeof value !== "string" || value === "") {
731
+ return 0;
732
+ }
733
+
734
+ if (value.endsWith("px")) {
735
+ return parseFloat(value) || 0;
736
+ }
737
+
738
+ try {
739
+ return convertToPixels(value);
740
+ } catch {
741
+ return 0;
742
+ }
743
+ }
744
+
745
+ /**
746
+ * @private
747
+ * @param {Object} layout
748
+ * @param {boolean} shouldShowSwitch
749
+ * @return {void}
750
+ */
751
+ function updateControlSizing(layout, shouldShowSwitch) {
752
+ const mainItems = [...layout.visibleItemsInMainSlot];
753
+ if (
754
+ shouldShowSwitch &&
755
+ this[popperNavElementSymbol] instanceof HTMLElement
756
+ ) {
757
+ mainItems.push(this[popperNavElementSymbol]);
758
+ }
759
+
760
+ const controlItems = [
761
+ ...mainItems,
762
+ ...layout.itemsToMoveToPopper,
763
+ ];
764
+ for (const element of controlItems) {
765
+ applyControlSizing.call(this, element);
766
+ }
767
+ }
768
+
769
+ /**
770
+ * @private
771
+ * @param {HTMLElement} element
772
+ * @return {void}
773
+ */
774
+ function applyControlSizing(element) {
775
+ setStylePropertyIfChanged(element, "boxSizing", "border-box");
776
+ setStylePropertyIfChanged(
777
+ element,
778
+ "height",
779
+ "var(--monster-control-bar-height)",
780
+ );
781
+ setStylePropertyIfChanged(
782
+ element,
783
+ "minHeight",
784
+ "var(--monster-control-bar-height)",
785
+ );
786
+ if (isWrapperElement(element)) {
787
+ setStylePropertyIfChanged(element, "display", "flex");
788
+ setStylePropertyIfChanged(element, "alignItems", "stretch");
789
+ }
790
+
791
+ for (const control of getContainedControlElements(element)) {
792
+ setStylePropertyIfChanged(control, "display", "flex");
793
+ setStylePropertyIfChanged(control, "boxSizing", "border-box");
794
+ setStylePropertyIfChanged(control, "height", "100%");
795
+ setStylePropertyIfChanged(control, "minHeight", "0px");
796
+ if (control.tagName === "MONSTER-INPUT-GROUP") {
797
+ setStylePropertyIfChanged(control, "flex", "1 1 auto");
798
+ }
799
+ }
800
+ }
801
+
802
+ /**
803
+ * @private
804
+ * @param {HTMLElement} element
805
+ * @return {boolean}
806
+ */
807
+ function isWrapperElement(element) {
808
+ return ["DIV", "FORM", "SECTION"].includes(element.tagName);
809
+ }
810
+
811
+ /**
812
+ * @private
813
+ * @param {HTMLElement} element
814
+ * @return {HTMLElement[]}
815
+ */
816
+ function getContainedControlElements(element) {
817
+ if (!isWrapperElement(element)) {
818
+ return [];
819
+ }
820
+
821
+ return Array.from(
822
+ element.querySelectorAll(
823
+ [
824
+ "monster-input-group",
825
+ "monster-select",
826
+ "monster-button",
827
+ "monster-state-button",
828
+ "monster-message-state-button",
829
+ "monster-action-button",
830
+ "monster-api-button",
831
+ "monster-confirm-button",
832
+ ].join(","),
833
+ ),
834
+ ).filter((control) => control instanceof HTMLElement);
835
+ }
836
+
837
+ /**
838
+ * @private
839
+ * @param {Object} layout
840
+ * @param {boolean} shouldShowSwitch
841
+ * @return {void}
842
+ */
843
+ function updateJoinedBorders(layout, shouldShowSwitch) {
844
+ const mainItems = [...layout.visibleItemsInMainSlot];
845
+ if (
846
+ shouldShowSwitch &&
847
+ this[popperNavElementSymbol] instanceof HTMLElement &&
848
+ this[switchElementSymbol] instanceof HTMLElement
849
+ ) {
850
+ mainItems.push(this[popperNavElementSymbol]);
851
+ }
852
+
853
+ const marginLeftByElement = new Map();
854
+ const marginTopByElement = new Map();
855
+
856
+ collectInlineJoinedBorders.call(this, mainItems, marginLeftByElement);
857
+ collectBlockJoinedBorders.call(
858
+ this,
859
+ layout.itemsToMoveToPopper,
860
+ marginTopByElement,
861
+ );
862
+ applyJoinedBorderOffsets.call(this, marginLeftByElement, marginTopByElement);
863
+ }
864
+
865
+ /**
866
+ * @private
867
+ * @param {HTMLElement[]} elements
868
+ * @param {Map<HTMLElement, string>} marginLeftByElement
869
+ * @return {void}
870
+ */
871
+ function collectInlineJoinedBorders(elements, marginLeftByElement) {
872
+ for (let i = 1; i < elements.length; i++) {
873
+ const previous = elements[i - 1];
874
+ const current = elements[i];
875
+ const overlap = getBorderOverlap(
876
+ getVisualBorderWidth.call(this, previous, "right"),
877
+ getVisualBorderWidth.call(this, current, "left"),
878
+ );
879
+
880
+ if (overlap > 0) {
881
+ marginLeftByElement.set(current, `${-1 * overlap}px`);
882
+ }
883
+ }
884
+ }
885
+
886
+ /**
887
+ * @private
888
+ * @param {HTMLElement[]} elements
889
+ * @param {Map<HTMLElement, string>} marginTopByElement
890
+ * @return {void}
891
+ */
892
+ function collectBlockJoinedBorders(elements, marginTopByElement) {
893
+ for (let i = 1; i < elements.length; i++) {
894
+ const previous = elements[i - 1];
895
+ const current = elements[i];
896
+ const overlap = getBorderOverlap(
897
+ getVisualBorderWidth.call(this, previous, "bottom"),
898
+ getVisualBorderWidth.call(this, current, "top"),
899
+ );
900
+
901
+ if (overlap > 0) {
902
+ marginTopByElement.set(current, `${-1 * overlap}px`);
903
+ }
904
+ }
905
+ }
906
+
907
+ /**
908
+ * @private
909
+ * @param {Map<HTMLElement, string>} marginLeftByElement
910
+ * @param {Map<HTMLElement, string>} marginTopByElement
911
+ * @return {void}
912
+ */
913
+ function applyJoinedBorderOffsets(marginLeftByElement, marginTopByElement) {
914
+ for (const element of getJoinedBorderOffsetElements.call(this)) {
915
+ setStylePropertyIfChanged(
916
+ element,
917
+ "marginLeft",
918
+ marginLeftByElement.get(element) || "",
919
+ );
920
+ setStylePropertyIfChanged(
921
+ element,
922
+ "marginTop",
923
+ marginTopByElement.get(element) || "",
924
+ );
925
+ }
926
+ }
927
+
928
+ /**
929
+ * @private
930
+ * @return {HTMLElement[]}
931
+ */
932
+ function getJoinedBorderOffsetElements() {
933
+ const elements = Array.from(this.children).filter(
934
+ (element) => element instanceof HTMLElement,
935
+ );
936
+ if (this[popperNavElementSymbol] instanceof HTMLElement) {
937
+ elements.push(this[popperNavElementSymbol]);
938
+ }
939
+
940
+ return elements;
941
+ }
942
+
943
+ /**
944
+ * @private
945
+ * @param {HTMLElement} element
946
+ * @param {"marginLeft"|"marginTop"} property
947
+ * @param {string} value
948
+ * @return {void}
949
+ */
950
+ function setStylePropertyIfChanged(element, property, value) {
951
+ if (element.style[property] !== value) {
952
+ element.style[property] = value;
953
+ }
954
+ }
955
+
956
+ /**
957
+ * @private
958
+ * @param {number} previousBorderWidth
959
+ * @param {number} currentBorderWidth
960
+ * @return {number}
961
+ */
962
+ function getBorderOverlap(previousBorderWidth, currentBorderWidth) {
963
+ return Math.min(previousBorderWidth, currentBorderWidth);
964
+ }
965
+
966
+ /**
967
+ * @private
968
+ * @param {HTMLElement} element
969
+ * @param {"top"|"right"|"bottom"|"left"} side
970
+ * @return {number}
971
+ */
972
+ function getVisualBorderWidth(element, side) {
973
+ const visualElement = getVisualBorderElement.call(this, element, side);
974
+ const computedStyle = getComputedStyle(visualElement);
975
+ const value = computedStyle.getPropertyValue(`border-${side}-width`);
976
+
977
+ return getComputedCssPixels(value);
978
+ }
979
+
980
+ /**
981
+ * @private
982
+ * @param {HTMLElement} element
983
+ * @param {"top"|"right"|"bottom"|"left"} side
984
+ * @return {HTMLElement}
985
+ */
986
+ function getVisualBorderElement(element, side) {
987
+ if (element === this[popperNavElementSymbol]) {
988
+ return this[switchElementSymbol] || element;
989
+ }
990
+
991
+ const selector = [
992
+ "button",
993
+ "input",
994
+ "select",
995
+ "textarea",
996
+ "monster-input-group",
997
+ "monster-select",
998
+ "[data-monster-role=control]",
999
+ ].join(",");
1000
+ if (element.shadowRoot instanceof ShadowRoot) {
1001
+ return element.shadowRoot.querySelector(selector) || element;
1002
+ }
1003
+
1004
+ const candidates = Array.from(element.querySelectorAll(selector)).filter(
1005
+ (candidate) => candidate instanceof HTMLElement,
1006
+ );
1007
+ if (candidates.length === 0) {
1008
+ return element;
1009
+ }
1010
+
1011
+ if (side === "right" || side === "bottom") {
1012
+ return candidates[candidates.length - 1];
1013
+ }
1014
+
1015
+ return candidates[0];
1016
+ }
1017
+
1018
+ /**
1019
+ * @private
1020
+ * @return {Object}
1021
+ */
1022
+ function calculateControlBarDimensions() {
1023
+ if (!(this.parentElement instanceof HTMLElement)) {
1024
+ this[dimensionsSymbol].setVia("data.space", 0);
1025
+ this[dimensionsSymbol].setVia("data.visible", false);
1026
+ this[dimensionsSymbol].setVia("data.calculated", true);
1027
+ this[dimensionsSymbol].setVia("data.itemReferences", []);
1028
+ return;
1029
+ }
1030
+
1031
+ const computedStyle = getComputedStyle(this.parentElement);
1032
+
1033
+ if (computedStyle === null) {
1034
+ throw new Error("no computed style");
1035
+ }
1036
+
1037
+ let width = this.parentElement.clientWidth;
1038
+ if (computedStyle.getPropertyValue("box-sizing") !== "border-box") {
1039
+ width = computedStyle.getPropertyValue("width");
1040
+
1041
+ const pixel = getComputedCssPixels(width);
1042
+
1043
+ this[dimensionsSymbol].setVia("data.space", pixel);
1044
+ } else {
1045
+ let borderWidth = getComputedStyle(this).getPropertyValue(
1046
+ "--monster-border-width",
1047
+ );
1048
+ if (borderWidth === null || borderWidth === "") {
1049
+ borderWidth = "0px";
1050
+ }
1051
+
1052
+ const borderWidthWithoutUnit = getComputedCssPixels(borderWidth);
1053
+
1054
+ // space to be allocated
1055
+ this[dimensionsSymbol].setVia(
1056
+ "data.space",
1057
+ width - 2 * borderWidthWithoutUnit,
1058
+ );
1059
+ }
1060
+
1061
+ this[dimensionsSymbol].setVia("data.visible", !(width === 0));
1062
+
1063
+ const itemReferences = [];
1064
+
1065
+ // Get all direct controls, regardless of their current slot.
1066
+ const combinedItems = Array.from(this.children).filter(
1067
+ (item, index, self) => {
1068
+ if (!(item instanceof HTMLElement)) {
1069
+ return false;
1070
+ }
1071
+ if (item.slot && item.slot !== "popper") {
1072
+ return false;
1073
+ }
1074
+ // Filter out duplicates based on data-monster-reference if present, or element itself
1075
+ return (
1076
+ self.findIndex((other) => {
1077
+ if (!(other instanceof HTMLElement)) {
1078
+ return false;
1079
+ }
1080
+ const itemReference = item.dataset.monsterReference;
1081
+ const otherReference = other.dataset.monsterReference;
1082
+ if (itemReference && otherReference) {
1083
+ return itemReference === otherReference;
1084
+ }
1085
+ return other === item;
1086
+ }) === index
1087
+ );
1088
+ },
1089
+ );
1090
+
1091
+ for (const item of combinedItems) {
1092
+ if (!(item instanceof HTMLElement)) {
1093
+ continue;
1094
+ }
1095
+
1096
+ if (!item.hasAttribute("data-monster-reference")) {
1097
+ item.setAttribute("data-monster-reference", new ID("ctrl").toString());
1098
+ }
1099
+
1100
+ const ref = item.getAttribute("data-monster-reference");
1101
+ if (ref === null) continue;
1102
+
1103
+ itemReferences.push(ref);
1104
+
1105
+ // Only calculate width for visible items. Assume invisible ones
1106
+ // (e.g. in popper) have their width calculated previously and stored.
1107
+ if (isElementTrulyVisible(item)) {
1108
+ this[dimensionsSymbol].setVia(
1109
+ `data.item.${ref}`,
1110
+ calcBoxWidth.call(this, item),
1111
+ );
1112
+ }
1113
+ }
1114
+
1115
+ if (this[switchElementSymbol]) {
1116
+ this[dimensionsSymbol].setVia(
1117
+ "data.switchWidth",
1118
+ this[switchElementSymbol].offsetWidth,
1119
+ );
1120
+ }
1121
+
1122
+ this[dimensionsSymbol].setVia("data.calculated", true);
1123
+ this[dimensionsSymbol].setVia("data.itemReferences", itemReferences);
1124
+ }
1125
+
1126
+ /**
1127
+ * @private
1128
+ */
1129
+ function updateResizeObserverObservation() {
1130
+ const observedNodes = getLayoutObservedNodes.call(this);
1131
+ const signature = getLayoutObservedNodesSignature(observedNodes);
1132
+ if (this[observedLayoutNodesSignatureSymbol] === signature) {
1133
+ return;
1134
+ }
1135
+ this[observedLayoutNodesSignatureSymbol] = signature;
1136
+
1137
+ this[resizeObserverSymbol].disconnect();
1138
+ if (this[mutationObserverSymbol]) {
1139
+ this[mutationObserverSymbol].disconnect();
1140
+ }
1141
+
1142
+ observedNodes.forEach((node) => {
1143
+ this[resizeObserverSymbol].observe(node);
1144
+ if (node !== this.parentElement && this[mutationObserverSymbol]) {
1145
+ this[mutationObserverSymbol].observe(node, {
1146
+ attributes: true,
1147
+ attributeFilter: ["style", "class", "hidden"],
1148
+ });
1149
+ }
1150
+ });
1151
+ }
1152
+
1153
+ /**
1154
+ * @private
1155
+ * @return {HTMLElement[]}
1156
+ */
1157
+ function getLayoutObservedNodes() {
1158
+ const observedNodes = [];
1159
+ Array.from(this.children).forEach((node) => {
1160
+ if (node instanceof HTMLElement) {
1161
+ observedNodes.push(node);
1162
+ }
1163
+ });
1164
+
1165
+ let parent = this.parentNode;
1166
+ while (!(parent instanceof HTMLElement) && parent !== null) {
1167
+ parent = parent.parentNode;
1168
+ }
1169
+
1170
+ if (parent instanceof HTMLElement) {
1171
+ observedNodes.push(parent);
1172
+ }
1173
+
1174
+ return observedNodes;
1175
+ }
1176
+
1177
+ /**
1178
+ * @private
1179
+ * @param {HTMLElement[]} nodes
1180
+ * @return {string}
1181
+ */
1182
+ function getLayoutObservedNodesSignature(nodes) {
1183
+ return nodes.map(getLayoutNodeId).join("|");
1184
+ }
1185
+
1186
+ /**
1187
+ * @private
1188
+ * @param {HTMLElement} node
1189
+ * @return {number}
1190
+ */
1191
+ function getLayoutNodeId(node) {
1192
+ let id = layoutNodeIds.get(node);
1193
+ if (id === undefined) {
1194
+ id = ++layoutNodeId;
1195
+ layoutNodeIds.set(node, id);
1196
+ }
1197
+ return id;
1198
+ }
1199
+
1200
+ /**
1201
+ * @private
1202
+ * @return {void}
1203
+ */
1204
+ function suppressLayoutFeedback() {
1205
+ const state = this[layoutStateSymbol];
1206
+ if (!state) {
1207
+ return;
1208
+ }
1209
+
1210
+ state.suppressSlotChange = true;
1211
+ state.suppressMutation = true;
1212
+ queueMicrotask(() => {
1213
+ state.suppressSlotChange = false;
1214
+ state.suppressMutation = false;
1215
+ });
1216
+ }
1217
+
1218
+ /**
1219
+ * @private
1220
+ * @param {boolean} visible
1221
+ * @return {void}
1222
+ */
1223
+ function setSwitchVisible(visible) {
1224
+ if (!(this[switchElementSymbol] instanceof HTMLElement)) {
1225
+ return;
1226
+ }
1227
+
1228
+ if (visible) {
1229
+ if (this[switchElementSymbol].hasAttribute("hidden")) {
1230
+ this[switchElementSymbol].removeAttribute("hidden");
1231
+ }
1232
+ if (this[switchElementSymbol].classList.contains("hidden")) {
1233
+ this[switchElementSymbol].classList.remove("hidden");
1234
+ }
1235
+ return;
1236
+ }
1237
+
1238
+ if (!this[switchElementSymbol].hasAttribute("hidden")) {
1239
+ this[switchElementSymbol].setAttribute("hidden", "");
1240
+ }
1241
+ if (!this[switchElementSymbol].classList.contains("hidden")) {
1242
+ this[switchElementSymbol].classList.add("hidden");
1243
+ }
1244
+ }
1245
+
1246
+ /**
1247
+ * @private
1248
+ */
1249
+ function disconnectResizeObserver() {
1250
+ if (this[resizeObserverSymbol] instanceof ResizeObserver) {
1251
+ this[resizeObserverSymbol].disconnect();
1252
+ }
1253
+ this[observedLayoutNodesSignatureSymbol] = null;
1254
+ }
1255
+
1256
+ /**
1257
+ * @private
1258
+ */
1259
+ function toggle() {
1260
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
1261
+ hide.call(this);
1262
+ } else {
1263
+ show.call(this);
1264
+ }
1265
+ }
1266
+
1267
+ /**
1268
+ * @private
1269
+ */
1270
+ function hide() {
1271
+ this[popperElementSymbol].style.display = "none";
1272
+ removeAttributeToken(this[controlElementSymbol], "class", "open");
1273
+ }
1274
+
1275
+ /**
1276
+ * @private
1277
+ * @this PopperButton
1278
+ */
1279
+ function show() {
1280
+ if (this.getOption("disabled", false) === true) {
1281
+ return;
1282
+ }
1283
+
1284
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
1285
+ return;
1286
+ }
1287
+
1288
+ this[popperElementSymbol].style.visibility = "hidden";
1289
+ this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
1290
+
1291
+ addAttributeToken(this[controlElementSymbol], "class", "open");
1292
+
1293
+ updatePopper.call(this);
1294
+ }
1295
+
1296
+ /**
1297
+ * @private
1298
+ */
1299
+ function updatePopper() {
1300
+ if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
1301
+ return;
1302
+ }
1303
+
1304
+ if (this.getOption("disabled", false) === true) {
1305
+ return;
1306
+ }
1307
+
1308
+ positionPopper.call(
1309
+ this,
1310
+ this[switchElementSymbol],
1311
+ this[popperElementSymbol],
1312
+ this.getOption("popper", {}),
1313
+ );
1314
+ }
1315
+
1316
+ /**
1317
+ * @private
1318
+ */
1319
+ function applyLayoutAlignment() {
1320
+ if (!(this[controlBarElementSymbol] instanceof HTMLElement)) {
1321
+ return;
1322
+ }
1323
+
1324
+ const alignment = this.getOption("layout.alignment", "left");
1325
+
1326
+ if (alignment === "right") {
1327
+ this[controlBarElementSymbol].setAttribute(
1328
+ ATTRIBUTE_LAYOUT_ALIGNMENT,
1329
+ "right",
1330
+ );
1331
+ return;
1332
+ }
1333
+
1334
+ this[controlBarElementSymbol].setAttribute(ATTRIBUTE_LAYOUT_ALIGNMENT, "left");
1335
+ }
1336
+
1337
+ /**
1338
+ * @private
1339
+ * @return {Select}
1340
+ * @throws {Error} no shadow-root is defined
1341
+ */
1342
+ function initControlReferences() {
1343
+ if (!this.shadowRoot) {
1344
+ throw new Error("no shadow-root is defined");
1345
+ }
1346
+
1347
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
1348
+ `[${ATTRIBUTE_ROLE}=control]`,
1349
+ );
1350
+ this[controlBarElementSymbol] =
1351
+ this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}=control-bar]`) ||
1352
+ this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}=button-bar]`);
1353
+ this[popperElementSymbol] = this.shadowRoot.querySelector(
1354
+ `[${ATTRIBUTE_ROLE}=popper]`,
1355
+ );
1356
+ this[popperNavElementSymbol] = this.shadowRoot.querySelector(
1357
+ `[${ATTRIBUTE_ROLE}=popper-nav]`,
1358
+ );
1359
+ this[switchElementSymbol] = this.shadowRoot.querySelector(
1360
+ `[${ATTRIBUTE_ROLE}=switch]`,
1361
+ );
1362
+
1363
+ this[controlBarSlotElementSymbol] = null;
1364
+ if (this[controlBarElementSymbol])
1365
+ this[controlBarSlotElementSymbol] =
1366
+ this[controlBarElementSymbol].querySelector(`slot`);
1367
+ this[popperSlotElementSymbol] = null;
1368
+ if (this[popperElementSymbol])
1369
+ this[popperSlotElementSymbol] =
1370
+ this[popperElementSymbol].querySelector(`slot`);
1371
+ }
1372
+
1373
+ /**
1374
+ * @private
1375
+ * @return {Promise<unknown>}
1376
+ * @throws {Error} no shadow-root is defined
1377
+ *
1378
+ */
1379
+ function initControlBar() {
1380
+ if (!this.shadowRoot) {
1381
+ throw new Error("no shadow-root is defined");
1382
+ }
1383
+
1384
+ scheduleLayout.call(this, { measure: true, layout: true, observe: true });
1385
+ }
1386
+
1387
+ /**
1388
+ * @private
1389
+ */
1390
+ function initPopperSwitch() {
1391
+ /**
1392
+ * @param {Event} event
1393
+ */
1394
+ this[popperSwitchEventHandler] = (event) => {
1395
+ const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "switch");
1396
+
1397
+ if (element instanceof HTMLButtonElement) {
1398
+ toggle.call(this);
1399
+ }
1400
+ };
1401
+
1402
+ for (const type of ["click", "touch"]) {
1403
+ this[switchElementSymbol].addEventListener(
1404
+ type,
1405
+ this[popperSwitchEventHandler],
1406
+ );
1407
+ }
1408
+ }
1409
+
1410
+ /**
1411
+ * @private
1412
+ * @return {string}
1413
+ */
1414
+ function getTemplate() {
1415
+ // language=HTML
1416
+ return `
1417
+ <div data-monster-role="control" part="control">
1418
+ <div data-monster-role="control-bar">
1419
+ <slot></slot>
1420
+ <div part="popper-nav" data-monster-role="popper-nav"
1421
+ tabindex="-1">
1422
+ <button part="popper-switch" data-monster-role="switch"
1423
+ class="monster-button-outline-tertiary hidden" hidden>
1424
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
1425
+ viewBox="0 0 16 16">
1426
+ <path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
1427
+ </svg>
1428
+ </button>
1429
+ </div>
1430
+ </div>
1431
+ <div data-monster-role="popper">
1432
+ <slot name="popper"></slot>
1433
+ </div>
1434
+ </div>`;
1435
+ }
1436
+
1437
+ registerCustomElement(ControlBar);