UNPKG

@umbraco-ui/uui-pagination

Version:

Umbraco UI pagination component. By implementing a resizeObserver it changes the number of visible buttons to fit the width of the container it sits in. Based on uui-button and uui-button-group.

307 lines (298 loc) 9.3 kB
import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { demandCustomElement } from '@umbraco-ui/uui-base/lib/utils'; import { css, LitElement, html } from 'lit'; import { queryAll, query, property, state } from 'lit/decorators.js'; import { UUIEvent } from '@umbraco-ui/uui-base/lib/events'; class UUIPaginationEvent extends UUIEvent { constructor(evName, eventInit = {}) { super(evName, { ...{ bubbles: true }, ...eventInit }); } } UUIPaginationEvent.CHANGE = "change"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; const PAGE_BUTTON_MAX_WIDTH = 45; const limit = (val, min, max) => { return Math.min(Math.max(val, min), max); }; const arrayOfNumbers = (start, stop) => { return Array.from({ length: stop - start + 1 }, (_, i) => start + i); }; let UUIPaginationElement = class extends LitElement { constructor() { super(...arguments); this._observer = new ResizeObserver(this._calculateRange.bind(this)); this.label = ""; this.ariaLabel = ""; this.firstLabel = "First"; this.previousLabel = "Previous"; this.nextLabel = "Next"; this.lastLabel = "Last"; this._total = 100; this._range = 0; this._visiblePages = []; this._current = 1; } connectedCallback() { super.connectedCallback(); if (!this.hasAttribute("role")) this.setAttribute("role", "navigation"); this._visiblePages = this._generateVisiblePages(this.current); demandCustomElement(this, "uui-button"); demandCustomElement(this, "uui-button-group"); } disconnectedCallback() { this._observer.disconnect(); } firstUpdated() { this._observer.observe(this._pagesGroup); this.updateLabel(); this._calculateRange(); } willUpdate(changedProperties) { if (changedProperties.has("current") || changedProperties.has("label")) { this.updateLabel(); } } updateLabel() { this.ariaLabel = `${this.label || "Pagination navigation"}. Current page: ${this.current}.`; } _calculateRange() { const containerWidth = this.offsetWidth; const navButtonsWidth = Array.from(this._navButtons).reduce( (totalWidth, button) => { return totalWidth + button.getBoundingClientRect().width; }, 0 ); const rangeBaseWidth = containerWidth - navButtonsWidth; const range = rangeBaseWidth / PAGE_BUTTON_MAX_WIDTH / 2; this._range = Math.max(1, Math.floor(range)); this._visiblePages = this._generateVisiblePages(this.current); } _generateVisiblePages(current) { const start = current < this._range ? 1 : current < this.total - this._range ? current - this._range : this.total - this._range * 2; const stop = current <= this._range ? this._range * 2 + 1 : current < this.total - this._range ? current + this._range : this.total; const pages = arrayOfNumbers( limit(start, 1, this.total), limit(stop, 1, this.total) ); return pages; } get total() { return this._total; } set total(newValue) { this._total = newValue; this._visiblePages = this._generateVisiblePages(this._current); this.requestUpdate("total", newValue); } get current() { return this._current; } set current(newValue) { const oldValue = this._current; this._current = limit(newValue, 1, this.total); this._visiblePages = this._generateVisiblePages(this._current); this.requestUpdate("current", oldValue); } /** * This method will change the page to a next one. * @memberof UUIPaginationElement */ goToNextPage() { this.current++; this.dispatchEvent(new UUIPaginationEvent(UUIPaginationEvent.CHANGE)); } /** * Change the page to a previous one. * @memberof UUIPaginationElement */ goToPreviousPage() { this.current--; this.dispatchEvent(new UUIPaginationEvent(UUIPaginationEvent.CHANGE)); } /** * Change the page to the one passed as an argument to this method. * @param {number} page * @memberof UUIPaginationElement */ goToPage(page) { this.current = page; this.dispatchEvent(new UUIPaginationEvent(UUIPaginationEvent.CHANGE)); } /** When having limited display of page-buttons and clicking a page-button that changes the current range, the focus stays on the position of the clicked button which is not anymore representing the number clicked, therefore we move focus to the button that represents the current page. */ focusActivePage() { requestAnimationFrame(() => { const activeButtonElement = this.renderRoot.querySelector(".active"); if (activeButtonElement) { activeButtonElement.focus(); } }); } renderFirst() { return html`<uui-button compact look="outline" class="nav" role="listitem" label=${this.firstLabel} ?disabled=${this._current === 1} @click=${() => this.goToPage(1)}></uui-button>`; } renderPrevious() { return html`<uui-button compact look="outline" class="nav" role="listitem" label=${this.previousLabel} ?disabled=${this._current === 1} @click=${this.goToPreviousPage}></uui-button>`; } renderNext() { return html`<uui-button compact look="outline" role="listitem" class="nav" label=${this.nextLabel} ?disabled=${this._current === this.total} @click=${this.goToNextPage}></uui-button>`; } renderLast() { return html` <uui-button compact look="outline" role="listitem" class="nav" label=${this.lastLabel} ?disabled=${this.total === this._current} @click=${() => this.goToPage(this.total)}></uui-button> `; } renderDots() { return html`<uui-button compact look="outline" role="listitem" tabindex="-1" class="dots" label="More pages" >...</uui-button > `; } renderPage(page) { return html`<uui-button compact look="outline" role="listitem" label="Go to page ${page}" class=${"page" + (page === this._current ? " active" : "")} tabindex=${page === this._current ? "-1" : ""} @click=${() => { if (page === this._current) return; this.goToPage(page); this.focusActivePage(); }}> ${page} </uui-button>`; } renderNavigationLeft() { return html` ${this.renderFirst()} ${this.renderPrevious()} ${this._visiblePages.includes(1) ? "" : this.renderDots()}`; } renderNavigationRight() { return html`${this._visiblePages.includes(this.total) ? "" : this.renderDots()} ${this.renderNext()} ${this.renderLast()}`; } render() { return html`<uui-button-group role="list" id="pages"> ${this.renderNavigationLeft()} ${this._visiblePages.map( (page) => this.renderPage(page) )} ${this.renderNavigationRight()} </uui-button-group> `; } }; UUIPaginationElement.styles = [ css` uui-button-group { width: 100%; } uui-button { --uui-button-border-color: var(--uui-color-border-standalone,#c2c2c2); --uui-button-border-color-hover: var(--uui-color-interactive-emphasis,#3544b1); --uui-button-border-color-disabled: var(--uui-color-border-standalone,#c2c2c2); } .page { min-width: 36px; max-width: 72px; } .page.active { --uui-button-background-color: var(--uui-color-current,#f5c1bc); } .nav { min-width: 72px; } .dots { pointer-events: none; } .active { pointer-events: none; } ` ]; __decorateClass([ queryAll("uui-button.nav") ], UUIPaginationElement.prototype, "_navButtons", 2); __decorateClass([ query("#pages") ], UUIPaginationElement.prototype, "_pagesGroup", 2); __decorateClass([ property() ], UUIPaginationElement.prototype, "label", 2); __decorateClass([ property({ reflect: true, attribute: "aria-label" }) ], UUIPaginationElement.prototype, "ariaLabel", 2); __decorateClass([ property() ], UUIPaginationElement.prototype, "firstLabel", 2); __decorateClass([ property() ], UUIPaginationElement.prototype, "previousLabel", 2); __decorateClass([ property() ], UUIPaginationElement.prototype, "nextLabel", 2); __decorateClass([ property() ], UUIPaginationElement.prototype, "lastLabel", 2); __decorateClass([ property({ type: Number }) ], UUIPaginationElement.prototype, "total", 1); __decorateClass([ state() ], UUIPaginationElement.prototype, "_range", 2); __decorateClass([ state() ], UUIPaginationElement.prototype, "_visiblePages", 2); __decorateClass([ property({ type: Number }) ], UUIPaginationElement.prototype, "current", 1); UUIPaginationElement = __decorateClass([ defineElement("uui-pagination") ], UUIPaginationElement); export { UUIPaginationElement, UUIPaginationEvent };