Update MegaMenu Application Customizer: Version bump to 1.1.0, localization support added, and unnecessary files removed

- Deleted obsolete localization file for English (en-us).
- Updated version number from 1.0.4 to 1.1.0 in manifests.js and manifests.json.
- Added localization support for German (de-de) in a new localization file.
- Removed unnecessary dependencies from manifests.
- Added .gitignore to exclude build artifacts and temporary files.
This commit is contained in:
Torsten Brendgen
2026-07-16 22:43:31 +02:00
parent a2b8165262
commit 97d2b6b5d2
53 changed files with 1071 additions and 1065 deletions
+50 -12
View File
@@ -13,25 +13,29 @@ const LOG_SOURCE: string = 'MegaMenuRenderer';
export class MegaMenuRenderer {
private static readonly hoverOpenDelayMs: number = 140;
private static readonly hoverCloseDelayMs: number = 180;
private _container: HTMLElement | undefined;
private _documentKeydownHandler: ((event: KeyboardEvent) => void) | undefined;
private _documentClickHandler: ((event: MouseEvent) => void) | undefined;
private _announcer: HTMLElement | undefined;
constructor(
private menuItems: IMenuItem[],
private debug: boolean = false
) { }
public render(container: HTMLElement) {
public render(container: HTMLElement): void {
this.dispose();
this._container = container;
container.innerHTML = '';
container.id = 'CustomNavigation';
const nav = document.createElement('nav');
nav.id = 'Mega-Menu';
nav.className = 'mega-menu-main';
nav.setAttribute('role', 'navigation');
nav.setAttribute('aria-label', 'Hauptnavigation');
const topLevelUl = document.createElement('ul');
topLevelUl.className = 'mega-menu-top-level';
topLevelUl.setAttribute('role', 'menubar');
this.menuItems.forEach(topLevelItem => {
const topLevelLi = this.createTopLevelItem(topLevelItem);
@@ -45,6 +49,25 @@ export class MegaMenuRenderer {
this.createScreenReaderAnnouncer();
}
public dispose(): void {
if (this._documentKeydownHandler) {
document.removeEventListener('keydown', this._documentKeydownHandler);
this._documentKeydownHandler = undefined;
}
if (this._documentClickHandler) {
document.removeEventListener('click', this._documentClickHandler);
this._documentClickHandler = undefined;
}
if (this._announcer && this._announcer.parentElement) {
this._announcer.parentElement.removeChild(this._announcer);
}
this._announcer = undefined;
if (this._container) {
this._container.innerHTML = '';
}
this._container = undefined;
}
private createTopLevelItem(item: IMenuItem): HTMLLIElement {
const li = document.createElement('li');
const hasChildren = item.hasChildren() && item.items && item.items.length > 0;
@@ -57,7 +80,6 @@ export class MegaMenuRenderer {
isActive ? 'is-active' : undefined,
isCurrent ? 'is-current' : undefined
);
li.setAttribute('role', 'none');
const topElement = this.createTopLevelElement(item, hasChildren, isActive, isCurrent);
li.appendChild(topElement);
@@ -83,7 +105,6 @@ export class MegaMenuRenderer {
element.setAttribute('tabindex', '0');
}
element.setAttribute('role', 'menuitem');
element.textContent = item.label;
element.className = this.joinClasses(
element.className,
@@ -111,7 +132,6 @@ export class MegaMenuRenderer {
private createMegaMenu(parentItem: IMenuItem): HTMLDivElement {
const megaMenuDiv = document.createElement('div');
megaMenuDiv.className = 'mega-menu';
megaMenuDiv.setAttribute('role', 'menu');
megaMenuDiv.setAttribute('aria-expanded', 'false');
megaMenuDiv.setAttribute('aria-label', parentItem.label + ' Unterkategorien');
@@ -213,7 +233,7 @@ export class MegaMenuRenderer {
}
private attachEventListeners(): void {
const headings = document.querySelectorAll('#Mega-Menu > ul > li > a, #Mega-Menu > ul > li > span[role="menuitem"]');
const headings = document.querySelectorAll('#Mega-Menu > ul > li > a, #Mega-Menu > ul > li > .menu-item-text');
for (let i = 0; i < headings.length; i++) {
const heading = headings[i] as HTMLElement;
@@ -336,7 +356,7 @@ export class MegaMenuRenderer {
}
private attachGlobalKeyboardNavigation(): void {
document.addEventListener('keydown', (e: KeyboardEvent) => {
this._documentKeydownHandler = (e: KeyboardEvent): void => {
const activeElement = document.activeElement as HTMLElement;
if (e.key === 'Escape') {
@@ -380,11 +400,12 @@ export class MegaMenuRenderer {
}
}
}
});
};
document.addEventListener('keydown', this._documentKeydownHandler);
}
private attachGlobalClickOutsideHandler(): void {
document.addEventListener('click', (e: MouseEvent) => {
this._documentClickHandler = (e: MouseEvent): void => {
const eventTarget = e.target as Element;
const isInsideNavigation = !!(eventTarget && typeof eventTarget.closest === 'function' &&
eventTarget.closest('#CustomNavigation'));
@@ -399,7 +420,8 @@ export class MegaMenuRenderer {
debugLog(this.debug, LOG_SOURCE, 'Closing menu because of outside click.');
this.closeAllMegaMenus();
}
});
};
document.addEventListener('click', this._documentClickHandler);
}
private openMegaMenu(trigger: HTMLElement, menu: HTMLElement): void {
@@ -407,6 +429,7 @@ export class MegaMenuRenderer {
trigger.setAttribute('aria-expanded', 'true');
menu.setAttribute('aria-expanded', 'true');
menu.classList.add('js-open');
this.announce((trigger.textContent || '') + ' geöffnet');
debugLog(this.debug, LOG_SOURCE, 'Menu opened:', trigger.textContent);
}
@@ -414,6 +437,7 @@ export class MegaMenuRenderer {
trigger.setAttribute('aria-expanded', 'false');
menu.setAttribute('aria-expanded', 'false');
menu.classList.remove('js-open');
this.announce((trigger.textContent || '') + ' geschlossen');
debugLog(this.debug, LOG_SOURCE, 'Menu closed:', trigger.textContent);
}
@@ -514,7 +538,9 @@ export class MegaMenuRenderer {
}
private createScreenReaderAnnouncer(): void {
if (document.getElementById('mega-menu-sr-announcer')) {
const existingAnnouncer: HTMLElement = document.getElementById('mega-menu-sr-announcer');
if (existingAnnouncer) {
this._announcer = existingAnnouncer;
return;
}
@@ -524,7 +550,19 @@ export class MegaMenuRenderer {
srAnnouncer.setAttribute('aria-atomic', 'true');
srAnnouncer.className = 'sr-only';
document.body.appendChild(srAnnouncer);
this._announcer = srAnnouncer;
debugLog(this.debug, LOG_SOURCE, 'Screen reader announcer is ready.');
}
private announce(message: string): void {
if (this._announcer) {
this._announcer.textContent = '';
window.setTimeout((): void => {
if (this._announcer) {
this._announcer.textContent = message;
}
}, 20);
}
}
}