Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
index 30918b2..664b3b8 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
@@ -149,12 +149,11 @@
     final Markup markup = tabGroup.getMarkup();
     final TobagoResponseWriter writer = getResponseWriter(facesContext);
 
-    writer.startElement(HtmlElements.DIV);
+    writer.startElement(HtmlElements.TOBAGO_TAB_GROUP);
     writer.writeIdAttribute(clientId);
     writer.writeClassAttribute(
-        TobagoClass.TAB_GROUP,
-        TobagoClass.TAB_GROUP.createMarkup(markup),
         BootstrapClass.CARD,
+        TobagoClass.TAB_GROUP.createMarkup(markup),
         tabGroup.getCustomClass(),
         markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
     HtmlRendererUtils.writeDataAttributes(facesContext, writer, tabGroup);
@@ -173,7 +172,7 @@
 
     encodeContent(facesContext, writer, tabGroup, activeIndex, switchType);
 
-    writer.endElement(HtmlElements.DIV);
+    writer.endElement(HtmlElements.TOBAGO_TAB_GROUP);
   }
 
   private int ensureRenderedActiveIndex(final FacesContext context, final AbstractUITabGroup tabGroup) {
@@ -217,6 +216,8 @@
       final int activeIndex, final SwitchType switchType)
       throws IOException {
 
+    final String tabGroupClientId = tabGroup.getClientId(facesContext);
+
     writer.startElement(HtmlElements.DIV);
     writer.writeClassAttribute(BootstrapClass.CARD_HEADER);
     writer.startElement(HtmlElements.UL);
@@ -249,7 +250,7 @@
             markup = markup.add(ComponentUtils.markupOfSeverity(maxSeverity));
           }
 
-          writer.startElement(HtmlElements.LI);
+          writer.startElement(HtmlElements.TOBAGO_TAB);
           writer.writeIdAttribute(tabId);
           writer.writeClassAttribute(
               TobagoClass.TAB,
@@ -257,6 +258,7 @@
               BootstrapClass.NAV_ITEM,
               barFacet != null ? TobagoClass.TAB__BAR_FACET : null,
               tab.getCustomClass());
+          writer.writeAttribute(HtmlAttributes.FOR, tabGroupClientId, true);
           writer.writeAttribute(HtmlAttributes.ROLE, HtmlRoleValues.PRESENTATION.toString(), false);
           writer.writeAttribute(DataAttributes.TAB_GROUP_INDEX, index);
           final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, tab);
@@ -322,7 +324,7 @@
             writer.endElement(HtmlElements.DIV);
           }
 
-          writer.endElement(HtmlElements.LI);
+          writer.endElement(HtmlElements.TOBAGO_TAB);
         }
         index++;
       }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
index 8766a3d..b84f6e1 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
@@ -138,7 +138,9 @@
   TOBAGO_POPUP("tobago-popup"),
   TOBAGO_SPLIT_LAYOUT("tobago-split-layout"),
   TOBAGO_STARS("tobago-stars"),
-  TOBAGO_SUGGEST("tobago-suggest");
+  TOBAGO_SUGGEST("tobago-suggest"),
+  TOBAGO_TAB("tobago-tab"),
+  TOBAGO_TAB_GROUP("tobago-tab-group");
 
   private final String value;
   private final boolean voidElement;
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-command.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-command.ts
index b66d8c4..d54e0bd 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-command.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-command.ts
@@ -244,46 +244,54 @@
   }
 
   static init = function (element: HTMLElement) {
+    Command.initialize(element, false);
+  };
+
+  static initialize = function (element: HTMLElement, force: boolean) {
 
     for (const commandElement of DomUtils.selfOrQuerySelectorAll(element, "[data-tobago-commands]")) {
 
-      const commandMap = new CommandMap(commandElement.dataset["tobagoCommands"]);
+      // TODO hack to set command eventListeners after tobago-tab EventListeners
+      if (force || commandElement.parentElement.tagName !== "TOBAGO-TAB") {
 
-      for (const entry of commandMap.commands.entries()) {
-        const key: string = entry[0];
-        const value: Command = entry[1];
+        const commandMap = new CommandMap(commandElement.dataset["tobagoCommands"]);
 
-        switch(key) {
-          case "change":
-            commandElement.addEventListener("change", CommandMap.change);
-            break;
-          case "complete":
-            if (parseFloat(commandElement.getAttribute("value")) >= parseFloat(commandElement.getAttribute("max"))) {
-              if (commandMap.complete.execute || commandMap.complete.render) {
-                jsf.ajax.request(
-                    this.id,
-                    null,
-                    {
-                      "javax.faces.behavior.event": "complete",
-                      execute: commandMap.complete.execute,
-                      render: commandMap.complete.render
-                    });
-              } else {
-                Command.submitAction(this, commandMap.complete.action, commandMap.complete);
+        for (const entry of commandMap.commands.entries()) {
+          const key: string = entry[0];
+          const value: Command = entry[1];
+
+          switch (key) {
+            case "change":
+              commandElement.addEventListener("change", CommandMap.change);
+              break;
+            case "complete":
+              if (parseFloat(commandElement.getAttribute("value")) >= parseFloat(commandElement.getAttribute("max"))) {
+                if (commandMap.complete.execute || commandMap.complete.render) {
+                  jsf.ajax.request(
+                      this.id,
+                      null,
+                      {
+                        "javax.faces.behavior.event": "complete",
+                        execute: commandMap.complete.execute,
+                        render: commandMap.complete.render
+                      });
+                } else {
+                  Command.submitAction(this, commandMap.complete.action, commandMap.complete);
+                }
               }
-            }
-            break;
-          case "load":
-            setTimeout(function () {
-                  Command.submitAction(this, commandMap.load.action, commandMap.load);
-                },
-                commandMap.load.delay || 100);
-            break;
-          case "resize":
-            window.addEventListener("resize", CommandMap.resize);
-            break;
-          default:
-            commandElement.addEventListener(key, CommandMap.otherEvent);
+              break;
+            case "load":
+              setTimeout(function () {
+                    Command.submitAction(this, commandMap.load.action, commandMap.load);
+                  },
+                  commandMap.load.delay || 100);
+              break;
+            case "resize":
+              window.addEventListener("resize", CommandMap.resize);
+              break;
+            default:
+              commandElement.addEventListener(key, CommandMap.otherEvent);
+          }
         }
       }
     }
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tab.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tab.ts
index a422bae..eabfdcc 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tab.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tab.ts
@@ -15,75 +15,77 @@
  * limitations under the License.
  */
 
-import {Listener, Phase} from "./tobago-listener";
-import {DomUtils} from "./tobago-utils";
+import {Command} from "./tobago-command";
 
-/**
- * Initializes the tab-groups.
- * @param element
- */
-class TabGroup {
-  static init = function (element: HTMLElement) {
+class TabGroup extends HTMLElement {
+  private markupCssClass: string = "tobago-tab-markup-selected";
 
-    const markupString = "selected";
-    const markupCssClass = "tobago-tab-markup-selected";
+  constructor() {
+    super();
+  }
 
-    for (const e of DomUtils.selfOrQuerySelectorAll(element, ".tobago-tabGroup")) {
-      const tabGroup: HTMLDivElement = e as HTMLDivElement;
-      const hiddenInput: HTMLInputElement = TabGroup.getHiddenInput(tabGroup);
-      const tabs: NodeListOf<HTMLLIElement> = TabGroup.getTabs(tabGroup);
+  connectedCallback() {
+    const tabGroup: TabGroup = this;
+    const hiddenInput: HTMLInputElement = this.querySelector(":scope > input[type=hidden]");
 
-      for (const tab of tabs) {
-        const navLink: HTMLLinkElement = tab.querySelector(":scope > .nav-link");
-        if (!navLink.classList.contains("disabled")) {
-          navLink.addEventListener('click', function () {
-            const tabGroupIndex: string = tab.dataset.tobagoTabGroupIndex;
-            hiddenInput.value = tabGroupIndex;
+    for (const tab of tabGroup.tabs) {
+      const navLink: HTMLLinkElement = tab.querySelector(":scope > .nav-link");
+      if (!navLink.classList.contains("disabled")) {
+        navLink.addEventListener('click', function () {
+          hiddenInput.value = tab.groupIndex;
 
-            if (tabGroup.dataset.tobagoSwitchType === "client") {
-              const activeTab: HTMLLIElement = TabGroup.getActiveTab(tabGroup);
-              const activeNavLink: HTMLLinkElement = TabGroup.getActiveNavLink(tabGroup);
-              const activeTabContent: HTMLDivElement = TabGroup.getActiveTabContent(tabGroup);
+          if (tabGroup.dataset.tobagoSwitchType === "client") {
+            tabGroup.activeTab.classList.remove(tabGroup.markupCssClass);
+            tabGroup.activeNavLink.classList.remove("active");
+            tabGroup.activeTabContent.classList.remove("active");
 
-              activeTab.classList.remove(markupCssClass);
-              activeNavLink.classList.remove("active");
-              activeTabContent.classList.remove("active");
-
-              tab.classList.add(markupCssClass);
-              navLink.classList.add("active");
-              TabGroup.getTabContent(tabGroup, tabGroupIndex).classList.add("active");
-            }
-          });
-        }
+            tab.classList.add(tabGroup.markupCssClass);
+            navLink.classList.add("active");
+            tabGroup.getTabContent(tab.groupIndex).classList.add("active");
+          }
+        });
       }
     }
-  };
 
-  static getHiddenInput(tabGroup: HTMLDivElement): HTMLInputElement {
-    return tabGroup.querySelector(":scope > input[type=hidden]");
+    Command.initialize(tabGroup, true);
   }
 
-  static getTabs(tabGroup: HTMLDivElement): NodeListOf<HTMLLIElement> {
-    return tabGroup.querySelectorAll(":scope > .card-header > .tobago-tabGroup-header > .tobago-tab");
+  get tabs(): NodeListOf<Tab> {
+    return this.querySelectorAll("tobago-tab[for='" + this.id + "']");
   }
 
-  static getActiveTab(tabGroup: HTMLDivElement): HTMLLIElement {
-    return this.getActiveNavLink(tabGroup).parentElement as HTMLLIElement;
+  get activeTab(): Tab {
+    return this.querySelector("tobago-tab[for='" + this.id + "']." + this.markupCssClass);
   }
 
-  static getActiveNavLink(tabGroup: HTMLDivElement): HTMLLinkElement {
-    return tabGroup.querySelector(":scope > .card-header > .tobago-tabGroup-header > .tobago-tab > .nav-link.active");
+  get activeNavLink(): HTMLLinkElement {
+    return this.querySelector("tobago-tab[for='" + this.id + "'] > .nav-link.active");
   }
 
-  static getActiveTabContent(tabGroup: HTMLDivElement): HTMLDivElement {
-    return tabGroup.querySelector(":scope > .card-body.tab-content > .tobago-tab-content.active");
+  get activeTabContent(): HTMLDivElement {
+    return this.querySelector(":scope > .card-body.tab-content > .tobago-tab-content.active");
   }
 
-  static getTabContent(tabGroup: HTMLDivElement, tabGroupIndex: string): HTMLDivElement {
-    return tabGroup.querySelector(":scope > .card-body > .tobago-tab-content[data-tobago-tab-group-index='"
+  getTabContent(tabGroupIndex: string): HTMLDivElement {
+    return this.querySelector(":scope > .card-body > .tobago-tab-content[data-tobago-tab-group-index='"
         + tabGroupIndex + "']");
   }
 }
 
-Listener.register(TabGroup.init, Phase.DOCUMENT_READY);
-Listener.register(TabGroup.init, Phase.AFTER_UPDATE);
+class Tab extends HTMLElement {
+  constructor() {
+    super();
+  }
+
+  connectedCallback() {
+  }
+
+  get groupIndex(): string {
+    return this.dataset.tobagoTabGroupIndex;
+  }
+}
+
+document.addEventListener("DOMContentLoaded", function (event) {
+  window.customElements.define('tobago-tab-group', TabGroup);
+  // window.customElements.define('tobago-tab', Tab);
+});