export interface Breadcrumb { url: string; text: string; } let breadcrumbsWidth = null; import {Component, Element, h, Listen, Prop, State, Watch} from '@stencil/core'; import {BreadcrumbVariants} from '../../utils/utils'; @Component({ tag: 'hy-breadcrumbs', styleUrl: 'hy-breadcrumbs.scss', shadow: false, }) export class HyBreadcrumbs { private _dataItems: Breadcrumb[]; @Prop() dataItems: Breadcrumb[] | string; @Prop() variant: BreadcrumbVariants = BreadcrumbVariants.default as any; @Prop() headerstyle: string = 'with-sidebar'; @State() menuOpen: boolean = false; @Element() el: HTMLElement; @Watch('dataItems') arrayDataWatcher(newValue: Breadcrumb[] | string) { if (typeof newValue === 'string') { this._dataItems = JSON.parse(newValue); } else { this._dataItems = newValue; } } componentWillLoad() { this.arrayDataWatcher(this.dataItems); } componentDidLoad() { let hyMainDiv = this.el.closest('.hy-main'); if (hyMainDiv) { if (!hyMainDiv.classList.contains('with-sidebar')) { this.headerstyle = 'without-sidebar'; } } // Set breadcumbs width + paddings. breadcrumbsWidth = this.el.offsetWidth + 64; const layoutContentElement = document.getElementsByClassName('layout-content')[0] as HTMLElement; const moreButton = document.querySelectorAll('.breadcrumb-item-dropdown-button')[0]; if (layoutContentElement) { if (breadcrumbsWidth >= layoutContentElement.offsetWidth) { moreButton.setAttribute('aria-hidden', 'false'); this.adjustBreadcrumbsMenuVisibility(); } } } adjustBreadcrumbsMenuVisibility(showMenu = true) { // Show ... and Hide intermediate links if (!showMenu) { this.closeMoreMenu(); } const crumbContainer = document.querySelectorAll('.hy-breadcrumbs')[0]; const moreDotsItem = document.querySelectorAll('#more')[0]; const moreDotsItemWrapper = document.querySelectorAll('.breadcrumb-item__more')[0]; if (moreDotsItem) { if (showMenu) { crumbContainer.classList.add('is-condensed'); moreDotsItem.classList.add('visible'); moreDotsItemWrapper.classList.add('visible'); } else { crumbContainer.classList.remove('is-condensed'); moreDotsItem.classList.remove('visible'); moreDotsItemWrapper.classList.remove('visible'); } } const intermediateItems = document.querySelectorAll('.intermediate'); if (intermediateItems) { for (let i = 0; i < intermediateItems.length; i++) { if (showMenu) { intermediateItems[i].classList.add('hidden'); } else { intermediateItems[i].classList.remove('hidden'); } } } } HomeItem(url) { const homeItemClass = ['hy-icon-wrapper', this.variant].join(' '); return ( <li class="breadcrumb-item home"> <a href={url} class={homeItemClass}> <hy-icon icon={'hy-icon-home'} class={`${this.variant}`} size={16} /> </a> <span class="breadcrumb-item__divider">/</span> </li> ); } BreadcrumbItem(label, url, className = '', withCaret = true) { const breadcrumbClass = ['breadcrumb-item', className].join(' '); if (url) { if (withCaret) { return ( <li class={breadcrumbClass}> <a href={url} class={`${this.variant}`}> {label} </a> <span class="breadcrumb-item__divider">/</span> </li> ); } else { return ( <li class={breadcrumbClass}> <a href={url} class={`${this.variant}`}> {label} </a> </li> ); } } else { return ( <li class={`${breadcrumbClass} breadcrumb-item__current`}> <a aria-current="page" href={url} class={`${this.variant}`}> {label.length > 20 ? `${label.substring(0, 19)}...` : label} </a> </li> ); } } BreadcrumbTextItem(label, className = '') { const breadcrumbClass = ['breadcrumb-item', className].join(' '); return <li class={breadcrumbClass}>{label}</li>; } DropdownMenuItem(items) { return ( <li class="breadcrumb-item__more"> <button type="button" aria-hidden="true" aria-expanded="false" id="more" key="more" class="breadcrumb-item-dropdown-button" aria-label="Open breadcrumb navigation" > <span class="breadcrumb-item-dropdown-button__content"> {/* Span is for ... */} <span></span> <hy-icon icon={'hy-icon-caret-right'} class={'breadcrumb-item-caret__drop breadcrumb-item__more__icon'} size={10} /> </span> </button> <ol class="breadcrumb-hidden-items" aria-hidden="true"> {items} </ol> <span class="breadcrumb-item__divider">/</span> </li> ); } adjustHiddenMenuWidth() { // set width to the menu area equal to the widest link + paddings const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0]; if (moreMenu) { if (document.body.scrollWidth < 480) { (moreMenu as HTMLElement).style.width = '100%'; } else { //maxIntermediateLinkWidth var maxIntermediateLinkWidth = 0; const moreMenuLinks = document.querySelectorAll('.breadcrumb-hidden-items .breadcrumb-item a'); if (moreMenuLinks) { for (let i = 0; i < moreMenuLinks.length; i++) { if (maxIntermediateLinkWidth < (moreMenuLinks[i] as HTMLElement).offsetWidth) { maxIntermediateLinkWidth = (moreMenuLinks[i] as HTMLElement).offsetWidth; } } maxIntermediateLinkWidth = maxIntermediateLinkWidth + 32 + 64; } (moreMenu as HTMLElement).style.width = maxIntermediateLinkWidth.toString().concat('px'); } } } closeMoreMenu() { const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0]; if (moreMenu) { moreMenu.classList.remove('breadcrumb-hidden-items__is-open'); this.menuOpen = false; } const moreBreadcrumb = document.querySelectorAll('#more')[0]; if (moreBreadcrumb) { moreBreadcrumb.classList.remove('is-open'); } } // When a ... is clicked, show/hide the Menu with hidden breadcrumbs @Listen('click') clickEventListener(event) { if (!event) return; const target = event.target; const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0]; const moreButton = document.querySelectorAll('.breadcrumb-item-dropdown-button')[0]; // Trigger if target is button or svg icon // TODO: Make this if prettier if ( target && (target.id === 'more' || ((target.tagName == 'svg' || 'path') && target.closest('hy-icon').classList.contains('breadcrumb-item__more__icon'))) ) { //@todo Show the menu on the right place of the screen if (moreMenu) { if (this.menuOpen) { moreMenu.classList.remove('breadcrumb-hidden-items__is-open'); moreMenu.setAttribute('aria-hidden', 'true'); moreButton.classList.remove('is-open'); moreButton.setAttribute('aria-expanded', 'false'); } else { moreMenu.classList.add('breadcrumb-hidden-items__is-open'); moreButton.classList.add('is-open'); moreMenu.setAttribute('aria-hidden', 'false'); moreButton.setAttribute('aria-expanded', 'true'); } this.menuOpen = !this.menuOpen; } } else { this.closeMoreMenu(); } event.stopPropagation(); event.stopImmediatePropagation(); } @Listen('resize', {target: 'window'}) resizeEventListener(event) { if (!event) return; const breadcrumbsElement = document.querySelectorAll('.hy-breadcrumbs')[0]; const moreButton = document.querySelectorAll('.breadcrumb-item-dropdown-button')[0]; if (breadcrumbsElement) { if (breadcrumbsWidth + 64 >= document.body.scrollWidth) { moreButton.setAttribute('aria-hidden', 'false'); this.adjustBreadcrumbsMenuVisibility(); } else { moreButton.setAttribute('aria-hidden', 'true'); this.adjustBreadcrumbsMenuVisibility(false); } } } render() { //@todo Accesibility const TOTAL_ITEMS = this._dataItems.length; const MAX_ITEMS_TO_SHOW = 3; let isMenuNeeded = TOTAL_ITEMS > MAX_ITEMS_TO_SHOW; let itemsBreadcrumbs = []; let itemsToShowInMenu = []; if (this.variant == BreadcrumbVariants.landingLarge) { // Landing pages, Large variant this._dataItems.map((x, index) => { if (index < 2) { if (index == 0) { itemsBreadcrumbs.push(this.HomeItem(x.url)); } else { itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, '', 'main')); } } }); } else { // Landing and Content pages, Standard variant this._dataItems.map((x, index) => { let breadcrumbEl = this.BreadcrumbItem(x.text, x.url, '', false); if (isMenuNeeded && index > 0 && index < TOTAL_ITEMS - 1) { itemsToShowInMenu.push(<li>{breadcrumbEl}</li>); itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, x.url, 'intermediate')); return; } else { if (index == 0) { itemsBreadcrumbs.push(this.HomeItem(x.url)); } else { itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, x.url, 'main')); } } }); } // Add items to show in breadcrumb popup in correct DOM position. itemsBreadcrumbs.splice(1, 0, this.DropdownMenuItem(itemsToShowInMenu)); const breadcrumbsClass = ['hy-breadcrumbs', this.variant, this.headerstyle].join(' '); return ( <nav aria-label="Breadcrumb" role="navigation" aria-labelledby="system-breadcrumb" class={breadcrumbsClass}> <ol class="breadcrumb-container">{itemsBreadcrumbs}</ol> </nav> ); } }