@schukai/monster 3.58.4 → 3.59.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);