/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import { LitElement, html, CSSResultArray, TemplateResult } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { styles } from './nile-pagination.css'; import NileElement from '../internal/nile-element'; import { calculateTotalPages, getPaginationItems, getHiddenPages, getRangeText, } from './nile-pagination-utils'; @customElement('nile-pagination') export class NilePagination extends NileElement { public static get styles(): CSSResultArray { return [styles]; } @property({attribute: 'totalitems',reflect: true,converter: { fromAttribute: (v: string) => { const n = Number(v); return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; }, }, })totalItems: number = 0; @property({attribute: 'currentpage', reflect: true, converter: { fromAttribute: (v: string) => { const n = Number(v); return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1; }, }, })currentPage: number = 1; @property({attribute: 'pagesize',reflect: true,converter: { fromAttribute: (v: string) => { const n = Number(v); return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 50; }, }, })pageSize: number = 50; @property({ attribute: 'pagesizeoptions', reflect: false, converter: { fromAttribute: (v: string) => { try { const arr = JSON.parse(v); if (Array.isArray(arr) && arr.every(x => typeof x === 'number')) { return arr as number[]; } } catch {} return [10, 25, 50, 100]; }}}) pageSizeOptions: number[] = [10, 25, 50, 100]; @property({ type: String })variant: 'fluid' | 'compact' | 'mini' = 'fluid'; @property({ type: Boolean, reflect: true }) disabled = false; @property({ type: Boolean }) showTitle = true; @state() private _pageSizeOpen = false; @state() private _pageOpen = false; @state() private _miniPageOpen = false; @query('.page-size-dropdown') private _pageSizeDropdown!: HTMLElement; firstUpdated() { if (this._pageSizeDropdown) { this._pageSizeDropdown.addEventListener('nile-show', () => { this._pageSizeOpen = true; }); this._pageSizeDropdown.addEventListener('nile-after-hide', () => { this._pageSizeOpen = false; }); } } private get totalPages(): number { return Math.max(1, calculateTotalPages(this.totalItems, this.pageSize)); } private goToPage(newPage: number) { if (this.disabled) return; const previousPage = this.currentPage; this.currentPage = newPage; this.emit('nile-change', { page: newPage, previousPage, pageSize: this.pageSize }); } private onPageSizeSelect(newSize: number) { if (this.disabled || this.pageSize === newSize) return; const previousPage = this.currentPage; this.pageSize = newSize; this.currentPage = 1; this.emit('nile-change', { page: 1, previousPage, pageSize: newSize }); } private renderCompactRangeText(): TemplateResult { const full = getRangeText(this.totalItems, this.pageSize, this.currentPage); const trimmed = full.replace(/^Showing\s*/, ''); return html`
${trimmed}
`; } private renderRangeText(): TemplateResult { if (this.totalItems === 0) { return html`
Showing 0 of 0
`; } return html`
${getRangeText(this.totalItems, this.pageSize, this.currentPage)}
`; } private renderPageSizeSelect(): TemplateResult | null { if (this.variant !== 'fluid') return null; return html`
${this.pageSize}
${this.pageSizeOptions.map( size => html` this.onPageSizeSelect(size)} > ${size} ` )}
Items per page
`; } private renderCompactPageSize(): TemplateResult { return html` (this._pageSizeOpen = true)} @nile-after-hide=${() => (this._pageSizeOpen = false)} > ${this.pageSize}
${this.pageSizeOptions.map( size => html` this.onPageSizeSelect(size)} > ${size} ` )}
`; } private renderPrevButton(): TemplateResult { return html`
  • this.goToPage(this.currentPage - 1)} class="prev-button" >
  • `; } private renderNextButton(): TemplateResult { return html`
  • this.goToPage(this.currentPage + 1)} class="next-button" >
  • `; } private renderPageItem( item: number | string, idx: number, items: (number | string)[] ): TemplateResult { if (item === '…') { return html`
  • ${getHiddenPages( this.totalPages, items, this.currentPage, idx < items.indexOf(this.currentPage) ? 'left' : 'right' ).map( page => html` this.goToPage(page)} > ${page} ` )}
  • `; } return html`
  • this.goToPage(item as number)} > ${item}
  • `; } private renderMiniPrevButton(): TemplateResult { return html`
  • this.goToPage(this.currentPage - 1)} class="mini-prev-button" >
  • `; } private renderMiniNextButton(): TemplateResult { return html`
  • this.goToPage(this.currentPage + 1)} class="mini-next-button" >
  • `; } private renderMini(): TemplateResult { return html`
    Showing (this._miniPageOpen = true)} @nile-after-hide=${() => (this._miniPageOpen = false)} > ${this.currentPage}
    ${Array.from({ length: this.totalPages }, (_, i) => i + 1).map( p => html` this.goToPage(p)} > ${p} ` )}
    of ${this.totalPages}
    `; } private renderPageList(): TemplateResult { if (this.variant === 'compact') { return html` `; } const items = getPaginationItems(this.totalPages, this.currentPage); return html` `; } public render(): TemplateResult { if (this.variant === 'mini') { return this.renderMini(); } return html`
    ${this.variant === 'fluid' && this.showTitle ? this.renderRangeText() : this.variant === 'compact' ? this.renderCompactRangeText() : null} ${this.variant === 'fluid' ? this.renderPageSizeSelect() : this.renderCompactPageSize()}
    ${this.renderPageList()}
    `; } } declare global { interface HTMLElementTagNameMap { 'nile-pagination': NilePagination; } }