@postnord/web-components
Version:
PostNord Web Components
504 lines (503 loc) • 19.7 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { Host, h } from "@stencil/core";
import { arrow_left, arrow_right } from "pn-design-assets/pn-assets/icons.js";
import { en, awaitTopbar } from "../../../index";
import { translations } from "./translations";
/**
* Accessible pagination needs each item to link to a different part of the document.
* This means that you must `preventDefault` on the `MouseEvent` emitted from the `pageSelected` event if you have a SPA.
*
* If you set a `page` value that is higher than `pages`, the component will automatically lower it to the `pages` value.
*
* @since v7.9.0
*/
export class PnPagination {
listElement;
hostElement;
list;
/** Set a label for the pagination element for screen readers. It defaults to `Pagination`. */
label;
/** Set a custom URL template. Use `{page}` within the string to position where the dynamic page number should be. */
urlTemplate = '?page={page}';
/** Set a HTML id on the pagination element. */
paginationId = null;
/** Manually set the language. */
language = null;
/**
* Set which page the user is currently viewing.
* Use this to set an inital page or if the selected page is changed from outside this component.
*
* @category Pages
**/
page = 1;
/** Set how many pages are available. @category Pages */
pages;
/** Set how many pages should be visible at one time. Use an odd number. Minimum is `5`. @category Pages */
pagesVisible = 5;
/**
* Allow the user to decide how many items should be shown per page.
* Make sure this value exists in the `rowsList` string.
*
* @see {@link rowsList}
* @category Rows per page
* */
rows = null;
/**
* Set available items per page options the user can select. Use a comma separated string.
*
* @see {@link rows}
* @category Rows per page
* */
rowsList = '10,25,50';
/**
* Default label is "Items per page".
* @see {@link rows}
* @category Rows per page
* */
rowsLabel = null;
handlePage() {
if (this.page > this.pages)
this.page = this.pages;
else if (this.page < 1)
this.page = 1;
}
handlePages() {
const list = [];
if (this.pages > 10000) {
console.error(`${this.hostElement.localName}: Invalid page count ${this.pages}. Maximum is 10000.`);
this.pages = 10000;
}
for (let index = 0; this.pages > index; index++) {
const page = index + 1;
list.push({ index, page });
}
this.list = list;
this.handlePage();
}
handlePagesVisible() {
if (5 > this.pagesVisible)
this.pagesVisible = 5;
if (this.pagesVisible % 2 === 0)
this.pagesVisible = Math.round(this.pagesVisible) - 1;
}
/** Emitted when the page is changed. Either via clicking a link or selecting a page in the select. */
pageSelected;
/** This event fires when the user changes how many items should be shown per page. */
rowsSelected;
async componentWillLoad() {
this.handlePagesVisible();
this.handlePages();
if (this.language === null)
await awaitTopbar(this.hostElement);
}
translate(prop) {
return translations[prop][this.language || en];
}
isActivePage(page) {
return page === this.page;
}
navigate({ page, event, focus }) {
if (typeof page === 'number' && page >= 1 && this.pages >= page)
this.page = page;
this.pageSelected.emit({ page: this.page, mouse: event });
if (focus)
requestAnimationFrame(() => {
const element = this.listElement.querySelector('a[aria-current="page"]');
element?.focus({ preventScroll: true });
});
}
useRowList() {
return !!this.rows && !!this.rowsList;
}
setRows(event) {
const target = event.target;
this.rows = Number(target.value);
this.rowsSelected.emit({ rows: this.rows, change: event });
}
getRowList() {
return this.rowsList.split(',').map(item => Number(item.trim()));
}
/** Get the page numbers of the lowest and highest visible page. */
getPaginationIndex() {
const current = this.page;
let low = current - Math.ceil(this.pagesVisible / 2);
let high = current + Math.floor(this.pagesVisible / 2);
for (let i = 0; this.pagesVisible > i; i++) {
if (low < 0) {
high++;
low++;
}
else if (high > this.pages) {
high--;
low--;
}
}
const lowest = 0 > low ? 0 : low;
const leftIndent = lowest >= 1;
const rightIndent = this.pages > high;
return {
low: rightIndent && leftIndent ? lowest + 1 : lowest,
high: rightIndent && leftIndent ? high - 1 : high,
leftIndent,
rightIndent,
};
}
getPagination() {
const { low, high } = this.getPaginationIndex();
return this.list.slice(low, high);
}
getUrl(page) {
return this.setPageText(this.urlTemplate, page);
}
setPageText(text, page) {
return text.replace('{page}', page.toString());
}
getPrevPage() {
let page = this.page;
return page > 1 ? (page -= 1) : 1;
}
getNextPage() {
let page = this.page;
return this.pages > page ? (page += 1) : this.pages;
}
showIntendation(last = false) {
const { leftIndent, rightIndent } = this.getPaginationIndex();
return last ? rightIndent : leftIndent;
}
renderDivider(last = false) {
return (h("span", { class: "pn-pagination-dot", "data-show": this.showIntendation(last) }, "..."));
}
renderButton({ page, dataAttr }) {
const active = this.isActivePage(page);
return (h("pn-button", { label: page.toString(), arialabel: `${this.translate('PAGE')} ${page}`, ariacurrent: active ? 'page' : null, href: this.getUrl(page), variant: active ? 'outlined' : '', appearance: "light", small: true, "data-arrow": dataAttr, "data-show": dataAttr && this.showIntendation(dataAttr === 'last'), onPnClick: ({ detail }) => this.navigate({ page, event: detail, focus: true }) }));
}
renderItem({ page }) {
return (h("li", { class: "pn-pagination-list-item", "aria-setsize": this.pages, "aria-posinset": page }, this.renderButton({ page })));
}
renderJumpButton(last = false) {
const rel = last ? 'next' : 'prev';
const icon = last ? arrow_right : arrow_left;
const page = last ? this.getNextPage() : this.getPrevPage();
const current = last ? this.pages === this.page : 1 === this.page;
const text = last ? 'NEXT_PAGE' : 'PREVIOUS_PAGE';
const showPageNumber = current ? '' : ` (${page})`;
const label = `${this.translate(text)}${showPageNumber}`;
return (h("pn-button", { arialabel: label, ariacurrent: current ? 'page' : null, href: this.getUrl(page), rel: current ? null : rel, appearance: "light", small: true, icon: icon, iconOnly: true, onPnClick: ({ detail }) => this.navigate({ page, event: detail }) }));
}
render() {
return (h(Host, { key: 'a975a04388ce0495aef863236d3fd2f936380fed' }, h("nav", { key: '920aeb21c535b7f09bc54924865d3b5be2a65652', id: this.paginationId, class: "pn-pagination", "aria-label": this.label || this.translate('PAGINATION') }, this.useRowList() && (h("pn-select", { key: '880de99ef3a2a1400ff08f420c8c81017846e1fa', class: "pn-pagination-rows", label: this.rowsLabel || this.translate('ROWS_PAGE'), onChange: event => this.setRows(event) }, this.getRowList().map(item => (h("option", { value: item, selected: this.rows === Number(item), innerHTML: `${item}` }))))), h("div", { key: 'd11a69a8d7f5efcaae031214f2abf417d8922207', class: "pn-pagination-container", "data-right": this.useRowList() }, this.renderJumpButton(), this.renderButton({ page: 1, dataAttr: 'first' }), this.renderDivider(), h("ol", { key: '2a5777aecd5210105ea26ed8c8cb9d99d1f1efc8', class: "pn-pagination-list", ref: el => (this.listElement = el) }, this.getPagination().map(item => this.renderItem(item))), this.renderDivider(true), this.renderButton({ page: this.pages, dataAttr: 'last' }), this.renderJumpButton(true)))));
}
static get is() { return "pn-pagination"; }
static get originalStyleUrls() {
return {
"$": ["pn-pagination.scss"]
};
}
static get styleUrls() {
return {
"$": ["pn-pagination.css"]
};
}
static get properties() {
return {
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Set a label for the pagination element for screen readers. It defaults to `Pagination`."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "label"
},
"urlTemplate": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set a custom URL template. Use `{page}` within the string to position where the dynamic page number should be."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "url-template",
"defaultValue": "'?page={page}'"
},
"paginationId": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Set a HTML id on the pagination element."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "pagination-id",
"defaultValue": "null"
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "PnLanguages",
"resolved": "\"\" | \"da\" | \"en\" | \"fi\" | \"no\" | \"sv\"",
"references": {
"PnLanguages": {
"location": "import",
"path": "@/index",
"id": "src/index.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"
},
"page": {
"type": "number",
"mutable": true,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "category",
"text": "Pages"
}],
"text": "Set which page the user is currently viewing.\nUse this to set an inital page or if the selected page is changed from outside this component."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "page",
"defaultValue": "1"
},
"pages": {
"type": "number",
"mutable": true,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [{
"name": "category",
"text": "Pages"
}],
"text": "Set how many pages are available."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "pages"
},
"pagesVisible": {
"type": "number",
"mutable": true,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "category",
"text": "Pages"
}],
"text": "Set how many pages should be visible at one time. Use an odd number. Minimum is `5`."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "pages-visible",
"defaultValue": "5"
},
"rows": {
"type": "number",
"mutable": true,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "see",
"text": "{@link rowsList }"
}, {
"name": "category",
"text": "Rows per page"
}],
"text": "Allow the user to decide how many items should be shown per page.\nMake sure this value exists in the `rowsList` string."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "rows",
"defaultValue": "null"
},
"rowsList": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "see",
"text": "{@link rows }"
}, {
"name": "category",
"text": "Rows per page"
}],
"text": "Set available items per page options the user can select. Use a comma separated string."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "rows-list",
"defaultValue": "'10,25,50'"
},
"rowsLabel": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "see",
"text": "{@link rows }"
}, {
"name": "category",
"text": "Rows per page"
}],
"text": "Default label is \"Items per page\"."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "rows-label",
"defaultValue": "null"
}
};
}
static get states() {
return {
"list": {}
};
}
static get events() {
return [{
"method": "pageSelected",
"name": "pageSelected",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the page is changed. Either via clicking a link or selecting a page in the select."
},
"complexType": {
"original": "{ page: number; mouse: MouseEvent }",
"resolved": "{ page: number; mouse: MouseEvent; }",
"references": {
"MouseEvent": {
"location": "global",
"id": "global::MouseEvent"
}
}
}
}, {
"method": "rowsSelected",
"name": "rowsSelected",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "This event fires when the user changes how many items should be shown per page."
},
"complexType": {
"original": "{ rows: number; change: Event }",
"resolved": "{ rows: number; change: Event; }",
"references": {
"Event": {
"location": "import",
"path": "@stencil/core",
"id": "node_modules::Event",
"referenceLocation": "Event"
}
}
}
}];
}
static get elementRef() { return "hostElement"; }
static get watchers() {
return [{
"propName": "page",
"methodName": "handlePage"
}, {
"propName": "pages",
"methodName": "handlePages"
}, {
"propName": "pagesVisible",
"methodName": "handlePagesVisible"
}];
}
}