ngx-com 0.0.1 → 0.0.4

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.
Files changed (82) hide show
  1. package/fesm2022/ngx-com-components-avatar.mjs +772 -0
  2. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-badge.mjs +138 -0
  4. package/fesm2022/ngx-com-components-badge.mjs.map +1 -0
  5. package/fesm2022/ngx-com-components-button.mjs +146 -0
  6. package/fesm2022/ngx-com-components-button.mjs.map +1 -0
  7. package/fesm2022/ngx-com-components-calendar.mjs +5046 -0
  8. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -0
  9. package/fesm2022/ngx-com-components-card.mjs +590 -0
  10. package/fesm2022/ngx-com-components-card.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-checkbox.mjs +344 -0
  12. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -0
  13. package/fesm2022/ngx-com-components-collapsible.mjs +612 -0
  14. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-confirm.mjs +562 -0
  16. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -0
  17. package/fesm2022/ngx-com-components-dropdown-testing.mjs +255 -0
  18. package/fesm2022/ngx-com-components-dropdown-testing.mjs.map +1 -0
  19. package/fesm2022/ngx-com-components-dropdown.mjs +2692 -0
  20. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-empty-state.mjs +382 -0
  22. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -0
  23. package/fesm2022/ngx-com-components-form-field.mjs +924 -0
  24. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -0
  25. package/fesm2022/ngx-com-components-icon.mjs +183 -0
  26. package/fesm2022/ngx-com-components-icon.mjs.map +1 -0
  27. package/fesm2022/ngx-com-components-item.mjs +578 -0
  28. package/fesm2022/ngx-com-components-item.mjs.map +1 -0
  29. package/fesm2022/ngx-com-components-menu.mjs +1200 -0
  30. package/fesm2022/ngx-com-components-menu.mjs.map +1 -0
  31. package/fesm2022/ngx-com-components-paginator.mjs +823 -0
  32. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -0
  33. package/fesm2022/ngx-com-components-popover.mjs +901 -0
  34. package/fesm2022/ngx-com-components-popover.mjs.map +1 -0
  35. package/fesm2022/ngx-com-components-radio.mjs +621 -0
  36. package/fesm2022/ngx-com-components-radio.mjs.map +1 -0
  37. package/fesm2022/ngx-com-components-segmented-control.mjs +538 -0
  38. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -0
  39. package/fesm2022/ngx-com-components-sort.mjs +368 -0
  40. package/fesm2022/ngx-com-components-sort.mjs.map +1 -0
  41. package/fesm2022/ngx-com-components-spinner.mjs +189 -0
  42. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -0
  43. package/fesm2022/ngx-com-components-tabs.mjs +1522 -0
  44. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -0
  45. package/fesm2022/ngx-com-components-tooltip.mjs +625 -0
  46. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -0
  47. package/fesm2022/ngx-com-components.mjs +17 -0
  48. package/fesm2022/ngx-com-components.mjs.map +1 -0
  49. package/fesm2022/ngx-com-tokens.mjs +12 -0
  50. package/fesm2022/ngx-com-tokens.mjs.map +1 -0
  51. package/fesm2022/ngx-com-utils.mjs +601 -0
  52. package/fesm2022/ngx-com-utils.mjs.map +1 -0
  53. package/fesm2022/ngx-com.mjs +9 -23
  54. package/fesm2022/ngx-com.mjs.map +1 -1
  55. package/package.json +105 -1
  56. package/types/ngx-com-components-avatar.d.ts +409 -0
  57. package/types/ngx-com-components-badge.d.ts +97 -0
  58. package/types/ngx-com-components-button.d.ts +69 -0
  59. package/types/ngx-com-components-calendar.d.ts +1665 -0
  60. package/types/ngx-com-components-card.d.ts +373 -0
  61. package/types/ngx-com-components-checkbox.d.ts +116 -0
  62. package/types/ngx-com-components-collapsible.d.ts +379 -0
  63. package/types/ngx-com-components-confirm.d.ts +160 -0
  64. package/types/ngx-com-components-dropdown-testing.d.ts +116 -0
  65. package/types/ngx-com-components-dropdown.d.ts +938 -0
  66. package/types/ngx-com-components-empty-state.d.ts +269 -0
  67. package/types/ngx-com-components-form-field.d.ts +531 -0
  68. package/types/ngx-com-components-icon.d.ts +94 -0
  69. package/types/ngx-com-components-item.d.ts +336 -0
  70. package/types/ngx-com-components-menu.d.ts +479 -0
  71. package/types/ngx-com-components-paginator.d.ts +265 -0
  72. package/types/ngx-com-components-popover.d.ts +309 -0
  73. package/types/ngx-com-components-radio.d.ts +258 -0
  74. package/types/ngx-com-components-segmented-control.d.ts +274 -0
  75. package/types/ngx-com-components-sort.d.ts +133 -0
  76. package/types/ngx-com-components-spinner.d.ts +120 -0
  77. package/types/ngx-com-components-tabs.d.ts +396 -0
  78. package/types/ngx-com-components-tooltip.d.ts +200 -0
  79. package/types/ngx-com-components.d.ts +12 -0
  80. package/types/ngx-com-tokens.d.ts +7 -0
  81. package/types/ngx-com-utils.d.ts +424 -0
  82. package/types/ngx-com.d.ts +10 -7
@@ -0,0 +1,823 @@
1
+ import * as i0 from '@angular/core';
2
+ import { viewChildren, input, booleanAttribute, output, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { ComIcon } from 'ngx-com/components/icon';
4
+ import { cva } from 'class-variance-authority';
5
+
6
+ /**
7
+ * Default range label function.
8
+ * Produces output like "1 – 10 of 100" or "0 of 0" when empty.
9
+ */
10
+ function defaultRangeLabel(page, pageSize, length) {
11
+ if (length === 0 || pageSize === 0) {
12
+ return `0 of ${length}`;
13
+ }
14
+ const startIndex = page * pageSize;
15
+ // Ensure end index doesn't exceed length
16
+ const endIndex = Math.min(startIndex + pageSize, length);
17
+ return `${startIndex + 1} – ${endIndex} of ${length}`;
18
+ }
19
+
20
+ // ─── Container Variants ───
21
+ /**
22
+ * CVA variants for the paginator container.
23
+ * Controls overall layout and spacing.
24
+ */
25
+ const paginatorContainerVariants = cva([
26
+ 'flex items-center',
27
+ 'text-foreground',
28
+ 'select-none',
29
+ ], {
30
+ variants: {
31
+ size: {
32
+ sm: 'gap-3 text-xs',
33
+ md: 'gap-4 text-sm',
34
+ },
35
+ layout: {
36
+ compact: 'justify-end',
37
+ spread: 'justify-between',
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ size: 'md',
42
+ layout: 'compact',
43
+ },
44
+ });
45
+ // ─── Button Variants ───
46
+ /**
47
+ * CVA variants for paginator navigation buttons.
48
+ * Controls button sizing, borders, and interactive states.
49
+ */
50
+ const paginatorButtonVariants = cva([
51
+ 'inline-flex items-center justify-center',
52
+ 'rounded-control',
53
+ 'border border-border',
54
+ 'bg-transparent',
55
+ 'transition-colors duration-150',
56
+ 'cursor-pointer',
57
+ 'hover:bg-muted hover:text-muted-foreground',
58
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
59
+ 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled',
60
+ ], {
61
+ variants: {
62
+ size: {
63
+ sm: 'h-7 w-7',
64
+ md: 'h-9 w-9',
65
+ },
66
+ },
67
+ defaultVariants: {
68
+ size: 'md',
69
+ },
70
+ });
71
+ // ─── Range Label Variants ───
72
+ /**
73
+ * CVA variants for the range label text.
74
+ * Controls typography and color.
75
+ */
76
+ const paginatorRangeLabelVariants = cva([
77
+ 'text-muted-foreground',
78
+ 'whitespace-nowrap',
79
+ ], {
80
+ variants: {
81
+ size: {
82
+ sm: 'text-xs',
83
+ md: 'text-sm',
84
+ },
85
+ },
86
+ defaultVariants: {
87
+ size: 'md',
88
+ },
89
+ });
90
+ // ─── Page Size Selector Variants ───
91
+ /**
92
+ * CVA variants for the page size select element.
93
+ * Controls sizing and styling of the native select.
94
+ */
95
+ const paginatorSelectVariants = cva([
96
+ 'appearance-none',
97
+ 'rounded-control',
98
+ 'border border-border',
99
+ 'bg-transparent',
100
+ 'text-foreground',
101
+ 'cursor-pointer',
102
+ 'transition-colors duration-150',
103
+ 'hover:bg-muted',
104
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
105
+ 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled',
106
+ 'pr-6', // Space for custom dropdown arrow
107
+ ], {
108
+ variants: {
109
+ size: {
110
+ sm: 'h-7 px-2 text-xs',
111
+ md: 'h-9 px-3 text-sm',
112
+ },
113
+ },
114
+ defaultVariants: {
115
+ size: 'md',
116
+ },
117
+ });
118
+ // ─── Variant Types ───
119
+ // ─── Page Button Variants ───
120
+ /**
121
+ * CVA variants for numbered page buttons.
122
+ * Controls button sizing, active state, and interactive states.
123
+ */
124
+ const paginatorPageButtonVariants = cva([
125
+ 'inline-flex items-center justify-center',
126
+ 'rounded-control',
127
+ 'font-medium',
128
+ 'transition-colors duration-150',
129
+ 'cursor-pointer',
130
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
131
+ 'disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground',
132
+ ], {
133
+ variants: {
134
+ size: {
135
+ sm: 'h-7 min-w-7 px-1.5 text-xs',
136
+ md: 'h-9 min-w-9 px-2 text-sm',
137
+ },
138
+ active: {
139
+ true: 'bg-primary text-primary-foreground',
140
+ false: 'bg-transparent text-foreground hover:bg-muted hover:text-muted-foreground',
141
+ },
142
+ },
143
+ defaultVariants: {
144
+ size: 'md',
145
+ active: false,
146
+ },
147
+ });
148
+ // ─── Ellipsis Variants ───
149
+ /**
150
+ * CVA variants for the ellipsis indicator.
151
+ * Controls sizing and styling of the "..." text.
152
+ */
153
+ const paginatorEllipsisVariants = cva([
154
+ 'inline-flex items-center justify-center',
155
+ 'text-muted-foreground',
156
+ 'select-none',
157
+ ], {
158
+ variants: {
159
+ size: {
160
+ sm: 'h-7 w-7 text-xs',
161
+ md: 'h-9 w-9 text-sm',
162
+ },
163
+ },
164
+ defaultVariants: {
165
+ size: 'md',
166
+ },
167
+ });
168
+
169
+ /**
170
+ * Paginator component — provides navigation for paginated content.
171
+ *
172
+ * Displays navigation controls, optional page size selector, and range label
173
+ * showing current position within the data set. Supports numbered page buttons
174
+ * when `showPageNumbers` is enabled.
175
+ *
176
+ * @tokens `--color-foreground`, `--color-muted-foreground`,
177
+ * `--color-border`, `--color-muted`,
178
+ * `--color-disabled`, `--color-disabled-foreground`,
179
+ * `--color-ring`, `--color-primary`, `--color-primary-foreground`
180
+ *
181
+ * @example Basic usage
182
+ * ```html
183
+ * <com-paginator
184
+ * [length]="100"
185
+ * [pageSize]="10"
186
+ * [pageIndex]="0"
187
+ * (page)="onPageChange($event)"
188
+ * />
189
+ * ```
190
+ *
191
+ * @example With page size options
192
+ * ```html
193
+ * <com-paginator
194
+ * [length]="100"
195
+ * [pageSize]="10"
196
+ * [pageIndex]="0"
197
+ * [pageSizeOptions]="[5, 10, 25, 50]"
198
+ * (page)="onPageChange($event)"
199
+ * />
200
+ * ```
201
+ *
202
+ * @example With first/last buttons
203
+ * ```html
204
+ * <com-paginator
205
+ * [length]="100"
206
+ * [pageSize]="10"
207
+ * [pageIndex]="0"
208
+ * [showFirstLastButtons]="true"
209
+ * (page)="onPageChange($event)"
210
+ * />
211
+ * ```
212
+ *
213
+ * @example With numbered page buttons
214
+ * ```html
215
+ * <com-paginator
216
+ * [length]="97"
217
+ * [pageSize]="10"
218
+ * [showPageNumbers]="true"
219
+ * (page)="onPageChange($event)"
220
+ * />
221
+ * ```
222
+ *
223
+ * @example Spread layout (summary left, controls right)
224
+ * ```html
225
+ * <com-paginator
226
+ * [length]="97"
227
+ * [pageSize]="10"
228
+ * [showPageNumbers]="true"
229
+ * layout="spread"
230
+ * (page)="onPageChange($event)"
231
+ * />
232
+ * ```
233
+ *
234
+ * @example Small size
235
+ * ```html
236
+ * <com-paginator
237
+ * [length]="50"
238
+ * [pageSize]="10"
239
+ * size="sm"
240
+ * (page)="onPageChange($event)"
241
+ * />
242
+ * ```
243
+ *
244
+ * @example Custom range label (i18n)
245
+ * ```html
246
+ * <com-paginator
247
+ * [length]="100"
248
+ * [pageSize]="10"
249
+ * [rangeLabel]="customLabel"
250
+ * (page)="onPageChange($event)"
251
+ * />
252
+ * ```
253
+ * ```ts
254
+ * customLabel = (page, pageSize, length) => `Seite ${page + 1} von ${Math.ceil(length / pageSize)}`;
255
+ * ```
256
+ */
257
+ class ComPaginator {
258
+ // ─── View Queries ───
259
+ /** Page number buttons for keyboard navigation. */
260
+ pageButtons = viewChildren('pageBtn', ...(ngDevMode ? [{ debugName: "pageButtons" }] : []));
261
+ // ─── Inputs ───
262
+ /** Total number of items being paged. */
263
+ length = input(0, ...(ngDevMode ? [{ debugName: "length" }] : []));
264
+ /** Number of items to display per page. */
265
+ pageSize = input(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
266
+ /** Current zero-based page index. */
267
+ pageIndex = input(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : []));
268
+ /** Available page size options. Hides selector if empty. */
269
+ pageSizeOptions = input([], ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : []));
270
+ /** Whether to show first/last navigation buttons. */
271
+ showFirstLastButtons = input(false, { ...(ngDevMode ? { debugName: "showFirstLastButtons" } : {}), transform: booleanAttribute });
272
+ /** Whether to show numbered page buttons. */
273
+ showPageNumbers = input(false, { ...(ngDevMode ? { debugName: "showPageNumbers" } : {}), transform: booleanAttribute });
274
+ /** Whether all controls are disabled. */
275
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), transform: booleanAttribute });
276
+ /** Whether to hide the page size selector. */
277
+ hidePageSize = input(false, { ...(ngDevMode ? { debugName: "hidePageSize" } : {}), transform: booleanAttribute });
278
+ /** Size variant. */
279
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
280
+ /** Layout variant. Only applies when showPageNumbers is true. */
281
+ layout = input('compact', ...(ngDevMode ? [{ debugName: "layout" }] : []));
282
+ /** Number of pages to show on each side of the current page. */
283
+ siblingCount = input(1, ...(ngDevMode ? [{ debugName: "siblingCount" }] : []));
284
+ /** Number of pages to always show at the start and end. */
285
+ boundaryCount = input(1, ...(ngDevMode ? [{ debugName: "boundaryCount" }] : []));
286
+ /** Accessible label for the nav element. */
287
+ ariaLabel = input('Pagination', { ...(ngDevMode ? { debugName: "ariaLabel" } : {}), alias: 'aria-label' });
288
+ /** Custom function for range label formatting. */
289
+ rangeLabel = input(defaultRangeLabel, ...(ngDevMode ? [{ debugName: "rangeLabel" }] : []));
290
+ // ─── Outputs ───
291
+ /** Emits when page index or page size changes. */
292
+ page = output();
293
+ // ─── Computed ───
294
+ /** Total number of pages. */
295
+ numberOfPages = computed(() => {
296
+ const size = this.pageSize();
297
+ const len = this.length();
298
+ if (size === 0 || len === 0) {
299
+ return 0;
300
+ }
301
+ return Math.ceil(len / size);
302
+ }, ...(ngDevMode ? [{ debugName: "numberOfPages" }] : []));
303
+ /** Whether there is a previous page. */
304
+ hasPreviousPage = computed(() => {
305
+ return this.pageIndex() > 0 && this.pageSize() > 0;
306
+ }, ...(ngDevMode ? [{ debugName: "hasPreviousPage" }] : []));
307
+ /** Whether there is a next page. */
308
+ hasNextPage = computed(() => {
309
+ const total = this.numberOfPages();
310
+ return total > 0 && this.pageIndex() < total - 1;
311
+ }, ...(ngDevMode ? [{ debugName: "hasNextPage" }] : []));
312
+ /** The formatted range label text. */
313
+ rangeLabelText = computed(() => {
314
+ const fn = this.rangeLabel();
315
+ return fn(this.pageIndex(), this.pageSize(), this.length());
316
+ }, ...(ngDevMode ? [{ debugName: "rangeLabelText" }] : []));
317
+ /** Icon size based on component size. */
318
+ iconSize = computed(() => {
319
+ return this.size() === 'sm' ? 'xs' : 'sm';
320
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
321
+ /** Unique ID for page size label. */
322
+ pageSizeLabelId = computed(() => {
323
+ return `com-paginator-page-size-label-${uniqueId++}`;
324
+ }, ...(ngDevMode ? [{ debugName: "pageSizeLabelId" }] : []));
325
+ /** Classes for the container. */
326
+ containerClasses = computed(() => paginatorContainerVariants({
327
+ size: this.size(),
328
+ layout: this.showPageNumbers() ? this.layout() : 'compact',
329
+ }), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
330
+ /** Classes for navigation buttons. */
331
+ buttonClasses = computed(() => paginatorButtonVariants({ size: this.size() }), ...(ngDevMode ? [{ debugName: "buttonClasses" }] : []));
332
+ /** Classes for the range label. */
333
+ rangeLabelClasses = computed(() => paginatorRangeLabelVariants({ size: this.size() }), ...(ngDevMode ? [{ debugName: "rangeLabelClasses" }] : []));
334
+ /** Classes for the page size select. */
335
+ selectClasses = computed(() => paginatorSelectVariants({ size: this.size() }), ...(ngDevMode ? [{ debugName: "selectClasses" }] : []));
336
+ /** Classes for the ellipsis indicator. */
337
+ ellipsisClasses = computed(() => paginatorEllipsisVariants({ size: this.size() }), ...(ngDevMode ? [{ debugName: "ellipsisClasses" }] : []));
338
+ /** Cached classes for active page button. */
339
+ activePageButtonClasses = computed(() => paginatorPageButtonVariants({ size: this.size(), active: true }), ...(ngDevMode ? [{ debugName: "activePageButtonClasses" }] : []));
340
+ /** Cached classes for inactive page button. */
341
+ inactivePageButtonClasses = computed(() => paginatorPageButtonVariants({ size: this.size(), active: false }), ...(ngDevMode ? [{ debugName: "inactivePageButtonClasses" }] : []));
342
+ /**
343
+ * Computed page range for numbered pagination.
344
+ * Returns array like [0, 'ellipsis', 3, 4, 5, 'ellipsis', 9] (zero-indexed).
345
+ */
346
+ pageRange = computed(() => {
347
+ const totalPages = this.numberOfPages();
348
+ const current = this.pageIndex();
349
+ const siblings = this.siblingCount();
350
+ const boundaries = this.boundaryCount();
351
+ if (totalPages === 0)
352
+ return [];
353
+ // If all pages fit without ellipses, show them all
354
+ const totalSlots = 2 * boundaries + 2 * siblings + 3;
355
+ if (totalPages <= totalSlots) {
356
+ return this.range(0, totalPages - 1);
357
+ }
358
+ // Collect all page indices that should be visible (Set auto-deduplicates)
359
+ const pages = new Set();
360
+ // Boundary pages (always visible)
361
+ for (let i = 0; i < boundaries; i++)
362
+ pages.add(i);
363
+ for (let i = totalPages - boundaries; i < totalPages; i++)
364
+ pages.add(i);
365
+ // Sibling pages around current (including current)
366
+ const siblingStart = Math.max(0, current - siblings);
367
+ const siblingEnd = Math.min(totalPages - 1, current + siblings);
368
+ for (let i = siblingStart; i <= siblingEnd; i++)
369
+ pages.add(i);
370
+ // Convert to sorted array and insert ellipses at gaps
371
+ const sorted = Array.from(pages).sort((a, b) => a - b);
372
+ const result = [];
373
+ for (let i = 0; i < sorted.length; i++) {
374
+ const page = sorted[i];
375
+ const prevPage = sorted[i - 1];
376
+ if (prevPage !== undefined && page - prevPage > 1) {
377
+ result.push('ellipsis');
378
+ }
379
+ result.push(page);
380
+ }
381
+ return result;
382
+ }, ...(ngDevMode ? [{ debugName: "pageRange" }] : []));
383
+ // ─── Navigation Methods ───
384
+ /** Navigate to the first page. */
385
+ firstPage() {
386
+ if (this.hasPreviousPage()) {
387
+ this.emitPageEvent(0);
388
+ }
389
+ }
390
+ /** Navigate to the previous page. */
391
+ previousPage() {
392
+ if (this.hasPreviousPage()) {
393
+ this.emitPageEvent(this.pageIndex() - 1);
394
+ }
395
+ }
396
+ /** Navigate to the next page. */
397
+ nextPage() {
398
+ if (this.hasNextPage()) {
399
+ this.emitPageEvent(this.pageIndex() + 1);
400
+ }
401
+ }
402
+ /** Navigate to the last page. */
403
+ lastPage() {
404
+ if (this.hasNextPage()) {
405
+ this.emitPageEvent(this.numberOfPages() - 1);
406
+ }
407
+ }
408
+ /** Navigate to a specific page by index (zero-based). */
409
+ goToPage(pageIndex) {
410
+ if (pageIndex >= 0 && pageIndex < this.numberOfPages() && pageIndex !== this.pageIndex()) {
411
+ this.emitPageEvent(pageIndex);
412
+ }
413
+ }
414
+ /** Handle page size selection change. */
415
+ onPageSizeChange(event) {
416
+ const select = event.target;
417
+ const newPageSize = Number(select.value);
418
+ const previousPageSize = this.pageSize();
419
+ const currentPageIndex = this.pageIndex();
420
+ // Calculate new page index to keep the first item on the current page visible
421
+ const startIndex = currentPageIndex * previousPageSize;
422
+ const newPageIndex = Math.floor(startIndex / newPageSize);
423
+ this.page.emit({
424
+ pageIndex: newPageIndex,
425
+ previousPageIndex: currentPageIndex,
426
+ pageSize: newPageSize,
427
+ length: this.length(),
428
+ });
429
+ }
430
+ /** Handle keyboard navigation within page buttons (roving tabindex). */
431
+ onPageButtonsKeydown(event) {
432
+ const target = event.target;
433
+ // Get enabled buttons from viewChildren query
434
+ const buttons = this.pageButtons()
435
+ .map((ref) => ref.nativeElement)
436
+ .filter((btn) => !btn.disabled);
437
+ const currentIndex = buttons.indexOf(target);
438
+ if (currentIndex === -1)
439
+ return;
440
+ let newIndex;
441
+ switch (event.key) {
442
+ case 'ArrowLeft':
443
+ newIndex = currentIndex > 0 ? currentIndex - 1 : buttons.length - 1;
444
+ break;
445
+ case 'ArrowRight':
446
+ newIndex = currentIndex < buttons.length - 1 ? currentIndex + 1 : 0;
447
+ break;
448
+ case 'Home':
449
+ newIndex = 0;
450
+ break;
451
+ case 'End':
452
+ newIndex = buttons.length - 1;
453
+ break;
454
+ default:
455
+ return;
456
+ }
457
+ event.preventDefault();
458
+ buttons[newIndex]?.focus();
459
+ }
460
+ /** Track function for page items. */
461
+ trackPageItem(index, item) {
462
+ return item === 'ellipsis' ? `ellipsis-${index}` : `page-${item}`;
463
+ }
464
+ // ─── Private Methods ───
465
+ emitPageEvent(newPageIndex) {
466
+ this.page.emit({
467
+ pageIndex: newPageIndex,
468
+ previousPageIndex: this.pageIndex(),
469
+ pageSize: this.pageSize(),
470
+ length: this.length(),
471
+ });
472
+ }
473
+ /** Generate a range of numbers from start to end (inclusive). */
474
+ range(start, end) {
475
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
476
+ }
477
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComPaginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
478
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComPaginator, isStandalone: true, selector: "com-paginator", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showFirstLastButtons: { classPropertyName: "showFirstLastButtons", publicName: "showFirstLastButtons", isSignal: true, isRequired: false, transformFunction: null }, showPageNumbers: { classPropertyName: "showPageNumbers", publicName: "showPageNumbers", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidePageSize: { classPropertyName: "hidePageSize", publicName: "hidePageSize", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, siblingCount: { classPropertyName: "siblingCount", publicName: "siblingCount", isSignal: true, isRequired: false, transformFunction: null }, boundaryCount: { classPropertyName: "boundaryCount", publicName: "boundaryCount", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, rangeLabel: { classPropertyName: "rangeLabel", publicName: "rangeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { page: "page" }, host: { classAttribute: "com-paginator" }, viewQueries: [{ propertyName: "pageButtons", predicate: ["pageBtn"], descendants: true, isSignal: true }], exportAs: ["comPaginator"], ngImport: i0, template: `
479
+ <nav
480
+ role="navigation"
481
+ [attr.aria-label]="ariaLabel()"
482
+ [class]="containerClasses()"
483
+ >
484
+ @if (showPageNumbers()) {
485
+ <!-- Spread Layout: Summary on left -->
486
+ @if (layout() === 'spread') {
487
+ <span [class]="rangeLabelClasses()">
488
+ {{ rangeLabelText() }}
489
+ </span>
490
+ }
491
+
492
+ <!-- Navigation Controls -->
493
+ <div
494
+ class="flex items-center gap-1"
495
+ role="group"
496
+ aria-label="Page navigation"
497
+ (keydown)="onPageButtonsKeydown($event)"
498
+ >
499
+ <!-- Previous Page -->
500
+ <button
501
+ type="button"
502
+ [class]="buttonClasses()"
503
+ [disabled]="disabled() || !hasPreviousPage()"
504
+ aria-label="Previous page"
505
+ (click)="previousPage()"
506
+ >
507
+ <com-icon name="chevron-left" [size]="iconSize()" />
508
+ </button>
509
+
510
+ <!-- Page Numbers -->
511
+ @for (item of pageRange(); track trackPageItem($index, item)) {
512
+ @if (item === 'ellipsis') {
513
+ <span
514
+ [class]="ellipsisClasses()"
515
+ aria-hidden="true"
516
+ >…</span>
517
+ } @else {
518
+ <button
519
+ #pageBtn
520
+ type="button"
521
+ [class]="item === pageIndex() ? activePageButtonClasses() : inactivePageButtonClasses()"
522
+ [disabled]="disabled()"
523
+ [attr.aria-label]="'Page ' + (item + 1)"
524
+ [attr.aria-current]="item === pageIndex() ? 'page' : null"
525
+ [tabindex]="item === pageIndex() ? 0 : -1"
526
+ (click)="goToPage(item)"
527
+ >{{ item + 1 }}</button>
528
+ }
529
+ }
530
+
531
+ <!-- Next Page -->
532
+ <button
533
+ type="button"
534
+ [class]="buttonClasses()"
535
+ [disabled]="disabled() || !hasNextPage()"
536
+ aria-label="Next page"
537
+ (click)="nextPage()"
538
+ >
539
+ <com-icon name="chevron-right" [size]="iconSize()" />
540
+ </button>
541
+ </div>
542
+
543
+ <!-- Compact Layout: Summary on right -->
544
+ @if (layout() === 'compact') {
545
+ <span [class]="rangeLabelClasses()">
546
+ {{ rangeLabelText() }}
547
+ </span>
548
+ }
549
+ } @else {
550
+ <!-- Original Layout (no page numbers) -->
551
+
552
+ <!-- Page Size Selector -->
553
+ @if (!hidePageSize() && pageSizeOptions().length > 0) {
554
+ <div class="flex items-center gap-2">
555
+ <label
556
+ [id]="pageSizeLabelId()"
557
+ class="text-muted-foreground"
558
+ [class.text-xs]="size() === 'sm'"
559
+ [class.text-sm]="size() === 'md'"
560
+ >
561
+ Items per page:
562
+ </label>
563
+ <div class="relative">
564
+ <select
565
+ [attr.aria-labelledby]="pageSizeLabelId()"
566
+ [class]="selectClasses()"
567
+ [disabled]="disabled()"
568
+ [value]="pageSize()"
569
+ (change)="onPageSizeChange($event)"
570
+ >
571
+ @for (option of pageSizeOptions(); track option) {
572
+ <option [value]="option">{{ option }}</option>
573
+ }
574
+ </select>
575
+ <com-icon
576
+ name="chevron-down"
577
+ size="xs"
578
+ class="pointer-events-none absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground"
579
+ />
580
+ </div>
581
+ </div>
582
+ }
583
+
584
+ <!-- Range Label -->
585
+ <span [class]="rangeLabelClasses()">
586
+ {{ rangeLabelText() }}
587
+ </span>
588
+
589
+ <!-- Navigation Buttons -->
590
+ <div class="flex items-center gap-1">
591
+ <!-- First Page -->
592
+ @if (showFirstLastButtons()) {
593
+ <button
594
+ type="button"
595
+ [class]="buttonClasses()"
596
+ [disabled]="disabled() || !hasPreviousPage()"
597
+ aria-label="First page"
598
+ (click)="firstPage()"
599
+ >
600
+ <com-icon name="chevrons-left" [size]="iconSize()" />
601
+ </button>
602
+ }
603
+
604
+ <!-- Previous Page -->
605
+ <button
606
+ type="button"
607
+ [class]="buttonClasses()"
608
+ [disabled]="disabled() || !hasPreviousPage()"
609
+ aria-label="Previous page"
610
+ (click)="previousPage()"
611
+ >
612
+ <com-icon name="chevron-left" [size]="iconSize()" />
613
+ </button>
614
+
615
+ <!-- Next Page -->
616
+ <button
617
+ type="button"
618
+ [class]="buttonClasses()"
619
+ [disabled]="disabled() || !hasNextPage()"
620
+ aria-label="Next page"
621
+ (click)="nextPage()"
622
+ >
623
+ <com-icon name="chevron-right" [size]="iconSize()" />
624
+ </button>
625
+
626
+ <!-- Last Page -->
627
+ @if (showFirstLastButtons()) {
628
+ <button
629
+ type="button"
630
+ [class]="buttonClasses()"
631
+ [disabled]="disabled() || !hasNextPage()"
632
+ aria-label="Last page"
633
+ (click)="lastPage()"
634
+ >
635
+ <com-icon name="chevrons-right" [size]="iconSize()" />
636
+ </button>
637
+ }
638
+ </div>
639
+ }
640
+ </nav>
641
+ `, isInline: true, styles: ["com-paginator{display:block}\n"], dependencies: [{ kind: "component", type: ComIcon, selector: "com-icon", inputs: ["name", "img", "color", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
642
+ }
643
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComPaginator, decorators: [{
644
+ type: Component,
645
+ args: [{ selector: 'com-paginator', exportAs: 'comPaginator', template: `
646
+ <nav
647
+ role="navigation"
648
+ [attr.aria-label]="ariaLabel()"
649
+ [class]="containerClasses()"
650
+ >
651
+ @if (showPageNumbers()) {
652
+ <!-- Spread Layout: Summary on left -->
653
+ @if (layout() === 'spread') {
654
+ <span [class]="rangeLabelClasses()">
655
+ {{ rangeLabelText() }}
656
+ </span>
657
+ }
658
+
659
+ <!-- Navigation Controls -->
660
+ <div
661
+ class="flex items-center gap-1"
662
+ role="group"
663
+ aria-label="Page navigation"
664
+ (keydown)="onPageButtonsKeydown($event)"
665
+ >
666
+ <!-- Previous Page -->
667
+ <button
668
+ type="button"
669
+ [class]="buttonClasses()"
670
+ [disabled]="disabled() || !hasPreviousPage()"
671
+ aria-label="Previous page"
672
+ (click)="previousPage()"
673
+ >
674
+ <com-icon name="chevron-left" [size]="iconSize()" />
675
+ </button>
676
+
677
+ <!-- Page Numbers -->
678
+ @for (item of pageRange(); track trackPageItem($index, item)) {
679
+ @if (item === 'ellipsis') {
680
+ <span
681
+ [class]="ellipsisClasses()"
682
+ aria-hidden="true"
683
+ >…</span>
684
+ } @else {
685
+ <button
686
+ #pageBtn
687
+ type="button"
688
+ [class]="item === pageIndex() ? activePageButtonClasses() : inactivePageButtonClasses()"
689
+ [disabled]="disabled()"
690
+ [attr.aria-label]="'Page ' + (item + 1)"
691
+ [attr.aria-current]="item === pageIndex() ? 'page' : null"
692
+ [tabindex]="item === pageIndex() ? 0 : -1"
693
+ (click)="goToPage(item)"
694
+ >{{ item + 1 }}</button>
695
+ }
696
+ }
697
+
698
+ <!-- Next Page -->
699
+ <button
700
+ type="button"
701
+ [class]="buttonClasses()"
702
+ [disabled]="disabled() || !hasNextPage()"
703
+ aria-label="Next page"
704
+ (click)="nextPage()"
705
+ >
706
+ <com-icon name="chevron-right" [size]="iconSize()" />
707
+ </button>
708
+ </div>
709
+
710
+ <!-- Compact Layout: Summary on right -->
711
+ @if (layout() === 'compact') {
712
+ <span [class]="rangeLabelClasses()">
713
+ {{ rangeLabelText() }}
714
+ </span>
715
+ }
716
+ } @else {
717
+ <!-- Original Layout (no page numbers) -->
718
+
719
+ <!-- Page Size Selector -->
720
+ @if (!hidePageSize() && pageSizeOptions().length > 0) {
721
+ <div class="flex items-center gap-2">
722
+ <label
723
+ [id]="pageSizeLabelId()"
724
+ class="text-muted-foreground"
725
+ [class.text-xs]="size() === 'sm'"
726
+ [class.text-sm]="size() === 'md'"
727
+ >
728
+ Items per page:
729
+ </label>
730
+ <div class="relative">
731
+ <select
732
+ [attr.aria-labelledby]="pageSizeLabelId()"
733
+ [class]="selectClasses()"
734
+ [disabled]="disabled()"
735
+ [value]="pageSize()"
736
+ (change)="onPageSizeChange($event)"
737
+ >
738
+ @for (option of pageSizeOptions(); track option) {
739
+ <option [value]="option">{{ option }}</option>
740
+ }
741
+ </select>
742
+ <com-icon
743
+ name="chevron-down"
744
+ size="xs"
745
+ class="pointer-events-none absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground"
746
+ />
747
+ </div>
748
+ </div>
749
+ }
750
+
751
+ <!-- Range Label -->
752
+ <span [class]="rangeLabelClasses()">
753
+ {{ rangeLabelText() }}
754
+ </span>
755
+
756
+ <!-- Navigation Buttons -->
757
+ <div class="flex items-center gap-1">
758
+ <!-- First Page -->
759
+ @if (showFirstLastButtons()) {
760
+ <button
761
+ type="button"
762
+ [class]="buttonClasses()"
763
+ [disabled]="disabled() || !hasPreviousPage()"
764
+ aria-label="First page"
765
+ (click)="firstPage()"
766
+ >
767
+ <com-icon name="chevrons-left" [size]="iconSize()" />
768
+ </button>
769
+ }
770
+
771
+ <!-- Previous Page -->
772
+ <button
773
+ type="button"
774
+ [class]="buttonClasses()"
775
+ [disabled]="disabled() || !hasPreviousPage()"
776
+ aria-label="Previous page"
777
+ (click)="previousPage()"
778
+ >
779
+ <com-icon name="chevron-left" [size]="iconSize()" />
780
+ </button>
781
+
782
+ <!-- Next Page -->
783
+ <button
784
+ type="button"
785
+ [class]="buttonClasses()"
786
+ [disabled]="disabled() || !hasNextPage()"
787
+ aria-label="Next page"
788
+ (click)="nextPage()"
789
+ >
790
+ <com-icon name="chevron-right" [size]="iconSize()" />
791
+ </button>
792
+
793
+ <!-- Last Page -->
794
+ @if (showFirstLastButtons()) {
795
+ <button
796
+ type="button"
797
+ [class]="buttonClasses()"
798
+ [disabled]="disabled() || !hasNextPage()"
799
+ aria-label="Last page"
800
+ (click)="lastPage()"
801
+ >
802
+ <com-icon name="chevrons-right" [size]="iconSize()" />
803
+ </button>
804
+ }
805
+ </div>
806
+ }
807
+ </nav>
808
+ `, imports: [ComIcon], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
809
+ class: 'com-paginator',
810
+ }, styles: ["com-paginator{display:block}\n"] }]
811
+ }], propDecorators: { pageButtons: [{ type: i0.ViewChildren, args: ['pageBtn', { isSignal: true }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], showFirstLastButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFirstLastButtons", required: false }] }], showPageNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageNumbers", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hidePageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePageSize", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], boundaryCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundaryCount", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], rangeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeLabel", required: false }] }], page: [{ type: i0.Output, args: ["page"] }] } });
812
+ /** Counter for generating unique IDs. */
813
+ let uniqueId = 0;
814
+
815
+ // Public API for the paginator component
816
+ // Main component
817
+
818
+ /**
819
+ * Generated bundle index. Do not edit.
820
+ */
821
+
822
+ export { ComPaginator, defaultRangeLabel, paginatorButtonVariants, paginatorContainerVariants, paginatorEllipsisVariants, paginatorPageButtonVariants, paginatorRangeLabelVariants, paginatorSelectVariants };
823
+ //# sourceMappingURL=ngx-com-components-paginator.mjs.map