import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { BootstrapElement, defineElement, type Size } from '@bootstrap-wc/core';
export type PaginationAlign = 'start' | 'center' | 'end';
/**
* `` — Bootstrap pagination.
*
* @slot prev - Custom content for the "Previous" control (e.g. an icon). When
* used, `prev-label` is applied as the `aria-label` on the link for a11y.
* @slot next - Custom content for the "Next" control (e.g. an icon). When
* used, `next-label` is applied as the `aria-label` on the link for a11y.
*
* @fires bs-page-change - `{detail: {page: number}}` when the user selects a page.
*/
export class BsPagination extends BootstrapElement {
@property({ type: Number }) total = 1;
@property({ type: Number }) current = 1;
@property({ type: Number }) window = 2;
@property({ type: String }) size?: Size;
@property({ type: String }) align: PaginationAlign = 'start';
@property({ type: Boolean, attribute: 'no-nav' }) noNav = false;
@property({ type: String, attribute: 'prev-label' }) prevLabel = 'Previous';
@property({ type: String, attribute: 'next-label' }) nextLabel = 'Next';
private _hasPrevSlot = false;
private _hasNextSlot = false;
private _go = (page: number) => (ev: Event) => {
ev.preventDefault();
if (page < 1 || page > this.total || page === this.current) return;
this.current = page;
this.dispatchEvent(
new CustomEvent('bs-page-change', { bubbles: true, composed: true, detail: { page } }),
);
};
private _pagesToShow(): (number | '...')[] {
const out: (number | '...')[] = [];
const w = this.window;
const last = this.total;
const cur = this.current;
const start = Math.max(1, cur - w);
const end = Math.min(last, cur + w);
if (start > 1) {
out.push(1);
if (start > 2) out.push('...');
}
for (let i = start; i <= end; i++) out.push(i);
if (end < last) {
if (end < last - 1) out.push('...');
out.push(last);
}
return out;
}
private _onSlotChange(name: 'prev' | 'next') {
return (ev: Event) => {
const slot = ev.target as HTMLSlotElement;
const hasContent = slot.assignedNodes({ flatten: true }).some((n) => {
if (n.nodeType === Node.ELEMENT_NODE) return true;
return !!(n.textContent && n.textContent.trim());
});
if (name === 'prev' && this._hasPrevSlot !== hasContent) {
this._hasPrevSlot = hasContent;
this.requestUpdate();
} else if (name === 'next' && this._hasNextSlot !== hasContent) {
this._hasNextSlot = hasContent;
this.requestUpdate();
}
};
}
override render() {
const ulClasses = classMap({
pagination: true,
[`pagination-${this.size}`]: !!this.size && this.size !== 'md',
[`justify-content-${this.align}`]: this.align === 'center' || this.align === 'end',
});
const pages = this._pagesToShow();
const prevDisabled = this.current === 1;
const nextDisabled = this.current === this.total;
return html`
`;
}
}
defineElement('bs-pagination', BsPagination);
declare global {
interface HTMLElementTagNameMap {
'bs-pagination': BsPagination;
}
}