import {Component, Element, Event, EventEmitter, h, Host, Listen, Prop} from '@stencil/core'; import {MenuType} from '../../../utils/utils'; // import {MenuItemVariants} from '../../utils/utils'; @Component({ tag: 'hy-menu-item', styleUrl: 'menu-item.scss', shadow: true, }) export class MenuItem { @Element() el: HTMLElement; @Event() addBreadcrumb: EventEmitter; @Event() menuContainerActiveTrail: EventEmitter; @Event() menuContainerToggled: EventEmitter; @Event() routeClicked: EventEmitter; @Prop() inActiveTrail: boolean = false; @Prop() isActive: boolean = false; @Prop() isActiveChild?: boolean = false; @Prop() isHeading: boolean = false; @Prop() isParent: boolean = false; @Prop() isDemo: boolean = false; @Prop() menuItemAlternative: boolean = false; @Prop() menuLinkId: string = ''; @Prop() menuType: MenuType = MenuType.desktop; @Prop() menuButtonSubmenuExpand?: string = ''; @Prop({mutable: true}) ariaExpanded: boolean = false; @Prop({mutable: true, reflect: true}) depth: number = 0; @Prop({mutable: true}) hasChildren: boolean = null; @Prop({mutable: true}) label: string = ''; @Prop({mutable: true}) parentAsHeading: string = ''; @Prop({mutable: true, reflect: true}) parentExpanded: boolean = false; @Prop({mutable: true}) url: string = ''; // Listener for submenu expand button. Listener will toggle menu level // container and add its parent to breadcrumbs. @Listen('handleClick', {capture: true}) handleClick() { this.menuContainerToggled.emit({ triggerItem: this.menuLinkId, triggerType: 'add', }); const currentParent = this.el.parentNode; this.addBreadcrumb.emit({ url: this.url, label: currentParent.parentElement.getAttribute('label'), bid: this.menuLinkId, }); } componentWillLoad() { // If is-active class is added by system, add it to menu component as well. if (this.el.classList.contains('is-active')) { this.isActive = true; } // Notify breadcrumbs if item is in active trail. if (this.inActiveTrail && !this.isActive) { const currentParent = this.el.parentNode; this.addBreadcrumb.emit({ url: this.url, label: currentParent.parentElement.getAttribute('label'), bid: this.menuLinkId, }); if (this.menuType === MenuType.sidenav) { this.el.children[0].setAttribute('class', 'is-open'); } } // If current menu item is active, trigger all parent menuLevelContainer // elements in the same active-trail to open the menu. if (this.isActive) { const getParents = (elem) => { let parents = []; while (elem.parentNode && elem.parentNode.nodeName.toLowerCase() != 'hy-menu') { elem = elem.parentNode; parents.push(elem); } return parents; }; const parents = getParents(this.el); parents.forEach((element) => { if (element.tagName.toLowerCase() === 'hy-menu-item') { this.menuContainerActiveTrail.emit({ triggerItem: element.getAttribute('menu-link-id'), }); } }); // If side navigation menu item has is-active state, prepare the menu items // for the last children. if (this.menuType === MenuType.sidenav) { parents.forEach((element) => { if (element.tagName.toLowerCase() === 'hy-menu-item') { element.classList.add('is-hidden--child'); } }); // If current active menu item have children, set the child // menu-level-container open. if (this.el.children.length > 0) { this.el.children[0].setAttribute('class', 'is-open'); } // If current active menu item does not have any children, mock the // current menu item and parent menu item to behave like there would // be child menu items available. else { const parentMenuItem = this.el.parentElement.closest('hy-menu-item'); const parentMenuItemAnchor = parentMenuItem.shadowRoot.querySelector('a.hy-menu-item--sidenav'); parentMenuItem.classList.remove('is-hidden--child'); parentMenuItemAnchor.classList.add('is-active--heading'); const menuItemSiblings = Array.prototype.slice.call(this.el.closest('hy-menu-level-container').children); menuItemSiblings.forEach((element) => { if (element.tagName.toLowerCase() === 'hy-menu-item') { element.classList.add('is-active--child'); } }); } } } } componentWillRender() { // Assign depth value to current menu item instance; 1st level, 2nd level, etc. this.hasChildren = this.el.getElementsByTagName('hy-menu-level-container').length >= 1; let parentMenuItem = this.el.closest('hy-menu-item'); let nextParentMenuItem; this.depth = 0; while (parentMenuItem) { nextParentMenuItem = parentMenuItem.parentElement.closest('hy-menu-item'); if (nextParentMenuItem === parentMenuItem) { break; } else { if (nextParentMenuItem !== null) { this.parentAsHeading = nextParentMenuItem; } parentMenuItem = nextParentMenuItem; this.depth = this.depth + 1; } } } render() { switch (this.menuType) { case MenuType.desktop: return ( <Host class={{ 'is-active': this.isActive, 'hy-menu-item': true, 'hy-menu-item--alternative': this.menuItemAlternative, 'hy-menu-item--desktop': true, 'is-demo': this.isDemo, }} > <a aria-current={this.isHeading.toString()} href={this.url} class={{ 'is-active': this.isActive, 'in-active-trail': this.inActiveTrail, 'is-heading': this.isHeading, 'hy-menu-item--desktop': true, 'is-demo': this.isDemo, }} > {this.depth == 1 && ( <span class="hy-menu-item__heading__icon"> <hy-icon icon={'hy-icon-arrow-right'} size={40} /> </span> )} {this.depth == 2 && ( <span class="hy-menu-item__heading__icon"> <hy-icon icon={'hy-icon-caret-right'} size={12} /> </span> )} <span class={'hy-menu-item__label'}>{this.label}</span> </a> {this.hasChildren && <slot />} </Host> ); case MenuType.mobile: return ( <Host class={{ 'is-active': this.isActive, 'hy-menu-item': true, 'hy-menu-item--alternative': this.menuItemAlternative, 'hy-menu-item--mobile': true, 'is-demo': this.isDemo, }} > <a aria-current={this.isHeading.toString()} href={this.url} class={{ 'is-active': this.isActive, 'in-active-trail': this.inActiveTrail, 'is-heading': this.isHeading, 'hy-menu-item--mobile': true, 'is-demo': this.isDemo, }} > <span class={'hy-menu-item__label'}>{this.label}</span> </a> {this.hasChildren && ( <button aria-haspopup={'true'} aria-label={`Open submenu for ${this.label}`} onClick={() => this.handleClick()} class={'hy-menu-item__button'} > <hy-icon icon={'hy-icon-caret-right'} size={14} /> </button> )} {this.hasChildren && <slot />} </Host> ); case MenuType.sidenav: let classAttributes = [ 'hy-menu-item--sidenav', this.isDemo ? 'is-demo' : '', this.isActive ? 'is-active' : '', this.isParent ? 'is-parent' : '', this.isActiveChild ? 'is-active--child' : '', this.inActiveTrail ? 'in-active-trail' : '', this.depth != null ? 'hy-menu-item--level-' + this.depth : '', this.isActive && this.hasChildren ? 'is-active--heading' : '', ]; let anchorClassAttributes = [...classAttributes, this.isHeading ? 'is-heading' : '']; classAttributes = [...classAttributes, 'hy-menu-item']; return this.isParent ? ( <Host class={classAttributes.join(' ')}> <a href={this.url} class={anchorClassAttributes.join(' ')}> <span class={'hy-menu-item__label__icon'}> <hy-icon icon={'hy-icon-arrow-left'} fill={'currentColor'} size={18} /> </span> <span class={'hy-menu-item__label'}>{this.label}</span> </a> </Host> ) : ( <Host class={classAttributes.join(' ')}> <a aria-current={this.isHeading.toString()} href={this.url} class={anchorClassAttributes.join(' ')}> <span class={'hy-menu-item__label'}>{this.label}</span> {this.hasChildren && ( <span class={'hy-menu-item__label__icon'}> <hy-icon icon={'hy-icon-caret-right'} fill={'currentColor'} size={12} /> </span> )} </a> {this.hasChildren && <slot />} </Host> ); } } }