Prototype for a new mobile menu
authorAlexander Ebert <ebert@woltlab.com>
Sun, 12 Dec 2021 13:49:03 +0000 (14:49 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 12 Dec 2021 13:49:03 +0000 (14:49 +0100)
12 files changed:
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/header.tpl
com.woltlab.wcf/templates/pageHeader.tpl
global.d.ts
ts/WoltLabSuite/Core/Ui/Mobile.ts
ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts [new file with mode: 0644]
ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts
ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js [new file with mode: 0644]

index 947a8e640b6a7900a850798cef6ca9f70d94f5a3..c4d8f38d4844c9010f9e5a3d4c17819d1f25b1dd 100644 (file)
@@ -16,6 +16,7 @@
        var ENABLE_PRODUCTION_DEBUG_MODE = {if ENABLE_PRODUCTION_DEBUG_MODE}true{else}false{/if};
        var ENABLE_DEVELOPER_TOOLS = {if ENABLE_DEVELOPER_TOOLS}true{else}false{/if};
        var WSC_API_VERSION = {@WSC_API_VERSION};
+       var PAGE_TITLE = '{PAGE_TITLE|language|encodeJS}';
        
        var REACTION_TYPES = {@$__wcf->getReactionHandler()->getReactionsJSVariable()};
        
index d9f66db755a12f88f53ef39bea73171b04fe7377..5de4b28037e9f4b625bcc8455c5d893185fc9fba 100644 (file)
        
        {include file='headInclude'}
        
+<style>
+
+</style>
+
        {if !$canonicalURL|empty}
                <link rel="canonical" href="{$canonicalURL}">
        {/if}
index 8512fbbf1a5443257bad4c4ee3573829400bca2f..4887cec3631c2f218a5f812c7bc4c4f0eff4dd37 100644 (file)
@@ -20,6 +20,9 @@
                        require(['WoltLabSuite/Core/Ui/Page/Header/Fixed'], function(UiPageHeaderFixed) {
                                UiPageHeaderFixed.init();
                        });
+                       require(["WoltLabSuite/Core/Ui/Page/Menu/Main"], ({ PageMenuMain }) => {
+                               new PageMenuMain();
+                       })
                </script>
        </header>
        
index 0f1e135d7683e41558d8f0390cc0ff2c7595ce73..fec4b85e6fb33e19b8958e09d22a17c383cfd675 100644 (file)
@@ -13,6 +13,7 @@ declare global {
     ENABLE_DEBUG_MODE: boolean;
     ENABLE_DEVELOPER_TOOLS: boolean;
     LANGUAGE_ID: number;
+    PAGE_TITLE: string;
     REACTION_TYPES: {
       [key: string]: Reaction;
     };
index bda5ab1f35089f111fea7fa3bba967f13a054d58..3f15dc50e176602305165af1fc5beacf45340690 100644 (file)
@@ -14,13 +14,13 @@ import * as EventHandler from "../Event/Handler";
 import * as UiAlignment from "./Alignment";
 import UiCloseOverlay from "./CloseOverlay";
 import * as UiDropdownReusable from "./Dropdown/Reusable";
-import UiPageMenuMain from "./Page/Menu/Main";
+//import UiPageMenuMain from "./Page/Menu/Main";
 import UiPageMenuUser from "./Page/Menu/User";
 import * as UiScreen from "./Screen";
 
 interface MainMenuMorePayload {
   identifier: string;
-  handler: UiPageMenuMain;
+  handler: any;//UiPageMenuMain;
 }
 
 let _dropdownMenu: HTMLUListElement | null = null;
@@ -30,7 +30,7 @@ let _enabledLGTouchNavigation = false;
 let _enableMobileMenu = false;
 const _knownMessages = new WeakSet<HTMLElement>();
 let _mobileSidebarEnabled = false;
-let _pageMenuMain: UiPageMenuMain;
+//let _pageMenuMain: UiPageMenuMain;
 let _pageMenuUser: UiPageMenuUser | undefined = undefined;
 let _messageGroups: HTMLCollection | null = null;
 const _sidebars: HTMLElement[] = [];
@@ -163,7 +163,7 @@ function initMessages(): void {
 
 function initMobileMenu(): void {
   if (_enableMobileMenu) {
-    _pageMenuMain = new UiPageMenuMain();
+    //_pageMenuMain = new UiPageMenuMain();
 
     if (UiPageMenuUser.hasValidMenu()) {
       _pageMenuUser = new UiPageMenuUser();
@@ -378,7 +378,7 @@ export function setup(enableMobileMenu: boolean): void {
 export function enable(): void {
   _enabled = true;
   if (_enableMobileMenu) {
-    _pageMenuMain.enable();
+    //_pageMenuMain.enable();
     _pageMenuUser?.enable();
   }
 }
@@ -398,7 +398,7 @@ export function enableShadow(): void {
 export function disable(): void {
   _enabled = false;
   if (_enableMobileMenu) {
-    _pageMenuMain.disable();
+    //_pageMenuMain.disable();
     _pageMenuUser?.disable();
   }
 }
diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts
new file mode 100644 (file)
index 0000000..7aa8eca
--- /dev/null
@@ -0,0 +1,76 @@
+import { PageMenuProvider } from "./Provider";
+import { createFocusTrap, FocusTrap } from "focus-trap";
+import { scrollDisable, scrollEnable } from "../../Screen";
+
+export class PageMenuContainer {
+  private readonly container = document.createElement("div");
+  private readonly content = document.createElement("div");
+  private focusTrap?: FocusTrap = undefined;
+  private readonly provider: PageMenuProvider;
+
+  constructor(provider: PageMenuProvider) {
+    this.provider = provider;
+  }
+
+  open(): void {
+    this.buildElements();
+
+    this.content.innerHTML = "";
+    this.content.append(this.provider.getContent());
+    this.provider.getMenuButton().setAttribute("aria-expanded", "true");
+
+    scrollDisable();
+
+    this.container.hidden = false;
+    this.getFocusTrap().activate();
+  }
+
+  close(): void {
+    this.provider.getMenuButton().setAttribute("aria-expanded", "false");
+
+    scrollEnable();
+
+    this.container.hidden = true;
+    this.getFocusTrap().deactivate();
+  }
+
+  toggle(): void {
+    if (this.container.hidden) {
+      this.open();
+    } else {
+      this.close();
+    }
+  }
+
+  private buildElements(): void {
+    if (this.container.classList.contains("pageMenuContainer")) {
+      return;
+    }
+
+    this.container.classList.add("pageMenuContainer");
+    this.container.hidden = true;
+    this.container.addEventListener("click", (event) => {
+      if (event.target === this.container) {
+        this.close();
+      }
+    });
+
+    this.content.classList.add("pageMenuContent");
+
+    this.container.append(this.content);
+
+    document.body.append(this.container);
+  }
+
+  private getFocusTrap(): FocusTrap {
+    if (this.focusTrap === undefined) {
+      this.focusTrap = createFocusTrap(this.content, {
+        allowOutsideClick: true,
+      });
+    }
+
+    return this.focusTrap;
+  }
+}
+
+export default PageMenuContainer;
index 68194d366c36c0a7ebee1504826930334ae565a1..e1ca3e1d5b2383ebb8eb97cb8751299fa5649ae7 100644 (file)
 /**
  * Provides the touch-friendly fullscreen main menu.
  *
- * @author  Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module  WoltLabSuite/Core/Ui/Page/Menu/Main
+ * @author Alexander Ebert
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Page/Menu/Main
  */
 
-import * as Core from "../../../Core";
-import DomUtil from "../../../Dom/Util";
+import PageMenuContainer from "./Container";
+import { PageMenuProvider } from "./Provider";
 import * as Language from "../../../Language";
-import UiPageMenuAbstract from "./Abstract";
+import DomUtil from "../../../Dom/Util";
 
-class UiPageMenuMain extends UiPageMenuAbstract {
-  private hasItems = false;
-  private readonly navigationList: HTMLOListElement;
-  private readonly title: HTMLElement;
+type MenuItem = {
+  active: boolean;
+  children: MenuItem[];
+  counter: number;
+  link?: string;
+  title: string;
+};
+
+function normalizeMenuItem(menuItem: HTMLElement): MenuItem {
+  const anchor = menuItem.querySelector(".boxMenuLink") as HTMLAnchorElement;
+  const title = anchor.querySelector(".boxMenuLinkTitle")!.textContent as string;
+
+  let counter = 0;
+  const outstandingItems = anchor.querySelector(".boxMenuLinkOutstandingItems");
+  if (outstandingItems) {
+    counter = +outstandingItems.textContent!.replace(/[^0-9]/, "");
+  }
+
+  const subMenu = menuItem.querySelector("ol");
+  let children: MenuItem[] = [];
+  if (subMenu instanceof HTMLOListElement) {
+    children = Array.from(subMenu.children).map((subMenuItem: HTMLElement) => {
+      return normalizeMenuItem(subMenuItem);
+    });
+  }
+
+  // `link.href` represents the computed link, not the raw value.
+  const href = anchor.getAttribute("href");
+  let link: string | undefined = undefined;
+  if (href && href !== "#") {
+    link = anchor.href;
+  }
+
+  const active = menuItem.classList.contains("active");
+
+  return {
+    active,
+    children,
+    counter,
+    link,
+    title,
+  };
+}
+
+export class PageMenuMain implements PageMenuProvider {
+  private readonly container: PageMenuContainer;
+  private readonly mainMenu: HTMLElement;
 
-  /**
-   * Initializes the touch-friendly fullscreen main menu.
-   */
   constructor() {
-    super("com.woltlab.wcf.MainMenuMobile", "pageMainMenuMobile", "#pageHeader .mainMenu");
+    this.mainMenu = document.querySelector(".mainMenu")!;
 
-    this.title = document.getElementById("pageMainMenuMobilePageOptionsTitle") as HTMLElement;
-    if (this.title !== null) {
-      this.navigationList = document.querySelector(".jsPageNavigationIcons") as HTMLOListElement;
-    }
+    this.container = new PageMenuContainer(this);
+
+    this.mainMenu.addEventListener("click", (event) => {
+      event.preventDefault();
 
-    this.button.setAttribute("aria-label", Language.get("wcf.menu.page"));
-    this.button.setAttribute("role", "button");
+      this.container.toggle();
+    });
   }
 
-  open(event?: MouseEvent): boolean {
-    if (!super.open(event)) {
-      return false;
-    }
+  getContent(): DocumentFragment {
+    const fragment = document.createDocumentFragment();
 
-    if (this.title === null) {
-      return true;
-    }
+    fragment.append(...this.buildMainMenu());
+
+    return fragment;
+  }
 
-    this.hasItems = this.navigationList && this.navigationList.childElementCount > 0;
+  getMenuButton(): HTMLElement {
+    return this.mainMenu;
+  }
+
+  private buildMainMenu(): HTMLElement[] {
+    const menu = this.mainMenu.querySelector(".boxMenu")!;
+    const menuItems: MenuItem[] = Array.from(menu.children).map((element: HTMLElement) => {
+      return normalizeMenuItem(element);
+    });
 
-    if (this.hasItems) {
-      while (this.navigationList.childElementCount) {
-        const item = this.navigationList.children[0];
+    const nav = document.createElement("nav");
+    nav.classList.add("pageMenuMainNavigation");
+    nav.setAttribute("aria-label", window.PAGE_TITLE);
+    nav.setAttribute("role", "navigation");
+    nav.append(this.buildMenuItemList(menuItems));
 
-        item.classList.add("menuOverlayItem", "menuOverlayItemOption");
-        item.addEventListener("click", (ev) => {
-          ev.stopPropagation();
+    return [nav];
+  }
 
-          this.close();
-        });
+  private buildMenuItemList(menuItems: MenuItem[]): HTMLUListElement {
+    const list = document.createElement("ul");
+    list.classList.add("pageMenuMainItemList");
 
-        const link = item.children[0];
-        link.classList.add("menuOverlayItemLink");
-        link.classList.add("box24");
+    menuItems
+      .filter((menuItem) => {
+        // Remove links that have no target (`#`) and do not contain any children.
+        if (!menuItem.link && menuItem.children.length === 0) {
+          return false;
+        }
 
-        link.children[1].classList.remove("invisible");
-        link.children[1].classList.add("menuOverlayItemTitle");
+        return true;
+      })
+      .forEach((menuItem) => {
+        list.append(this.buildMenuItem(menuItem));
+      });
+
+    return list;
+  }
 
-        this.title.insertAdjacentElement("afterend", item);
+  private buildMenuItem(menuItem: MenuItem): HTMLLIElement {
+    const listItem = document.createElement("li");
+    listItem.classList.add("pageMenuMainItem");
+
+    if (menuItem.link) {
+      const link = document.createElement("a");
+      link.classList.add("pageMenuMainItemLink");
+      link.href = menuItem.link;
+      link.textContent = menuItem.title;
+      if (menuItem.active) {
+        link.setAttribute("aria-current", "page");
       }
 
-      DomUtil.show(this.title);
+      listItem.append(link);
     } else {
-      DomUtil.hide(this.title);
+      const label = document.createElement("span");
+      label.textContent = menuItem.title;
+
+      listItem.append(label);
     }
 
-    return true;
-  }
+    if (menuItem.children.length) {
+      listItem.classList.add("pageMenuMainItemExpandable");
 
-  close(event?: Event): boolean {
-    if (!super.close(event)) {
-      return false;
-    }
+      const menuId = DomUtil.getUniqueId();
 
-    if (this.hasItems) {
-      DomUtil.hide(this.title);
+      const button = document.createElement("a");
+      button.classList.add("pageMenuMainItemToggle");
+      button.tabIndex = 0;
+      button.setAttribute("role", "button");
+      button.setAttribute("aria-expanded", "false");
+      button.setAttribute("aria-controls", menuId);
+      button.setAttribute("aria-label", Language.get("TODO"));
+      button.innerHTML = '<span class="icon icon24 fa-angle-down" aria-hidden="true"></span>';
 
-      let item = this.title.nextElementSibling;
-      while (item && item.classList.contains("menuOverlayItemOption")) {
-        item.classList.remove("menuOverlayItem", "menuOverlayItemOption");
-        item.removeEventListener("click", (ev) => {
-          ev.stopPropagation();
+      const list = this.buildMenuItemList(menuItem.children);
+      list.id = menuId;
+      list.hidden = true;
 
-          this.close();
-        });
+      button.addEventListener("click", (event) => {
+        event.preventDefault();
 
-        const link = item.children[0];
-        link.classList.remove("menuOverlayItemLink");
-        link.classList.remove("box24");
+        this.toggleList(button, list);
+      });
+      button.addEventListener("keydown", (event) => {
+        if (event.key === "Enter" || event.key === " ") {
+          event.preventDefault();
 
-        link.children[1].classList.add("invisible");
-        link.children[1].classList.remove("menuOverlayItemTitle");
+          button.click();
+        }
+      });
 
-        this.navigationList.appendChild(item);
+      list.addEventListener("keydown", (event) => {
+        if (event.key === "Escape") {
+          event.preventDefault();
+          event.stopPropagation();
 
-        item = item.nextElementSibling;
-      }
+          this.toggleList(button, list);
+        }
+      });
+
+      listItem.append(button, list);
     }
 
-    return true;
+    return listItem;
   }
-}
 
-Core.enableLegacyInheritance(UiPageMenuMain);
+  private toggleList(button: HTMLAnchorElement, list: HTMLUListElement): void {
+    if (list.hidden) {
+      button.setAttribute("aria-expanded", "true");
+      list.hidden = false;
+    } else {
+      button.setAttribute("aria-expanded", "false");
+      list.hidden = true;
+
+      if (document.activeElement !== button) {
+        button.focus();
+      }
+    }
+  }
+}
 
-export = UiPageMenuMain;
+export default PageMenuMain;
diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts
new file mode 100644 (file)
index 0000000..ac074c8
--- /dev/null
@@ -0,0 +1,5 @@
+export interface PageMenuProvider {
+  getContent(): DocumentFragment;
+
+  getMenuButton(): HTMLElement;
+}
index e89509b56dd0b89e149e01443b3b58b29929669f..98c916b1b61247ad9b0066c804626dcb31842132 100644 (file)
@@ -6,7 +6,7 @@
  * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module  WoltLabSuite/Core/Ui/Mobile
  */
-define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../Environment", "../Event/Handler", "./Alignment", "./CloseOverlay", "./Dropdown/Reusable", "./Page/Menu/Main", "./Page/Menu/User", "./Screen"], function (require, exports, tslib_1, Core, Listener_1, Environment, EventHandler, UiAlignment, CloseOverlay_1, UiDropdownReusable, Main_1, User_1, UiScreen) {
+define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../Environment", "../Event/Handler", "./Alignment", "./CloseOverlay", "./Dropdown/Reusable", "./Page/Menu/User", "./Screen"], function (require, exports, tslib_1, Core, Listener_1, Environment, EventHandler, UiAlignment, CloseOverlay_1, UiDropdownReusable, User_1, UiScreen) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.removeShadow = exports.rebuildShadow = exports.disableShadow = exports.disable = exports.enableShadow = exports.enable = exports.setup = void 0;
@@ -17,7 +17,6 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../
     UiAlignment = (0, tslib_1.__importStar)(UiAlignment);
     CloseOverlay_1 = (0, tslib_1.__importDefault)(CloseOverlay_1);
     UiDropdownReusable = (0, tslib_1.__importStar)(UiDropdownReusable);
-    Main_1 = (0, tslib_1.__importDefault)(Main_1);
     User_1 = (0, tslib_1.__importDefault)(User_1);
     UiScreen = (0, tslib_1.__importStar)(UiScreen);
     let _dropdownMenu = null;
@@ -27,7 +26,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../
     let _enableMobileMenu = false;
     const _knownMessages = new WeakSet();
     let _mobileSidebarEnabled = false;
-    let _pageMenuMain;
+    //let _pageMenuMain: UiPageMenuMain;
     let _pageMenuUser = undefined;
     let _messageGroups = null;
     const _sidebars = [];
@@ -137,7 +136,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../
     }
     function initMobileMenu() {
         if (_enableMobileMenu) {
-            _pageMenuMain = new Main_1.default();
+            //_pageMenuMain = new UiPageMenuMain();
             if (User_1.default.hasValidMenu()) {
                 _pageMenuUser = new User_1.default();
             }
@@ -317,7 +316,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../
     function enable() {
         _enabled = true;
         if (_enableMobileMenu) {
-            _pageMenuMain.enable();
+            //_pageMenuMain.enable();
             _pageMenuUser === null || _pageMenuUser === void 0 ? void 0 : _pageMenuUser.enable();
         }
     }
@@ -337,7 +336,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../
     function disable() {
         _enabled = false;
         if (_enableMobileMenu) {
-            _pageMenuMain.disable();
+            //_pageMenuMain.disable();
             _pageMenuUser === null || _pageMenuUser === void 0 ? void 0 : _pageMenuUser.disable();
         }
     }
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js
new file mode 100644 (file)
index 0000000..a2ce396
--- /dev/null
@@ -0,0 +1,61 @@
+define(["require", "exports", "focus-trap", "../../Screen"], function (require, exports, focus_trap_1, Screen_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.PageMenuContainer = void 0;
+    class PageMenuContainer {
+        constructor(provider) {
+            this.container = document.createElement("div");
+            this.content = document.createElement("div");
+            this.focusTrap = undefined;
+            this.provider = provider;
+        }
+        open() {
+            this.buildElements();
+            this.content.innerHTML = "";
+            this.content.append(this.provider.getContent());
+            this.provider.getMenuButton().setAttribute("aria-expanded", "true");
+            (0, Screen_1.scrollDisable)();
+            this.container.hidden = false;
+            this.getFocusTrap().activate();
+        }
+        close() {
+            this.provider.getMenuButton().setAttribute("aria-expanded", "false");
+            (0, Screen_1.scrollEnable)();
+            this.container.hidden = true;
+            this.getFocusTrap().deactivate();
+        }
+        toggle() {
+            if (this.container.hidden) {
+                this.open();
+            }
+            else {
+                this.close();
+            }
+        }
+        buildElements() {
+            if (this.container.classList.contains("pageMenuContainer")) {
+                return;
+            }
+            this.container.classList.add("pageMenuContainer");
+            this.container.hidden = true;
+            this.container.addEventListener("click", (event) => {
+                if (event.target === this.container) {
+                    this.close();
+                }
+            });
+            this.content.classList.add("pageMenuContent");
+            this.container.append(this.content);
+            document.body.append(this.container);
+        }
+        getFocusTrap() {
+            if (this.focusTrap === undefined) {
+                this.focusTrap = (0, focus_trap_1.createFocusTrap)(this.content, {
+                    allowOutsideClick: true,
+                });
+            }
+            return this.focusTrap;
+        }
+    }
+    exports.PageMenuContainer = PageMenuContainer;
+    exports.default = PageMenuContainer;
+});
index 6ce989588bc0b998b61aaaea32242d4d44295e94..e0cd9a127121e8b6e3536bbdfb7fd36445a4f8a8 100644 (file)
 /**
  * Provides the touch-friendly fullscreen main menu.
  *
- * @author  Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module  WoltLabSuite/Core/Ui/Page/Menu/Main
+ * @author Alexander Ebert
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Page/Menu/Main
  */
-define(["require", "exports", "tslib", "../../../Core", "../../../Dom/Util", "../../../Language", "./Abstract"], function (require, exports, tslib_1, Core, Util_1, Language, Abstract_1) {
+define(["require", "exports", "tslib", "./Container", "../../../Language", "../../../Dom/Util"], function (require, exports, tslib_1, Container_1, Language, Util_1) {
     "use strict";
-    Core = (0, tslib_1.__importStar)(Core);
-    Util_1 = (0, tslib_1.__importDefault)(Util_1);
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.PageMenuMain = void 0;
+    Container_1 = (0, tslib_1.__importDefault)(Container_1);
     Language = (0, tslib_1.__importStar)(Language);
-    Abstract_1 = (0, tslib_1.__importDefault)(Abstract_1);
-    class UiPageMenuMain extends Abstract_1.default {
-        /**
-         * Initializes the touch-friendly fullscreen main menu.
-         */
+    Util_1 = (0, tslib_1.__importDefault)(Util_1);
+    function normalizeMenuItem(menuItem) {
+        const anchor = menuItem.querySelector(".boxMenuLink");
+        const title = anchor.querySelector(".boxMenuLinkTitle").textContent;
+        let counter = 0;
+        const outstandingItems = anchor.querySelector(".boxMenuLinkOutstandingItems");
+        if (outstandingItems) {
+            counter = +outstandingItems.textContent.replace(/[^0-9]/, "");
+        }
+        const subMenu = menuItem.querySelector("ol");
+        let children = [];
+        if (subMenu instanceof HTMLOListElement) {
+            children = Array.from(subMenu.children).map((subMenuItem) => {
+                return normalizeMenuItem(subMenuItem);
+            });
+        }
+        // `link.href` represents the computed link, not the raw value.
+        const href = anchor.getAttribute("href");
+        let link = undefined;
+        if (href && href !== "#") {
+            link = anchor.href;
+        }
+        const active = menuItem.classList.contains("active");
+        return {
+            active,
+            children,
+            counter,
+            link,
+            title,
+        };
+    }
+    class PageMenuMain {
         constructor() {
-            super("com.woltlab.wcf.MainMenuMobile", "pageMainMenuMobile", "#pageHeader .mainMenu");
-            this.hasItems = false;
-            this.title = document.getElementById("pageMainMenuMobilePageOptionsTitle");
-            if (this.title !== null) {
-                this.navigationList = document.querySelector(".jsPageNavigationIcons");
-            }
-            this.button.setAttribute("aria-label", Language.get("wcf.menu.page"));
-            this.button.setAttribute("role", "button");
+            this.mainMenu = document.querySelector(".mainMenu");
+            this.container = new Container_1.default(this);
+            this.mainMenu.addEventListener("click", (event) => {
+                event.preventDefault();
+                this.container.toggle();
+            });
         }
-        open(event) {
-            if (!super.open(event)) {
-                return false;
-            }
-            if (this.title === null) {
+        getContent() {
+            const fragment = document.createDocumentFragment();
+            fragment.append(...this.buildMainMenu());
+            return fragment;
+        }
+        getMenuButton() {
+            return this.mainMenu;
+        }
+        buildMainMenu() {
+            const menu = this.mainMenu.querySelector(".boxMenu");
+            const menuItems = Array.from(menu.children).map((element) => {
+                return normalizeMenuItem(element);
+            });
+            const nav = document.createElement("nav");
+            nav.classList.add("pageMenuMainNavigation");
+            nav.setAttribute("aria-label", window.PAGE_TITLE);
+            nav.setAttribute("role", "navigation");
+            nav.append(this.buildMenuItemList(menuItems));
+            return [nav];
+        }
+        buildMenuItemList(menuItems) {
+            const list = document.createElement("ul");
+            list.classList.add("pageMenuMainItemList");
+            menuItems
+                .filter((menuItem) => {
+                // Remove links that have no target (`#`) and do not contain any children.
+                if (!menuItem.link && menuItem.children.length === 0) {
+                    return false;
+                }
                 return true;
-            }
-            this.hasItems = this.navigationList && this.navigationList.childElementCount > 0;
-            if (this.hasItems) {
-                while (this.navigationList.childElementCount) {
-                    const item = this.navigationList.children[0];
-                    item.classList.add("menuOverlayItem", "menuOverlayItemOption");
-                    item.addEventListener("click", (ev) => {
-                        ev.stopPropagation();
-                        this.close();
-                    });
-                    const link = item.children[0];
-                    link.classList.add("menuOverlayItemLink");
-                    link.classList.add("box24");
-                    link.children[1].classList.remove("invisible");
-                    link.children[1].classList.add("menuOverlayItemTitle");
-                    this.title.insertAdjacentElement("afterend", item);
+            })
+                .forEach((menuItem) => {
+                list.append(this.buildMenuItem(menuItem));
+            });
+            return list;
+        }
+        buildMenuItem(menuItem) {
+            const listItem = document.createElement("li");
+            listItem.classList.add("pageMenuMainItem");
+            if (menuItem.link) {
+                const link = document.createElement("a");
+                link.classList.add("pageMenuMainItemLink");
+                link.href = menuItem.link;
+                link.textContent = menuItem.title;
+                if (menuItem.active) {
+                    link.setAttribute("aria-current", "page");
                 }
-                Util_1.default.show(this.title);
+                listItem.append(link);
             }
             else {
-                Util_1.default.hide(this.title);
+                const label = document.createElement("span");
+                label.textContent = menuItem.title;
+                listItem.append(label);
             }
-            return true;
+            if (menuItem.children.length) {
+                listItem.classList.add("pageMenuMainItemExpandable");
+                const menuId = Util_1.default.getUniqueId();
+                const button = document.createElement("a");
+                button.classList.add("pageMenuMainItemToggle");
+                button.tabIndex = 0;
+                button.setAttribute("role", "button");
+                button.setAttribute("aria-expanded", "false");
+                button.setAttribute("aria-controls", menuId);
+                button.setAttribute("aria-label", Language.get("TODO"));
+                button.innerHTML = '<span class="icon icon24 fa-angle-down" aria-hidden="true"></span>';
+                const list = this.buildMenuItemList(menuItem.children);
+                list.id = menuId;
+                list.hidden = true;
+                button.addEventListener("click", (event) => {
+                    event.preventDefault();
+                    this.toggleList(button, list);
+                });
+                button.addEventListener("keydown", (event) => {
+                    if (event.key === "Enter" || event.key === " ") {
+                        event.preventDefault();
+                        button.click();
+                    }
+                });
+                list.addEventListener("keydown", (event) => {
+                    if (event.key === "Escape") {
+                        event.preventDefault();
+                        event.stopPropagation();
+                        this.toggleList(button, list);
+                    }
+                });
+                listItem.append(button, list);
+            }
+            return listItem;
         }
-        close(event) {
-            if (!super.close(event)) {
-                return false;
+        toggleList(button, list) {
+            if (list.hidden) {
+                button.setAttribute("aria-expanded", "true");
+                list.hidden = false;
             }
-            if (this.hasItems) {
-                Util_1.default.hide(this.title);
-                let item = this.title.nextElementSibling;
-                while (item && item.classList.contains("menuOverlayItemOption")) {
-                    item.classList.remove("menuOverlayItem", "menuOverlayItemOption");
-                    item.removeEventListener("click", (ev) => {
-                        ev.stopPropagation();
-                        this.close();
-                    });
-                    const link = item.children[0];
-                    link.classList.remove("menuOverlayItemLink");
-                    link.classList.remove("box24");
-                    link.children[1].classList.add("invisible");
-                    link.children[1].classList.remove("menuOverlayItemTitle");
-                    this.navigationList.appendChild(item);
-                    item = item.nextElementSibling;
+            else {
+                button.setAttribute("aria-expanded", "false");
+                list.hidden = true;
+                if (document.activeElement !== button) {
+                    button.focus();
                 }
             }
-            return true;
         }
     }
-    Core.enableLegacyInheritance(UiPageMenuMain);
-    return UiPageMenuMain;
+    exports.PageMenuMain = PageMenuMain;
+    exports.default = PageMenuMain;
 });
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js
new file mode 100644 (file)
index 0000000..2ae92b6
--- /dev/null
@@ -0,0 +1,4 @@
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+});