UNPKG

@postnord/web-components

Version:

PostNord Web Components

421 lines (420 loc) 17.7 kB
/*! * Built with Stencil * By PostNord. */ import { h, Host, forceUpdate, } from "@stencil/core"; import { uuidv4 } from "../../../../globals/helpers"; import { angle_down, arrow_left, arrow_right } from "pn-design-assets/pn-assets/icons.js"; /** * @deprecated This component will be removed in v8. Please use the `pn-tablist` instead. * @see {@link https://portal.postnord.com/web-components/?path=/docs/components-navigation-tablist--docs} Use `pn-tablist` instead. * * @slot dropdown-item - Place your `pn-page-nav-dropdown-item` components here. */ export class PnPageNav { constructor() { this.currentSelection = undefined; this.showScrollArrows = false; this.showLeftArrow = false; this.showRightArrow = false; this.dropdownOpen = false; this.dropdownLinks = []; this.dropdownActive = false; this.value = undefined; this.dropdown = false; this.navid = `pn-page-nav-${uuidv4()}`; } mo; navContainer; navWrapper; navItems = []; dropdownButton; dropdownEl; dropdownItems; dropdownEls; eventHandler = this.keyboardEvents.bind(this); globalEventHandler = this.globalEvents.bind(this); activeBg; hoverBg; scrollRegistered = false; hostElement; /** Emits the value of the selected item. */ navchange; changeHandler({ target }) { this.currentSelection = target.closest('pn-page-nav-item'); if (target.value) this.value = target.value; if (this.dropdownOpen) this.dropdownOpen = false; } handleResize() { this.rerender(); } valueHandler() { if (!this.value) this.currentSelection = null; this.calcHighlight(this.currentSelection, this.activeBg); this.navchange.emit(this.value); if (!this.dropdownActive) return; this.isDropdownItemActive(); } dropdownHandler() { if (this.dropdownOpen) { requestAnimationFrame(() => { this.addGlobalEventListeners(); }); return; } this.removeGlobalEventListeners(); } /* ---------------------------------------LIFECYCLE--------------------------------------- */ componentWillLoad() { if (!this.dropdown) return; this.dropdownEls = Array.from(this.hostElement.querySelectorAll('pn-page-nav-dropdown-item')); if (this.dropdownEls.length) { this.dropdownActive = true; this.initiateDropdown(); } } componentDidLoad() { if (this.mo) this.mo.disconnect(); this.mo = new MutationObserver(() => { forceUpdate(this.hostElement); this.setActiveNavItem(); this.rerender(); }); this.mo.observe(this.hostElement, { childList: true, subtree: true }); this.navWrapper = this.hostElement.querySelector('.pn-page-nav'); this.navContainer = this.hostElement.querySelector('.pn-page-nav-items'); this.activeBg = this.hostElement.querySelector('.pn-pn-active'); this.hoverBg = this.hostElement.querySelector('.pn-pn-hover'); this.hostElement.addEventListener('mouseover', ({ target }) => this.calcHighlight(target, this.hoverBg)); this.setActiveNavItem(); this.rerender(); } /* ---------------------------------------/LIFECYCLE--------------------------------------- */ setActiveNavItem() { this.navItems = Array.from(this.hostElement.querySelectorAll('pn-page-nav-item')); this.navItems.forEach(navItemEl => { if (this.value === navItemEl.value) { this.currentSelection = navItemEl; } else { navItemEl.removeAttribute('selected'); } navItemEl .querySelector('a') .addEventListener('focus', ({ target }) => this.calcHighlight(target, this.hoverBg)); }); /* -----------------dropdown------------------ */ if (!this.dropdownActive) return; this.dropdownItems = Array.from(this.hostElement.querySelectorAll('.pn-page-nav-dropdown-item')); //Check active state on each item this.isDropdownItemActive(); //Store all values to check if dropdown button should be active this.dropdownLinks = this.dropdownItems.map((el, i) => { el.setAttribute('data-index', `${i}`); return el.closest('pn-page-nav-dropdown-item').value; }); /* -----------------/dropdown------------------ */ } rerender() { requestAnimationFrame(() => { this.calcHighlight(this.currentSelection, this.activeBg); this.scrollArrowRender(); }); } /*---------------------------------------HIGHLIGHT LOGIC-------------------------------------------*/ calcHighlight(el, bgEl) { if (!el?.closest('pn-page-nav-item')) { bgEl?.classList.add('hidden'); return; } if (bgEl) bgEl.classList.remove('hidden'); const elRect = el.closest('pn-page-nav-item').getBoundingClientRect(); const { left: hostLeft } = this.navContainer.getBoundingClientRect(); const { left: navLeft, height: navHeight, width: navWidth } = elRect; const offset = navLeft - hostLeft + this.navContainer.scrollLeft; bgEl.style.setProperty('transform', `translate(${offset}px, -50%`); bgEl.style.setProperty('width', `${navWidth}px`); bgEl.style.setProperty('height', `${navHeight}px`); } /*---------------------------------------/HIGHLIGHT LOGIC-------------------------------------------*/ /*---------------------------------------SCROLL ARROW LOGIC-------------------------------------------*/ scrollArrowRender() { if (!this.navWrapper) return; if (this.navWrapper.scrollWidth > this.navWrapper.clientWidth) { this.showScrollArrows = true; if (!this.scrollRegistered) { this.navWrapper.addEventListener('scroll', this.scrollArrowRender.bind(this)); this.scrollRegistered = true; } const amountScrolled = Math.round(this.navWrapper.scrollWidth - this.navWrapper.scrollLeft); const distanceToEnd = amountScrolled - this.navWrapper.clientWidth; const distanceToStart = this.navWrapper.scrollLeft; this.showLeftArrow = distanceToStart > 0; this.showRightArrow = distanceToEnd > 0; return; } this.showLeftArrow = false; this.showRightArrow = false; this.showScrollArrows = false; } scroll(val) { let amount = this.navWrapper.scrollLeft + val; this.navWrapper.scroll({ left: amount, behavior: 'smooth', }); } scrollArrowClasses() { let classNames = 'pn-pn-arrows '; if (this.showLeftArrow) classNames += 'pn-pn-left-visible '; if (this.showRightArrow) classNames += 'pn-pn-right-visible '; return classNames; } /*---------------------------------------/SCROLL ARROW LOGIC-------------------------------------------*/ /* ---------------------------------------DROPDOWN LOGIC--------------------------------------- */ initiateDropdown() { requestAnimationFrame(() => { this.dropdownButton = this.hostElement.querySelector('.pn-page-nav-dropdown-button'); this.dropdownEl = this.hostElement.querySelector('.pn-page-nav-dropdown'); this.addDropdownEventListeners(); }); } toggleDropdown() { this.dropdownOpen = !this.dropdownOpen; } isDropdownItemActive() { this.dropdownEls.forEach(el => { if (el.value && this.value === el.value) { el.setAttribute('active', 'true'); return; } el.removeAttribute('active'); }); } /* -----------------events------------------ */ /* -----------------temporary events------------------ */ addDropdownEventListeners() { this.hostElement.addEventListener('keydown', this.eventHandler); this.hostElement.addEventListener('click', this.eventHandler); } addGlobalEventListeners() { const root = this.hostElement.getRootNode(); root.addEventListener('focusin', this.globalEventHandler); root.addEventListener('keydown', this.globalEventHandler); root.addEventListener('click', this.globalEventHandler); } removeGlobalEventListeners() { const root = this.hostElement.getRootNode(); root.removeEventListener('focusin', this.globalEventHandler); root.removeEventListener('keydown', this.globalEventHandler); root.removeEventListener('click', this.globalEventHandler); } /* -----------------/temporary events------------------ */ /* -----------------Open dropdown with keyboard------------------ */ keyboardEvents(e) { const target = e.composedPath()[0]; // As long as the dropdown is closed, we only want it to react to keyboard input // is the user has focus on the button if (e.type === 'keydown') { if (!this.dropdownOpen && target === this.dropdownButton && ['ArrowUp', 'ArrowDown'].includes(e.code)) { this.dropdownOpen = true; requestAnimationFrame(() => { this.focusNextDropdownItem(); }); } } } /* -----------------/Open dropdown with keyboard------------------ */ globalEvents(e) { const target = e.composedPath()[0]; if (e.type === 'keydown' && e.code === 'Escape') { this.dropdownOpen = false; this.dropdownButton.focus(); } if (e.code === 'ArrowDown') this.focusNextDropdownItem(); if (e.code === 'ArrowUp') this.focusPrevDropdownItem(); if ((e.type === 'click' || e.type === 'focusin') && !this.dropdownEl.contains(target)) { this.dropdownOpen = false; } } /* -----------------/events------------------ */ /* -----------------focusing------------------ */ focusNextDropdownItem() { const { activeElement } = this.hostElement.getRootNode(); if (!activeElement.classList.contains('pn-page-nav-dropdown-item')) { this.dropdownItems[0].focus(); return; } // focus next item const index = parseInt(activeElement.getAttribute('data-index')); if (index < this.dropdownItems.length - 1) { this.dropdownItems[index + 1].focus(); } } focusPrevDropdownItem() { const { activeElement } = this.hostElement.getRootNode(); if (!activeElement.classList.contains('dropdown-item')) { this.dropdownItems[this.dropdownItems.length - 1].focus(); } // focus previous item const index = parseInt(activeElement.getAttribute('data-index')); if (index > 0) { this.dropdownItems[index - 1].focus(); return; } this.dropdownButton.focus(); } /* -----------------/focusing------------------ */ dropdownButtonClasses() { let classList = 'pn-page-nav-dropdown-button '; if (this.dropdownLinks.includes(this.value)) classList += 'pn-page-nav-dropdown-active '; return classList; } dropdownClasses() { let classList = 'pn-page-nav-dropdown '; if (this.dropdownOpen) classList += 'pn-page-nav-dropdown-open '; return classList; } /* ---------------------------------------/DROPDOWN LOGIC--------------------------------------- */ render() { return (h(Host, { key: '9c4bd4aeecdc22a7c1d10816d70fbb514ab613f3' }, h("div", { key: '0902e5f87e7eb1a3fa1efa06441f63634722bdd2', class: "pn-page-nav-wrapper" }, h("nav", { key: 'c3bc0a339f0339d22c637866ba153f87f5c06420', class: "pn-page-nav" }, this.dropdownActive && (h("button", { key: 'e1bc1da3bdf6b279d2550124af6908ba338d2a8a', type: "button", class: this.dropdownButtonClasses(), onClick: () => this.toggleDropdown(), "aria-controls": "page-nav-dropdown", "aria-expanded": `${this.dropdownOpen}` }, this.dropdown, h("pn-icon", { key: 'f913dd0b5abb7815f889029c597c26ec92d26dfa', icon: angle_down, color: "white", small: true }), h("div", { key: '42e5cbd460c914cbb763a2552159a4d07d4005f7', class: "pn-page-nav-divider" }))), h("ul", { key: 'f4ef8ae4b301e0dc6da25d6be0f5c3c3e93c5c75', class: "pn-page-nav-items" }, h("slot", { key: '4aaa8d2f47534f95ea40ea96ce48d278aca3c139' }), h("li", { key: 'b18fd3fe98b2890a26a19cc7384b5a41dec7ea79', class: "pn-pn-bg pn-pn-active", role: "presentation" }), h("li", { key: '5cf37103941a756c161d95ced420b2b21d58461d', class: "pn-pn-bg pn-pn-hover", role: "presentation" }))), this.showScrollArrows && (h("div", { key: '1a851e18abc8b3093b2e70b4efb549443a5feccf', class: this.scrollArrowClasses() }, h("button", { key: '6a0639c1d254c46fe4d446094eff00896693c977', class: "pn-pn-arrow-left", onClick: () => this.scroll(-120), tabindex: "-1" }, h("pn-icon", { key: '26d489b47136dd30e7ce764b508fa5104e652588', icon: arrow_left, color: "white" })), h("button", { key: 'dc835f73f754ea6acabd8596e924c461207c4387', class: "pn-pn-arrow-right", onClick: () => this.scroll(120), tabindex: "-1" }, h("pn-icon", { key: '2df638385b40a1c0eaa01d795a1d0b796c50e7b5', icon: arrow_right, color: "blue700" }))))), this.dropdownActive && (h("ul", { key: 'b9088ed407e8952ee95ba7a2239bea98c49066b0', id: this.navid, class: this.dropdownClasses() }, h("slot", { key: '0bf168ec966e4bb68e5a4e18bcee76807e7c96d7', name: "dropdown-item" }))))); } static get is() { return "pn-page-nav"; } static get originalStyleUrls() { return { "$": ["pn-page-nav.scss"] }; } static get styleUrls() { return { "$": ["pn-page-nav.css"] }; } static get properties() { return { "value": { "type": "string", "mutable": true, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Currently active menu item value" }, "attribute": "value", "reflect": false }, "dropdown": { "type": "any", "mutable": false, "complexType": { "original": "string | boolean", "resolved": "boolean | string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Pass a string which will be the text on the dropdown button." }, "attribute": "dropdown", "reflect": false, "defaultValue": "false" }, "navid": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Set a unique HTML ID." }, "attribute": "navid", "reflect": false, "defaultValue": "`pn-page-nav-${uuidv4()}`" } }; } static get states() { return { "currentSelection": {}, "showScrollArrows": {}, "showLeftArrow": {}, "showRightArrow": {}, "dropdownOpen": {}, "dropdownLinks": {}, "dropdownActive": {} }; } static get events() { return [{ "method": "navchange", "name": "navchange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emits the value of the selected item." }, "complexType": { "original": "string", "resolved": "string", "references": {} } }]; } static get elementRef() { return "hostElement"; } static get watchers() { return [{ "propName": "value", "methodName": "valueHandler" }, { "propName": "dropdownOpen", "methodName": "dropdownHandler" }]; } static get listeners() { return [{ "name": "itemselection", "method": "changeHandler", "target": undefined, "capture": false, "passive": false }, { "name": "resize", "method": "handleResize", "target": "window", "capture": false, "passive": true }]; } } //# sourceMappingURL=pn-page-nav.js.map