feat: Enhance MegaMenu with localization, caching, and new properties
- Added localization support for loading states, empty navigation, and submenu actions in both German and English. - Introduced new properties in IMenuItem for description, openInNewWindow, and external links. - Updated SPTermStorePickerService to handle cache versioning and language-specific caching. - Implemented a new MegaMenuCore service for handling term properties, cache management, and navigation URL resolution. - Refactored TaxonomyNavigationService to build menu hierarchy and filter hidden terms. - Added automated tests for MegaMenu core functionalities and static asset validation. - Removed unused ItemDictionary service. - Created a comprehensive ToDo document outlining the implementation status and future tasks.
This commit is contained in:
@@ -6,9 +6,22 @@
|
||||
import { IMenuItem } from '../../services/IMenuItem';
|
||||
import { debugLog } from '../../services/MegaMenuDebug';
|
||||
import { MegaMenuMode } from './MegaMenuMode';
|
||||
import { isNavigationUrlActive } from '../../services/MegaMenuCore';
|
||||
|
||||
const LOG_SOURCE: string = 'MegaMenuRenderer';
|
||||
|
||||
export interface IMegaMenuRendererStrings {
|
||||
navigationLabel: string;
|
||||
openNavigation: string;
|
||||
closeNavigation: string;
|
||||
openSubmenu: string;
|
||||
closeSubmenu: string;
|
||||
submenuLabel: string;
|
||||
externalLink: string;
|
||||
opened: string;
|
||||
closed: string;
|
||||
}
|
||||
|
||||
export class MegaMenuRenderer {
|
||||
private static readonly hoverOpenDelayMs: number = 140;
|
||||
private static readonly hoverCloseDelayMs: number = 180;
|
||||
@@ -16,11 +29,13 @@ export class MegaMenuRenderer {
|
||||
private _documentKeydownHandler: ((event: KeyboardEvent) => void) | undefined;
|
||||
private _documentClickHandler: ((event: MouseEvent) => void) | undefined;
|
||||
private _announcer: HTMLElement | undefined;
|
||||
private _mobileToggle: HTMLButtonElement | undefined;
|
||||
|
||||
constructor(
|
||||
private menuItems: IMenuItem[],
|
||||
private debug: boolean = false,
|
||||
private menuMode: MegaMenuMode = 'megaMenu'
|
||||
private menuMode: MegaMenuMode = 'megaMenu',
|
||||
private strings: IMegaMenuRendererStrings
|
||||
) { }
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
@@ -38,10 +53,22 @@ export class MegaMenuRenderer {
|
||||
nav.id = 'Mega-Menu';
|
||||
nav.className = this.joinClasses('mega-menu-main', 'mega-menu-mode-' + this.menuMode);
|
||||
nav.setAttribute('data-menu-mode', this.menuMode);
|
||||
nav.setAttribute('aria-label', 'Hauptnavigation');
|
||||
nav.setAttribute('aria-label', this.strings.navigationLabel);
|
||||
|
||||
this._mobileToggle = document.createElement('button');
|
||||
this._mobileToggle.type = 'button';
|
||||
this._mobileToggle.className = 'mega-menu-mobile-toggle';
|
||||
this._mobileToggle.setAttribute('aria-expanded', 'false');
|
||||
this._mobileToggle.setAttribute('aria-controls', 'Mega-Menu-Items');
|
||||
this._mobileToggle.setAttribute('aria-label', this.strings.openNavigation);
|
||||
this._mobileToggle.innerHTML = '<span class="mega-menu-mobile-icon" aria-hidden="true"></span><span class="mega-menu-mobile-label"></span>';
|
||||
const mobileLabel = this._mobileToggle.querySelector('.mega-menu-mobile-label') as HTMLElement;
|
||||
mobileLabel.textContent = this.strings.navigationLabel;
|
||||
nav.appendChild(this._mobileToggle);
|
||||
|
||||
const topLevelUl = document.createElement('ul');
|
||||
topLevelUl.className = 'mega-menu-top-level';
|
||||
topLevelUl.id = 'Mega-Menu-Items';
|
||||
|
||||
this.menuItems.forEach(topLevelItem => {
|
||||
const topLevelLi = this.createTopLevelItem(topLevelItem);
|
||||
@@ -76,6 +103,7 @@ export class MegaMenuRenderer {
|
||||
this._announcer.parentElement.removeChild(this._announcer);
|
||||
}
|
||||
this._announcer = undefined;
|
||||
this._mobileToggle = undefined;
|
||||
if (this._container) {
|
||||
this._container.innerHTML = '';
|
||||
}
|
||||
@@ -100,9 +128,20 @@ export class MegaMenuRenderer {
|
||||
li.appendChild(topElement);
|
||||
|
||||
if (hasChildren) {
|
||||
const toggle: HTMLButtonElement = document.createElement('button');
|
||||
const menuId: string = 'Mega-Menu-Panel-' + item.id.replace(/[^a-z0-9_-]/gi, '');
|
||||
toggle.type = 'button';
|
||||
toggle.className = 'menu-item-toggle';
|
||||
toggle.setAttribute('aria-haspopup', 'true');
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
toggle.setAttribute('aria-controls', menuId);
|
||||
toggle.setAttribute('aria-label', this.strings.openSubmenu + ' ' + item.label);
|
||||
toggle.innerHTML = '<span aria-hidden="true"></span>';
|
||||
li.appendChild(toggle);
|
||||
const childMenu = this.menuMode === 'flyout'
|
||||
? this.createFlyoutMenu(item)
|
||||
: this.createMegaMenu(item);
|
||||
childMenu.id = menuId;
|
||||
li.appendChild(childMenu);
|
||||
}
|
||||
|
||||
@@ -119,7 +158,9 @@ export class MegaMenuRenderer {
|
||||
} else {
|
||||
element = document.createElement('span');
|
||||
element.className = 'menu-item-text';
|
||||
element.setAttribute('tabindex', '0');
|
||||
if (!hasChildren) {
|
||||
element.setAttribute('tabindex', '0');
|
||||
}
|
||||
}
|
||||
|
||||
element.textContent = item.label;
|
||||
@@ -130,11 +171,6 @@ export class MegaMenuRenderer {
|
||||
isCurrent ? 'is-current' : undefined
|
||||
);
|
||||
|
||||
if (hasChildren) {
|
||||
element.setAttribute('aria-haspopup', 'true');
|
||||
element.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
if (isCurrent && item.url) {
|
||||
element.setAttribute('aria-current', 'page');
|
||||
}
|
||||
@@ -143,6 +179,10 @@ export class MegaMenuRenderer {
|
||||
element.title = item.hoverText;
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
this.applyLinkBehavior(element as HTMLAnchorElement, item);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -150,7 +190,7 @@ export class MegaMenuRenderer {
|
||||
const megaMenuDiv = document.createElement('div');
|
||||
megaMenuDiv.className = 'mega-menu';
|
||||
megaMenuDiv.setAttribute('aria-expanded', 'false');
|
||||
megaMenuDiv.setAttribute('aria-label', parentItem.label + ' Unterkategorien');
|
||||
megaMenuDiv.setAttribute('aria-label', parentItem.label + ' ' + this.strings.submenuLabel);
|
||||
|
||||
const gridDiv = document.createElement('div');
|
||||
gridDiv.className = 'mega-menu-grid';
|
||||
@@ -184,6 +224,7 @@ export class MegaMenuRenderer {
|
||||
const link = document.createElement('a');
|
||||
link.href = item.url;
|
||||
link.textContent = item.label;
|
||||
this.applyLinkBehavior(link, item);
|
||||
link.className = this.joinClasses(
|
||||
isActive ? 'is-active' : undefined,
|
||||
isCurrent ? 'is-current' : undefined
|
||||
@@ -207,6 +248,13 @@ export class MegaMenuRenderer {
|
||||
h3.appendChild(span);
|
||||
}
|
||||
|
||||
if (item.description) {
|
||||
const description: HTMLParagraphElement = document.createElement('p');
|
||||
description.className = 'mega-menu-description';
|
||||
description.textContent = item.description;
|
||||
categoryDiv.appendChild(description);
|
||||
}
|
||||
|
||||
categoryDiv.appendChild(h3);
|
||||
|
||||
if (item.hasChildren() && item.items && item.items.length > 0) {
|
||||
@@ -226,6 +274,7 @@ export class MegaMenuRenderer {
|
||||
|
||||
link.href = thirdLevelItem.url || '#';
|
||||
link.textContent = thirdLevelItem.label;
|
||||
this.applyLinkBehavior(link, thirdLevelItem);
|
||||
link.className = this.joinClasses(
|
||||
isLinkActive ? 'is-active' : undefined,
|
||||
isLinkCurrent ? 'is-current' : undefined
|
||||
@@ -253,7 +302,7 @@ export class MegaMenuRenderer {
|
||||
const flyout = document.createElement('div');
|
||||
flyout.className = 'mega-menu mega-menu-flyout';
|
||||
flyout.setAttribute('aria-expanded', 'false');
|
||||
flyout.setAttribute('aria-label', parentItem.label + ' Untermenü');
|
||||
flyout.setAttribute('aria-label', parentItem.label + ' ' + this.strings.submenuLabel);
|
||||
|
||||
const list = document.createElement('ul');
|
||||
list.className = 'flyout-list flyout-level-2';
|
||||
@@ -285,9 +334,19 @@ export class MegaMenuRenderer {
|
||||
li.appendChild(element);
|
||||
|
||||
if (hasChildren && item.items) {
|
||||
const toggle: HTMLButtonElement = document.createElement('button');
|
||||
const childId: string = 'Mega-Menu-Flyout-' + item.id.replace(/[^a-z0-9_-]/gi, '');
|
||||
toggle.type = 'button';
|
||||
toggle.className = 'flyout-toggle';
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
toggle.setAttribute('aria-controls', childId);
|
||||
toggle.setAttribute('aria-label', this.strings.openSubmenu + ' ' + item.label);
|
||||
toggle.innerHTML = '<span aria-hidden="true"></span>';
|
||||
li.appendChild(toggle);
|
||||
const childList = document.createElement('ul');
|
||||
childList.className = 'flyout-list flyout-level-3';
|
||||
childList.setAttribute('aria-label', item.label + ' Untermenü');
|
||||
childList.id = childId;
|
||||
childList.setAttribute('aria-label', item.label + ' ' + this.strings.submenuLabel);
|
||||
|
||||
item.items.forEach(thirdLevelItem => {
|
||||
childList.appendChild(this.createFlyoutItem(thirdLevelItem));
|
||||
@@ -306,6 +365,7 @@ export class MegaMenuRenderer {
|
||||
|
||||
if (item.url) {
|
||||
(element as HTMLAnchorElement).href = item.url;
|
||||
this.applyLinkBehavior(element as HTMLAnchorElement, item);
|
||||
} else {
|
||||
element.setAttribute('tabindex', '0');
|
||||
}
|
||||
@@ -318,9 +378,6 @@ export class MegaMenuRenderer {
|
||||
isCurrent ? 'is-current' : undefined
|
||||
);
|
||||
|
||||
if (hasChildren) {
|
||||
element.setAttribute('aria-haspopup', 'true');
|
||||
}
|
||||
if (isCurrent && item.url) {
|
||||
element.setAttribute('aria-current', 'page');
|
||||
}
|
||||
@@ -332,7 +389,7 @@ export class MegaMenuRenderer {
|
||||
}
|
||||
|
||||
private attachEventListeners(): void {
|
||||
const headings = document.querySelectorAll('#Mega-Menu > ul > li > a, #Mega-Menu > ul > li > .menu-item-text');
|
||||
const headings = document.querySelectorAll('#Mega-Menu > ul > li > .menu-item-toggle');
|
||||
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
const heading = headings[i] as HTMLElement;
|
||||
@@ -347,21 +404,17 @@ export class MegaMenuRenderer {
|
||||
|
||||
this.attachGlobalKeyboardNavigation();
|
||||
this.attachGlobalClickOutsideHandler();
|
||||
this.attachMobileToggle();
|
||||
this.attachFlyoutToggles();
|
||||
}
|
||||
|
||||
private attachKeyboardNavigation(heading: HTMLElement, megaMenu: HTMLElement): void {
|
||||
heading.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.toggleMegaMenu(heading, megaMenu);
|
||||
});
|
||||
heading.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (heading.tagName === 'A') {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.toggleMegaMenu(heading, megaMenu);
|
||||
} else if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.toggleMegaMenu(heading, megaMenu);
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
this.openMegaMenu(heading, megaMenu);
|
||||
this.focusFirstLink(megaMenu);
|
||||
@@ -375,12 +428,6 @@ export class MegaMenuRenderer {
|
||||
}
|
||||
});
|
||||
|
||||
if (heading.tagName === 'A') {
|
||||
heading.addEventListener('click', () => {
|
||||
debugLog(this.debug, LOG_SOURCE, 'Link clicked:', (heading as HTMLAnchorElement).href);
|
||||
});
|
||||
}
|
||||
|
||||
heading.addEventListener('focus', () => {
|
||||
debugLog(this.debug, LOG_SOURCE, 'Focus moved to:', heading.textContent);
|
||||
});
|
||||
@@ -444,7 +491,7 @@ export class MegaMenuRenderer {
|
||||
const isInsideThisMenu = megaMenu.contains(focusedElement);
|
||||
const isOnThisTrigger = focusedElement === heading;
|
||||
const isInAnyMegaMenu = focusedElement.closest('.mega-menu');
|
||||
const isOnAnyTopLevel = focusedElement.closest('#Mega-Menu > ul > li > a, #Mega-Menu > ul > li > span');
|
||||
const isOnAnyTopLevel = focusedElement.closest('.mega-menu-top-item');
|
||||
|
||||
if (!isInsideThisMenu && !isOnThisTrigger && !isInAnyMegaMenu && !isOnAnyTopLevel) {
|
||||
debugLog(this.debug, LOG_SOURCE, 'Closing menu because focus left the navigation.');
|
||||
@@ -467,9 +514,30 @@ export class MegaMenuRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && activeElement.closest('.mega-menu-top-item')) {
|
||||
const currentItem = activeElement.closest('.mega-menu-top-item') as HTMLElement;
|
||||
const items = document.querySelectorAll('#Mega-Menu > ul > .mega-menu-top-item');
|
||||
let currentIndex: number = -1;
|
||||
for (let index: number = 0; index < items.length; index++) {
|
||||
if (items[index] === currentItem) {
|
||||
currentIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentIndex >= 0) {
|
||||
e.preventDefault();
|
||||
const offset: number = e.key === 'ArrowRight' ? 1 : -1;
|
||||
const targetIndex: number = (currentIndex + offset + items.length) % items.length;
|
||||
const target = (items[targetIndex] as HTMLElement).querySelector('a, .menu-item-toggle') as HTMLElement;
|
||||
if (target) {
|
||||
target.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'Tab') {
|
||||
if (!e.shiftKey) {
|
||||
const currentTopLevel = activeElement.closest('#Mega-Menu > ul > li > a, #Mega-Menu > ul > li > span') as HTMLElement;
|
||||
const currentTopLevel = activeElement.closest('.menu-item-toggle') as HTMLElement;
|
||||
if (currentTopLevel) {
|
||||
const parentLi = currentTopLevel.closest('li') as HTMLElement;
|
||||
const megaMenu = parentLi.querySelector('.mega-menu.js-open') as HTMLElement;
|
||||
@@ -523,21 +591,86 @@ export class MegaMenuRenderer {
|
||||
document.addEventListener('click', this._documentClickHandler);
|
||||
}
|
||||
|
||||
private attachMobileToggle(): void {
|
||||
if (!this._mobileToggle) {
|
||||
return;
|
||||
}
|
||||
this._mobileToggle.addEventListener('click', (): void => {
|
||||
const nav: HTMLElement = this._mobileToggle.closest('#Mega-Menu') as HTMLElement;
|
||||
const isOpen: boolean = this._mobileToggle.getAttribute('aria-expanded') === 'true';
|
||||
this._mobileToggle.setAttribute('aria-expanded', String(!isOpen));
|
||||
this._mobileToggle.setAttribute('aria-label', isOpen ? this.strings.openNavigation : this.strings.closeNavigation);
|
||||
nav.classList.toggle('is-mobile-open', !isOpen);
|
||||
if (isOpen) {
|
||||
this.closeAllMegaMenus();
|
||||
}
|
||||
});
|
||||
|
||||
const nav: HTMLElement = this._mobileToggle.closest('#Mega-Menu') as HTMLElement;
|
||||
nav.addEventListener('click', (event: MouseEvent): void => {
|
||||
const target: Element = event.target as Element;
|
||||
if (target && target.closest('a') && window.innerWidth <= 767) {
|
||||
this._mobileToggle.setAttribute('aria-expanded', 'false');
|
||||
this._mobileToggle.setAttribute('aria-label', this.strings.openNavigation);
|
||||
nav.classList.remove('is-mobile-open');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private attachFlyoutToggles(): void {
|
||||
const toggles = document.querySelectorAll('#Mega-Menu .flyout-toggle');
|
||||
for (let i: number = 0; i < toggles.length; i++) {
|
||||
const toggle: HTMLButtonElement = toggles[i] as HTMLButtonElement;
|
||||
const parent: HTMLElement = toggle.parentElement as HTMLElement;
|
||||
const setOpen = (open: boolean): void => {
|
||||
toggle.setAttribute('aria-expanded', String(open));
|
||||
toggle.setAttribute('aria-label', (open ? this.strings.closeSubmenu : this.strings.openSubmenu)
|
||||
+ ' ' + this.getFlyoutToggleLabel(toggle));
|
||||
parent.classList.toggle('is-open', open);
|
||||
};
|
||||
toggle.addEventListener('click', (event: MouseEvent): void => {
|
||||
event.preventDefault();
|
||||
setOpen(toggle.getAttribute('aria-expanded') !== 'true');
|
||||
});
|
||||
toggle.addEventListener('keydown', (event: KeyboardEvent): void => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
setOpen(false);
|
||||
toggle.focus();
|
||||
}
|
||||
});
|
||||
parent.addEventListener('mouseenter', (): void => setOpen(true));
|
||||
parent.addEventListener('mouseleave', (): void => setOpen(false));
|
||||
parent.addEventListener('focusin', (): void => setOpen(true));
|
||||
parent.addEventListener('focusout', (): void => {
|
||||
window.setTimeout((): void => {
|
||||
if (!parent.contains(document.activeElement)) {
|
||||
setOpen(false);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private openMegaMenu(trigger: HTMLElement, menu: HTMLElement): void {
|
||||
this.closeAllMegaMenus(menu);
|
||||
const label: string = this.getTriggerLabel(trigger);
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
trigger.setAttribute('aria-label', this.strings.closeSubmenu + ' ' + label);
|
||||
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);
|
||||
this.announce(label + ' ' + this.strings.opened);
|
||||
debugLog(this.debug, LOG_SOURCE, 'Menu opened:', label);
|
||||
}
|
||||
|
||||
private closeMegaMenu(trigger: HTMLElement, menu: HTMLElement): void {
|
||||
const label: string = this.getTriggerLabel(trigger);
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
trigger.setAttribute('aria-label', this.strings.openSubmenu + ' ' + label);
|
||||
menu.setAttribute('aria-expanded', 'false');
|
||||
menu.classList.remove('js-open');
|
||||
this.announce((trigger.textContent || '') + ' geschlossen');
|
||||
debugLog(this.debug, LOG_SOURCE, 'Menu closed:', trigger.textContent);
|
||||
this.announce(label + ' ' + this.strings.closed);
|
||||
debugLog(this.debug, LOG_SOURCE, 'Menu closed:', label);
|
||||
}
|
||||
|
||||
private toggleMegaMenu(trigger: HTMLElement, menu: HTMLElement): void {
|
||||
@@ -550,7 +683,7 @@ export class MegaMenuRenderer {
|
||||
}
|
||||
|
||||
private closeAllMegaMenus(exceptMenu?: HTMLElement): void {
|
||||
const allTriggers = document.querySelectorAll('#Mega-Menu > ul > li > a[aria-expanded="true"], #Mega-Menu > ul > li > span[aria-expanded="true"]');
|
||||
const allTriggers = document.querySelectorAll('#Mega-Menu > ul > li > .menu-item-toggle[aria-expanded="true"]');
|
||||
for (let i = 0; i < allTriggers.length; i++) {
|
||||
const trigger = allTriggers[i] as HTMLElement;
|
||||
const menu = trigger.nextElementSibling as HTMLElement;
|
||||
@@ -586,36 +719,7 @@ export class MegaMenuRenderer {
|
||||
}
|
||||
|
||||
private isCurrentUrl(url?: string): boolean {
|
||||
const currentPath = this.normalizePath(window.location.href);
|
||||
const itemPath = this.normalizePath(url);
|
||||
|
||||
if (!currentPath || !itemPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemPath === '/') {
|
||||
return currentPath === '/';
|
||||
}
|
||||
|
||||
if (currentPath === itemPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return currentPath.indexOf(itemPath + '/') === 0;
|
||||
}
|
||||
|
||||
private normalizePath(url?: string): string {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedUrl = new URL(url, window.location.origin);
|
||||
const pathname = (parsedUrl.pathname || '/').replace(/\/+$/, '');
|
||||
return (pathname || '/').toLowerCase();
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
return isNavigationUrlActive(window.location.href, url || '', window.location.origin);
|
||||
}
|
||||
|
||||
private getColumnCount(items?: IMenuItem[]): string {
|
||||
@@ -624,6 +728,31 @@ export class MegaMenuRenderer {
|
||||
return columnCount.toString();
|
||||
}
|
||||
|
||||
private applyLinkBehavior(link: HTMLAnchorElement, item: IMenuItem): void {
|
||||
if (item.openInNewWindow) {
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
}
|
||||
if (item.external) {
|
||||
const currentLabel: string = link.getAttribute('aria-label') || item.label;
|
||||
link.setAttribute('aria-label', currentLabel + ' (' + this.strings.externalLink + ')');
|
||||
}
|
||||
}
|
||||
|
||||
private getTriggerLabel(trigger: HTMLElement): string {
|
||||
const labelElement: HTMLElement = trigger.parentElement
|
||||
? trigger.parentElement.querySelector('.menu-item-link, .menu-item-text') as HTMLElement
|
||||
: undefined;
|
||||
return labelElement ? labelElement.textContent || '' : '';
|
||||
}
|
||||
|
||||
private getFlyoutToggleLabel(toggle: HTMLElement): string {
|
||||
const labelElement: HTMLElement = toggle.parentElement
|
||||
? toggle.parentElement.querySelector('.flyout-link') as HTMLElement
|
||||
: undefined;
|
||||
return labelElement ? labelElement.textContent || '' : '';
|
||||
}
|
||||
|
||||
private joinClasses(...classNames: Array<string | undefined>): string {
|
||||
const filteredClassNames: string[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user