@postnord/web-components
Version:
PostNord Web Components
328 lines (327 loc) • 12.6 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { h, Host, forceUpdate, } from "@stencil/core";
import { angle_left, angle_right } from "pn-design-assets/pn-assets/icons.js";
import translations from "./translations";
import { awaitTopbar, en } from "../../../globals/helpers";
/**
* @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 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 awaitTopbar(this.hostElement);
}
componentDidLoad() {
if (this.mo)
this.mo.disconnect();
this.mo = new MutationObserver(() => {
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 || en];
}
render() {
return (h(Host, { key: '4d0d29f48ee2952b58fdae8738795e3247c7257e' }, 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) }, h("slot", { key: 'bd1d7ddee497d0c737e254d97dfda435964cfbb3' }), h("div", { key: '660b591ce4d462c7d390b4479094fb2452ef9dbd', class: "pn-line" }, h("div", { key: 'daf52564a72d07a7fbb29e77752a9ec1e4cc2289', ref: el => (this.lineActive = el), class: "pn-line-item pn-line-active" }), h("div", { key: 'fcdf804b3b6589b9e971bd72b033741c34874989', ref: el => (this.lineHovered = el), class: "pn-line-item pn-line-hovered" }))), h("div", { key: 'e46e9ba8bdf7334ed5f6ca82499e97a798cb2bfb', class: "pn-scroll-arrows" }, 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 }), 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 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,
"attribute": "label",
"reflect": false
},
"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,
"attribute": "value",
"reflect": true
},
"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,
"attribute": "small",
"reflect": false
},
"stackedicons": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Icons are stacked vertically instead of the default rows"
},
"getter": false,
"setter": false,
"attribute": "stackedicons",
"reflect": false,
"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"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Manually set the language."
},
"getter": false,
"setter": false,
"attribute": "language",
"reflect": false,
"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
}];
}
}
//# sourceMappingURL=pn-tablist.js.map