133 lines
3.3 KiB
TypeScript
133 lines
3.3 KiB
TypeScript
import { customElement, html, DeesElement, property, css, cssManager, type TemplateResult } from '@design.estate/dees-element';
|
||
import { demoFunc } from './dees-pagination.demo.js';
|
||
|
||
declare global {
|
||
interface HTMLElementTagNameMap {
|
||
'dees-pagination': DeesPagination;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* A simple pagination component.
|
||
* @fires page-change - Emitted when the page is changed. detail: { page: number }
|
||
*/
|
||
@customElement('dees-pagination')
|
||
export class DeesPagination extends DeesElement {
|
||
public static demo = demoFunc;
|
||
/** Current page (1-based) */
|
||
@property({ type: Number, reflect: true })
|
||
public page = 1;
|
||
|
||
/** Total number of pages */
|
||
@property({ type: Number, reflect: true })
|
||
public total = 1;
|
||
|
||
public static styles = [
|
||
cssManager.defaultStyles,
|
||
css`
|
||
:host {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
button {
|
||
background: none;
|
||
border: none;
|
||
margin: 0 2px;
|
||
padding: 6px 10px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||
border-radius: 3px;
|
||
transition: background 0.2s;
|
||
}
|
||
button:hover:not(:disabled) {
|
||
background: ${cssManager.bdTheme('#eee', '#444')};
|
||
}
|
||
button:disabled {
|
||
cursor: default;
|
||
color: ${cssManager.bdTheme('#aaa', '#666')};
|
||
}
|
||
button.current {
|
||
background: #0050b9;
|
||
color: #fff;
|
||
cursor: default;
|
||
}
|
||
span.ellipsis {
|
||
margin: 0 4px;
|
||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||
}
|
||
`,
|
||
];
|
||
|
||
private get pages(): (number | string)[] {
|
||
const pages: (number | string)[] = [];
|
||
const total = this.total;
|
||
const current = this.page;
|
||
if (total <= 7) {
|
||
for (let i = 1; i <= total; i++) {
|
||
pages.push(i);
|
||
}
|
||
} else {
|
||
pages.push(1);
|
||
if (current > 4) {
|
||
pages.push('...');
|
||
}
|
||
const start = Math.max(2, current - 2);
|
||
const end = Math.min(total - 1, current + 2);
|
||
for (let i = start; i <= end; i++) {
|
||
pages.push(i);
|
||
}
|
||
if (current < total - 3) {
|
||
pages.push('...');
|
||
}
|
||
pages.push(total);
|
||
}
|
||
return pages;
|
||
}
|
||
|
||
public render(): TemplateResult {
|
||
return html`
|
||
<button
|
||
@click=${() => this.changePage(this.page - 1)}
|
||
?disabled=${this.page <= 1}
|
||
aria-label="Previous page"
|
||
>
|
||
‹
|
||
</button>
|
||
${this.pages.map((p) =>
|
||
p === '...'
|
||
? html`<span class="ellipsis">…</span>`
|
||
: html`
|
||
<button
|
||
class="${p === this.page ? 'current' : ''}"
|
||
@click=${() => this.changePage(p as number)}
|
||
?disabled=${p === this.page}
|
||
aria-label="Page ${p}"
|
||
>
|
||
${p}
|
||
</button>
|
||
`
|
||
)}
|
||
<button
|
||
@click=${() => this.changePage(this.page + 1)}
|
||
?disabled=${this.page >= this.total}
|
||
aria-label="Next page"
|
||
>
|
||
›
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
private changePage(newPage: number) {
|
||
if (newPage < 1 || newPage > this.total || newPage === this.page) {
|
||
return;
|
||
}
|
||
this.page = newPage;
|
||
this.dispatchEvent(
|
||
new CustomEvent('page-change', {
|
||
detail: { page: this.page },
|
||
bubbles: true,
|
||
})
|
||
);
|
||
}
|
||
} |