@postnord/web-components
Version:
PostNord Web Components
313 lines (308 loc) • 18.6 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
'use strict';
var index = require('./index-DVv2io0H.js');
var index$1 = require('./index.cjs.js');
var arrow_left = require('./arrow_left-Crkz484c.js');
var arrow_right = require('./arrow_right-CABi9Wkp.js');
var chevron_down = require('./chevron_down-BLU6_yOG.js');
const pnPageNavCss = "pn-page-nav{display:block;position:relative;font-size:clamp(0.8em, 2vw, 1em);z-index:5}.pn-page-nav-wrapper{transform:translateZ(0);font-size:inherit;max-width:100%;overflow:hidden;position:relative;display:inline-flex;border-radius:3em}.pn-page-nav{display:flex;align-items:center;border-radius:3em;min-height:3em;background:#0d234b;border:0.0625em solid #d3cecb;position:relative;overflow-x:auto;scroll-snap-type:x mandatory}.pn-page-nav::-webkit-scrollbar{display:none}.pn-page-nav ul.pn-page-nav-items{display:flex;list-style-type:none;padding:0.25em;margin:0;position:relative}.pn-page-nav-items>.pn-pn-bg{position:absolute;top:50%;transform:translateY(-50%);will-change:transform;left:0;border-radius:3em;transition:width 0.25s cubic-bezier(0.29, 0.15, 0.24, 0.97), transform 0.25s cubic-bezier(0.29, 0.15, 0.24, 0.97), background 0.15s, opacity 0.15s, box-shadow 0.15s}.pn-page-nav-items>.pn-pn-bg.pn-pn-active{box-shadow:0px 0.3px 0.9px rgba(0, 0, 0, 0.1), 0px 1.6px 3.6px rgba(0, 0, 0, 0.13);background:#005d92;z-index:1}.pn-page-nav-items>.pn-pn-bg.pn-pn-hover{background:#ffffff;opacity:0;border:0.0625em solid transparent;z-index:0}.pn-page-nav-items>.pn-pn-bg.hidden{opacity:0}@media (hover: hover){ul.pn-page-nav-items:focus-within .pn-pn-hover,ul.pn-page-nav-items:hover .pn-pn-hover{opacity:0.12}}pn-page-nav .pn-pn-arrows{position:absolute;top:50%;transform:translateY(-50%);left:0;height:100%;width:100%;z-index:3;display:flex;justify-content:space-between;align-items:center;pointer-events:none;padding:0.125em 0.1em}pn-page-nav .pn-pn-arrows svg{width:1.5em}pn-page-nav .pn-pn-arrows svg.pn-icon-svg path{fill:#005d92}pn-page-nav .pn-pn-arrows>*{transition:transform 0.2s, opacity 0.2s, background 0.1s, box-shadow 0.1s;pointer-events:all;height:100%;aspect-ratio:1;background:#ffffff;display:flex;align-items:center;justify-content:center;outline:none;cursor:pointer;opacity:0;will-change:transform;-webkit-tap-highlight-color:transparent;border-radius:50%;border:0.0625em solid #0d234b}pn-page-nav .pn-pn-arrows>*.pn-pn-arrow-left{transform:translateX(-100%)}pn-page-nav .pn-pn-arrows>*.pn-pn-arrow-right{transform:translateX(100%)}@media screen and (max-width: 30em){pn-page-nav .pn-pn-arrows>*{border:none}pn-page-nav .pn-pn-arrows>* pn-icon .pn-icon-svg{width:100%;height:2em;animation:arrow 0.6s ease-in-out alternate infinite}pn-page-nav .pn-pn-arrows>* pn-icon .pn-icon-svg path{fill:#ffffff}pn-page-nav .pn-pn-arrows>*.pn-pn-arrow-left{background:linear-gradient(to left, transparent, #0d234b 60%);border-radius:50% 0 0 50%}pn-page-nav .pn-pn-arrows>*.pn-pn-arrow-right{background:linear-gradient(to right, transparent, #0d234b 40%);border-radius:0 50% 50% 0}}pn-page-nav .pn-pn-arrows.pn-pn-left-visible .pn-pn-arrow-left,pn-page-nav .pn-pn-arrows.pn-pn-right-visible .pn-pn-arrow-right{opacity:1;transform:translateX(0)}@keyframes arrow{to{transform:translateX(10%)}}button.pn-page-nav-dropdown-button{font-size:1em;border-radius:3em 0 0 3em;background:#0d234b;color:#ffffff;padding:0.5em 1em 0.5em 1.5em;cursor:pointer;display:flex;align-items:center;border:none;transition:background 0.15s;outline:none;height:100%;position:relative;margin:0 0.5em 0 0;font-weight:500}button.pn-page-nav-dropdown-button pn-icon{margin-left:0.5em}button.pn-page-nav-dropdown-button:hover,button.pn-page-nav-dropdown-button:focus{background:#263655}button.pn-page-nav-dropdown-button:focus{box-shadow:inset 0 0 0 0.1em #005d92, inset 0 0 0 0.2em #d3cecb}button.pn-page-nav-dropdown-button.pn-page-nav-dropdown-active{background:#005d92}button.pn-page-nav-dropdown-button.pn-page-nav-dropdown-active:hover,button.pn-page-nav-dropdown-button.pn-page-nav-dropdown-active:focus{background:#005d92}.pn-page-nav-divider{height:60%;background:#d3cecb;width:0.1em;position:absolute;right:0;transform:translateX(50%)}ul.pn-page-nav-dropdown{position:absolute;left:0;top:110%;background:#0d234b;border-radius:0.5em;margin:0;padding:0;list-style-type:none;box-shadow:0px 6.4000000954px 14.3999996185px 0px rgba(0, 0, 0, 0.1294117647), 0px 1.2000000477px 3.5999999046px 0px rgba(0, 0, 0, 0.1019607843);font-size:inherit;display:none}ul.pn-page-nav-dropdown.pn-page-nav-dropdown-open{display:block}";
const PnPageNav = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.navchange = index.createEvent(this, "navchange");
}
mo;
navContainer;
navWrapper;
navItems = [];
dropdownButton;
dropdownEl;
dropdownItems;
dropdownEls;
eventHandler = this.keyboardEvents.bind(this);
globalEventHandler = this.globalEvents.bind(this);
activeBg;
hoverBg;
scrollRegistered = false;
get hostElement() { return index.getElement(this); }
currentSelection;
showScrollArrows = false;
showLeftArrow = false;
showRightArrow = false;
dropdownOpen = false;
dropdownLinks = [];
dropdownActive = false;
/** Currently active menu item value */
value;
/** Pass a string which will be the text on the dropdown button. */
dropdown = false;
/** Set a unique HTML ID. */
navid = `pn-page-nav-${index$1.uuidv4()}`;
/** 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(() => {
index.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) {
/** @ts-ignore This component will be removed soon, no need to deal with this. */
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 (index.h(index.Host, { key: '84aa7d48fb35062728db273ed972832a2387c17f' }, index.h("div", { key: 'e3b748a26a92de0be7e5f34ac07bbd02d79ac051', class: "pn-page-nav-wrapper" }, index.h("nav", { key: '7f368d201e6b4f6fdb8d3bbb88a82fd083248b66', class: "pn-page-nav" }, this.dropdownActive && (index.h("button", { key: 'b195fad1c0f965fd206781c2f68bf1e096ec1145', type: "button", class: this.dropdownButtonClasses(), onClick: () => this.toggleDropdown(), "aria-controls": "page-nav-dropdown", "aria-expanded": `${this.dropdownOpen}` }, this.dropdown, index.h("pn-icon", { key: '7d861107335a022e21f00f9ad724adfb7ec6e83c', icon: chevron_down.chevron_down, color: "white", small: true }), index.h("div", { key: '724514a04c237aab0bde8eeaf0a5ea0ba4d57754', class: "pn-page-nav-divider" }))), index.h("ul", { key: 'a9904dfd2acf7425900bd5b1c400a40ef46930e5', class: "pn-page-nav-items" }, index.h("slot", { key: '16240b85d64bc3667315389756505f426d202f16' }), index.h("li", { key: 'da34a7805394b01718ab808c5c12f24fd434c692', class: "pn-pn-bg pn-pn-active", role: "presentation" }), index.h("li", { key: '6431c8c54c3547dd20a12b758740327729e41ce7', class: "pn-pn-bg pn-pn-hover", role: "presentation" }))), this.showScrollArrows && (index.h("div", { key: '94c7037eac435f475df4bf74214d423ab6a222a2', class: this.scrollArrowClasses() }, index.h("button", { key: '4f54461490ffd67f0bc8c049b81d956704b5d2a0', class: "pn-pn-arrow-left", onClick: () => this.scroll(-120), tabindex: "-1" }, index.h("pn-icon", { key: '17c0a6744795cac8a6be6e876b440f4bf6cc826c', icon: arrow_left.arrow_left, color: "white" })), index.h("button", { key: '80548ab34ed33c15cae5e375de5971fbeb40efa2', class: "pn-pn-arrow-right", onClick: () => this.scroll(120), tabindex: "-1" }, index.h("pn-icon", { key: '6a6ed35f46f835a7c57584b2ef4faf5376ffa90e', icon: arrow_right.arrow_right, color: "blue700" }))))), this.dropdownActive && (index.h("ul", { key: '6e48de81da28bd439fcea04f33a5e1ac6da5f1cd', id: this.navid, class: this.dropdownClasses() }, index.h("slot", { key: 'cd0c26b03d91afc099cefe622e46907eea1cb85a', name: "dropdown-item" })))));
}
static get watchers() { return {
"value": ["valueHandler"],
"dropdownOpen": ["dropdownHandler"]
}; }
};
PnPageNav.style = pnPageNavCss;
exports.pn_page_nav = PnPageNav;
//# sourceMappingURL=pn-page-nav.entry.cjs.js.map