export interface ShortcutLinks { shortcut_title: string; shortcut_url: string; shortcut_is_external: string; shortcut_aria_label: string; } export interface DesktopLinks { label: string; labelExtra: string; url: string; description: string; menuLinkId: string; isActive: string; shortcutsTitle: string; closeButtonTitle: string; items: Array<DesktopLinks>; shortcuts: Array<ShortcutLinks>; } import {ColorVariant} from '../../../utils/utils'; import {Component, h, Element, Prop, State, Watch} from '@stencil/core'; @Component({ tag: 'hy-desktop-menu-links', styleUrl: 'hy-desktop-menu-links.scss', shadow: true, }) export class HyDesktopMenuLinks { @Element() el: HTMLElement; /* First level menu links to be displayed on Desktop screens. * */ @Prop() dataDesktopLinks: DesktopLinks[] | string; private _dataDesktopLinks: DesktopLinks[]; @State() firstLevelLinksList: Array<object> = []; @State() menuLinkItems: Array<object> = []; @State() hasToolbar: boolean = false; @State() isDesktopMenuOpen: boolean = false; private _submenuLeftMargin: number = 32; private _headerBorderOffset: number = 0; @Watch('dataDesktopLinks') dataDesktopLinksWatcher(data: DesktopLinks[] | string) { this._dataDesktopLinks = typeof data === 'string' ? JSON.parse(data) : data; } componentWillLoad() { this.dataDesktopLinksWatcher(this.dataDesktopLinks); } /* @Listen('resize', {target: 'window'}) resizeEventListener(event) { if (!event) return; const breadcrumbsElement = document.querySelectorAll('.hy-breadcrumbs')[0]; if (breadcrumbsElement) { if (breadcrumbsWidth + 64 >= document.body.scrollWidth) { this.adjustBreadcrumbsMenuVisibility(true); } else { this.adjustBreadcrumbsMenuVisibility(false); } } } */ showBackdropShadow(state = 'close', top = 0) { let hyHeader = this.el.closest('.hy-site-header') as HTMLElement; let hyBackdropDiv = hyHeader.children[0] as HTMLElement; if (hyBackdropDiv) { if (state === 'open') { hyBackdropDiv.classList.add('is-active'); hyBackdropDiv.style.top = `${top}px`; } if (state === 'close') { hyBackdropDiv.classList.remove('is-active'); hyBackdropDiv.style.top = '0'; } } } handleDesktopMenuClose() { /* this.isDesktopMenuOpen = false; this.showBackdropShadow(); const menuItems = this.el.shadowRoot.querySelectorAll(`.desktop-menu-link`); const menuPanelItems = this.el.shadowRoot.querySelectorAll('.hy-desktop-menu-panel'); const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[aria-expanded="true"]`) as HTMLElement; // Return focus to the button of the last desktop panel that was active. if (activeMenuItem !== null) activeMenuItem.focus(); // Reset elements by removing the active classes. menuItems.forEach((item) => { item.classList.remove('desktop-menu-link--is-active'); item.setAttribute('aria-expanded', 'false'); }); menuPanelItems.forEach((item) => { item.classList.remove('hy-desktop-menu-panel--is-active'); item.setAttribute('aria-hidden', 'true'); }); */ } handleDesktopMenuToggle(id) { this.isDesktopMenuOpen = true; const menuItems = this.el.shadowRoot.querySelectorAll(`.desktop-menu-link`); const menuPanelItems = this.el.shadowRoot.querySelectorAll('.hy-desktop-menu-panel'); // all panels const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[link-id="${id}"]`) as HTMLElement; const activeMenuItemSibling = activeMenuItem.nextElementSibling as HTMLElement; // current panel // Reset elements by removing the active classes. menuItems.forEach((item) => { item.classList.remove('desktop-menu-link--is-active'); item.setAttribute('aria-expanded', 'false'); }); menuPanelItems.forEach((item) => { item.classList.remove('hy-desktop-menu-panel--is-active'); item.setAttribute('aria-hidden', 'true'); }); // Add active classes to the currently active item and its sibling element. activeMenuItem.classList.add('desktop-menu-link--is-active'); activeMenuItem.setAttribute('aria-expanded', 'true'); activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active'); if (this.hasToolbar) { activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active--has-toolbar'); } activeMenuItemSibling.setAttribute('aria-hidden', 'false'); // Add panels top value automatically with the correct header height const headerHeight = `${ this.el.parentElement.offsetTop + this.el.parentElement.offsetHeight + this._headerBorderOffset }px`; activeMenuItemSibling.style.top = headerHeight; // Add shadow backdrop let rect = activeMenuItemSibling.getBoundingClientRect(); this.showBackdropShadow('open', rect.bottom); // Position submenu block under the activated top menu item. let activeButtonRect = activeMenuItem.getBoundingClientRect(); const menuPanelLeftPosition = activeButtonRect.left - this._submenuLeftMargin; activeMenuItemSibling.style.paddingLeft = `${menuPanelLeftPosition}px`; // Position shortcuts block. let shortcutsDiv = activeMenuItemSibling.querySelectorAll('ul.shortcuts-panel')[0] as HTMLElement; // shortcuts block if (shortcutsDiv) { let subMenuDiv = activeMenuItemSibling.querySelectorAll( '.hy-desktop-menu-panel__desktop-menu__menu-items' )[0] as HTMLElement; // 2nd level menu block let spaceLeftAfterSubmenu = subMenuDiv.getBoundingClientRect().right + shortcutsDiv.offsetWidth; if (spaceLeftAfterSubmenu >= document.body.scrollWidth) { // Shortcuts should be placed to the left. let shortcutsLeftPosition = subMenuDiv.getBoundingClientRect().left - shortcutsDiv.offsetWidth; shortcutsDiv.style.left = shortcutsLeftPosition.toString().concat('px'); } else { // Shortcuts should be placed to the right. let shortcutsLeftPosition = subMenuDiv.getBoundingClientRect().right; shortcutsDiv.style.left = shortcutsLeftPosition.toString().concat('px'); } } } handleDesktopMenuClick(id) { const activeMenuItem = this.el.shadowRoot.querySelector(`.desktop-menu-link[link-id="${id}"]`); const activeMenuItemSibling = activeMenuItem.nextElementSibling as HTMLElement; // current panel if (!this.isDesktopMenuOpen) { // Add active classes to the currently active item and its sibling element. this.isDesktopMenuOpen = true; activeMenuItem.classList.add('desktop-menu-link--is-active'); activeMenuItem.setAttribute('aria-expanded', 'true'); activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active'); if (this.hasToolbar) { activeMenuItemSibling.classList.add('hy-desktop-menu-panel--is-active--has-toolbar'); } activeMenuItemSibling.setAttribute('aria-hidden', 'false'); let rect = activeMenuItemSibling.getBoundingClientRect(); this.showBackdropShadow('open', rect.bottom); } else { // Remove active classes to the currently active item and its sibling element. this.isDesktopMenuOpen = false; activeMenuItem.classList.remove('desktop-menu-link--is-active'); activeMenuItem.setAttribute('aria-expanded', 'false'); activeMenuItemSibling.classList.remove('hy-desktop-menu-panel--is-active'); if (this.hasToolbar) { activeMenuItemSibling.classList.remove('hy-desktop-menu-panel--is-active--has-toolbar'); } activeMenuItemSibling.setAttribute('aria-hidden', 'true'); this.showBackdropShadow(); } } componentDidLoad() { let hyToolbar = document.querySelectorAll('#toolbar-administration')[0]; if (hyToolbar) { this.hasToolbar = true; } const links = this._dataDesktopLinks as Array<DesktopLinks>; let menuLinkItems = []; if (links) { links.map( ({ menuLinkId: id, shortcuts, items, url, description, label, labelExtra, isActive, shortcutsTitle, closeButtonTitle, }) => { let classAttributes = [ 'desktop-menu-link', isActive === 'true' ? 'desktop-menu-link--is-active-trail' : '', ].join(' '); menuLinkItems.push( <li> <button type="button" class={classAttributes} link-id={id} onClick={() => this.handleDesktopMenuClick(id)} onMouseOver={() => this.handleDesktopMenuToggle(id)} onFocus={() => this.handleDesktopMenuToggle(id)} aria-expanded="false" > <span class="desktop-menu-link__label">{label}</span> <hy-icon icon={'hy-icon-caret-down'} size={32} /> </button> <div class="hy-desktop-menu-panel" onMouseLeave={() => this.handleDesktopMenuClose()} aria-hidden="true"> <div class="hy-desktop-menu-panel__desktop-menu"> <div class="hy-desktop-menu-panel__desktop-menu__menu-items"> <a aria-current={label} href={url} class="hy-desktop-menu-panel__desktop-menu__first-level-menu-item" menu-link-id={id} > <span class="heading-icon"> <hy-icon icon={'hy-icon-arrow-right'} size={40} /> </span> {labelExtra ? <span class="label">{labelExtra}</span> : <span class="label">{label}</span>} {description && <span class="description">{description}</span>} </a> <ul class={'hy-desktop-menu-panel__desktop-menu__second-level-menu'} menu-link-id={id}> {items.map(({label, url}) => ( <li> <a href={url}> <span class="heading-icon"> <hy-icon icon={'hy-icon-caret-right'} size={12} /> </span> <span class="label">{label}</span> </a> </li> ))} </ul> </div> {shortcuts.length > 0 && ( <ul class="shortcuts-panel"> <h2 class="shortcuts-panel__title">{shortcutsTitle}</h2> {shortcuts.map( ({shortcut_title, shortcut_url, shortcut_is_external, shortcut_aria_label}, index) => { let target = shortcut_is_external ? '_blank' : '_self'; let shortcutClass = [ 'shortcuts-panel__shortcut-item', index == 0 ? 'shortcuts-panel__shortcut-item__first' : '', ].join(' '); return ( <li class={shortcutClass}> <a aria-current={shortcut_aria_label} href={shortcut_url} class="shortcut-item__link" target={target} aria-label={shortcut_aria_label} > <span class="label">{shortcut_title}</span> <span class="icon"> <hy-icon icon={'hy-icon-arrow-right'} size={24} /> </span> </a> </li> ); } )} </ul> )} </div> <button onClick={() => this.handleDesktopMenuClose()} class={{ 'hy-desktop-menu-panel__panel-toggle': true, }} aria-label="Close menu" > <span class="hy-desktop-menu-panel__panel-toggle__label"> <span class="hy-desktop-menu-panel__panel-toggle__label__title">{closeButtonTitle}</span> <hy-icon icon={'hy-icon-remove'} size={20} fill={ColorVariant.black} /> </span> </button> </div> </li> ); } ); } this.menuLinkItems = menuLinkItems; } render() { return ( <nav role={'navigation'} class="hy-site-header__menu-desktop"> <ul class="hy-site-header__menu-desktop-container">{this.menuLinkItems}</ul> </nav> ); } }