Repository: jwstegemann/fritz2
Issue: Fix ``aria-haspopup`` for Popover-Panel based components

Currently components based upon ``PopoverPanel`` set the ``aria-haspopup`` property always to ``true``.

According to its [definition](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup#description) the value only might be ``true`` if the component represents some "menu". In other cases there are dedicated values, that should be set:
- menu
- listbox
- tree
- grid
- dialog

So there must be some property that can be configured by the call-side.

Changed files:
headless/src/jsMain/kotlin/dev/fritz2/headless/components/listbox.kt, headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt, headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/aria.kt, headless/src/jsMain/kotlin/dev/fritz2/headless/components/menu.kt, headless/src/jsMain/kotlin/dev/fritz2/headless/components/popOver.kt, headless/src/jsMain/kotlin/dev/fritz2/headless/components/tooltip.kt

diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/listbox.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/listbox.kt
index 9a45bfac..4619ccfa 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/listbox.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/listbox.kt
@@ -169,7 +169,8 @@ class Listbox<T, C : HTMLElement>(tag: Tag<C>, id: String?) : Tag<C> by tag, Ope
         "$componentId-items",
         scope,
         this@Listbox.opened,
-        reference = button
+        reference = button,
+        ariaHasPopup = Aria.HasPopup.listbox
     ) {
 
         private fun nextItem(currentIndex: Int, direction: Direction, entries: List<ListboxEntry<T>>): Int =
diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/menu.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/menu.kt
index 6ac20e4d..393e4f71 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/menu.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/menu.kt
@@ -66,7 +66,7 @@ class Menu<C : HTMLElement>(tag: Tag<C>, id: String?) : Tag<C> by tag, OpenClose
         scope: (ScopeContext.() -> Unit) = {},
         tag: TagFactory<Tag<CB>>,
         content: Tag<CB>.() -> Unit
-    ) : Tag<CB> {
+    ): Tag<CB> {
         addComponentStructureInfo("menuButton", this@menuButton.scope, this)
         return tag(this, classes, "$componentId-button", scope) {
             if (!openState.isSet) openState(storeOf(false))
@@ -95,7 +95,16 @@ class Menu<C : HTMLElement>(tag: Tag<C>, id: String?) : Tag<C> by tag, OpenClose
         tagFactory: TagFactory<Tag<CI>>,
         classes: String?,
         scope: ScopeContext.() -> Unit
-    ) : PopUpPanel<CI>(renderContext, tagFactory, classes, "$componentId-items", scope, this@Menu.opened, reference = button) {
+    ) : PopUpPanel<CI>(
+        renderContext,
+        tagFactory,
+        classes,
+        "$componentId-items",
+        scope,
+        this@Menu.opened,
+        reference = button,
+        ariaHasPopup = Aria.HasPopup.menu
+    ) {
 
         private fun nextItem(currentIndex: Int, direction: Direction, items: List<MenuEntry>): Int =
             when (direction) {
diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/popOver.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/popOver.kt
index 8b7f95d9..113feba5 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/popOver.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/popOver.kt
@@ -36,7 +36,7 @@ class PopOver<C : HTMLElement>(tag: Tag<C>, id: String?) : Tag<C> by tag, OpenCl
         scope: (ScopeContext.() -> Unit) = {},
         tag: TagFactory<Tag<CB>>,
         content: Tag<CB>.() -> Unit
-    ) : Tag<CB> {
+    ): Tag<CB> {
         addComponentStructureInfo("popOverButton", this@popOverButton.scope, this)
         return tag(this, classes, "$componentId-button", scope) {
             if (!openState.isSet) openState(storeOf(false))
@@ -65,7 +65,16 @@ class PopOver<C : HTMLElement>(tag: Tag<C>, id: String?) : Tag<C> by tag, OpenCl
         tagFactory: TagFactory<Tag<C>>,
         classes: String?,
         scope: ScopeContext.() -> Unit
-    ) : PopUpPanel<C>(renderContext, tagFactory, classes, "$componentId-items", scope, this@PopOver.opened, reference = button)
+    ) : PopUpPanel<C>(
+        renderContext,
+        tagFactory,
+        classes,
+        "$componentId-items",
+        scope,
+        this@PopOver.opened,
+        reference = button,
+        ariaHasPopup = Aria.HasPopup.dialog
+    )
 
     /**
      * Factory function to create a [popOverPanel].
diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/tooltip.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/tooltip.kt
index 53cc3674..e4a632ff 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/components/tooltip.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/components/tooltip.kt
@@ -23,11 +23,17 @@ class Tooltip<C : HTMLElement>(
     id: String?,
     scope: ScopeContext.() -> Unit
 ) : PopUpPanel<C>(
-    renderContext.annex, tagFactory, classes, id, scope,
+    renderContext.annex,
+    tagFactory,
+    classes,
+    id,
+    scope,
     opened = renderContext.run {
         merge(mouseenters.map { true }, mouseleaves.map { false })
     },
-    fullWidth = false, renderContext
+    fullWidth = false,
+    renderContext,
+    ariaHasPopup = Aria.HasPopup.dialog
 )
 
 /**
diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt
index 951cc51a..e068f94e 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt
@@ -20,6 +20,7 @@ abstract class PopUpPanel<C : HTMLElement>(
     private val opened: Flow<Boolean>,
     private val fullWidth: Boolean = true,
     private val reference: Tag<HTMLElement>?,
+    private val ariaHasPopup: String,
     private val popperDiv: HtmlTag<HTMLDivElement> = renderContext.div(POPUP_HIDDEN) {}, //never add other classes to popperDiv, they will be overridden
     tag: Tag<C> = tagFactory(popperDiv, classes, id, scope) {}
 ) : Tag<C> by tag {
@@ -181,7 +182,7 @@ abstract class PopUpPanel<C : HTMLElement>(
             reference.apply {
                 attr(Aria.labelledby, reference.id)
                 attr(Aria.controls, this@PopUpPanel.id.whenever(opened))
-                attr(Aria.haspopup, "true")
+                attrIfNotSet(Aria.haspopup, ariaHasPopup)
             }
             opened handledBy {
                 if (it) {
diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/aria.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/aria.kt
index 14f65e3b..4fc3b300 100644
--- a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/aria.kt
+++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/aria.kt
@@ -148,6 +148,14 @@ object Aria {
         const val scrollbar = "scrollbar"
         const val group = "group"
     }
+
+    object HasPopup {
+        const val menu = "menu"
+        const val listbox = "listbox"
+        const val tree = "tree"
+        const val grid = "grid"
+        const val dialog = "dialog"
+    }
 }
 
 /**