Propagate the updated value of a menu
authorAlexander Ebert <ebert@woltlab.com>
Tue, 3 Oct 2023 15:24:51 +0000 (17:24 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Thu, 5 Oct 2023 15:22:07 +0000 (17:22 +0200)
ts/WoltLabSuite/Core/Element/woltlab-core-menu-group.ts
ts/WoltLabSuite/Core/Element/woltlab-core-menu.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Element/woltlab-core-menu-group.js
wcfsetup/install/files/js/WoltLabSuite/Core/Element/woltlab-core-menu.js

index 39cd8d94929ab19abcd207cbab43a2569578a203..a38b01ad5ada445b4aaa2b0801ab864702c83fe5 100644 (file)
@@ -1,5 +1,10 @@
 import WoltlabCoreMenuItemElement from "./woltlab-core-menu-item";
 
+interface WoltlabCoreMenuGroupEventMap {
+  change: CustomEvent;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
 export class WoltlabCoreMenuGroupElement extends HTMLElement {
   readonly #items = new Set<WoltlabCoreMenuItemElement>();
   #value = "";
@@ -80,6 +85,17 @@ export class WoltlabCoreMenuGroupElement extends HTMLElement {
   }
 }
 
-export default WoltlabCoreMenuGroupElement;
+// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+export interface WoltlabCoreMenuGroupElement extends HTMLElement {
+  addEventListener: {
+    <T extends keyof WoltlabCoreMenuGroupEventMap>(
+      type: T,
+      listener: (this: WoltlabCoreMenuItemElement, ev: WoltlabCoreMenuGroupEventMap[T]) => any,
+      options?: boolean | AddEventListenerOptions,
+    ): void;
+  } & HTMLElement["addEventListener"];
+}
 
 window.customElements.define("woltlab-core-menu-group", WoltlabCoreMenuGroupElement);
+
+export default WoltlabCoreMenuGroupElement;
index 3a717f3eebe7f43494109e1258fae67bff23c7b2..65b07d3e222b5e8e0739369061a722c801bfc908 100644 (file)
@@ -1,7 +1,17 @@
+import WoltlabCoreMenuGroupElement from "./woltlab-core-menu-group";
 import WoltlabCoreMenuItemElement from "./woltlab-core-menu-item";
 
+type MenuChild = WoltlabCoreMenuGroupElement | WoltlabCoreMenuItemElement;
+
+interface WoltlabCoreMenuEventMap {
+  change: CustomEvent;
+  close: CustomEvent;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
 export class WoltlabCoreMenuElement extends HTMLElement {
   #index = -1;
+  #items = new Set<MenuChild>();
 
   constructor() {
     super();
@@ -12,6 +22,48 @@ export class WoltlabCoreMenuElement extends HTMLElement {
   }
 
   connectedCallback() {
+    const shadow = this.attachShadow({ mode: "open" });
+
+    const slot = document.createElement("slot");
+    shadow.append(slot);
+
+    slot.addEventListener("slotchange", () => {
+      for (const element of slot.assignedElements()) {
+        if (!(element instanceof WoltlabCoreMenuGroupElement) && !(element instanceof WoltlabCoreMenuItemElement)) {
+          element.remove();
+          continue;
+        }
+
+        if (this.#items.has(element)) {
+          continue;
+        }
+
+        this.#items.add(element);
+
+        element.addEventListener("change", () => {
+          this.#items.forEach((item) => {
+            if (item === element) {
+              return;
+            }
+
+            if (item instanceof WoltlabCoreMenuGroupElement) {
+              item.value = "";
+            } else {
+              item.selected = false;
+            }
+          });
+
+          const evt = new CustomEvent("change");
+          this.dispatchEvent(evt);
+
+          if (element instanceof WoltlabCoreMenuItemElement) {
+            const evt = new CustomEvent("close");
+            this.dispatchEvent(evt);
+          }
+        });
+      }
+    });
+
     this.setAttribute("role", "menu");
 
     this.label = this.getAttribute("label")!;
@@ -29,6 +81,22 @@ export class WoltlabCoreMenuElement extends HTMLElement {
     this.setAttribute("aria-label", label);
   }
 
+  get value(): string {
+    for (const item of Array.from(this.#items)) {
+      const value = item.value;
+
+      if (item instanceof WoltlabCoreMenuGroupElement) {
+        if (value !== "") {
+          return value;
+        }
+      } else if (item.selected) {
+        return value;
+      }
+    }
+
+    return "";
+  }
+
   #keydown(event: KeyboardEvent): void {
     const { code, key } = event;
 
@@ -106,6 +174,17 @@ export class WoltlabCoreMenuElement extends HTMLElement {
   }
 }
 
-export default WoltlabCoreMenuElement;
+// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+export interface WoltlabCoreMenuElement extends HTMLElement {
+  addEventListener: {
+    <T extends keyof WoltlabCoreMenuEventMap>(
+      type: T,
+      listener: (this: WoltlabCoreMenuItemElement, ev: WoltlabCoreMenuEventMap[T]) => any,
+      options?: boolean | AddEventListenerOptions,
+    ): void;
+  } & HTMLElement["addEventListener"];
+}
 
 window.customElements.define("woltlab-core-menu", WoltlabCoreMenuElement);
+
+export default WoltlabCoreMenuElement;
index d6390089a527898ee25d6adf31338704820c9b05..0ad0acd128e3158a457933473e12bc844adaf1f2 100644 (file)
@@ -3,6 +3,7 @@ define(["require", "exports", "tslib", "./woltlab-core-menu-item"], function (re
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.WoltlabCoreMenuGroupElement = void 0;
     woltlab_core_menu_item_1 = tslib_1.__importDefault(woltlab_core_menu_item_1);
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
     class WoltlabCoreMenuGroupElement extends HTMLElement {
         #items = new Set();
         #value = "";
@@ -66,6 +67,6 @@ define(["require", "exports", "tslib", "./woltlab-core-menu-item"], function (re
         }
     }
     exports.WoltlabCoreMenuGroupElement = WoltlabCoreMenuGroupElement;
-    exports.default = WoltlabCoreMenuGroupElement;
     window.customElements.define("woltlab-core-menu-group", WoltlabCoreMenuGroupElement);
+    exports.default = WoltlabCoreMenuGroupElement;
 });
index 8ac9e1b4500fdb34da68a264f1cd4012717c4622..8a4b722ab029304c632ad6a57f22133747073e48 100644 (file)
@@ -1,9 +1,13 @@
-define(["require", "exports"], function (require, exports) {
+define(["require", "exports", "tslib", "./woltlab-core-menu-group", "./woltlab-core-menu-item"], function (require, exports, tslib_1, woltlab_core_menu_group_1, woltlab_core_menu_item_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.WoltlabCoreMenuElement = void 0;
+    woltlab_core_menu_group_1 = tslib_1.__importDefault(woltlab_core_menu_group_1);
+    woltlab_core_menu_item_1 = tslib_1.__importDefault(woltlab_core_menu_item_1);
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
     class WoltlabCoreMenuElement extends HTMLElement {
         #index = -1;
+        #items = new Set();
         constructor() {
             super();
             this.addEventListener("keydown", (event) => {
@@ -11,6 +15,40 @@ define(["require", "exports"], function (require, exports) {
             });
         }
         connectedCallback() {
+            const shadow = this.attachShadow({ mode: "open" });
+            const slot = document.createElement("slot");
+            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)) {
+                        element.remove();
+                        continue;
+                    }
+                    if (this.#items.has(element)) {
+                        continue;
+                    }
+                    this.#items.add(element);
+                    element.addEventListener("change", () => {
+                        this.#items.forEach((item) => {
+                            if (item === element) {
+                                return;
+                            }
+                            if (item instanceof woltlab_core_menu_group_1.default) {
+                                item.value = "";
+                            }
+                            else {
+                                item.selected = false;
+                            }
+                        });
+                        const evt = new CustomEvent("change");
+                        this.dispatchEvent(evt);
+                        if (element instanceof woltlab_core_menu_item_1.default) {
+                            const evt = new CustomEvent("close");
+                            this.dispatchEvent(evt);
+                        }
+                    });
+                }
+            });
             this.setAttribute("role", "menu");
             this.label = this.getAttribute("label");
             this.#index = 0;
@@ -23,6 +61,20 @@ define(["require", "exports"], function (require, exports) {
             this.setAttribute("label", label);
             this.setAttribute("aria-label", label);
         }
+        get value() {
+            for (const item of Array.from(this.#items)) {
+                const value = item.value;
+                if (item instanceof woltlab_core_menu_group_1.default) {
+                    if (value !== "") {
+                        return value;
+                    }
+                }
+                else if (item.selected) {
+                    return value;
+                }
+            }
+            return "";
+        }
         #keydown(event) {
             const { code, key } = event;
             // Ignore any keystrokes that are most likely keyboard shortcuts.
@@ -84,6 +136,6 @@ define(["require", "exports"], function (require, exports) {
         }
     }
     exports.WoltlabCoreMenuElement = WoltlabCoreMenuElement;
-    exports.default = WoltlabCoreMenuElement;
     window.customElements.define("woltlab-core-menu", WoltlabCoreMenuElement);
+    exports.default = WoltlabCoreMenuElement;
 });