@postnord/web-components
Version:
PostNord Web Components
221 lines (220 loc) • 8.98 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { h, Host, forceUpdate, transformTag } from "@stencil/core";
import { arrow_left, arrow_right } from "pn-design-assets/pn-assets/icons.js";
/**
* The `pn-segmented-control` and `pn-segmented` is built with native `input[type="radio"]` in the background.
* This means you can use native events to listen to the changes.
*
* @nativeChange Use the `change` event to listen when the segment state is being changed.
*/
export class PnSegmentedControl {
mo;
segmentContainer;
segments = [];
activeBg;
hoverBg;
scrollRegistered = false;
hostElement;
showScrollArrows = false;
showLeftArrow = false;
showRightArrow = false;
/** This is the name of the radio buttons inside the controller. @deprecated Set the name in the nested `pn-segment` components. */
name;
/** Currently active segment value. */
value;
valueHandler() {
this.setActiveSegment();
}
changeHandler(event) {
const target = event.target;
this.value = target.value;
this.setActiveSegment();
}
handleResize() {
this.rerender();
}
handleHover(event) {
const { target } = event.detail;
this.calcHighlight(target, true);
}
connectedCallback() {
this.mo = new MutationObserver(() => {
forceUpdate(this.hostElement);
this.setSegments();
});
this.mo.observe(this.hostElement, { childList: true, subtree: true });
}
disconnectedCallback() {
if (this.mo)
this.mo.disconnect();
}
componentDidLoad() {
this.setSegments();
}
setSegments() {
const segmentTag = transformTag('pn-segment');
const list = this.hostElement.querySelectorAll(segmentTag);
this.segments = Array.from(list);
this.setActiveSegment();
}
setActiveSegment() {
this.segments.forEach(segmentEl => {
if (this.value === segmentEl.value)
segmentEl.setAttribute('selected', 'true');
else
segmentEl.removeAttribute('selected');
});
this.rerender();
}
rerender() {
requestAnimationFrame(() => {
this.calcHighlight();
this.scrollArrowRender();
});
}
/*---------------------------------------HIGHLIGHT LOGIC-------------------------------------------*/
calcHighlight(element, hover) {
const chip = hover ? this.hoverBg : this.activeBg;
requestAnimationFrame(() => {
const el = element || this.segments.find(({ value }) => value === this.value);
const segmentTag = transformTag('pn-segment');
const elSegment = el?.closest(segmentTag);
if (!elSegment)
return;
const elRect = elSegment.getBoundingClientRect();
const { left: hostLeft } = this.segmentContainer.getBoundingClientRect();
const { left: segmentLeft, height: segmentHeight, width: segmentWidth } = elRect;
const offset = segmentLeft - hostLeft + this.segmentContainer.scrollLeft;
chip.style.setProperty('transform', `translate(${offset}px, -50%`);
chip.style.setProperty('width', `${segmentWidth}px`);
chip.style.setProperty('height', `${segmentHeight}px`);
el.disabled ? (chip.dataset.disabled = '') : delete chip.dataset.disabled;
});
}
/*---------------------------------------/HIGHLIGHT LOGIC-------------------------------------------*/
/*---------------------------------------SCROLL ARROW LOGIC-------------------------------------------*/
scrollArrowRender() {
if (this.segmentContainer.scrollWidth > this.segmentContainer.clientWidth) {
this.showScrollArrows = true;
if (!this.scrollRegistered) {
this.segmentContainer.addEventListener('scroll', this.scrollArrowRender.bind(this));
this.scrollRegistered = true;
}
const amountScrolled = Math.round(this.segmentContainer.scrollWidth - this.segmentContainer.scrollLeft);
const distanceToEnd = amountScrolled - this.segmentContainer.clientWidth;
const distanceToStart = this.segmentContainer.scrollLeft;
this.showLeftArrow = distanceToStart > 0;
this.showRightArrow = distanceToEnd > 0;
}
else {
this.showLeftArrow = false;
this.showRightArrow = false;
this.showScrollArrows = false;
}
}
scroll(val) {
let amount = this.segmentContainer.scrollLeft + val;
this.segmentContainer.scroll({
left: amount,
behavior: 'smooth',
});
}
/*---------------------------------------/SCROLL ARROW LOGIC-------------------------------------------*/
render() {
return (h(Host, { key: '314c016bb1daea417c741babc236f8de14b9ab21' }, h("div", { key: '10af3eb77723a2f99d6e665e6340caf9aaf03d27', class: "pn-segmented-control", ref: el => (this.segmentContainer = el) }, h("slot", { key: '516890ccb71a58628d1f527272424b295c8959ab' }), h("div", { key: 'f0f162d91a3c14a93b60195492978f03b41a0ecf', class: "pn-sc-bg", "data-active": true, ref: el => (this.activeBg = el) }), h("div", { key: '9ca1016209402986a1702db3f50f9d52d913d249', class: "pn-sc-bg", "data-hover": true, ref: el => (this.hoverBg = el) })), h("div", { key: '61d56ebba096df1a0cfab0aea8aa52059c62d13e', class: "pn-sc-arrows", "data-left": this.showLeftArrow, "data-right": this.showRightArrow, hidden: !this.showScrollArrows }, h("button", { key: '1da8665af3d990175b92e50cf83e01adca082909', "aria-label": "Left", class: "pn-sc-arrow pn-sc-arrow-left", onClick: () => this.scroll(-120), tabindex: "-1" }, h("pn-icon", { key: '3e78658670b082a938a5f976ea34b51449d669af', icon: arrow_left })), h("button", { key: '03fad3f5c772b282850098e31d8ea717f5efb054', "aria-label": "Right", class: "pn-sc-arrow pn-sc-arrow-right", onClick: () => this.scroll(120), tabindex: "-1" }, h("pn-icon", { key: '659dfab232a3256a9819cdf4ec34445f9bbfa875', icon: arrow_right })))));
}
static get is() { return "pn-segmented-control"; }
static get originalStyleUrls() {
return {
"$": ["pn-segmented-control.scss"]
};
}
static get styleUrls() {
return {
"$": ["pn-segmented-control.css"]
};
}
static get properties() {
return {
"name": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [{
"name": "deprecated",
"text": "Set the name in the nested `pn-segment` components."
}],
"text": "This is the name of the radio buttons inside the controller."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "name"
},
"value": {
"type": "string",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Currently active segment value."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "value"
}
};
}
static get states() {
return {
"showScrollArrows": {},
"showLeftArrow": {},
"showRightArrow": {}
};
}
static get elementRef() { return "hostElement"; }
static get watchers() {
return [{
"propName": "value",
"methodName": "valueHandler"
}];
}
static get listeners() {
return [{
"name": "change",
"method": "changeHandler",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "resize",
"method": "handleResize",
"target": "window",
"capture": false,
"passive": true
}, {
"name": "segmentHover",
"method": "handleHover",
"target": undefined,
"capture": false,
"passive": false
}];
}
}