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