All files / src/lib/components/pagination pagination.component.ts

56.86% Statements 29/51
39.28% Branches 11/28
50% Functions 5/10
65.85% Lines 27/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361                    3x 3x 3x 3x 3x 3x     3x                                                                                                     3x                                             1x                                                             1x               1x 1x                                                   1x 1x                                                                                                                               1x   1x     6x 4x             1x       1x 1x     1x     1x     1x     1x 1x     1x                                                                                                                                                                                                              
/**
 * @module module:lib/components/pagination/pagination.component
 * @description Pagination component module.
 * @summary Provides `PaginationComponent` for displaying page navigation controls
 * and emitting pagination events. Use this component to navigate paginated data
 * in lists and other collections.
 *
 * @link {@link PaginationComponent}
 */
 
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { chevronBackOutline, chevronForwardOutline } from 'ionicons/icons';
import { NgxComponentDirective } from '../../engine/NgxComponentDirective';
import { KeyValue } from '../../engine/types';
import { IPaginationCustomEvent } from '../../engine/interfaces';
import { ComponentEventNames } from '@decaf-ts/ui-decorators';
 
/**
 * @description A pagination component for navigating through multiple pages of content.
 * @summary This component provides a user interface for paginated content navigation,
 * displaying page numbers and navigation controls. It supports customizable page counts,
 * current page tracking, and emits events when users navigate between pages.
 *
 * The component intelligently handles large numbers of pages by showing a subset of page
 * numbers with ellipses to indicate skipped pages, ensuring the UI remains clean and usable
 * even with many pages.
 *
 * @mermaid
 * sequenceDiagram
 *   participant U as User
 *   participant P as PaginationComponent
 *   participant E as External Component
 *
 *   U->>P: Click page number
 *   P->>P: navigate(page)
 *   P->>P: handleClick(direction, page)
 *   P->>E: Emit clickEvent with IPaginationCustomEvent
 *
 *   U->>P: Click next button
 *   P->>P: next()
 *   P->>P: handleClick('next')
 *   P->>E: Emit clickEvent with IPaginationCustomEvent
 *
 *   U->>P: Click previous button
 *   P->>P: previous()
 *   P->>P: handleClick('previous')
 *   P->>E: Emit clickEvent with IPaginationCustomEvent
 *
 * @example
 * <ngx-decaf-pagination
 *   [pages]="10"
 *   [current]="3"
 *   (clickEvent)="handlePageChange($event)">
 * </ngx-decaf-pagination>
 *
 * @extends {NgxBaseComponentDirective}
 * @implements {OnInit}
 */
@Component({
  selector: 'ngx-decaf-pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.scss'],
  imports: [TranslatePipe, IonIcon],
  standalone: true,
  host: { '[attr.id]': 'uid' },
})
export class PaginationComponent extends NgxComponentDirective implements OnInit {
  /**
   * @description The total number of pages to display in the pagination component.
   * @summary Specifies the total number of pages available for navigation. This is a required
   * input that determines how many page numbers will be generated and displayed.
   *
   * @type {number}
   * @required
   * @memberOf PaginationComponent
   */
  @Input({ required: true })
  totalPages!: number;
 
  /**
   * @description The currently active page number.
   * @summary Specifies which page is currently active or selected. This value is used
   * to highlight the current page in the UI and as a reference point for navigation.
   *
   * @type {number}
   * @default 1
   * @memberOf PaginationComponent
   */
  @Input()
  current = 1;
 
  /**
   * @description Array of page objects for rendering in the template.
   * @summary Contains the processed page data used for rendering the pagination UI.
   * Each object includes an index (page number) and text representation.
   *
   * @type {KeyValue[]}
   * @memberOf PaginationComponent
   */
  pages!: KeyValue[];
 
  /**
   * @description The last page number in the pagination.
   * @summary Stores the number of the last page for boundary checking during navigation.
   *
   * @type {number}
   * @memberOf PaginationComponent
   */
  last!: number;
 
  /**
   * @description Event emitter for pagination navigation events.
   * @summary Emits a custom event when users navigate between pages, either by clicking
   * on page numbers or using the next/previous buttons. The event contains information
   * about the navigation direction and the target page number.
   *
   * @type {EventEmitter<IPaginationCustomEvent>}
   * @memberOf PaginationComponent
   */
  @Output()
  clickEvent: EventEmitter<IPaginationCustomEvent> = new EventEmitter<IPaginationCustomEvent>();
 
  /**
   * @constructor
   * @description Initializes a new instance of the PaginationComponent.
   * Calls the parent constructor with the component name for generate base locale string.
   */
  constructor() {
    super('PaginationComponent');
    addIcons({ chevronBackOutline, chevronForwardOutline });
  }
 
  /**
   * @description Initializes the component after Angular sets the input properties.
   * @summary Sets up the component by initializing the locale settings based on the
   * translatable property, generating the page numbers based on the total pages and
   * current page, and storing the last page number for boundary checking.
   *
   * @mermaid
   * sequenceDiagram
   *   participant A as Angular Lifecycle
   *   participant P as PaginationComponent
   *
   *   A->>P: ngOnInit()
   *   P->>P: getLocale(translatable)
   *   P->>P: Set locale
   *   P->>P: getPages(data, current)
   *   P->>P: Set pages array
   *   P->>P: Set last page number
   *
   * @returns {void}
   * @memberOf PaginationComponent
   */
  ngOnInit(): void {
    // this.locale = this.getLocale(this.translatable);
    this.pages = this.getPages(this.totalPages, this.current) as KeyValue[];
    this.last = this.totalPages;
  }
 
  /**
   * @description Handles click events on pagination controls.
   * @summary Processes user interactions with the pagination component, updating the
   * current page if specified and emitting an event with navigation details. This method
   * is called when users click on page numbers or navigation buttons.
   *
   * @param {('next' | 'previous')} direction - The direction of navigation
   * @param {number} [page] - Optional page number to navigate to directly
   * @returns {void}
   *
   * @mermaid
   * sequenceDiagram
   *   participant U as User
   *   participant P as PaginationComponent
   *   participant E as External Component
   *
   *   U->>P: Click pagination control
   *   P->>P: handleClick(direction, page?)
   *   alt page is provided
   *     P->>P: Update current page
   *   end
   *   P->>E: Emit clickEvent with direction and page
   *
   * @memberOf PaginationComponent
   */
  handleClick(direction: 'next' | 'previous', page?: number): void {
    Iif (page) this.current = page;
    this.clickEvent.emit({
      name: ComponentEventNames.Click,
      data: {
        direction,
        page: this.current,
      },
      component: this.componentName,
    } as IPaginationCustomEvent);
  }
 
  /**
   * @description Generates the array of page objects for display.
   * @summary Creates an array of page objects based on the total number of pages and
   * the current page. For small page counts (≤5), all pages are shown. For larger page
   * counts, a subset is shown with ellipses to indicate skipped pages. This ensures
   * the pagination UI remains clean and usable even with many pages.
   *
   * @param {number} total - The total number of pages
   * @param {number} [current] - The current active page (defaults to this.current)
   * @returns {KeyValue[]} Array of page objects with index and text properties
   *
   * @mermaid
   * flowchart TD
   *   A[Start] --> B{total <= 5?}
   *   B -->|Yes| C[Show all pages]
   *   B -->|No| D[Show first page]
   *   D --> E[Show last pages]
   *   E --> F[Add ellipses for skipped pages]
   *   C --> G[Return pages array]
   *   F --> G
   *
   * @memberOf PaginationComponent
   */
  getPages(total: number, current?: number): KeyValue[] {
    Iif (!current) current = this.current;
 
    const pages: KeyValue[] = [];
 
    function getPage(index: number | null, text = '', clazz = 'button'): void {
      Iif (pages.some((item) => item['index'] === index)) return;
      pages.push({
        index,
        text: index != null ? index.toString().padStart(2, '0') : text,
        class: clazz,
      });
    }
 
    Iif (total <= 5) {
      for (let i = 1; i <= total; i++) getPage(i);
    } else {
      // Adiciona os dois primeiros
      getPage(1);
      getPage(2);
 
      // Adiciona "..." entre os blocos
      Iif (current && current > 3) getPage(null, '...');
 
      // Adiciona a página atual (se estiver no meio)
      Iif (current && current > 2 && current < total - 1) getPage(current);
 
      // Adiciona "..." entre os blocos
      Iif (current && current < total - 2) getPage(null, '...', 'separator');
 
      // Adiciona os dois últimos
      getPage(total - 1);
      getPage(total);
    }
 
    return pages;
  }
 
  /**
   * @description Gets the current active page number.
   * @summary Returns the current page number that is active in the pagination component.
   * This method provides a way to access the current page state from outside the component.
   *
   * @returns {number} The current page number
   * @memberOf PaginationComponent
   */
  getCurrent(): number {
    return this.current;
  }
 
  /**
   * @description Navigates to the next page.
   * @summary Increments the current page number if not at the last page and triggers
   * the click event handler with 'next' direction. This method is typically called
   * when the user clicks on the "next" button in the pagination UI.
   *
   * @returns {void}
   *
   * @mermaid
   * sequenceDiagram
   *   participant U as User
   *   participant P as PaginationComponent
   *
   *   U->>P: Click next button
   *   P->>P: next()
   *   alt page <= max pages
   *     P->>P: Increment current page
   *     P->>P: handleClick('next')
   *   end
   *
   * @memberOf PaginationComponent
   */
  next(): void {
    const page = this.current + 1;
    Iif (page <= Object.keys(this.pages)?.length || 0) {
      this.current = page;
      this.handleClick('next');
    }
  }
 
  /**
   * @description Navigates to the previous page.
   * @summary Decrements the current page number if not at the first page and triggers
   * the click event handler with 'previous' direction. This method is typically called
   * when the user clicks on the "previous" button in the pagination UI.
   *
   * @returns {void}
   *
   * @mermaid
   * sequenceDiagram
   *   participant U as User
   *   participant P as PaginationComponent
   *
   *   U->>P: Click previous button
   *   P->>P: previous()
   *   alt page > 0
   *     P->>P: Decrement current page
   *     P->>P: handleClick('previous')
   *   end
   *
   * @memberOf PaginationComponent
   */
  previous(): void {
    const page = this.current - 1;
    Iif (page > 0) {
      this.current = page;
      this.handleClick('previous');
    }
  }
 
  /**
   * @description Navigates to a specific page number.
   * @summary Updates the current page to the specified page number and triggers
   * the click event handler with the appropriate direction. This method is typically
   * called when the user clicks directly on a page number in the pagination UI.
   *
   * @param {number | null} page - The page number to navigate to
   * @returns {void}
   *
   * @mermaid
   * sequenceDiagram
   *   participant U as User
   *   participant P as PaginationComponent
   *
   *   U->>P: Click page number
   *   P->>P: navigate(page)
   *   alt page is not null and different from current
   *     P->>P: Determine direction (next/previous)
   *     P->>P: handleClick(direction, page)
   *   end
   *
   * @memberOf PaginationComponent
   */
  navigate(page: number | null): void {
    Iif (page !== null && this.current !== (page as number))
      this.handleClick(page > this.current ? 'next' : 'previous', page);
  }
}