@schukai/monster 4.139.1 → 4.140.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.
@@ -122,6 +122,12 @@ const closeEventHandler = Symbol("closeEventHandler");
122
122
  */
123
123
  const mutationObserverSymbol = Symbol("mutationObserver");
124
124
 
125
+ /**
126
+ * @private
127
+ * @type {symbol}
128
+ */
129
+ const tabListMutationObserverSymbol = Symbol("tabListMutationObserver");
130
+
125
131
  /**
126
132
  * @private
127
133
  * @type {symbol}
@@ -141,6 +147,13 @@ const timerCallbackSymbol = Symbol("timerCallback");
141
147
  */
142
148
  const resizeObserverSymbol = Symbol("resizeObserver");
143
149
 
150
+ /**
151
+ * local symbol
152
+ * @private
153
+ * @type {symbol}
154
+ */
155
+ const activeReferenceSymbol = Symbol("activeReference");
156
+
144
157
  /**
145
158
  * A Tabs Control
146
159
  *
@@ -183,6 +196,22 @@ class Tabs extends CustomElement {
183
196
  * @property {number} features.openDelay=500 Open delay in milliseconds
184
197
  * @property {string} features.removeBehavior Remove behavior, auto, next, previous and none
185
198
  * @property {boolean} features.openFirst Open the first tab when no active tab is set
199
+ * @property {boolean} features.deriveLabelFromContent=true Generate labels from panel text when no `data-monster-button-label` is set
200
+ *
201
+ * Tab panels can use `data-monster-name` as stable public identity, while
202
+ * DOM ids remain the internal button reference. `data-monster-tab-available`
203
+ * controls whether a panel participates in the tab control. Missing or
204
+ * `"true"` means available; `"false"` or an empty attribute means unavailable.
205
+ * `data-monster-tab-disabled="true"` keeps a button visible but prevents
206
+ * activation. `data-monster-tab-disabled-reason` is exposed on the disabled
207
+ * button as contextual help. `data-monster-tab-kind`,
208
+ * `data-monster-tab-priority`, and `data-monster-tab-group` are copied into
209
+ * button metadata and tab event details.
210
+ *
211
+ * @fires monster-tab-change
212
+ * @fires monster-tab-changed
213
+ * @fires monster-tab-remove
214
+ * @fires monster-tab-availability-changed
186
215
  * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
187
216
  * @property {String} fetch.redirect=error
188
217
  * @property {String} fetch.method=GET
@@ -220,6 +249,7 @@ class Tabs extends CustomElement {
220
249
  openDelay: null,
221
250
  removeBehavior: "auto",
222
251
  openFirst: true,
252
+ deriveLabelFromContent: true,
223
253
  },
224
254
 
225
255
  classes: {
@@ -272,13 +302,13 @@ class Tabs extends CustomElement {
272
302
  this[dimensionsSymbol] = new Pathfinder({ data: {} });
273
303
 
274
304
  initEventHandler.call(this);
305
+ attachTabChangeObserver.call(this);
275
306
 
276
307
  // setup structure
277
308
  initTabButtons.call(this).then(() => {
278
309
  initPopperSwitch.call(this);
279
310
  initPopper.call(this);
280
311
  attachResizeObserver.call(this);
281
- attachTabChangeObserver.call(this);
282
312
  });
283
313
  }
284
314
 
@@ -306,7 +336,7 @@ class Tabs extends CustomElement {
306
336
  * @returns {Tabs}
307
337
  */
308
338
  removeTab(tabId) {
309
- const tabs = this.getTabs();
339
+ const tabs = getAllTabPanels.call(this);
310
340
  for (const tab of tabs) {
311
341
  if (
312
342
  (tab.getAttribute("id") === tabId ||
@@ -326,14 +356,7 @@ class Tabs extends CustomElement {
326
356
  * @returns {[]}
327
357
  */
328
358
  getTabs() {
329
- const nodes = getSlottedElements.call(this);
330
- const tabs = [];
331
- for (const node of nodes) {
332
- if (node instanceof HTMLElement) {
333
- tabs.push(node);
334
- }
335
- }
336
- return tabs;
359
+ return getAllTabPanels.call(this).filter((node) => isTabAvailable(node));
337
360
  }
338
361
 
339
362
  /**
@@ -365,7 +388,7 @@ class Tabs extends CustomElement {
365
388
  }
366
389
 
367
390
  // check if id is already used
368
- const existingTabs = this.getTabs();
391
+ const existingTabs = getAllTabPanels.call(this);
369
392
  for (const existingTab of existingTabs) {
370
393
  if (
371
394
  existingTab.getAttribute("id") === tabId ||
@@ -398,43 +421,80 @@ class Tabs extends CustomElement {
398
421
  }
399
422
 
400
423
  /**
401
- * A function that activates a tab based on the provided name.
424
+ * Rebuild tab buttons and synchronize the active tab.
402
425
  *
403
- * The tabs have to be named with the `data-monster-name` attribute.
426
+ * @return {Promise<Tabs>}
427
+ */
428
+ refreshTabs() {
429
+ this[dimensionsSymbol].setVia("data.calculated", false);
430
+ return initTabButtons.call(this).then(() => this);
431
+ }
432
+
433
+ /**
434
+ * Alias for refreshTabs().
404
435
  *
405
- * @param {type} idOrName - the name or id of the tab to activate
406
- * @return {Tabs} - The current instance
436
+ * @return {Promise<Tabs>}
407
437
  */
408
- activeTab(idOrName) {
409
- let found = false;
438
+ syncTabs() {
439
+ return this.refreshTabs();
440
+ }
410
441
 
411
- getSlottedElements.call(this).forEach((node) => {
412
- if (found === true) {
413
- return;
442
+ /**
443
+ * Hide a tab from the control without removing its panel.
444
+ *
445
+ * @param {string} idOrName
446
+ * @return {Tabs}
447
+ */
448
+ hideTab(idOrName) {
449
+ const tab = findTabPanel.call(this, idOrName, { availableOnly: false });
450
+ if (tab instanceof HTMLElement) {
451
+ const previousAvailable = isTabAvailable(tab);
452
+ tab.setAttribute("data-monster-tab-available", "false");
453
+ if (!hasObjectLink(tab, mutationObserverSymbol)) {
454
+ emitAvailabilityChanged.call(this, tab, previousAvailable, false);
414
455
  }
456
+ }
457
+ return this;
458
+ }
415
459
 
416
- if (node.getAttribute("data-monster-name") === idOrName) {
417
- const tabControl = this.shadowRoot.querySelector(
418
- `[data-monster-tab-reference="${node.getAttribute("id")}"]`,
419
- );
420
-
421
- if (tabControl) {
422
- tabControl.click();
423
- found = true;
424
- }
460
+ /**
461
+ * Show a tab in the control.
462
+ *
463
+ * @param {string} idOrName
464
+ * @return {Tabs}
465
+ */
466
+ showTab(idOrName) {
467
+ const tab = findTabPanel.call(this, idOrName, { availableOnly: false });
468
+ if (tab instanceof HTMLElement) {
469
+ const previousAvailable = isTabAvailable(tab);
470
+ tab.setAttribute("data-monster-tab-available", "true");
471
+ if (!hasObjectLink(tab, mutationObserverSymbol)) {
472
+ emitAvailabilityChanged.call(this, tab, previousAvailable, true);
425
473
  }
474
+ }
475
+ return this;
476
+ }
426
477
 
427
- if (node.getAttribute("id") === idOrName) {
428
- const tabControl = this.shadowRoot.querySelector(
429
- `[data-monster-tab-reference="${node.getAttribute("id")}"]`,
430
- );
478
+ /**
479
+ * A function that activates a tab based on the provided name.
480
+ *
481
+ * The tabs have to be named with the `data-monster-name` attribute.
482
+ *
483
+ * @param {type} idOrName - the name or id of the tab to activate
484
+ * @return {Tabs} - The current instance
485
+ */
486
+ activeTab(idOrName) {
487
+ const node = findTabPanel.call(this, idOrName, {
488
+ availableOnly: true,
489
+ enabledOnly: true,
490
+ });
431
491
 
432
- if (tabControl) {
433
- tabControl.click();
434
- found = true;
435
- }
492
+ if (node instanceof HTMLElement) {
493
+ const tabControl = findButtonElement.call(this, node.getAttribute("id"));
494
+ if (tabControl instanceof HTMLButtonElement) {
495
+ tabControl.click();
436
496
  }
437
- });
497
+ }
438
498
 
439
499
  return this;
440
500
  }
@@ -492,6 +552,7 @@ class Tabs extends CustomElement {
492
552
  }
493
553
 
494
554
  this[popperInstanceSymbol]?.destroy();
555
+ this[tabListMutationObserverSymbol]?.disconnect();
495
556
  }
496
557
  }
497
558
 
@@ -542,6 +603,173 @@ function getTranslations() {
542
603
  }
543
604
  }
544
605
 
606
+ /**
607
+ * @private
608
+ * @return {HTMLElement[]}
609
+ */
610
+ function getAllTabPanels() {
611
+ const nodes = getSlottedElements.call(this);
612
+ const tabs = [];
613
+ for (const node of nodes) {
614
+ if (node instanceof HTMLElement) {
615
+ tabs.push(node);
616
+ }
617
+ }
618
+ return tabs;
619
+ }
620
+
621
+ /**
622
+ * @private
623
+ * @param {HTMLElement} node
624
+ * @return {boolean}
625
+ */
626
+ function isTabAvailable(node) {
627
+ if (!node.hasAttribute("data-monster-tab-available")) {
628
+ return true;
629
+ }
630
+ return node.getAttribute("data-monster-tab-available") === "true";
631
+ }
632
+
633
+ /**
634
+ * @private
635
+ * @param {string|null} value
636
+ * @return {boolean}
637
+ */
638
+ function parseTabAvailableValue(value) {
639
+ if (value === null) {
640
+ return true;
641
+ }
642
+ return value === "true";
643
+ }
644
+
645
+ /**
646
+ * @private
647
+ * @param {HTMLElement} node
648
+ * @return {boolean}
649
+ */
650
+ function isTabDisabled(node) {
651
+ return (
652
+ node.hasAttribute("disabled") ||
653
+ node.disabled === true ||
654
+ node.getAttribute("data-monster-tab-disabled") === "true"
655
+ );
656
+ }
657
+
658
+ /**
659
+ * @private
660
+ * @param {HTMLElement} node
661
+ * @return {string|null}
662
+ */
663
+ function getTabName(node) {
664
+ if (node.hasAttribute("data-monster-name")) {
665
+ return node.getAttribute("data-monster-name");
666
+ }
667
+ return null;
668
+ }
669
+
670
+ /**
671
+ * @private
672
+ * @param {HTMLElement} node
673
+ * @return {string|null}
674
+ */
675
+ function getTabReference(node) {
676
+ return node.getAttribute("id");
677
+ }
678
+
679
+ /**
680
+ * @private
681
+ * @param {HTMLElement} node
682
+ * @return {string|null}
683
+ */
684
+ function getTabPublicReference(node) {
685
+ return getTabName(node) || getTabReference(node);
686
+ }
687
+
688
+ /**
689
+ * @private
690
+ * @param {HTMLElement} node
691
+ * @return {Object}
692
+ */
693
+ function getTabMetadata(node) {
694
+ const metadata = {};
695
+ for (const key of ["kind", "priority", "group"]) {
696
+ const attr = `data-monster-tab-${key}`;
697
+ if (node.hasAttribute(attr)) {
698
+ metadata[key] = node.getAttribute(attr);
699
+ }
700
+ }
701
+ return metadata;
702
+ }
703
+
704
+ /**
705
+ * @private
706
+ * @param {HTMLElement} node
707
+ * @return {Object}
708
+ */
709
+ function getTabEventDetail(node) {
710
+ return {
711
+ reference: getTabReference(node),
712
+ name: getTabName(node),
713
+ tab: getTabPublicReference(node),
714
+ metadata: getTabMetadata(node),
715
+ };
716
+ }
717
+
718
+ /**
719
+ * @private
720
+ * @param {string} idOrName
721
+ * @param {Object} options
722
+ * @return {HTMLElement|null}
723
+ */
724
+ function findTabPanel(
725
+ idOrName,
726
+ { availableOnly = false, enabledOnly = false } = {},
727
+ ) {
728
+ const tabs = getAllTabPanels.call(this);
729
+ for (const node of tabs) {
730
+ if (availableOnly === true && isTabAvailable(node) !== true) {
731
+ continue;
732
+ }
733
+ if (enabledOnly === true && isTabDisabled(node) === true) {
734
+ continue;
735
+ }
736
+ if (getTabName(node) === idOrName) {
737
+ return node;
738
+ }
739
+ }
740
+
741
+ for (const node of tabs) {
742
+ if (availableOnly === true && isTabAvailable(node) !== true) {
743
+ continue;
744
+ }
745
+ if (enabledOnly === true && isTabDisabled(node) === true) {
746
+ continue;
747
+ }
748
+ if (getTabReference(node) === idOrName) {
749
+ return node;
750
+ }
751
+ }
752
+
753
+ return null;
754
+ }
755
+
756
+ /**
757
+ * @private
758
+ * @param {HTMLElement} tab
759
+ * @param {boolean} previousAvailable
760
+ * @param {boolean} available
761
+ */
762
+ function emitAvailabilityChanged(tab, previousAvailable, available) {
763
+ if (previousAvailable === available) {
764
+ return;
765
+ }
766
+
767
+ fireCustomEvent(this, "monster-tab-availability-changed", {
768
+ ...getTabEventDetail(tab),
769
+ available,
770
+ });
771
+ }
772
+
545
773
  /**
546
774
  * @private
547
775
  */
@@ -661,7 +889,7 @@ function attachResizeObserver() {
661
889
  */
662
890
  function attachTabChangeObserver() {
663
891
  // against flickering
664
- new MutationObserver((mutations) => {
892
+ this[tabListMutationObserverSymbol] = new MutationObserver((mutations) => {
665
893
  let runUpdate = false;
666
894
 
667
895
  for (const mutation of mutations) {
@@ -680,7 +908,9 @@ function attachTabChangeObserver() {
680
908
  this[dimensionsSymbol].setVia("data.calculated", false);
681
909
  initTabButtons.call(this);
682
910
  }
683
- }).observe(this, {
911
+ });
912
+
913
+ this[tabListMutationObserverSymbol].observe(this, {
684
914
  childList: true,
685
915
  });
686
916
  }
@@ -744,11 +974,16 @@ function show(element, options = {}) {
744
974
  const id = node.getAttribute("id");
745
975
 
746
976
  if (id === reference) {
977
+ if (isTabAvailable(node) !== true || isTabDisabled(node) === true) {
978
+ continue;
979
+ }
980
+
747
981
  node.classList.add("active");
982
+ this[activeReferenceSymbol] = reference;
748
983
 
749
984
  if (emitEvents) {
750
985
  fireCustomEvent(this, "monster-tab-change", {
751
- reference,
986
+ ...getTabEventDetail(node),
752
987
  });
753
988
  }
754
989
 
@@ -774,6 +1009,9 @@ function show(element, options = {}) {
774
1009
  "data-monster-button-label",
775
1010
  "data-monster-objectlink",
776
1011
  "data-monster-role",
1012
+ "data-monster-tab-available",
1013
+ "data-monster-tab-disabled",
1014
+ "data-monster-tab-disabled-reason",
777
1015
  ];
778
1016
 
779
1017
  for (const [, attr] of Object.entries(node.attributes)) {
@@ -798,7 +1036,7 @@ function show(element, options = {}) {
798
1036
  .then(() => {
799
1037
  if (emitEvents) {
800
1038
  fireCustomEvent(this, "monster-tab-changed", {
801
- reference,
1039
+ ...getTabEventDetail(node),
802
1040
  });
803
1041
  }
804
1042
  })
@@ -808,7 +1046,7 @@ function show(element, options = {}) {
808
1046
  } else {
809
1047
  if (emitEvents) {
810
1048
  fireCustomEvent(this, "monster-tab-changed", {
811
- reference,
1049
+ ...getTabEventDetail(node),
812
1050
  data,
813
1051
  });
814
1052
  }
@@ -883,10 +1121,13 @@ function initEventHandler() {
883
1121
  activeReference = activeButton?.reference || null;
884
1122
  }
885
1123
 
886
- const nodes = getSlottedElements.call(this);
887
1124
  const orderedTabs = [];
888
- for (const node of nodes) {
889
- if (node instanceof HTMLElement) {
1125
+ for (const node of getAllTabPanels.call(this)) {
1126
+ if (
1127
+ node instanceof HTMLElement &&
1128
+ isTabAvailable(node) === true &&
1129
+ isTabDisabled(node) !== true
1130
+ ) {
890
1131
  orderedTabs.push(node);
891
1132
  }
892
1133
  }
@@ -951,7 +1192,7 @@ function initEventHandler() {
951
1192
  self.activeTab(targetReference);
952
1193
  }
953
1194
  fireCustomEvent(this, "monster-tab-remove", {
954
- reference,
1195
+ ...getTabEventDetail(container),
955
1196
  });
956
1197
  }
957
1198
  }
@@ -1013,7 +1254,15 @@ function attachTabMutationObserver(observedNode) {
1013
1254
  const observer = new MutationObserver(function (mutations) {
1014
1255
  if (isArray(mutations)) {
1015
1256
  const mutation = mutations.pop();
1016
- if (mutation instanceof MutationRecord) {
1257
+ if (mutation?.type === "attributes") {
1258
+ if (mutation.attributeName === "data-monster-tab-available") {
1259
+ emitAvailabilityChanged.call(
1260
+ self,
1261
+ observedNode,
1262
+ parseTabAvailableValue(mutation.oldValue),
1263
+ isTabAvailable(observedNode),
1264
+ );
1265
+ }
1017
1266
  initTabButtons.call(self);
1018
1267
  }
1019
1268
  }
@@ -1022,6 +1271,7 @@ function attachTabMutationObserver(observedNode) {
1022
1271
  observer.observe(observedNode, {
1023
1272
  childList: false,
1024
1273
  attributes: true,
1274
+ attributeOldValue: true,
1025
1275
  subtree: false,
1026
1276
  attributeFilter: [
1027
1277
  "disabled",
@@ -1029,6 +1279,12 @@ function attachTabMutationObserver(observedNode) {
1029
1279
  `${ATTRIBUTE_PREFIX}button-icon`,
1030
1280
  "data-monster-tab-error",
1031
1281
  "data-monster-tab-error-message",
1282
+ "data-monster-tab-available",
1283
+ "data-monster-tab-disabled",
1284
+ "data-monster-tab-disabled-reason",
1285
+ "data-monster-tab-kind",
1286
+ "data-monster-tab-priority",
1287
+ "data-monster-tab-group",
1032
1288
  ],
1033
1289
  });
1034
1290
 
@@ -1071,6 +1327,8 @@ function initTabButtons() {
1071
1327
  }
1072
1328
 
1073
1329
  let activeReference;
1330
+ let invalidActive = false;
1331
+ const previousActiveReference = this[activeReferenceSymbol] || null;
1074
1332
 
1075
1333
  const dimensionsCalculated = this[dimensionsSymbol].getVia(
1076
1334
  "data.calculated",
@@ -1078,25 +1336,34 @@ function initTabButtons() {
1078
1336
  );
1079
1337
 
1080
1338
  const buttons = [];
1081
- const nodes = getSlottedElements.call(this, undefined, null); // null ↦ only unnamed slots
1339
+ const nodes = getAllTabPanels.call(this);
1082
1340
 
1083
1341
  for (const node of nodes) {
1084
1342
  if (!(node instanceof HTMLElement)) continue;
1085
- let label = getButtonLabel.call(this, node);
1343
+ const wasActive = node.matches(".active") === true;
1086
1344
 
1087
- let reference;
1088
- if (node.hasAttribute("id")) {
1089
- reference = node.getAttribute("id");
1345
+ if (!node.hasAttribute("id")) {
1346
+ node.setAttribute("id", new ID("tab").toString());
1090
1347
  }
1091
1348
 
1092
- let disabled;
1093
- if (node.hasAttribute("disabled") || node.disabled === true) {
1094
- disabled = true;
1349
+ attachTabMutationObserver.call(this, node);
1350
+
1351
+ if (isTabAvailable(node) !== true) {
1352
+ if (wasActive === true) {
1353
+ invalidActive = true;
1354
+ node.classList.remove("active");
1355
+ }
1356
+ continue;
1095
1357
  }
1096
1358
 
1097
- if (!reference) {
1098
- reference = new ID("tab").toString();
1099
- node.setAttribute("id", reference);
1359
+ let label = getButtonLabel.call(this, node);
1360
+
1361
+ let reference = node.getAttribute("id");
1362
+
1363
+ let disabled = isTabDisabled(node);
1364
+ if (wasActive === true && disabled === true) {
1365
+ invalidActive = true;
1366
+ node.classList.remove("active");
1100
1367
  }
1101
1368
 
1102
1369
  if (node.hasAttribute(`${ATTRIBUTE_PREFIX}button-icon`)) {
@@ -1114,32 +1381,47 @@ function initTabButtons() {
1114
1381
 
1115
1382
  if (node.matches(".active") === true && disabled !== true) {
1116
1383
  activeReference = reference;
1384
+ this[activeReferenceSymbol] = reference;
1117
1385
  }
1118
1386
 
1119
1387
  const state = "";
1120
1388
  const classes = dimensionsCalculated ? "" : "invisible";
1389
+ const disabledReason = node.getAttribute(
1390
+ "data-monster-tab-disabled-reason",
1391
+ );
1392
+ const metadata = getTabMetadata(node);
1121
1393
 
1122
1394
  buttons.push({
1123
1395
  reference,
1396
+ name: getTabName(node),
1397
+ tab: getTabPublicReference(node),
1124
1398
  label,
1125
1399
  error: errorMarkup,
1126
1400
  state,
1127
1401
  class: classes,
1128
1402
  disabled,
1403
+ title: disabled === true ? disabledReason : null,
1404
+ "aria-label": disabled === true ? disabledReason : null,
1129
1405
  remove,
1406
+ kind: metadata.kind,
1407
+ priority: metadata.priority,
1408
+ group: metadata.group,
1130
1409
  });
1131
-
1132
- attachTabMutationObserver.call(this, node);
1133
1410
  }
1134
1411
 
1135
1412
  setButtonCollections.call(this, buttons, []);
1136
1413
  this.setOption("marker", random());
1137
1414
 
1138
1415
  return adjustButtonVisibility.call(this).then(() => {
1139
- if (!activeReference && this.getOption("features.openFirst") === true) {
1140
- const firstButton = this.getOption("buttons.standard").find(
1141
- (button) => button.disabled !== true,
1142
- );
1416
+ if (
1417
+ !activeReference &&
1418
+ (invalidActive === true ||
1419
+ previousActiveReference !== null ||
1420
+ this.getOption("features.openFirst") === true)
1421
+ ) {
1422
+ const firstButton = this.getOption("buttons.standard")
1423
+ .concat(this.getOption("buttons.popper"))
1424
+ .find((button) => button.disabled !== true);
1143
1425
  if (firstButton) {
1144
1426
  activeReference = firstButton.reference;
1145
1427
  }
@@ -1152,6 +1434,8 @@ function initTabButtons() {
1152
1434
  );
1153
1435
  if (button instanceof HTMLButtonElement && button.disabled !== true) {
1154
1436
  show.call(this, button, { emitEvents: false });
1437
+ } else {
1438
+ this[activeReferenceSymbol] = null;
1155
1439
  }
1156
1440
  })
1157
1441
  .run(undefined)
@@ -1161,10 +1445,32 @@ function initTabButtons() {
1161
1445
  });
1162
1446
  }
1163
1447
 
1448
+ clearActiveTabs.call(this);
1164
1449
  return Promise.resolve();
1165
1450
  });
1166
1451
  }
1167
1452
 
1453
+ /**
1454
+ * @private
1455
+ */
1456
+ function clearActiveTabs() {
1457
+ this[activeReferenceSymbol] = null;
1458
+
1459
+ for (const node of getAllTabPanels.call(this)) {
1460
+ node.classList.remove("active");
1461
+ }
1462
+
1463
+ const standardButtons = this.getOption("buttons.standard");
1464
+ for (const index in standardButtons) {
1465
+ this.setOption(`buttons.standard.${index}.state`, "inactive");
1466
+ }
1467
+
1468
+ const popperButtons = this.getOption("buttons.popper");
1469
+ for (const index in popperButtons) {
1470
+ this.setOption(`buttons.popper.${index}.state`, "inactive");
1471
+ }
1472
+ }
1473
+
1168
1474
  function checkAndRearrangeButtons() {
1169
1475
  if (this[dimensionsSymbol].getVia("data.calculated", false) !== true) {
1170
1476
  calculateNavigationButtonsDimensions.call(this);
@@ -1416,8 +1722,12 @@ function getButtonLabel(node) {
1416
1722
  if (node.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
1417
1723
  label = node.getAttribute(ATTRIBUTE_BUTTON_LABEL);
1418
1724
  } else {
1419
- label = node.innerText;
1420
- setLabel = true;
1725
+ if (this.getOption("features.deriveLabelFromContent") === false) {
1726
+ label = this.getOption("labels.new-tab-label", "New Tab");
1727
+ } else {
1728
+ label = node.innerText;
1729
+ setLabel = true;
1730
+ }
1421
1731
  }
1422
1732
 
1423
1733
  if (!isString(label)) {
@@ -1514,7 +1824,13 @@ function getTemplate() {
1514
1824
  data-monster-attributes="
1515
1825
  class path:classes.button,
1516
1826
  data-monster-state path:buttons.state,
1517
- disabled path:buttons.disabled | if:true,
1827
+ disabled path:buttons.disabled | if:true,
1828
+ title path:buttons.title,
1829
+ aria-label path:buttons.aria-label,
1830
+ data-monster-tab-name path:buttons.name,
1831
+ data-monster-tab-kind path:buttons.kind,
1832
+ data-monster-tab-priority path:buttons.priority,
1833
+ data-monster-tab-group path:buttons.group,
1518
1834
  data-monster-tab-reference path:buttons.reference"><span part="label"
1519
1835
  data-monster-replace="path:buttons.label"></span><div part="error"
1520
1836
  data-monster-replace="path:buttons.error"></div><span part="remove-tab"