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,
|
|||
|
})
|
|||
|
);
|
|||
|
}
|
|||
|
}
|