@schukai/monster 3.58.4 → 3.59.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,1075 @@
1
+ /**
2
+ * Copyright schukai GmbH and contributors 2023. All Rights Reserved.
3
+ * Node module: @schukai/monster
4
+ * This file is licensed under the AGPLv3 License.
5
+ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
6
+ */
7
+ import { instanceSymbol } from "../../constants.mjs";
8
+ import { createPopper } from "@popperjs/core";
9
+ import { extend } from "../../data/extend.mjs";
10
+ import { Pathfinder } from "../../data/pathfinder.mjs";
11
+ import {
12
+ addAttributeToken,
13
+ addToObjectLink,
14
+ hasObjectLink,
15
+ } from "../../dom/attributes.mjs";
16
+ import {
17
+ ATTRIBUTE_ERRORMESSAGE,
18
+ ATTRIBUTE_PREFIX,
19
+ ATTRIBUTE_ROLE,
20
+ } from "../../dom/constants.mjs";
21
+ import {
22
+ assembleMethodSymbol,
23
+ CustomElement,
24
+ getSlottedElements,
25
+ registerCustomElement,
26
+ } from "../../dom/customelement.mjs";
27
+ import {
28
+ findTargetElementFromEvent,
29
+ fireCustomEvent,
30
+ } from "../../dom/events.mjs";
31
+ import { getDocument } from "../../dom/util.mjs";
32
+ import { random } from "../../math/random.mjs";
33
+ import { getGlobal } from "../../types/global.mjs";
34
+ import { ID } from "../../types/id.mjs";
35
+ import { isArray, isString } from "../../types/is.mjs";
36
+ import { TokenList } from "../../types/tokenlist.mjs";
37
+ import { clone } from "../../util/clone.mjs";
38
+ import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
39
+ import { Processing } from "../../util/processing.mjs";
40
+ import {
41
+ ATTRIBUTE_BUTTON_LABEL,
42
+ ATTRIBUTE_FORM_RELOAD,
43
+ ATTRIBUTE_FORM_URL,
44
+ STYLE_DISPLAY_MODE_BLOCK,
45
+ } from "../form/constants.mjs";
46
+
47
+ import { TabsStyleSheet } from "./stylesheet/tabs.mjs";
48
+ import { loadAndAssignContent } from "../form/util/fetch.mjs";
49
+ import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
50
+ import {
51
+ popperInstanceSymbol,
52
+ setEventListenersModifiers,
53
+ } from "../form/util/popper.mjs";
54
+
55
+ export { Tabs };
56
+
57
+ /**
58
+ * @private
59
+ * @type {symbol}
60
+ */
61
+ const popperElementSymbol = Symbol("popperElement");
62
+
63
+ /**
64
+ * @private
65
+ * @type {symbol}
66
+ */
67
+ const popperNavElementSymbol = Symbol("popperNavElement");
68
+
69
+ /**
70
+ * @private
71
+ * @type {symbol}
72
+ */
73
+ const controlElementSymbol = Symbol("controlElement");
74
+
75
+ /**
76
+ * @private
77
+ * @type {symbol}
78
+ */
79
+ const navElementSymbol = Symbol("navElement");
80
+ /**
81
+ * @private
82
+ * @type {symbol}
83
+ */
84
+ const switchElementSymbol = Symbol("switchElement");
85
+
86
+ /**
87
+ * @private
88
+ * @type {symbol}
89
+ */
90
+ const changeTabEventHandler = Symbol("changeTabEventHandler");
91
+ /**
92
+ * @private
93
+ * @type {symbol}
94
+ */
95
+ const removeTabEventHandler = Symbol("removeTabEventHandler");
96
+
97
+ /**
98
+ * @private
99
+ * @type {symbol}
100
+ */
101
+ const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
102
+
103
+ /**
104
+ * local symbol
105
+ * @private
106
+ * @type {symbol}
107
+ */
108
+ const closeEventHandler = Symbol("closeEventHandler");
109
+
110
+ /**
111
+ * @private
112
+ * @type {symbol}
113
+ */
114
+ const mutationObserverSymbol = Symbol("mutationObserver");
115
+
116
+ /**
117
+ * @private
118
+ * @type {symbol}
119
+ */
120
+ const dimensionsSymbol = Symbol("dimensions");
121
+
122
+ /**
123
+ * @private
124
+ * @type {symbol}
125
+ */
126
+ const timerCallbackSymbol = Symbol("timerCallback");
127
+
128
+ /**
129
+ * local symbol
130
+ * @private
131
+ * @type {symbol}
132
+ */
133
+ const resizeObserverSymbol = Symbol("resizeObserver");
134
+
135
+ /**
136
+ * This CustomControl creates a tab element with a variety of options.
137
+ *
138
+ * <img src="./images/tabs.png">
139
+ *
140
+ * You can create this control either by specifying the HTML tag `<monster-tabs />` directly in the HTML or using
141
+ * Javascript via the `document.createElement('monster-tabs');` method.
142
+ *
143
+ * ```html
144
+ * <monster-tabs></monster-tabs>
145
+ * ```
146
+ *
147
+ * Or you can create this CustomControl directly in Javascript:
148
+ *
149
+ * ```js
150
+ * import {Tabs} from '@schukai/monster/components/layout/tabs.mjs';
151
+ * document.createElement('monster-tabs');
152
+ * ```
153
+ *
154
+ * @example <caption>Create a simple tab control</caption>
155
+ * <monster-tabs>
156
+ * <div id="tab1">Tab 1</div>
157
+ * <div id="tab2">Tab 2</div>
158
+ * </monster-tabs>
159
+ *
160
+ * @startuml tabs.png
161
+ * skinparam monochrome true
162
+ * skinparam shadowing false
163
+ * HTMLElement <|-- CustomElement
164
+ * CustomElement <|-- Tabs
165
+ * @enduml
166
+ *
167
+ * @since 1.10.0
168
+ * @copyright schukai GmbH
169
+ * @memberOf Monster.Components.Layout
170
+ * @summary A configurable tab control
171
+ * @fires Monster.Components.Layout.event:monster-fetched
172
+ */
173
+ class Tabs extends CustomElement {
174
+ /**
175
+ * This method is called by the `instanceof` operator.
176
+ * @returns {symbol}
177
+ * @since 2.1.0
178
+ */
179
+ static get [instanceSymbol]() {
180
+ return Symbol.for("@schukai/monster/components/layout/tabs");
181
+ }
182
+
183
+ /**
184
+ * To set the options via the html tag the attribute `data-monster-options` must be used.
185
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
186
+ *
187
+ * The individual configuration values can be found in the table.
188
+ *
189
+ * @property {Object} templates Template definitions
190
+ * @property {string} templates.main Main template
191
+ * @property {Object} labels
192
+ * @property {string} labels.new-tab-label="New Tab"
193
+ * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
194
+ * @property {String} fetch.redirect=error
195
+ * @property {String} fetch.method=GET
196
+ * @property {String} fetch.mode=same-origin
197
+ * @property {String} fetch.credentials=same-origin
198
+ * @property {Object} fetch.headers={"accept":"text/html"}}
199
+ * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
200
+ * @property {string} popper.placement=bottom PopperJS placement
201
+ * @property {Object[]} modifiers={name:offset} PopperJS placement
202
+ */
203
+ get defaults() {
204
+ return Object.assign({}, super.defaults, {
205
+ templates: {
206
+ main: getTemplate(),
207
+ },
208
+ labels: {
209
+ "new-tab-label": "New Tab",
210
+ },
211
+ buttons: {
212
+ standard: [],
213
+ popper: [],
214
+ },
215
+ fetch: {
216
+ redirect: "error",
217
+ method: "GET",
218
+ mode: "same-origin",
219
+ credentials: "same-origin",
220
+ headers: {
221
+ accept: "text/html",
222
+ },
223
+ },
224
+
225
+ classes: {
226
+ button: "monster-theme-primary-1",
227
+ popper: "monster-theme-primary-1",
228
+ },
229
+
230
+ popper: {
231
+ placement: "bottom",
232
+ modifiers: [
233
+ {
234
+ name: "offset",
235
+ options: {
236
+ offset: [0, 2],
237
+ },
238
+ },
239
+
240
+ {
241
+ name: "eventListeners",
242
+ enabled: false,
243
+ },
244
+ ],
245
+ },
246
+ });
247
+ }
248
+
249
+ /**
250
+ * This method is called internal and should not be called directly.
251
+ */
252
+ [assembleMethodSymbol]() {
253
+ super[assembleMethodSymbol]();
254
+
255
+ initControlReferences.call(this);
256
+
257
+ this[dimensionsSymbol] = new Pathfinder({ data: {} });
258
+
259
+ initEventHandler.call(this);
260
+
261
+ // setup structure
262
+ initTabButtons.call(this).then(() => {
263
+ initPopperSwitch.call(this);
264
+ initPopper.call(this);
265
+ attachResizeObserver.call(this);
266
+ attachTabChangeObserver.call(this);
267
+ });
268
+ }
269
+
270
+ /**
271
+ * This method is called internal and should not be called directly.
272
+ *
273
+ * @return {CSSStyleSheet[]}
274
+ */
275
+ static getCSSStyleSheet() {
276
+ return [TabsStyleSheet, ThemeStyleSheet];
277
+ }
278
+
279
+ /**
280
+ * This method is called internal and should not be called directly.
281
+ *
282
+ * @return {string}
283
+ */
284
+ static getTag() {
285
+ return "monster-tabs";
286
+ }
287
+
288
+ /**
289
+ * This method is called by the dom and should not be called directly.
290
+ *
291
+ * @return {void}
292
+ */
293
+ connectedCallback() {
294
+ super.connectedCallback();
295
+
296
+ const document = getDocument();
297
+
298
+ for (const [, type] of Object.entries(["click", "touch"])) {
299
+ // close on outside ui-events
300
+ document.addEventListener(type, this[closeEventHandler]);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * This method is called by the dom and should not be called directly.
306
+ *
307
+ * @return {void}
308
+ */
309
+ disconnectedCallback() {
310
+ super.disconnectedCallback();
311
+
312
+ const document = getDocument();
313
+
314
+ // close on outside ui-events
315
+ for (const [, type] of Object.entries(["click", "touch"])) {
316
+ document.removeEventListener(type, this[closeEventHandler]);
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * @private
323
+ */
324
+ function initPopperSwitch() {
325
+ const nodes = getSlottedElements.call(this, `[${ATTRIBUTE_ROLE}="switch"]`); // null ↦ only unnamed slots
326
+ let switchButton;
327
+ if (nodes.size === 0) {
328
+ switchButton = document.createElement("button");
329
+ switchButton.setAttribute(ATTRIBUTE_ROLE, "switch");
330
+ switchButton.setAttribute("part", "switch");
331
+ switchButton.classList.add("hidden");
332
+ const classList = this.getOption("classes.button");
333
+ if (classList) {
334
+ switchButton.classList.add(classList);
335
+ }
336
+ switchButton.innerHTML =
337
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><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"/></svg>';
338
+ this[navElementSymbol].prepend(switchButton);
339
+ } else {
340
+ switchButton = nodes.next();
341
+ }
342
+
343
+ /**
344
+ * @param {Event} event
345
+ */
346
+ this[popperSwitchEventHandler] = (event) => {
347
+ const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "switch");
348
+
349
+ if (element instanceof HTMLButtonElement) {
350
+ togglePopper.call(this);
351
+ }
352
+ };
353
+
354
+ for (const type of ["click", "touch"]) {
355
+ switchButton.addEventListener(type, this[popperSwitchEventHandler]);
356
+ }
357
+
358
+ this[switchElementSymbol] = switchButton;
359
+ }
360
+
361
+ /**
362
+ * @private
363
+ */
364
+ function hidePopper() {
365
+ if (!this[popperInstanceSymbol]) {
366
+ return;
367
+ }
368
+
369
+ this[popperElementSymbol].style.display = "none";
370
+ // performance https://popper.js.org/docs/v2/tutorial/#performance
371
+ setEventListenersModifiers.call(this, false);
372
+ }
373
+
374
+ /**
375
+ * @private
376
+ */
377
+ function showPopper() {
378
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
379
+ return;
380
+ }
381
+
382
+ this[popperElementSymbol].style.visibility = "hidden";
383
+ this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
384
+ // performance https://popper.js.org/docs/v2/tutorial/#performance
385
+ setEventListenersModifiers.call(this, true);
386
+
387
+ this[popperInstanceSymbol].update();
388
+
389
+ new Processing(() => {
390
+ this[popperElementSymbol].style.removeProperty("visibility");
391
+ })
392
+ .run(undefined)
393
+ .then(() => {})
394
+ .catch((e) => {
395
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
396
+ });
397
+ }
398
+
399
+ /**
400
+ * @private
401
+ */
402
+ function togglePopper() {
403
+ if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
404
+ hidePopper.call(this);
405
+ } else {
406
+ showPopper.call(this);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * @private
412
+ */
413
+ function attachResizeObserver() {
414
+ // against flickering
415
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
416
+ if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
417
+ try {
418
+ this[timerCallbackSymbol].touch();
419
+ return;
420
+ } catch (e) {
421
+ delete this[timerCallbackSymbol];
422
+ }
423
+ }
424
+
425
+ this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
426
+ this[dimensionsSymbol].setVia("data.calculated", false);
427
+ checkAndRearrangeButtons.call(this);
428
+ });
429
+ });
430
+
431
+ this[resizeObserverSymbol].observe(this[navElementSymbol]);
432
+ }
433
+
434
+ /**
435
+ * @private
436
+ */
437
+ function attachTabChangeObserver() {
438
+ // against flickering
439
+ new MutationObserver((mutations) => {
440
+ let runUpdate = false;
441
+
442
+ for (const mutation of mutations) {
443
+ if (mutation.type === "childList") {
444
+ if (
445
+ mutation.addedNodes.length > 0 ||
446
+ mutation.removedNodes.length > 0
447
+ ) {
448
+ runUpdate = true;
449
+ break;
450
+ }
451
+ }
452
+ }
453
+
454
+ if (runUpdate === true) {
455
+ this[dimensionsSymbol].setVia("data.calculated", false);
456
+ initTabButtons.call(this);
457
+ }
458
+ }).observe(this, {
459
+ childList: true,
460
+ });
461
+ }
462
+
463
+ /**
464
+ * @private
465
+ * @return {Select}
466
+ * @external "external:createPopper"
467
+ * @see {@link Plugins}
468
+ */
469
+ function initPopper() {
470
+ const self = this;
471
+
472
+ const options = extend({}, self.getOption("popper"));
473
+
474
+ self[popperInstanceSymbol] = createPopper(
475
+ self[switchElementSymbol],
476
+ self[popperElementSymbol],
477
+ options,
478
+ );
479
+
480
+ const observer1 = new MutationObserver(function (mutations) {
481
+ let runUpdate = false;
482
+
483
+ for (const mutation of mutations) {
484
+ if (mutation.type === "childList") {
485
+ if (
486
+ mutation.addedNodes.length > 0 ||
487
+ mutation.removedNodes.length > 0
488
+ ) {
489
+ runUpdate = true;
490
+ break;
491
+ }
492
+ }
493
+ }
494
+
495
+ if (runUpdate === true) {
496
+ self[popperInstanceSymbol].update();
497
+ }
498
+ });
499
+
500
+ observer1.observe(self[popperNavElementSymbol], {
501
+ childList: true,
502
+ subtree: true,
503
+ });
504
+
505
+ return self;
506
+ }
507
+
508
+ /**
509
+ * @private
510
+ * @param {HTMLElement} element
511
+ */
512
+ function show(element) {
513
+ if (!this.shadowRoot) {
514
+ throw new Error("no shadow-root is defined");
515
+ }
516
+
517
+ const reference = element.getAttribute(`${ATTRIBUTE_PREFIX}tab-reference`);
518
+
519
+ const nodes = getSlottedElements.call(this);
520
+ for (const node of nodes) {
521
+ const id = node.getAttribute("id");
522
+
523
+ if (id === reference) {
524
+ node.classList.add("active");
525
+
526
+ // get all data- from button and filter out data-monster-attributes and data-monster-insert
527
+ const data = {};
528
+ const mask = [
529
+ "data-monster-attributes",
530
+ "data-monster-insert-reference",
531
+ "data-monster-state",
532
+ "data-monster-button-label",
533
+ "data-monster-objectlink",
534
+ "data-monster-role",
535
+ ];
536
+
537
+ for (const [, attr] of Object.entries(node.attributes)) {
538
+ if (attr.name.startsWith("data-") && mask.indexOf(attr.name) === -1) {
539
+ data[attr.name] = attr.value;
540
+ }
541
+ }
542
+
543
+ if (node.hasAttribute(ATTRIBUTE_FORM_URL)) {
544
+ const url = node.getAttribute(ATTRIBUTE_FORM_URL);
545
+
546
+ if (
547
+ !node.hasAttribute(ATTRIBUTE_FORM_RELOAD) ||
548
+ node.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase() === "onshow"
549
+ ) {
550
+ node.removeAttribute(ATTRIBUTE_FORM_URL);
551
+ }
552
+
553
+ const options = this.getOption("fetch", {});
554
+ const filter = undefined;
555
+ loadAndAssignContent(node, url, options, filter)
556
+ .then(() => {
557
+ fireCustomEvent(this, "monster-tab-changed", {
558
+ reference,
559
+ });
560
+ })
561
+ .catch((e) => {
562
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
563
+ });
564
+ } else {
565
+ fireCustomEvent(this, "monster-tab-changed", {
566
+ reference,
567
+ data,
568
+ });
569
+ }
570
+ } else {
571
+ node.classList.remove("active");
572
+ }
573
+ }
574
+
575
+ const standardButtons = this.getOption("buttons.standard");
576
+ for (const index in standardButtons) {
577
+ const button = standardButtons[index];
578
+ const state = button["reference"] === reference ? "active" : "inactive";
579
+ this.setOption(`buttons.standard.${index}.state`, state);
580
+ }
581
+
582
+ const popperButton = this.getOption("buttons.popper");
583
+ for (const index in popperButton) {
584
+ const button = popperButton[index];
585
+ const state = button["reference"] === reference ? "active" : "inactive";
586
+ this.setOption(`buttons.popper.${index}.state`, state);
587
+ }
588
+
589
+ hidePopper.call(this);
590
+ }
591
+
592
+ /**
593
+ * @private
594
+ */
595
+ function initEventHandler() {
596
+ if (!this.shadowRoot) {
597
+ throw new Error("no shadow-root is defined");
598
+ }
599
+
600
+ /**
601
+ * @param {Event} event
602
+ */
603
+ this[changeTabEventHandler] = (event) => {
604
+ const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button");
605
+
606
+ if (element instanceof HTMLButtonElement && element.disabled !== true) {
607
+ show.call(this, element);
608
+ }
609
+ };
610
+
611
+ /**
612
+ * event:monster-tab-remove
613
+ * @event Monster.Components.Layout.event:monster-tab-remove
614
+ */
615
+
616
+ /**
617
+ * @param {Event} event
618
+ * @fires Monster.Components.Layout.event:monster-tab-remove
619
+ */
620
+ this[removeTabEventHandler] = (event) => {
621
+ const element = findTargetElementFromEvent(
622
+ event,
623
+ ATTRIBUTE_ROLE,
624
+ "remove-tab",
625
+ );
626
+
627
+ if (element instanceof HTMLElement) {
628
+ const button = findTargetElementFromEvent(
629
+ event,
630
+ ATTRIBUTE_ROLE,
631
+ "button",
632
+ );
633
+
634
+ if (button instanceof HTMLButtonElement && button.disabled !== true) {
635
+ const reference = button.getAttribute(
636
+ `${ATTRIBUTE_PREFIX}tab-reference`,
637
+ );
638
+ if (reference) {
639
+ const container = this.querySelector(`[id=${reference}]`);
640
+ if (container instanceof HTMLElement) {
641
+ container.remove();
642
+ initTabButtons.call(this);
643
+ fireCustomEvent(this, "monster-tab-remove", {
644
+ reference,
645
+ });
646
+ }
647
+ }
648
+ }
649
+ }
650
+ };
651
+
652
+ this[navElementSymbol].addEventListener("touch", this[changeTabEventHandler]);
653
+ this[navElementSymbol].addEventListener("click", this[changeTabEventHandler]);
654
+
655
+ this[navElementSymbol].addEventListener("touch", this[removeTabEventHandler]);
656
+ this[navElementSymbol].addEventListener("click", this[removeTabEventHandler]);
657
+
658
+ /**
659
+ * @param {Event} event
660
+ */
661
+ this[closeEventHandler] = (event) => {
662
+ const path = event.composedPath();
663
+
664
+ for (const [, element] of Object.entries(path)) {
665
+ if (element === this) {
666
+ return;
667
+ }
668
+ }
669
+
670
+ hidePopper.call(this);
671
+ };
672
+
673
+ return this;
674
+ }
675
+
676
+ /**
677
+ * @private
678
+ * @param observedNode
679
+ */
680
+ function attachTabMutationObserver(observedNode) {
681
+ const self = this;
682
+
683
+ if (hasObjectLink(observedNode, mutationObserverSymbol)) {
684
+ return;
685
+ }
686
+
687
+ /**
688
+ * this construct monitors a node whether it is disabled or modified
689
+ * @type {MutationObserver}
690
+ */
691
+ const observer = new MutationObserver(function (mutations) {
692
+ if (isArray(mutations)) {
693
+ const mutation = mutations.pop();
694
+ if (mutation instanceof MutationRecord) {
695
+ initTabButtons.call(self);
696
+ }
697
+ }
698
+ });
699
+
700
+ observer.observe(observedNode, {
701
+ childList: false,
702
+ attributes: true,
703
+ subtree: false,
704
+ attributeFilter: [
705
+ "disabled",
706
+ ATTRIBUTE_BUTTON_LABEL,
707
+ `${ATTRIBUTE_PREFIX}button-icon`,
708
+ ],
709
+ });
710
+
711
+ addToObjectLink(observedNode, mutationObserverSymbol, observer);
712
+ }
713
+
714
+ /**
715
+ * @private
716
+ * @return {Select}
717
+ * @throws {Error} no shadow-root is defined
718
+ */
719
+ function initControlReferences() {
720
+ if (!this.shadowRoot) {
721
+ throw new Error("no shadow-root is defined");
722
+ }
723
+
724
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
725
+ `[${ATTRIBUTE_ROLE}=control]`,
726
+ );
727
+ this[navElementSymbol] = this.shadowRoot.querySelector(
728
+ `nav[${ATTRIBUTE_ROLE}=nav]`,
729
+ );
730
+ this[popperElementSymbol] = this.shadowRoot.querySelector(
731
+ `[${ATTRIBUTE_ROLE}=popper]`,
732
+ );
733
+ this[popperNavElementSymbol] = this.shadowRoot.querySelector(
734
+ `[${ATTRIBUTE_ROLE}=popper-nav]`,
735
+ );
736
+ }
737
+
738
+ /**
739
+ * @private
740
+ * @return {Promise<unknown>}
741
+ * @throws {Error} no shadow-root is defined
742
+ *
743
+ */
744
+ function initTabButtons() {
745
+ if (!this.shadowRoot) {
746
+ throw new Error("no shadow-root is defined");
747
+ }
748
+
749
+ let activeReference;
750
+
751
+ const dimensionsCalculated = this[dimensionsSymbol].getVia(
752
+ "data.calculated",
753
+ false,
754
+ );
755
+
756
+ const buttons = [];
757
+ const nodes = getSlottedElements.call(this, undefined, null); // null ↦ only unnamed slots
758
+
759
+ for (const node of nodes) {
760
+ if (!(node instanceof HTMLElement)) continue;
761
+ let label = getButtonLabel.call(this, node);
762
+
763
+ let reference;
764
+ if (node.hasAttribute("id")) {
765
+ reference = node.getAttribute("id");
766
+ }
767
+
768
+ let disabled;
769
+ if (node.hasAttribute("disabled") || node.disabled === true) {
770
+ disabled = true;
771
+ }
772
+
773
+ if (!reference) {
774
+ reference = new ID("tab").toString();
775
+ node.setAttribute("id", reference);
776
+ }
777
+
778
+ if (node.hasAttribute(`${ATTRIBUTE_PREFIX}button-icon`)) {
779
+ label = `<span part="label">${label}</span><img part="icon" src="${node.getAttribute(
780
+ `${ATTRIBUTE_PREFIX}button-icon`,
781
+ )}">`;
782
+ }
783
+
784
+ let remove = false;
785
+ if (node.hasAttribute(`${ATTRIBUTE_PREFIX}removable`)) {
786
+ remove = true;
787
+ }
788
+
789
+ if (node.matches(".active") === true && disabled !== true) {
790
+ node.classList.remove("active");
791
+ activeReference = reference;
792
+ }
793
+
794
+ const state = "";
795
+ const classes = dimensionsCalculated ? "" : "invisible";
796
+
797
+ buttons.push({
798
+ reference,
799
+ label,
800
+ state,
801
+ class: classes,
802
+ disabled,
803
+ remove,
804
+ });
805
+
806
+ attachTabMutationObserver.call(this, node);
807
+ }
808
+
809
+ this.setOption("buttons.standard", clone(buttons));
810
+ this.setOption("buttons.popper", []);
811
+ this.setOption("marker", random());
812
+
813
+ return adjustButtonVisibility.call(this).then(() => {
814
+ if (activeReference) {
815
+ return new Processing(() => {
816
+ const button = this.shadowRoot.querySelector(
817
+ `[${ATTRIBUTE_PREFIX}tab-reference="${activeReference}"]`,
818
+ );
819
+ if (button instanceof HTMLButtonElement && button.disabled !== true) {
820
+ show.call(this, button);
821
+ }
822
+ })
823
+ .run(undefined)
824
+ .then(() => {})
825
+ .catch((e) => {
826
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
827
+ });
828
+ }
829
+
830
+ return Promise.resolve();
831
+ });
832
+ }
833
+
834
+ function checkAndRearrangeButtons() {
835
+ if (this[dimensionsSymbol].getVia("data.calculated", false) !== true) {
836
+ calculateNavigationButtonsDimensions.call(this);
837
+ }
838
+
839
+ rearrangeButtons.call(this);
840
+ }
841
+
842
+ /**
843
+ * @private
844
+ * @return {Promise<unknown>}
845
+ */
846
+ function adjustButtonVisibility() {
847
+ const self = this;
848
+
849
+ return new Promise((resolve) => {
850
+ const observer = new MutationObserver(function (mutations) {
851
+ const defCount = self.getOption("buttons.standard").length;
852
+ const domCount = self[navElementSymbol].querySelectorAll(
853
+ 'button[data-monster-role="button"]',
854
+ ).length;
855
+
856
+ // in drawing
857
+ if (defCount !== domCount) return;
858
+
859
+ observer.disconnect();
860
+
861
+ checkAndRearrangeButtons.call(self);
862
+
863
+ resolve();
864
+ });
865
+
866
+ observer.observe(self[navElementSymbol], {
867
+ attributes: true,
868
+ });
869
+ });
870
+ }
871
+
872
+ /**
873
+ * @private
874
+ * @param {string} value
875
+ * @return {number}
876
+ */
877
+ function getDimValue(value) {
878
+ if ([undefined, null].indexOf(value) !== -1) {
879
+ return 0;
880
+ }
881
+
882
+ const valueAsInt = parseInt(value, 10);
883
+
884
+ if (isNaN(valueAsInt)) {
885
+ return 0;
886
+ }
887
+
888
+ return valueAsInt;
889
+ }
890
+
891
+ /**
892
+ * @private
893
+ * @param {HTMLElement} node
894
+ * @return {number}
895
+ */
896
+ function calcBoxWidth(node) {
897
+ const dim = getGlobal("window").getComputedStyle(node);
898
+ const bounding = node.getBoundingClientRect();
899
+
900
+ return (
901
+ getDimValue(dim["border-left-width"]) +
902
+ getDimValue(dim["padding-left"]) +
903
+ getDimValue(dim["margin-left"]) +
904
+ getDimValue(bounding["width"]) +
905
+ getDimValue(dim["border-right-width"]) +
906
+ getDimValue(dim["margin-right"]) +
907
+ getDimValue(dim["padding-left"])
908
+ );
909
+ }
910
+
911
+ /**
912
+ * @private
913
+ * @return {Object}
914
+ */
915
+ function rearrangeButtons() {
916
+ const standardButtons = [];
917
+ const popperButtons = [];
918
+
919
+ let sum = 0;
920
+ const space = this[dimensionsSymbol].getVia("data.space");
921
+
922
+ const buttons = this.getOption("buttons.standard");
923
+ for (const [, button] of buttons.entries()) {
924
+ const ref = button?.reference;
925
+
926
+ sum += this[dimensionsSymbol].getVia(`data.button.${ref}`);
927
+
928
+ if (sum > space) {
929
+ popperButtons.push(clone(button));
930
+ } else {
931
+ standardButtons.push(clone(button));
932
+ }
933
+ }
934
+
935
+ this.setOption("buttons.standard", standardButtons);
936
+ this.setOption("buttons.popper", popperButtons);
937
+
938
+ if (this[switchElementSymbol]) {
939
+ if (popperButtons.length > 0) {
940
+ this[switchElementSymbol].classList.remove("hidden");
941
+ } else {
942
+ this[switchElementSymbol].classList.add("hidden");
943
+ }
944
+ }
945
+ }
946
+
947
+ /**
948
+ * @private
949
+ * @return {Object}
950
+ */
951
+ function calculateNavigationButtonsDimensions() {
952
+ const width = this[navElementSymbol].getBoundingClientRect().width;
953
+
954
+ let startEndWidth = 0;
955
+
956
+ getSlottedElements.call(this, undefined, "start").forEach((node) => {
957
+ startEndWidth += calcBoxWidth.call(this, node);
958
+ });
959
+
960
+ getSlottedElements.call(this, undefined, "end").forEach((node) => {
961
+ startEndWidth += calcBoxWidth.call(this, node);
962
+ });
963
+
964
+ this[dimensionsSymbol].setVia("data.space", width - startEndWidth - 2);
965
+ this[dimensionsSymbol].setVia("data.visible", !(width === 0));
966
+
967
+ const buttons = this.getOption("buttons.standard").concat(
968
+ this.getOption("buttons.popper"),
969
+ );
970
+
971
+ for (const [i, button] of buttons.entries()) {
972
+ const ref = button?.reference;
973
+ const element = this[navElementSymbol].querySelector(
974
+ `:scope > [${ATTRIBUTE_PREFIX}tab-reference="${ref}"]`,
975
+ );
976
+ if (!(element instanceof HTMLButtonElement)) continue;
977
+
978
+ this[dimensionsSymbol].setVia(
979
+ `data.button.${ref}`,
980
+ calcBoxWidth.call(this, element),
981
+ );
982
+ button["class"] = new TokenList(button["class"])
983
+ .remove("invisible")
984
+ .toString();
985
+ }
986
+
987
+ const slots = this[controlElementSymbol].querySelectorAll(
988
+ `nav[${ATTRIBUTE_PREFIX}role=nav] > slot.invisible, slot[${ATTRIBUTE_PREFIX}role=slot].invisible`,
989
+ );
990
+ for (const [, slot] of slots.entries()) {
991
+ slot.classList.remove("invisible");
992
+ }
993
+
994
+ this[dimensionsSymbol].setVia("data.calculated", true);
995
+ this.setOption("buttons.standard", clone(buttons));
996
+ }
997
+
998
+ /**
999
+ * @private
1000
+ * @param {HTMLElement} node
1001
+ * @return {string}
1002
+ */
1003
+ function getButtonLabel(node) {
1004
+ let label;
1005
+ let setLabel = false;
1006
+ if (node.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
1007
+ label = node.getAttribute(ATTRIBUTE_BUTTON_LABEL);
1008
+ } else {
1009
+ label = node.innerText;
1010
+ setLabel = true;
1011
+ }
1012
+
1013
+ if (!isString(label)) {
1014
+ label = "";
1015
+ }
1016
+
1017
+ label = label.trim();
1018
+
1019
+ if (label === "") {
1020
+ label = this.getOption("labels.new-tab-label", "New Tab");
1021
+ }
1022
+
1023
+ if (label.length > 100) {
1024
+ label = `${label.substring(0, 99)}…`;
1025
+ }
1026
+
1027
+ if (setLabel === true) {
1028
+ node.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
1029
+ }
1030
+
1031
+ return label;
1032
+ }
1033
+
1034
+ /**
1035
+ * @private
1036
+ * @return {string}
1037
+ */
1038
+ function getTemplate() {
1039
+ // language=HTML
1040
+ return `
1041
+ <template id="buttons">
1042
+ <button part="button"
1043
+ data-monster-role="button"
1044
+ data-monster-attributes="
1045
+ class path:classes.button,
1046
+ data-monster-state path:buttons.state,
1047
+ disabled path:buttons.disabled | if:true,
1048
+ data-monster-tab-reference path:buttons.reference"><span
1049
+ data-monster-replace="path:buttons.label"></span><span part="remove-tab"
1050
+ data-monster-attributes="class path:buttons.remove | ?:remove-tab:hidden "
1051
+ data-monster-role="remove-tab"
1052
+ tabindex="-1"></span></button>
1053
+ </template>
1054
+ <div data-monster-role="control" part="control">
1055
+ <nav data-monster-role="nav" part="nav"
1056
+ data-monster-attributes="data-monster-marker path:marker"
1057
+ data-monster-insert="buttons path:buttons.standard">
1058
+ <slot name="start" class="invisible"></slot>
1059
+ <div data-monster-role="popper" part="popper" tabindex="-1"
1060
+ data-monster-attributes="class path:classes.popper">
1061
+ <div data-popper-arrow></div>
1062
+
1063
+
1064
+ <div part="popper-nav" data-monster-role="popper-nav"
1065
+ data-monster-insert="buttons path:buttons.popper"
1066
+ tabindex="-1"></div>
1067
+ </div>
1068
+ <slot name="popper" class="invisible"></slot>
1069
+ <slot name="end" class="invisible"></slot>
1070
+ </nav>
1071
+ <slot data-monster-role="slot" class="invisible"></slot>
1072
+ </div>`;
1073
+ }
1074
+
1075
+ registerCustomElement(Tabs);