@postnord/web-components
Version:
PostNord Web Components
338 lines (337 loc) • 13.1 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { h, Host, forceUpdate, transformTag, } from "@stencil/core";
import { chevron_left, chevron_right } from "pn-design-assets/pn-assets/icons.js";
import translations from "./translations";
import { awaitTopbar, en } from "../../../globals/helpers";
/**
* The tablist allows you to create a list with nested `pn-tab` components.
* These should control the visibility of panel elements on the page.
*
* If you use the slot name "menu" the tablist will adapt to be used inside of
* a `pn-header` component. This also allows the pn-tabs to use the `href` prop.
*
* @slot menu - Can only be used inside of a `pn-header`. Allows the `href` attribute on the `pn-tab` when active.
*/
export class PnTablist {
mo;
isMenu = false;
isTabHovered = false;
tabListEl;
lineActive;
lineHovered;
tabElement;
hostElement;
/** 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 tabName = transformTag('pn-tab');
const tabs = Array.from(this.hostElement.querySelectorAll(tabName));
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('.pn-tab').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);
}
connectedCallback() {
this.mo = new MutationObserver(() => {
forceUpdate(this.hostElement);
this.setValue();
this.rerender();
});
this.mo.observe(this.hostElement, { subtree: true, childList: true });
}
disconnectedCallback() {
this.mo?.disconnect();
}
async componentWillLoad() {
this.isMenu = this.hostElement.slot === 'menu';
if (this.language === null)
await awaitTopbar(this.hostElement);
}
componentDidRender() {
this.setValue();
this.rerender();
}
getRect(element) {
return element.getBoundingClientRect();
}
activateTab(element) {
this.styleLines(element, true);
this.styleLines(element, false);
}
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 || en];
}
render() {
return (h(Host, { key: '7dca608244acd89c6506d10db8bd774055623f77' }, h("nav", { key: '263e6774e93dd98d82282d9f591b4f2053c0421c', 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) }, h("slot", { key: '3a5a6f587b750c035f1b61b1184bf7d9fd843cd2' }), h("div", { key: 'a21c49db6f1f38066d7b54d11faeb441cfbd52b1', class: "pn-tablist-line" }, h("div", { key: '35f4d30ba1e7de4737dd65d38febd6cf0dc0ab41', ref: el => (this.lineActive = el), class: "pn-tablist-line-item pn-tablist-line-active" }), h("div", { key: 'ab05a4b61abac290894cd93e89bd9de80c710189', ref: el => (this.lineHovered = el), class: "pn-tablist-line-item pn-tablist-line-hovered" }))), h("div", { key: '1845500af4535f9a7d4006fc781f52c7a31776d9', class: "pn-tablist-scroll" }, h("pn-button", { key: 'c7421e16d531fe9559b46a3e0fa8bdcb4bf5a142', class: "pn-tablist-arrow", "data-show": this.arrowLeft, onClick: () => this.scroll(), noTab: true, appearance: "light", variant: "outlined", icon: chevron_left, iconOnly: true, arialabel: this.translate('LEFT'), small: true }), h("pn-button", { key: 'a79c5096ee1f9ca79755b54db6bc0b05d3c8ac5a', class: "pn-tablist-arrow", "data-show": this.arrowRight, onClick: () => this.scroll({ right: true }), noTab: true, appearance: "light", variant: "outlined", icon: chevron_right, iconOnly: true, arialabel: this.translate('RIGHT'), small: true }))));
}
static get is() { return "pn-tablist"; }
static get originalStyleUrls() {
return {
"$": ["pn-tablist.scss"]
};
}
static get styleUrls() {
return {
"$": ["pn-tablist.css"]
};
}
static get properties() {
return {
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Give the tablist a name for screenreaders."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "label"
},
"value": {
"type": "string",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "The value of the tab that is currently active, each `<pn-tab value=\"example-value\">` also expects a unique value"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "value"
},
"small": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Make the tablist smaller"
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "small"
},
"stackedicons": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Icons are stacked vertically instead of the default rows"
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "stackedicons",
"defaultValue": "false"
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "PnLanguages",
"resolved": "\"\" | \"da\" | \"en\" | \"fi\" | \"no\" | \"sv\"",
"references": {
"PnLanguages": {
"location": "import",
"path": "@/globals/types",
"id": "src/globals/types.ts::PnLanguages",
"referenceLocation": "PnLanguages"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Manually set the language."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "language",
"defaultValue": "null"
}
};
}
static get states() {
return {
"showScrollArrows": {},
"arrowLeft": {},
"arrowRight": {}
};
}
static get events() {
return [{
"method": "tabchange",
"name": "tabchange",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "This will emit when a tab is changed. The detail property of the event will contain the value of the selected tab.\nThis is the event and value you listen to when you toggle the visibility among your tabpanels."
},
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
}
}];
}
static get elementRef() { return "hostElement"; }
static get watchers() {
return [{
"propName": "value",
"methodName": "setValue"
}, {
"propName": "showScrollArrows",
"methodName": "scrollHandler"
}];
}
static get listeners() {
return [{
"name": "setActiveTab",
"method": "setActiveTabHandler",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "resize",
"method": "rerender",
"target": "window",
"capture": false,
"passive": true
}, {
"name": "tabEnter",
"method": "handleEnter",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "tabLeave",
"method": "handleLeave",
"target": undefined,
"capture": false,
"passive": false
}];
}
}