UNPKG

@postnord/web-components

Version:
504 lines (503 loc) 19.7 kB
/*! * 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" }]; } }