@postnord/web-components
Version:
PostNord Web Components
299 lines (288 loc) • 17.2 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const index = require('./index-78a59ba0.js');
const helpers = require('./helpers-2e2349b4.js');
const icon$1 = '<svg class="pn-icon-svg" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6.293 12.707a1 1 0 0 1 0-1.414l8-8a1 1 0 1 1 1.414 1.414L8.414 12l7.293 7.293a1 1 0 0 1-1.414 1.414z" clip-rule="evenodd"/></svg>';
const angle_left = icon$1;
const icon = '<svg class="pn-icon-svg" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M17.707 11.293a1 1 0 0 1 0 1.414l-8 8a1 1 0 0 1-1.414-1.414L15.586 12 8.293 4.707a1 1 0 0 1 1.414-1.414z" clip-rule="evenodd"/></svg>';
const angle_right = icon;
const pnTabCss = "pn-tab{position:relative;overflow:hidden;flex-shrink:0;scroll-snap-align:center;display:inline-block;border-radius:0.5em}pn-tab .pn-tab{position:relative;cursor:pointer;margin:1em 0.25em;padding:0 0.25em;border:none;border-radius:0.5em;color:#2d2013;background-color:transparent;font-family:inherit;font-size:1em;font-weight:500;text-decoration:none;display:flex;gap:0.5em;align-items:center;justify-content:center;-webkit-tap-highlight-color:transparent;transition-property:color, outline-color, background-color;transition-duration:0.2s;transition-timing-function:cubic-bezier(0.7, 0, 0.3, 1);outline:0.2rem solid transparent;outline-offset:0.2rem}pn-tab .pn-tab .pn-ripple{animation:ripple 0.4s cubic-bezier(0.7, 0, 0.3, 1);position:absolute;border-radius:50%;background-color:#005d92;transform:translate(-50%, -50%) scale(0);opacity:0.1;pointer-events:none;z-index:3}@keyframes ripple{to{transform:translate(-50%, -50%) scale(1);opacity:0}}pn-tab .pn-tab>span{line-height:1.5em}pn-tab .pn-tab>pn-icon>.pn-icon-svg path,pn-tab .pn-tab>pn-icon>.pn-icon-svg polygon{transition:fill 0.2s cubic-bezier(0.7, 0, 0.3, 1);fill:#2d2013}pn-tab .pn-tab:focus-visible{overflow:hidden;outline-color:#005d92;background-color:#ffffff}pn-tab .pn-tab[aria-selected=true],pn-tab .pn-tab[aria-current=page]{color:#005d92}pn-tab .pn-tab[aria-selected=true]>pn-icon>.pn-icon-svg path,pn-tab .pn-tab[aria-selected=true]>pn-icon>.pn-icon-svg polygon,pn-tab .pn-tab[aria-current=page]>pn-icon>.pn-icon-svg path,pn-tab .pn-tab[aria-current=page]>pn-icon>.pn-icon-svg polygon{fill:#005d92}pn-tab .pn-tab[aria-selected=true]:focus,pn-tab .pn-tab[aria-current=page]:focus{color:#0d234b}pn-tab .pn-tab[aria-selected=true]:focus pn-icon>.pn-icon-svg path,pn-tab .pn-tab[aria-selected=true]:focus pn-icon>.pn-icon-svg polygon,pn-tab .pn-tab[aria-current=page]:focus pn-icon>.pn-icon-svg path,pn-tab .pn-tab[aria-current=page]:focus pn-icon>.pn-icon-svg polygon{fill:#0d234b}";
const PnTabStyle0 = pnTabCss;
const PnTab = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.setActiveTab = index.createEvent(this, "setActiveTab", 7);
this.tabEnter = index.createEvent(this, "tabEnter", 7);
this.tabLeave = index.createEvent(this, "tabLeave", 7);
}
tag = 'button';
get hostElement() { return index.getElement(this); }
/** Set a label for the tab. */
label;
/** This is the value that will be matched with `pn-tablist` value. (required) */
value;
/** Turns the tab from a `button` to an `a` element, but only if the `pn-tablist` is inside a `pn-header`. */
href;
/** The SVG content of the icon you want to display */
icon;
/** Use the ID of the container that this tab controls. */
ariacontrols;
/** Tab ID, use if you want to have the tab container be `aria-labelledby` by this tab. */
tabid;
/** Is set by `pn-tablist`, don't use this prop. @hide true */
activeTab;
/** Used by `pn-tab` to communicate with `pn-tablist`. Emits the selected tab value and element. */
setActiveTab;
setActiveTabHandler({ click = false, element } = {}) {
if (click || this.isActive()) {
const val = element?.value || this.value;
const el = (element?.value && element) || this.hostElement;
this.setActiveTab.emit({
val,
el,
});
}
}
/** Used by `pn-tab` to communicate with `pn-tablist`. Emits when the tab gets focus. */
tabEnter;
triggerEnter(event) {
this.tabEnter.emit(event);
}
/** Used by `pn-tab` to communicate with `pn-tablist`. Emits when the tab is blured. */
tabLeave;
triggerLeave(event) {
this.tabLeave.emit(event);
}
componentWillLoad() {
this.tabTag();
}
componentDidUpdate() {
this.setActiveTabHandler();
}
componentDidLoad() {
this.setActiveTabHandler();
}
arrowKeyNav(event) {
if (!/^(ArrowRight|ArrowLeft|Home|End)$/.test(event.key))
return;
event.preventDefault();
// Get tablist parent
const parent = event.target.closest('pn-tablist');
const list = Array.from(parent.querySelectorAll('pn-tab'));
const first = list[0];
const last = list[list.length - 1];
const nextElement = this.hostElement.nextElementSibling;
const previousElement = this.hostElement.previousElementSibling;
if (event.key === 'Home')
this.setActiveTabHandler({ element: first });
if (event.key === 'End')
this.setActiveTabHandler({ element: last });
if (event.key === 'ArrowRight') {
// Check the next element value. We do this because the last element i a DIV element. If no value, go to the first element.
this.setActiveTabHandler({ element: nextElement?.value ? nextElement : first });
}
if (event.key === 'ArrowLeft') {
// Go to the last element if there is no element to your left.
this.setActiveTabHandler({ element: previousElement?.value ? previousElement : last });
}
}
isActive() {
return this.activeTab === this.value;
}
tabTag() {
const isMenu = this.hostElement.closest('pn-tablist');
this.tag = isMenu.slot === 'menu' ? 'a' : 'button';
}
renderProperties() {
return this.tag === 'a'
? {
'href': this.href,
'aria-current': this.isActive() ? 'page' : 'false',
}
: {
'tabindex': this.isActive() ? 0 : -1,
'type': 'button',
'role': 'tab',
'aria-selected': this.isActive().toString(),
'aria-controls': this.ariacontrols,
};
}
handleClick(e) {
this.setActiveTabHandler({ click: true });
helpers.ripple(e, this.hostElement, '.pn-tab');
}
render() {
const Tag = this.tag;
return (index.h(index.Host, { key: '6549002f64a0f289189a941e00bc3a7ed5d3dc35' }, index.h(Tag, { key: '69b99a3df97967208a40c890a0b1001b2ea3cf64', id: this.tabid, class: "pn-tab", ...this.renderProperties(), onClick: (e) => this.handleClick(e), onMouseEnter: (e) => this.triggerEnter(e), onFocus: (e) => this.triggerEnter(e), onMouseLeave: (e) => this.triggerLeave(e), onBlur: (e) => this.triggerLeave(e), onKeyDown: (e) => this.arrowKeyNav(e) }, !!this.icon && index.h("pn-icon", { key: '11f3063b3d4612fcf51dd9a9bfeca8ba8fdd353b', icon: this.icon }), index.h("slot", { key: '60f34de2c5407fec8d443765f10d839c00f6f8de' }), index.h("span", { key: 'dae8cccba39a05351f61771c73a99beaaff94db2' }, this.label))));
}
};
PnTab.style = PnTabStyle0;
const translations = {
LEFT: {
sv: 'Bläddra åt vänster',
en: 'Scroll to the left',
da: 'Rul til venstre',
fi: 'Vieritä vasemmalle',
no: 'Rull til venstre',
},
RIGHT: {
sv: 'Bläddra åt höger',
en: 'Scroll to the right',
da: 'Rul til højre',
fi: 'Vieritä oikealle',
no: 'Rull til høyre',
},
};
const pnTablistCss = "pn-tablist{width:100%;min-width:0;position:relative;border-bottom:0.0625em solid #d3cecb;display:block}pn-tablist .pn-tablist,pn-tablist ol,pn-tablist ul{display:flex;width:100%;flex-direction:row;gap:1em;position:relative;list-style:none;padding:0;margin:0}pn-tablist .pn-tablist[data-stacked] .pn-tab,pn-tablist ol[data-stacked] .pn-tab,pn-tablist ul[data-stacked] .pn-tab{margin:0.5em 0.25em 0.75em;gap:0.25em;flex-direction:column}pn-tablist .pn-tablist[data-small] .pn-tab,pn-tablist ol[data-small] .pn-tab,pn-tablist ul[data-small] .pn-tab{margin:0.25em 0.25em 0.75em}pn-tablist .pn-tablist[data-small] .pn-tab>*:not(pn-icon),pn-tablist ol[data-small] .pn-tab>*:not(pn-icon),pn-tablist ul[data-small] .pn-tab>*:not(pn-icon){font-size:0.875em}pn-tablist .pn-tablist[data-large] .pn-tab>*:not(pn-icon),pn-tablist ol[data-large] .pn-tab>*:not(pn-icon),pn-tablist ul[data-large] .pn-tab>*:not(pn-icon){font-size:1.25em}pn-tablist .pn-tablist[data-scroll],pn-tablist ol[data-scroll],pn-tablist ul[data-scroll]{overflow-y:hidden;overflow-x:auto;scroll-snap-type:x mandatory}pn-tablist .pn-tablist::-webkit-scrollbar,pn-tablist ol::-webkit-scrollbar,pn-tablist ul::-webkit-scrollbar{display:none}pn-tablist>.pn-scroll-arrows{position:absolute;top:50%;left:0;right:0;pointer-events:none;transform:translateY(-50%);display:flex;justify-content:space-between;align-items:center;z-index:1;height:100%}pn-tablist>.pn-scroll-arrows>.pn-arrow{pointer-events:all;transition-property:transform, opacity, visibility;transition-duration:0.2s;transition-timing-function:cubic-bezier(0.7, 0, 0.3, 1);transform:scaleY(0);opacity:0;visibility:hidden}pn-tablist>.pn-scroll-arrows>.pn-arrow[data-show]{opacity:1;visibility:visible;transform:scaleY(1)}pn-tablist .pn-line{height:0.25em;position:absolute;bottom:0}pn-tablist .pn-line-item{position:absolute;transform-origin:left center;width:100%;height:inherit;border-radius:0.25em 0.25em 0 0;opacity:0;transition-property:transform, width, opacity;transition-duration:0.3s;transition-timing-function:cubic-bezier(0.7, 0, 0.3, 1)}pn-tablist .pn-line-active{z-index:1;background-color:#005d92;width:var(--pn-active-width);transform:translateX(var(--pn-active-offset));opacity:var(--pn-active-opacity)}pn-tablist .pn-line-hovered{background-color:#00a0d6;width:var(--pn-hover-width);transform:translateX(var(--pn-hover-offset));opacity:var(--pn-hover-opacity)}";
const PnTablistStyle0 = pnTablistCss;
const PnTablist = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.tabchange = index.createEvent(this, "tabchange", 7);
}
mo;
isMenu = false;
isTabHovered = false;
tabListEl;
lineActive;
lineHovered;
tabElement;
get hostElement() { return index.getElement(this); }
/** Display the scrolling arrows */
showScrollArrows = false;
/** Display the scroll arrow to the left */
arrowLeft = false;
/** Display the scroll arrow to the right */
arrowRight = false;
/** Give the tablist a name for screenreaders */
label;
/** The value of the tab that is currently active, each `<pn-tab value="example-value">` also expects a unique value */
value;
/** Make the tablist smaller */
small;
/** Icons are stacked vertically instead of the default rows */
stackedicons = false;
/** Manually set the language. */
language = null;
/**
* This will emit when a tab is changed. The detail property of the event will contain the value of the selected tab.
* This is the event and value you listen to when you toggle the visibility among your tabpanels.
**/
tabchange;
setValue() {
const tabs = Array.from(this.hostElement.querySelectorAll('pn-tab'));
tabs.forEach(tab => {
tab.activeTab = this.value;
});
}
scrollHandler() {
if (this.showScrollArrows) {
this.tabListEl.addEventListener('scroll', this.scrollIndicators.bind(this));
}
else {
this.tabListEl.removeEventListener('scroll', this.scrollIndicators);
}
}
setActiveTabHandler({ detail }) {
this.tabElement = detail.el;
requestAnimationFrame(() => this.activateTab(detail.el));
if (this.value === detail.val)
return;
this.value = detail.val;
this.tabchange.emit(this.value);
this.tabElement.querySelector(this.isMenu ? 'a' : 'button').focus();
}
rerender() {
requestAnimationFrame(() => {
this.scrollIndicators();
this.isTabHovered = false;
});
}
handleEnter(e) {
this.isTabHovered = true;
this.styleLines(e.target);
}
handleLeave() {
this.isTabHovered = false;
setTimeout(() => {
if (!this.isTabHovered)
this.lineHovered.style.setProperty('--pn-hover-opacity', '0');
}, 500);
}
async componentWillLoad() {
this.isMenu = this.hostElement.slot === 'menu';
if (this.language === null)
return await helpers.awaitTopbar(this.hostElement);
}
componentDidLoad() {
if (this.mo)
this.mo.disconnect();
this.mo = new MutationObserver(() => {
index.forceUpdate(this.hostElement);
this.rerender();
});
this.mo.observe(this.hostElement, { childList: true, subtree: true });
}
componentDidRender() {
this.rerender();
this.setValue();
}
getRect(element) {
return element.getBoundingClientRect();
}
activateTab(element) {
this.styleLines(element, true);
}
styleLines(element, active = false) {
const tabListCoords = this.getRect(this.tabListEl);
const scrollOffset = this.tabListEl.scrollLeft;
const line = this.getRect(element);
const width = line.width;
const offset = line.left + scrollOffset - tabListCoords.left;
const cssVar = active ? 'active' : 'hover';
const prop = active ? 'lineActive' : 'lineHovered';
this[prop].style.setProperty(`--pn-${cssVar}-width`, `${width}px`);
this[prop].style.setProperty(`--pn-${cssVar}-offset`, `${offset}px`);
this[prop].style.setProperty(`--pn-${cssVar}-opacity`, '1');
}
scrollIndicators() {
const { scrollWidth, scrollLeft } = this.tabListEl;
const { clientWidth } = this.hostElement;
this.showScrollArrows = scrollWidth > clientWidth;
this.arrowLeft = this.showScrollArrows && scrollLeft > 0;
this.arrowRight = this.showScrollArrows && clientWidth + 16 + scrollLeft < scrollWidth;
}
scroll({ right = false } = {}) {
const tabList = this.tabListEl;
const { scrollLeft, clientWidth } = tabList;
let left = scrollLeft;
// The width of the scroll arrow is 32px, so we remove that from this calculation so the scroll
const scrollAmount = clientWidth - 64;
if (right)
left += scrollAmount;
else
left -= scrollAmount;
tabList.scrollTo({
left,
behavior: 'smooth',
});
}
translate(prop) {
return translations?.[prop]?.[this.language || helpers.en];
}
render() {
return (index.h(index.Host, { key: '4d0d29f48ee2952b58fdae8738795e3247c7257e' }, index.h("nav", { key: 'c1efeea1978a6157572652e7ff8ad7a9d9905f5b', class: "pn-tablist", role: this.isMenu ? null : 'tablist', "aria-label": this.label, "data-stacked": this.stackedicons, "data-small": !this.isMenu && this.small, "data-large": this.isMenu, "data-scroll": this.showScrollArrows, ref: el => (this.tabListEl = el) }, index.h("slot", { key: 'bd1d7ddee497d0c737e254d97dfda435964cfbb3' }), index.h("div", { key: '660b591ce4d462c7d390b4479094fb2452ef9dbd', class: "pn-line" }, index.h("div", { key: 'daf52564a72d07a7fbb29e77752a9ec1e4cc2289', ref: el => (this.lineActive = el), class: "pn-line-item pn-line-active" }), index.h("div", { key: 'fcdf804b3b6589b9e971bd72b033741c34874989', ref: el => (this.lineHovered = el), class: "pn-line-item pn-line-hovered" }))), index.h("div", { key: 'e46e9ba8bdf7334ed5f6ca82499e97a798cb2bfb', class: "pn-scroll-arrows" }, index.h("pn-button", { key: '54b83c42942c072976e39d222957f673eb8015ac', class: "pn-arrow", "data-show": this.arrowLeft, onClick: () => this.scroll(), noTab: true, appearance: "light", variant: "outlined", icon: angle_left, iconOnly: true, arialabel: this.translate('LEFT'), small: true }), index.h("pn-button", { key: 'bc171ec91d1025111a7ec8f16549be969ac8e55b', class: "pn-arrow", "data-show": this.arrowRight, onClick: () => this.scroll({ right: true }), noTab: true, appearance: "light", variant: "outlined", icon: angle_right, iconOnly: true, arialabel: this.translate('RIGHT'), small: true }))));
}
static get watchers() { return {
"value": ["setValue"],
"showScrollArrows": ["scrollHandler"]
}; }
};
PnTablist.style = PnTablistStyle0;
exports.pn_tab = PnTab;
exports.pn_tablist = PnTablist;
//# sourceMappingURL=pn-tab_2.cjs.entry.js.map