tobago-popover: custom elements, remove jQuery

A new custom element named tobago-popover is added. It replace the
message layout button and help button which were using bootstrap.js with
jQuery.

issue: TOBAGO-1633: TS refactoring
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
index 67e81ff..ca3d051 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/MessageLayoutRendererBase.java
@@ -24,9 +24,9 @@
 import org.apache.myfaces.tobago.component.SupportsLabelLayout;
 import org.apache.myfaces.tobago.internal.util.StringUtils;
 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
+import org.apache.myfaces.tobago.renderkit.css.CssItem;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
-import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlButtonTypes;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
@@ -100,10 +100,10 @@
 
     if (hasMessage || hasHelp) {
       if (hasMessage) {
-        encodeFacesMessagesButton(writer, messages);
+        encodeFacesMessagesButton(facesContext, component, writer, messages);
       }
       if (hasHelp) {
-        encodeHelpButton(facesContext, writer, help);
+        encodeHelpButton(facesContext, component, writer, help);
       }
       writer.endElement(HtmlElements.DIV);
     }
@@ -113,22 +113,12 @@
 
   protected abstract void encodeEndField(FacesContext facesContext, UIComponent component) throws IOException;
 
-  private void encodeFacesMessagesButton(
+  private void encodeFacesMessagesButton(FacesContext facesContext, final UIComponent component,
       final TobagoResponseWriter writer, final List<FacesMessage> messages) throws IOException {
-    writer.startElement(HtmlElements.A);
-    writer.writeAttribute(HtmlAttributes.TABINDEX, "0", false);
-    writer.writeAttribute(HtmlAttributes.ROLE, HtmlButtonTypes.BUTTON);
-    writer.writeClassAttribute(
-        TobagoClass.MESSAGES__BUTTON,
-        BootstrapClass.BTN,
-        BootstrapClass.buttonColor(ComponentUtils.getMaximumSeverity(messages)));
-    writer.writeAttribute(DataAttributes.TOGGLE, "popover", false);
-    writer.writeAttribute(DataAttributes.TITLE, getTitle(messages), true);
-    writer.writeAttribute(DataAttributes.CONTENT, getMessage(messages), true);
-    writer.startElement(HtmlElements.I);
-    writer.writeClassAttribute(Icons.FA, Icons.EXCLAMATION);
-    writer.endElement(HtmlElements.I);
-    writer.endElement(HtmlElements.A);
+
+    encodePopover(writer, component.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "messages",
+        BootstrapClass.buttonColor(ComponentUtils.getMaximumSeverity(messages)),
+        Icons.EXCLAMATION, getTitle(messages), getMessage(messages));
   }
 
   private String getTitle(final List<FacesMessage> messages) {
@@ -217,21 +207,45 @@
     return stringBuilder.toString();
   }
 
-  private void encodeHelpButton(final FacesContext facesContext, final TobagoResponseWriter writer, final String help)
+  private void encodeHelpButton(final FacesContext facesContext, final UIComponent component,
+      final TobagoResponseWriter writer, final String help) throws IOException {
+
+    encodePopover(writer, component.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "help",
+        BootstrapClass.BTN_OUTLINE_INFO, Icons.QUESTION,
+        ResourceUtils.getString(facesContext, "help.title"), help);
+  }
+
+  private void encodePopover(final TobagoResponseWriter writer, final String popoverId,
+      final CssItem buttonColor, final Icons buttonIcon, final String title, final String content)
       throws IOException {
+    writer.startElement(HtmlElements.TOBAGO_POPOVER);
+    writer.writeIdAttribute(popoverId);
+
     writer.startElement(HtmlElements.A);
     writer.writeAttribute(HtmlAttributes.TABINDEX, "0", false);
     writer.writeAttribute(HtmlAttributes.ROLE, HtmlButtonTypes.BUTTON);
-    writer.writeClassAttribute(
-        TobagoClass.HELP__BUTTON,
-        BootstrapClass.BTN,
-        BootstrapClass.BTN_OUTLINE_INFO);
-    writer.writeAttribute(DataAttributes.TOGGLE, "popover", false);
-    writer.writeAttribute(DataAttributes.TITLE, ResourceUtils.getString(facesContext, "help.title"), true);
-    writer.writeAttribute(DataAttributes.CONTENT, help, true);
+    writer.writeClassAttribute(TobagoClass.POPOVER__BUTTON, BootstrapClass.BTN, buttonColor);
     writer.startElement(HtmlElements.I);
-    writer.writeClassAttribute(Icons.FA, Icons.QUESTION);
+    writer.writeClassAttribute(Icons.FA, buttonIcon);
     writer.endElement(HtmlElements.I);
     writer.endElement(HtmlElements.A);
+
+    writer.startElement(HtmlElements.DIV);
+    writer.writeClassAttribute(TobagoClass.POPOVER__BOX, BootstrapClass.POPOVER);
+    writer.writeNameAttribute(popoverId);
+    writer.startElement(HtmlElements.DIV);
+    writer.writeClassAttribute(BootstrapClass.ARROW);
+    writer.endElement(HtmlElements.DIV);
+    writer.startElement(HtmlElements.H3);
+    writer.writeClassAttribute(BootstrapClass.POPOVER_HEADER);
+    writer.writeText(title);
+    writer.endElement(HtmlElements.H3);
+    writer.startElement(HtmlElements.DIV);
+    writer.writeClassAttribute(BootstrapClass.POPOVER_BODY);
+    writer.writeText(content);
+    writer.endElement(HtmlElements.DIV);
+    writer.endElement(HtmlElements.DIV);
+
+    writer.endElement(HtmlElements.TOBAGO_POPOVER);
   }
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
index f0f15b4..42df2e6 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
@@ -57,6 +57,7 @@
   ALIGN_ITEMS_END("align-items-end"),
   ALIGN_ITEMS_START("align-items-start"),
   ALIGN_ITEMS_STRETCH("align-items-stretch"),
+  ARROW("arrow"),
   BG_DARK("bg-dark"),
   /**
    * @deprecated since 4.0.0, please use {@link #BG_DARK}
@@ -471,6 +472,9 @@
   PAGE_ITEM("page-item"),
   PAGE_LINK("page-link"),
   PAGINATION("pagination"),
+  POPOVER("popover"),
+  POPOVER_BODY("popover-body"),
+  POPOVER_HEADER("popover-header"),
   PROGRESS("progress"),
   PROGRESS_BAR("progress-bar"),
   ROW("row"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
index 33abe3a..bc2ab9a 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
@@ -130,6 +130,10 @@
   FORM("tobago-form"),
   GRID_LAYOUT("tobago-gridLayout"),
   HEADER("tobago-header"),
+  /**
+   * @deprecated Since 5.0.0. Please use {@link TobagoClass#POPOVER__BUTTON}
+   */
+  @Deprecated
   HELP__BUTTON("tobago-help-button"),
   IMAGE("tobago-image"),
   IN("tobago-in"),
@@ -144,6 +148,10 @@
   LINK("tobago-link"),
   LINKS("tobago-links"),
   MESSAGES("tobago-messages"),
+  /**
+   * @deprecated Since 5.0.0. Please use {@link TobagoClass#POPOVER__BUTTON}
+   */
+  @Deprecated
   MESSAGES__BUTTON("tobago-messages-button"),
   MESSAGES__CONTAINER("tobago-messages-container"),
   OBJECT("tobago-object"),
@@ -152,6 +160,8 @@
   PAGE__MENU_STORE("tobago-page-menuStore"),
   PAGE__NOSCRIPT("tobago-page-noscript"),
   PANEL("tobago-panel"),
+  POPOVER__BOX("tobago-popover-box"),
+  POPOVER__BUTTON("tobago-popover-button"),
   PROGRESS("tobago-progress"),
   SECTION("tobago-section"),
   SECTION__HEADER("tobago-section-header"),
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 3993af1..e6d396e 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
@@ -141,6 +141,7 @@
   TOBAGO_IN("tobago-in"),
   TOBAGO_PAGE("tobago-page"),
   TOBAGO_PANEL("tobago-panel"),
+  TOBAGO_POPOVER("tobago-popover"),
   TOBAGO_POPUP("tobago-popup"),
   TOBAGO_SPLIT_LAYOUT("tobago-split-layout"),
   TOBAGO_SELECT_BOOLEAN_CHECKBOX("tobago-select-boolean-checkbox"),
diff --git a/tobago-core/src/main/resources/scss/_tobago.scss b/tobago-core/src/main/resources/scss/_tobago.scss
index a56b6a5..323c02d 100644
--- a/tobago-core/src/main/resources/scss/_tobago.scss
+++ b/tobago-core/src/main/resources/scss/_tobago.scss
@@ -500,7 +500,16 @@
   }
 }
 
-a.tobago-messages-button, a.tobago-help-button {
+.tobago-popover-box {
+  display: none;
+  width: max-content;
+
+  &.show {
+    display: block;
+  }
+}
+
+a.tobago-messages-button, a.tobago-help-button, a.tobago-popover-button {
   padding-left: 0.4em;
   padding-right: 0.4em;
 }
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-popover.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-popover.ts
index 6a5c638..92202b9 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-popover.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-popover.ts
@@ -15,23 +15,80 @@
  * limitations under the License.
  */
 
-import {Listener, Phase} from "./tobago-listener";
+import Popper from "popper.js";
 
-class MessagePopover {
+class Popover extends HTMLElement {
 
-  static init = function (element: HTMLElement): void {
-    jQuery("[data-toggle=\"popover\"]").popover({
-      constraints: [
-        {
-          to: "window",
-          attachment: "together",
-          pin: true
+  private popper;
+
+  constructor() {
+    super();
+  }
+
+  connectedCallback(): void {
+    this.button.addEventListener("click", this.showPopover.bind(this));
+    this.button.addEventListener("blur", this.hidePopover.bind(this));
+  }
+
+  showPopover(): void {
+    this.menuStore.appendChild(this.popover);
+    this.popper = new Popper(this.button, this.popover, {
+      placement: "right",
+      modifiers: {
+        arrow: {
+          element: ".arrow"
         }
-      ],
-      trigger: "focus"
+      },
+      onCreate: this.updateBootstrapPopoverCss.bind(this),
+      onUpdate: this.updateBootstrapPopoverCss.bind(this)
     });
-  };
+    this.popover.classList.add("show");
+  }
+
+  hidePopover(): void {
+    this.popover.classList.remove("show");
+    this.appendChild(this.popover);
+
+    if (this.popper !== undefined && this.popper !== null) {
+      this.popper.destroy();
+      this.popper = null;
+    }
+  }
+
+  private updateBootstrapPopoverCss(): void {
+    const placement = this.popover.getAttribute("x-placement");
+    if (placement === "right" && !this.popover.classList.contains("bs-popover-right")) {
+      this.popover.classList.add("bs-popover-right");
+      this.popover.classList.remove("bs-popover-left");
+      this.updateAfterCssClassChange();
+    } else if (placement === "left" && !this.popover.classList.contains("bs-popover-left")) {
+      this.popover.classList.add("bs-popover-left");
+      this.popover.classList.remove("bs-popover-right");
+      this.updateAfterCssClassChange();
+    }
+  }
+
+  private updateAfterCssClassChange(): void {
+    if (this.popper !== undefined && this.popper !== null) {
+      this.popper.scheduleUpdate();
+    }
+  }
+
+  get button(): HTMLLinkElement {
+    return this.querySelector(":scope > .tobago-popover-button");
+  }
+
+  get popover(): HTMLDivElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(".tobago-popover-box[name='" + this.id + "']");
+  }
+
+  private get menuStore(): HTMLDivElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(".tobago-page-menuStore");
+  }
 }
 
-Listener.register(MessagePopover.init, Phase.DOCUMENT_READY);
-Listener.register(MessagePopover.init, Phase.AFTER_UPDATE);
+document.addEventListener("DOMContentLoaded", function (event: Event): void {
+  window.customElements.define("tobago-popover", Popover);
+});