@styloviz/pagination 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StyloViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @styloviz/pagination
2
+
3
+ > Smart page-window with ellipsis, a page-size selector, 3 variants and 3 sizes.
4
+
5
+ Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @styloviz/core @styloviz/pagination
11
+ ```
12
+
13
+ Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { SvPaginationComponent } from '@styloviz/pagination';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [SvPaginationComponent],
23
+ template: `
24
+ <sv-pagination [totalItems]="240" [pageSize]="20" [currentPage]="page" (pageChange)="page = $event" />
25
+ `,
26
+ })
27
+ export class DemoComponent {}
28
+ ```
29
+
30
+ ## Inputs
31
+
32
+ | Input | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | `totalItems` | `number` | `0` | Total number of items across all pages. |
35
+ | `pageSize` | `number (two-way)` | `10` | Number of items per page. Use `[(pageSize)]` for two-way binding. |
36
+ | `currentPage` | `number (two-way)` | `1` | 1-based current page index. Use `[(currentPage)]` for two-way binding. |
37
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Options shown in the page-size `<select>`. Empty array hides the selector entirely. |
38
+ | `variant` | `PaginationVariant` | `'default'` | Visual variant. |
39
+ | `size` | `PaginationSize` | `'md'` | Control size. |
40
+ | `showSummary` | `boolean` | `true` | Show total-item count summary (e.g. "Showing 1–10 of 234"). |
41
+ | `ariaLabel` | `string` | `'Pagination'` | Accessible label for the navigation landmark. |
42
+ | `customClass` | `string` | `''` | Additional CSS classes on the root wrapper. |
43
+
44
+ ## Outputs
45
+
46
+ | Output | Type | Description |
47
+ | --- | --- | --- |
48
+ | `pageChange` | `number` | Emitted when the user navigates to a different page. |
49
+
50
+ ## Documentation
51
+
52
+ Full API reference and live demos: https://styloviz.dev/docs/pagination
53
+
54
+ ## License
55
+
56
+ MIT
@@ -0,0 +1,146 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, model, output, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgClass } from '@angular/common';
4
+ import * as i1 from '@angular/forms';
5
+ import { FormsModule } from '@angular/forms';
6
+
7
+ // ─── Component ───────────────────────────────────────────────────────────────
8
+ /**
9
+ * Pagination — A fully-featured page navigator for tables, card grids, and lists.
10
+ *
11
+ * Features:
12
+ * - Page-window with leading/trailing ellipsis (smart truncation)
13
+ * - First / Previous / Next / Last buttons
14
+ * - Optional page-size selector
15
+ * - 3 variants: default (full controls) · simple (prev/next + label) · compact (minimal)
16
+ * - 3 sizes: sm · md · lg
17
+ * - Two-way `currentPage` binding via `model()`
18
+ * - `pageChange` and `pageSizeChange` outputs
19
+ * - Full dark-mode support
20
+ * - Accessible: `aria-label`, `aria-current="page"`, disabled states
21
+ */
22
+ class SvPaginationComponent {
23
+ // ── Inputs ────────────────────────────────────────────────────────────────
24
+ /** Total number of items across all pages. */
25
+ totalItems = input(0, ...(ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []));
26
+ /** Number of items per page. Use `[(pageSize)]` for two-way binding. */
27
+ pageSize = model(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
28
+ /** 1-based current page index. Use `[(currentPage)]` for two-way binding. */
29
+ currentPage = model(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
30
+ /**
31
+ * Options shown in the page-size `<select>`.
32
+ * Empty array hides the selector entirely. @default [10, 25, 50, 100]
33
+ */
34
+ pageSizeOptions = input([10, 25, 50, 100], ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
35
+ /** Visual variant. @default 'default' */
36
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
37
+ /** Control size. @default 'md' */
38
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
39
+ /** Show total-item count summary (e.g. "Showing 1–10 of 234"). @default true */
40
+ showSummary = input(true, ...(ngDevMode ? [{ debugName: "showSummary" }] : /* istanbul ignore next */ []));
41
+ /** Accessible label for the navigation landmark. @default 'Pagination' */
42
+ ariaLabel = input('Pagination', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
43
+ /** Additional CSS classes on the root wrapper. */
44
+ customClass = input('', ...(ngDevMode ? [{ debugName: "customClass" }] : /* istanbul ignore next */ []));
45
+ // ── Outputs ───────────────────────────────────────────────────────────────
46
+ /** Emitted when the user navigates to a different page. */
47
+ pageChange = output();
48
+ // Note: `pageSize = model<number>(10)` already auto-emits `pageSizeChange`
49
+ // when pageSize.set() is called — no separate output needed here.
50
+ // ── Computed ──────────────────────────────────────────────────────────────
51
+ totalPages = computed(() => Math.max(1, Math.ceil(this.totalItems() / this.pageSize())), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
52
+ /** First item index (1-based) on the current page. */
53
+ rangeStart = computed(() => Math.min((this.currentPage() - 1) * this.pageSize() + 1, this.totalItems()), ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
54
+ /** Last item index (1-based) on the current page. */
55
+ rangeEnd = computed(() => Math.min(this.currentPage() * this.pageSize(), this.totalItems()), ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
56
+ hasPrev = computed(() => this.currentPage() > 1, ...(ngDevMode ? [{ debugName: "hasPrev" }] : /* istanbul ignore next */ []));
57
+ hasNext = computed(() => this.currentPage() < this.totalPages(), ...(ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []));
58
+ /**
59
+ * Visible page numbers in the page window, with -1 as an ellipsis marker.
60
+ * Always shows: first, last, currentPage ±1, with ellipsis for gaps > 1.
61
+ */
62
+ pageWindow = computed(() => {
63
+ const total = this.totalPages();
64
+ const current = this.currentPage();
65
+ if (total <= 7)
66
+ return Array.from({ length: total }, (_, i) => i + 1);
67
+ const pages = [];
68
+ const add = (n) => { if (!pages.includes(n))
69
+ pages.push(n); };
70
+ add(1);
71
+ if (current > 3)
72
+ pages.push(-1); // left ellipsis
73
+ for (let p = Math.max(2, current - 1); p <= Math.min(total - 1, current + 1); p++)
74
+ add(p);
75
+ if (current < total - 2)
76
+ pages.push(-1); // right ellipsis
77
+ add(total);
78
+ return pages;
79
+ }, ...(ngDevMode ? [{ debugName: "pageWindow" }] : /* istanbul ignore next */ []));
80
+ // ── Size maps ─────────────────────────────────────────────────────────────
81
+ btnClass = computed(() => {
82
+ const s = this.size();
83
+ return {
84
+ sm: 'h-7 min-w-7 px-1.5 text-xs',
85
+ md: 'h-8 min-w-8 px-2 text-sm',
86
+ lg: 'h-10 min-w-10 px-3 text-sm',
87
+ }[s];
88
+ }, ...(ngDevMode ? [{ debugName: "btnClass" }] : /* istanbul ignore next */ []));
89
+ textClass = computed(() => ({
90
+ sm: 'text-xs',
91
+ md: 'text-sm',
92
+ lg: 'text-sm',
93
+ })[this.size()], ...(ngDevMode ? [{ debugName: "textClass" }] : /* istanbul ignore next */ []));
94
+ // ── Page button class helper ───────────────────────────────────────────────
95
+ pageButtonClass(page) {
96
+ const isActive = page === this.currentPage();
97
+ const base = [
98
+ 'inline-flex items-center justify-center rounded-lg font-medium transition-colors duration-100',
99
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1',
100
+ this.btnClass(),
101
+ ];
102
+ if (isActive) {
103
+ base.push('bg-primary-600 text-white dark:bg-primary-500');
104
+ }
105
+ else {
106
+ base.push('text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700');
107
+ }
108
+ return base.join(' ');
109
+ }
110
+ navButtonClass(disabled) {
111
+ return [
112
+ 'inline-flex items-center justify-center rounded-lg font-medium transition-colors duration-100',
113
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1',
114
+ this.btnClass(),
115
+ disabled
116
+ ? 'cursor-not-allowed text-gray-300 dark:text-gray-600'
117
+ : 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700',
118
+ ].join(' ');
119
+ }
120
+ // ── Navigation ────────────────────────────────────────────────────────────
121
+ goTo(page) {
122
+ const clamped = Math.max(1, Math.min(page, this.totalPages()));
123
+ if (clamped === this.currentPage())
124
+ return;
125
+ this.currentPage.set(clamped);
126
+ this.pageChange.emit(clamped);
127
+ }
128
+ onPageSizeChange(value) {
129
+ this.pageSize.set(Number(value)); // model auto-emits pageSizeChange
130
+ this.currentPage.set(1);
131
+ this.pageChange.emit(1);
132
+ }
133
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
134
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvPaginationComponent, isStandalone: true, selector: "sv-pagination", inputs: { totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, showSummary: { classPropertyName: "showSummary", publicName: "showSummary", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageSize: "pageSizeChange", currentPage: "currentPageChange", pageChange: "pageChange" }, ngImport: i0, template: "<!-- Root wrapper -->\n<div\n [ngClass]=\"[\n 'flex flex-wrap items-center gap-3',\n variant() === 'compact' ? 'justify-center' : 'justify-between',\n customClass()\n ]\"\n>\n\n <!-- \u2500\u2500 Summary + page-size selector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (variant() !== 'compact') {\n <div class=\"flex flex-wrap items-center gap-3\">\n\n @if (showSummary() && totalItems() > 0) {\n <p [ngClass]=\"['text-gray-500 dark:text-gray-400', textClass()]\">\n Showing\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeStart() }}</span>\n \u2013\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeEnd() }}</span>\n of\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ totalItems() }}</span>\n </p>\n }\n\n @if (pageSizeOptions().length > 0) {\n <div class=\"flex items-center gap-1.5\">\n <label [ngClass]=\"['text-gray-500 dark:text-gray-400 whitespace-nowrap', textClass()]\"\n for=\"page-size-select\">Rows per page</label>\n <select\n id=\"page-size-select\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n [ngClass]=\"[\n 'rounded-lg border border-gray-200 bg-white text-gray-900',\n 'dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100',\n 'focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20',\n textClass(),\n size() === 'sm' ? 'h-7 px-2 py-0' : size() === 'lg' ? 'h-10 px-3 py-1' : 'h-8 px-2.5 py-0.5'\n ]\"\n >\n @for (opt of pageSizeOptions(); track opt) {\n <option [ngValue]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n }\n\n </div>\n }\n\n <!-- \u2500\u2500 Page controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <nav [attr.aria-label]=\"ariaLabel()\">\n <ul class=\"flex items-center gap-1\">\n\n @if (variant() === 'simple') {\n <!-- Simple variant: prev / label / next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <li>\n <span [ngClass]=\"['px-3 text-gray-600 dark:text-gray-300', textClass()]\">\n Page {{ currentPage() }} of {{ totalPages() }}\n </span>\n </li>\n\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n } @else {\n <!-- Default / compact: full page window -->\n\n <!-- First -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(1)\"\n aria-label=\"First page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M11 17l-5-5 5-5M18 17l-5-5 5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n <!-- Prev -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Page numbers -->\n @for (page of pageWindow(); track $index) {\n <li>\n @if (page === -1) {\n <!-- Ellipsis -->\n <span [ngClass]=\"['inline-flex items-center justify-center text-gray-500 dark:text-gray-400', btnClass()]\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n [ngClass]=\"pageButtonClass(page)\"\n [attr.aria-current]=\"page === currentPage() ? 'page' : null\"\n [attr.aria-label]=\"'Page ' + page\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </button>\n }\n </li>\n }\n\n <!-- Next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Last -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(totalPages())\"\n aria-label=\"Last page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M13 17l5-5-5-5M6 17l5-5-5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n }\n\n </ul>\n </nav>\n\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
135
+ }
136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvPaginationComponent, decorators: [{
137
+ type: Component,
138
+ args: [{ selector: 'sv-pagination', standalone: true, imports: [NgClass, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Root wrapper -->\n<div\n [ngClass]=\"[\n 'flex flex-wrap items-center gap-3',\n variant() === 'compact' ? 'justify-center' : 'justify-between',\n customClass()\n ]\"\n>\n\n <!-- \u2500\u2500 Summary + page-size selector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (variant() !== 'compact') {\n <div class=\"flex flex-wrap items-center gap-3\">\n\n @if (showSummary() && totalItems() > 0) {\n <p [ngClass]=\"['text-gray-500 dark:text-gray-400', textClass()]\">\n Showing\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeStart() }}</span>\n \u2013\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeEnd() }}</span>\n of\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ totalItems() }}</span>\n </p>\n }\n\n @if (pageSizeOptions().length > 0) {\n <div class=\"flex items-center gap-1.5\">\n <label [ngClass]=\"['text-gray-500 dark:text-gray-400 whitespace-nowrap', textClass()]\"\n for=\"page-size-select\">Rows per page</label>\n <select\n id=\"page-size-select\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n [ngClass]=\"[\n 'rounded-lg border border-gray-200 bg-white text-gray-900',\n 'dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100',\n 'focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20',\n textClass(),\n size() === 'sm' ? 'h-7 px-2 py-0' : size() === 'lg' ? 'h-10 px-3 py-1' : 'h-8 px-2.5 py-0.5'\n ]\"\n >\n @for (opt of pageSizeOptions(); track opt) {\n <option [ngValue]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n }\n\n </div>\n }\n\n <!-- \u2500\u2500 Page controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <nav [attr.aria-label]=\"ariaLabel()\">\n <ul class=\"flex items-center gap-1\">\n\n @if (variant() === 'simple') {\n <!-- Simple variant: prev / label / next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <li>\n <span [ngClass]=\"['px-3 text-gray-600 dark:text-gray-300', textClass()]\">\n Page {{ currentPage() }} of {{ totalPages() }}\n </span>\n </li>\n\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n } @else {\n <!-- Default / compact: full page window -->\n\n <!-- First -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(1)\"\n aria-label=\"First page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M11 17l-5-5 5-5M18 17l-5-5 5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n <!-- Prev -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Page numbers -->\n @for (page of pageWindow(); track $index) {\n <li>\n @if (page === -1) {\n <!-- Ellipsis -->\n <span [ngClass]=\"['inline-flex items-center justify-center text-gray-500 dark:text-gray-400', btnClass()]\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n [ngClass]=\"pageButtonClass(page)\"\n [attr.aria-current]=\"page === currentPage() ? 'page' : null\"\n [attr.aria-label]=\"'Page ' + page\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </button>\n }\n </li>\n }\n\n <!-- Next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Last -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(totalPages())\"\n aria-label=\"Last page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M13 17l5-5-5-5M6 17l5-5-5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n }\n\n </ul>\n </nav>\n\n</div>\n" }]
139
+ }], propDecorators: { totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], showSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSummary", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], customClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "customClass", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
140
+
141
+ /**
142
+ * Generated bundle index. Do not edit.
143
+ */
144
+
145
+ export { SvPaginationComponent };
146
+ //# sourceMappingURL=styloviz-pagination.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styloviz-pagination.mjs","sources":["../../../../projects/pagination/src/lib/pagination.component.ts","../../../../projects/pagination/src/lib/pagination.component.html","../../../../projects/pagination/src/styloviz-pagination.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n input,\n model,\n output,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\nexport type PaginationVariant = 'default' | 'simple' | 'compact';\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * Pagination — A fully-featured page navigator for tables, card grids, and lists.\n *\n * Features:\n * - Page-window with leading/trailing ellipsis (smart truncation)\n * - First / Previous / Next / Last buttons\n * - Optional page-size selector\n * - 3 variants: default (full controls) · simple (prev/next + label) · compact (minimal)\n * - 3 sizes: sm · md · lg\n * - Two-way `currentPage` binding via `model()`\n * - `pageChange` and `pageSizeChange` outputs\n * - Full dark-mode support\n * - Accessible: `aria-label`, `aria-current=\"page\"`, disabled states\n */\n@Component({\n selector: 'sv-pagination',\n standalone: true,\n imports: [NgClass, FormsModule],\n templateUrl: './pagination.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvPaginationComponent {\n // ── Inputs ────────────────────────────────────────────────────────────────\n\n /** Total number of items across all pages. */\n totalItems = input<number>(0);\n\n /** Number of items per page. Use `[(pageSize)]` for two-way binding. */\n pageSize = model<number>(10);\n\n /** 1-based current page index. Use `[(currentPage)]` for two-way binding. */\n currentPage = model<number>(1);\n\n /**\n * Options shown in the page-size `<select>`.\n * Empty array hides the selector entirely. @default [10, 25, 50, 100]\n */\n pageSizeOptions = input<number[]>([10, 25, 50, 100]);\n\n /** Visual variant. @default 'default' */\n variant = input<PaginationVariant>('default');\n\n /** Control size. @default 'md' */\n size = input<PaginationSize>('md');\n\n /** Show total-item count summary (e.g. \"Showing 1–10 of 234\"). @default true */\n showSummary = input<boolean>(true);\n\n /** Accessible label for the navigation landmark. @default 'Pagination' */\n ariaLabel = input<string>('Pagination');\n\n /** Additional CSS classes on the root wrapper. */\n customClass = input<string>('');\n\n // ── Outputs ───────────────────────────────────────────────────────────────\n\n /** Emitted when the user navigates to a different page. */\n pageChange = output<number>();\n\n // Note: `pageSize = model<number>(10)` already auto-emits `pageSizeChange`\n // when pageSize.set() is called — no separate output needed here.\n\n // ── Computed ──────────────────────────────────────────────────────────────\n\n totalPages = computed(() => Math.max(1, Math.ceil(this.totalItems() / this.pageSize())));\n\n /** First item index (1-based) on the current page. */\n rangeStart = computed(() => Math.min((this.currentPage() - 1) * this.pageSize() + 1, this.totalItems()));\n\n /** Last item index (1-based) on the current page. */\n rangeEnd = computed(() => Math.min(this.currentPage() * this.pageSize(), this.totalItems()));\n\n hasPrev = computed(() => this.currentPage() > 1);\n hasNext = computed(() => this.currentPage() < this.totalPages());\n\n /**\n * Visible page numbers in the page window, with -1 as an ellipsis marker.\n * Always shows: first, last, currentPage ±1, with ellipsis for gaps > 1.\n */\n pageWindow = computed<number[]>(() => {\n const total = this.totalPages();\n const current = this.currentPage();\n\n if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);\n\n const pages: number[] = [];\n const add = (n: number) => { if (!pages.includes(n)) pages.push(n); };\n\n add(1);\n if (current > 3) pages.push(-1); // left ellipsis\n for (let p = Math.max(2, current - 1); p <= Math.min(total - 1, current + 1); p++) add(p);\n if (current < total - 2) pages.push(-1); // right ellipsis\n add(total);\n\n return pages;\n });\n\n // ── Size maps ─────────────────────────────────────────────────────────────\n\n btnClass = computed<string>(() => {\n const s = this.size();\n return {\n sm: 'h-7 min-w-7 px-1.5 text-xs',\n md: 'h-8 min-w-8 px-2 text-sm',\n lg: 'h-10 min-w-10 px-3 text-sm',\n }[s];\n });\n\n textClass = computed<string>(() => ({\n sm: 'text-xs',\n md: 'text-sm',\n lg: 'text-sm',\n })[this.size()]);\n\n // ── Page button class helper ───────────────────────────────────────────────\n\n pageButtonClass(page: number): string {\n const isActive = page === this.currentPage();\n const base = [\n 'inline-flex items-center justify-center rounded-lg font-medium transition-colors duration-100',\n 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1',\n this.btnClass(),\n ];\n if (isActive) {\n base.push('bg-primary-600 text-white dark:bg-primary-500');\n } else {\n base.push('text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700');\n }\n return base.join(' ');\n }\n\n navButtonClass(disabled: boolean): string {\n return [\n 'inline-flex items-center justify-center rounded-lg font-medium transition-colors duration-100',\n 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1',\n this.btnClass(),\n disabled\n ? 'cursor-not-allowed text-gray-300 dark:text-gray-600'\n : 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700',\n ].join(' ');\n }\n\n // ── Navigation ────────────────────────────────────────────────────────────\n\n goTo(page: number): void {\n const clamped = Math.max(1, Math.min(page, this.totalPages()));\n if (clamped === this.currentPage()) return;\n this.currentPage.set(clamped);\n this.pageChange.emit(clamped);\n }\n\n onPageSizeChange(value: number): void {\n this.pageSize.set(Number(value)); // model auto-emits pageSizeChange\n this.currentPage.set(1);\n this.pageChange.emit(1);\n }\n}\n","<!-- Root wrapper -->\n<div\n [ngClass]=\"[\n 'flex flex-wrap items-center gap-3',\n variant() === 'compact' ? 'justify-center' : 'justify-between',\n customClass()\n ]\"\n>\n\n <!-- ── Summary + page-size selector ────────────────────────────────── -->\n @if (variant() !== 'compact') {\n <div class=\"flex flex-wrap items-center gap-3\">\n\n @if (showSummary() && totalItems() > 0) {\n <p [ngClass]=\"['text-gray-500 dark:text-gray-400', textClass()]\">\n Showing\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeStart() }}</span>\n –\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ rangeEnd() }}</span>\n of\n <span class=\"font-medium text-gray-900 dark:text-white\">{{ totalItems() }}</span>\n </p>\n }\n\n @if (pageSizeOptions().length > 0) {\n <div class=\"flex items-center gap-1.5\">\n <label [ngClass]=\"['text-gray-500 dark:text-gray-400 whitespace-nowrap', textClass()]\"\n for=\"page-size-select\">Rows per page</label>\n <select\n id=\"page-size-select\"\n [ngModel]=\"pageSize()\"\n (ngModelChange)=\"onPageSizeChange($event)\"\n [ngClass]=\"[\n 'rounded-lg border border-gray-200 bg-white text-gray-900',\n 'dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100',\n 'focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20',\n textClass(),\n size() === 'sm' ? 'h-7 px-2 py-0' : size() === 'lg' ? 'h-10 px-3 py-1' : 'h-8 px-2.5 py-0.5'\n ]\"\n >\n @for (opt of pageSizeOptions(); track opt) {\n <option [ngValue]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n }\n\n </div>\n }\n\n <!-- ── Page controls ────────────────────────────────────────────────── -->\n <nav [attr.aria-label]=\"ariaLabel()\">\n <ul class=\"flex items-center gap-1\">\n\n @if (variant() === 'simple') {\n <!-- Simple variant: prev / label / next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <li>\n <span [ngClass]=\"['px-3 text-gray-600 dark:text-gray-300', textClass()]\">\n Page {{ currentPage() }} of {{ totalPages() }}\n </span>\n </li>\n\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n } @else {\n <!-- Default / compact: full page window -->\n\n <!-- First -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(1)\"\n aria-label=\"First page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M11 17l-5-5 5-5M18 17l-5-5 5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n <!-- Prev -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasPrev())\"\n [disabled]=\"!hasPrev()\"\n (click)=\"goTo(currentPage() - 1)\"\n aria-label=\"Previous page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Page numbers -->\n @for (page of pageWindow(); track $index) {\n <li>\n @if (page === -1) {\n <!-- Ellipsis -->\n <span [ngClass]=\"['inline-flex items-center justify-center text-gray-500 dark:text-gray-400', btnClass()]\">\n &hellip;\n </span>\n } @else {\n <button\n type=\"button\"\n [ngClass]=\"pageButtonClass(page)\"\n [attr.aria-current]=\"page === currentPage() ? 'page' : null\"\n [attr.aria-label]=\"'Page ' + page\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </button>\n }\n </li>\n }\n\n <!-- Next -->\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(currentPage() + 1)\"\n aria-label=\"Next page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9 18l6-6-6-6\"/>\n </svg>\n </button>\n </li>\n\n <!-- Last -->\n @if (variant() === 'default') {\n <li>\n <button\n type=\"button\"\n [ngClass]=\"navButtonClass(!hasNext())\"\n [disabled]=\"!hasNext()\"\n (click)=\"goTo(totalPages())\"\n aria-label=\"Last page\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M13 17l5-5-5-5M6 17l5-5-5-5\"/>\n </svg>\n </button>\n </li>\n }\n\n }\n\n </ul>\n </nav>\n\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@styloviz/pagination",
3
+ "version": "0.1.1",
4
+ "description": "Smart page-window with ellipsis, a page-size selector, 3 variants and 3 sizes.",
5
+ "license": "MIT",
6
+ "author": "sazzad-bs23",
7
+ "homepage": "https://styloviz.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
11
+ "directory": "projects/pagination"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
15
+ },
16
+ "peerDependencies": {
17
+ "@angular/common": ">=21.0.0",
18
+ "@angular/core": ">=21.0.0",
19
+ "@angular/forms": ">=21.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "engines": {
25
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
26
+ },
27
+ "sideEffects": false,
28
+ "module": "fesm2022/styloviz-pagination.mjs",
29
+ "typings": "types/styloviz-pagination.d.ts",
30
+ "exports": {
31
+ "./package.json": {
32
+ "default": "./package.json"
33
+ },
34
+ ".": {
35
+ "types": "./types/styloviz-pagination.d.ts",
36
+ "default": "./fesm2022/styloviz-pagination.mjs"
37
+ }
38
+ },
39
+ "type": "module",
40
+ "dependencies": {
41
+ "tslib": "^2.3.0"
42
+ }
43
+ }
@@ -0,0 +1,66 @@
1
+ import * as _angular_core from '@angular/core';
2
+
3
+ type PaginationSize = 'sm' | 'md' | 'lg';
4
+ type PaginationVariant = 'default' | 'simple' | 'compact';
5
+ /**
6
+ * Pagination — A fully-featured page navigator for tables, card grids, and lists.
7
+ *
8
+ * Features:
9
+ * - Page-window with leading/trailing ellipsis (smart truncation)
10
+ * - First / Previous / Next / Last buttons
11
+ * - Optional page-size selector
12
+ * - 3 variants: default (full controls) · simple (prev/next + label) · compact (minimal)
13
+ * - 3 sizes: sm · md · lg
14
+ * - Two-way `currentPage` binding via `model()`
15
+ * - `pageChange` and `pageSizeChange` outputs
16
+ * - Full dark-mode support
17
+ * - Accessible: `aria-label`, `aria-current="page"`, disabled states
18
+ */
19
+ declare class SvPaginationComponent {
20
+ /** Total number of items across all pages. */
21
+ totalItems: _angular_core.InputSignal<number>;
22
+ /** Number of items per page. Use `[(pageSize)]` for two-way binding. */
23
+ pageSize: _angular_core.ModelSignal<number>;
24
+ /** 1-based current page index. Use `[(currentPage)]` for two-way binding. */
25
+ currentPage: _angular_core.ModelSignal<number>;
26
+ /**
27
+ * Options shown in the page-size `<select>`.
28
+ * Empty array hides the selector entirely. @default [10, 25, 50, 100]
29
+ */
30
+ pageSizeOptions: _angular_core.InputSignal<number[]>;
31
+ /** Visual variant. @default 'default' */
32
+ variant: _angular_core.InputSignal<PaginationVariant>;
33
+ /** Control size. @default 'md' */
34
+ size: _angular_core.InputSignal<PaginationSize>;
35
+ /** Show total-item count summary (e.g. "Showing 1–10 of 234"). @default true */
36
+ showSummary: _angular_core.InputSignal<boolean>;
37
+ /** Accessible label for the navigation landmark. @default 'Pagination' */
38
+ ariaLabel: _angular_core.InputSignal<string>;
39
+ /** Additional CSS classes on the root wrapper. */
40
+ customClass: _angular_core.InputSignal<string>;
41
+ /** Emitted when the user navigates to a different page. */
42
+ pageChange: _angular_core.OutputEmitterRef<number>;
43
+ totalPages: _angular_core.Signal<number>;
44
+ /** First item index (1-based) on the current page. */
45
+ rangeStart: _angular_core.Signal<number>;
46
+ /** Last item index (1-based) on the current page. */
47
+ rangeEnd: _angular_core.Signal<number>;
48
+ hasPrev: _angular_core.Signal<boolean>;
49
+ hasNext: _angular_core.Signal<boolean>;
50
+ /**
51
+ * Visible page numbers in the page window, with -1 as an ellipsis marker.
52
+ * Always shows: first, last, currentPage ±1, with ellipsis for gaps > 1.
53
+ */
54
+ pageWindow: _angular_core.Signal<number[]>;
55
+ btnClass: _angular_core.Signal<string>;
56
+ textClass: _angular_core.Signal<string>;
57
+ pageButtonClass(page: number): string;
58
+ navButtonClass(disabled: boolean): string;
59
+ goTo(page: number): void;
60
+ onPageSizeChange(value: number): void;
61
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvPaginationComponent, never>;
62
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvPaginationComponent, "sv-pagination", never, { "totalItems": { "alias": "totalItems"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "currentPage": { "alias": "currentPage"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "showSummary": { "alias": "showSummary"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "customClass": { "alias": "customClass"; "required": false; "isSignal": true; }; }, { "pageSize": "pageSizeChange"; "currentPage": "currentPageChange"; "pageChange": "pageChange"; }, never, never, true, never>;
63
+ }
64
+
65
+ export { SvPaginationComponent };
66
+ export type { PaginationSize, PaginationVariant };