Add support for `<a>` elements inside menus menu
authorAlexander Ebert <ebert@woltlab.com>
Wed, 4 Oct 2023 16:56:11 +0000 (18:56 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Thu, 5 Oct 2023 15:22:08 +0000 (17:22 +0200)
ts/WoltLabSuite/Core/Component/Menu/Builder.ts
ts/WoltLabSuite/Core/Element/woltlab-core-menu.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Menu/Builder.js
wcfsetup/install/files/js/WoltLabSuite/Core/Element/woltlab-core-menu.js

index 296377db1e02567e0eb4cda7417e579e70d021ca..e87f8e624cb5b207de6fb1dc8596d3bdef93677c 100644 (file)
@@ -33,6 +33,16 @@ export class MenuBuilder {
     return this;
   }
 
+  addLink(label: string, href: string): this {
+    const link = document.createElement("a");
+    link.href = href;
+    link.setAttribute("role", "menuitem");
+    link.textContent = label;
+    this.#menu.append(link);
+
+    return this;
+  }
+
   addDivider(): this {
     const divider = document.createElement("woltlab-core-menu-separator");
     this.#menu.append(divider);
index 65b07d3e222b5e8e0739369061a722c801bfc908..40ae3654daf77836bc4e5c3a0fde788f93bce939 100644 (file)
@@ -1,7 +1,7 @@
 import WoltlabCoreMenuGroupElement from "./woltlab-core-menu-group";
 import WoltlabCoreMenuItemElement from "./woltlab-core-menu-item";
 
-type MenuChild = WoltlabCoreMenuGroupElement | WoltlabCoreMenuItemElement;
+type MenuChild = HTMLAnchorElement | WoltlabCoreMenuGroupElement | WoltlabCoreMenuItemElement;
 
 interface WoltlabCoreMenuEventMap {
   change: CustomEvent;
@@ -29,7 +29,11 @@ export class WoltlabCoreMenuElement extends HTMLElement {
 
     slot.addEventListener("slotchange", () => {
       for (const element of slot.assignedElements()) {
-        if (!(element instanceof WoltlabCoreMenuGroupElement) && !(element instanceof WoltlabCoreMenuItemElement)) {
+        if (
+          !(element instanceof HTMLAnchorElement) &&
+          !(element instanceof WoltlabCoreMenuGroupElement) &&
+          !(element instanceof WoltlabCoreMenuItemElement)
+        ) {
           element.remove();
           continue;
         }
@@ -38,6 +42,19 @@ export class WoltlabCoreMenuElement extends HTMLElement {
           continue;
         }
 
+        if (element instanceof HTMLAnchorElement) {
+          if (element.href === "" || element.href === "#") {
+            throw new Error(
+              "Anchor elements may only use for actual navigation and must contain a valid 'href' target. Use a `<woltlab-core-menu-item>` button for non navigational items.",
+              {
+                cause: { element },
+              },
+            );
+          }
+
+          element.setAttribute("role", "menuitem");
+        }
+
         this.#items.add(element);
 
         element.addEventListener("change", () => {
@@ -48,7 +65,7 @@ export class WoltlabCoreMenuElement extends HTMLElement {
 
             if (item instanceof WoltlabCoreMenuGroupElement) {
               item.value = "";
-            } else {
+            } else if (item instanceof WoltlabCoreMenuItemElement) {
               item.selected = false;
             }
           });
@@ -83,6 +100,10 @@ export class WoltlabCoreMenuElement extends HTMLElement {
 
   get value(): string {
     for (const item of Array.from(this.#items)) {
+      if (item instanceof HTMLAnchorElement) {
+        continue;
+      }
+
       const value = item.value;
 
       if (item instanceof WoltlabCoreMenuGroupElement) {
index fac2625c368ce74e4b5838b322fc31642d3b2376..a37bd751a11db6810d98a2a611920cc8cf91999e 100644 (file)
@@ -27,6 +27,14 @@ define(["require", "exports", "tslib", "./Group"], function (require, exports, t
             this.#menu.append(item);
             return this;
         }
+        addLink(label, href) {
+            const link = document.createElement("a");
+            link.href = href;
+            link.setAttribute("role", "menuitem");
+            link.textContent = label;
+            this.#menu.append(link);
+            return this;
+        }
         addDivider() {
             const divider = document.createElement("woltlab-core-menu-separator");
             this.#menu.append(divider);
index 8a4b722ab029304c632ad6a57f22133747073e48..4489a8e78a51d7f471581e8fcaa45a8534ae9beb 100644 (file)
@@ -20,13 +20,23 @@ define(["require", "exports", "tslib", "./woltlab-core-menu-group", "./woltlab-c
             shadow.append(slot);
             slot.addEventListener("slotchange", () => {
                 for (const element of slot.assignedElements()) {
-                    if (!(element instanceof woltlab_core_menu_group_1.default) && !(element instanceof woltlab_core_menu_item_1.default)) {
+                    if (!(element instanceof HTMLAnchorElement) &&
+                        !(element instanceof woltlab_core_menu_group_1.default) &&
+                        !(element instanceof woltlab_core_menu_item_1.default)) {
                         element.remove();
                         continue;
                     }
                     if (this.#items.has(element)) {
                         continue;
                     }
+                    if (element instanceof HTMLAnchorElement) {
+                        if (element.href === "" || element.href === "#") {
+                            throw new Error("Anchor elements may only use for actual navigation and must contain a valid 'href' target. Use a `<woltlab-core-menu-item>` button for non navigational items.", {
+                                cause: { element },
+                            });
+                        }
+                        element.setAttribute("role", "menuitem");
+                    }
                     this.#items.add(element);
                     element.addEventListener("change", () => {
                         this.#items.forEach((item) => {
@@ -36,7 +46,7 @@ define(["require", "exports", "tslib", "./woltlab-core-menu-group", "./woltlab-c
                             if (item instanceof woltlab_core_menu_group_1.default) {
                                 item.value = "";
                             }
-                            else {
+                            else if (item instanceof woltlab_core_menu_item_1.default) {
                                 item.selected = false;
                             }
                         });
@@ -63,6 +73,9 @@ define(["require", "exports", "tslib", "./woltlab-core-menu-group", "./woltlab-c
         }
         get value() {
             for (const item of Array.from(this.#items)) {
+                if (item instanceof HTMLAnchorElement) {
+                    continue;
+                }
                 const value = item.value;
                 if (item instanceof woltlab_core_menu_group_1.default) {
                     if (value !== "") {