ngx-com 0.0.19 → 0.0.21

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 (56) hide show
  1. package/fesm2022/ngx-com-components-alert.mjs +346 -0
  2. package/fesm2022/ngx-com-components-alert.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-button.mjs +1 -1
  4. package/fesm2022/ngx-com-components-button.mjs.map +1 -1
  5. package/fesm2022/ngx-com-components-calendar.mjs +29 -36
  6. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -1
  7. package/fesm2022/ngx-com-components-card.mjs +1 -1
  8. package/fesm2022/ngx-com-components-card.mjs.map +1 -1
  9. package/fesm2022/ngx-com-components-carousel.mjs +708 -0
  10. package/fesm2022/ngx-com-components-carousel.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-checkbox.mjs +17 -8
  12. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -1
  13. package/fesm2022/ngx-com-components-code-block.mjs +158 -0
  14. package/fesm2022/ngx-com-components-code-block.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-collapsible.mjs +1 -1
  16. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -1
  17. package/fesm2022/ngx-com-components-confirm.mjs +3 -3
  18. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -1
  19. package/fesm2022/ngx-com-components-dialog.mjs +703 -0
  20. package/fesm2022/ngx-com-components-dialog.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-dropdown.mjs +36 -31
  22. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -1
  23. package/fesm2022/ngx-com-components-form-field.mjs +48 -8
  24. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -1
  25. package/fesm2022/ngx-com-components-item.mjs +1 -1
  26. package/fesm2022/ngx-com-components-item.mjs.map +1 -1
  27. package/fesm2022/ngx-com-components-paginator.mjs +3 -3
  28. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -1
  29. package/fesm2022/ngx-com-components-radio.mjs +16 -9
  30. package/fesm2022/ngx-com-components-radio.mjs.map +1 -1
  31. package/fesm2022/ngx-com-components-segmented-control.mjs +1 -1
  32. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -1
  33. package/fesm2022/ngx-com-components-separator.mjs +102 -0
  34. package/fesm2022/ngx-com-components-separator.mjs.map +1 -0
  35. package/fesm2022/ngx-com-components-switch.mjs +258 -0
  36. package/fesm2022/ngx-com-components-switch.mjs.map +1 -0
  37. package/fesm2022/ngx-com-components-table.mjs +631 -0
  38. package/fesm2022/ngx-com-components-table.mjs.map +1 -0
  39. package/fesm2022/ngx-com-components-tabs.mjs +2 -2
  40. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -1
  41. package/fesm2022/ngx-com-components-toast.mjs +783 -0
  42. package/fesm2022/ngx-com-components-toast.mjs.map +1 -0
  43. package/package.json +33 -1
  44. package/types/ngx-com-components-alert.d.ts +166 -0
  45. package/types/ngx-com-components-carousel.d.ts +281 -0
  46. package/types/ngx-com-components-checkbox.d.ts +7 -2
  47. package/types/ngx-com-components-code-block.d.ts +66 -0
  48. package/types/ngx-com-components-confirm.d.ts +2 -2
  49. package/types/ngx-com-components-dialog.d.ts +264 -0
  50. package/types/ngx-com-components-dropdown.d.ts +8 -5
  51. package/types/ngx-com-components-form-field.d.ts +19 -3
  52. package/types/ngx-com-components-radio.d.ts +5 -3
  53. package/types/ngx-com-components-separator.d.ts +75 -0
  54. package/types/ngx-com-components-switch.d.ts +110 -0
  55. package/types/ngx-com-components-table.d.ts +377 -0
  56. package/types/ngx-com-components-toast.d.ts +217 -0
@@ -0,0 +1,708 @@
1
+ import { cva } from 'class-variance-authority';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, ElementRef, Directive, TemplateRef, DestroyRef, Renderer2, input, booleanAttribute, model, contentChildren, contentChild, viewChild, signal, computed, linkedSignal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { NgTemplateOutlet } from '@angular/common';
5
+ import { Platform } from '@angular/cdk/platform';
6
+ import { ComIcon } from 'ngx-com/components/icon';
7
+
8
+ // ─── Container ───
9
+ const carouselContainerVariants = cva('relative overflow-hidden');
10
+ // ─── Navigation Button ───
11
+ const carouselNavButtonVariants = cva([
12
+ 'absolute top-1/2 -translate-y-1/2 z-10',
13
+ 'inline-flex items-center justify-center',
14
+ 'h-10 w-10 rounded-control',
15
+ 'bg-background text-foreground',
16
+ 'border border-border-subtle shadow-sm',
17
+ 'transition-colors duration-150',
18
+ 'hover:bg-muted-hover',
19
+ 'outline-none focus-visible:outline-[1px] focus-visible:outline-offset-2 focus-visible:outline-ring',
20
+ 'disabled:bg-disabled disabled:text-disabled-foreground disabled:cursor-not-allowed disabled:shadow-none',
21
+ ], {
22
+ variants: {
23
+ position: {
24
+ prev: 'left-2',
25
+ next: 'right-2',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ position: 'prev',
30
+ },
31
+ });
32
+ // ─── Pagination Dot ───
33
+ const carouselDotVariants = cva([
34
+ 'inline-block h-2 w-2 rounded-pill',
35
+ 'transition-colors duration-150',
36
+ 'outline-none focus-visible:outline-[1px] focus-visible:outline-offset-2 focus-visible:outline-ring',
37
+ ], {
38
+ variants: {
39
+ active: {
40
+ true: 'bg-primary',
41
+ false: 'bg-muted hover:bg-muted-hover',
42
+ },
43
+ },
44
+ defaultVariants: {
45
+ active: false,
46
+ },
47
+ });
48
+
49
+ /**
50
+ * Marks an element as a carousel slide item.
51
+ *
52
+ * Apply this directive to each direct child element that should be
53
+ * treated as a slide within `<com-carousel>`.
54
+ *
55
+ * @example
56
+ * ```html
57
+ * <com-carousel>
58
+ * <div comCarouselItem>Slide 1</div>
59
+ * <div comCarouselItem>Slide 2</div>
60
+ * </com-carousel>
61
+ * ```
62
+ */
63
+ class ComCarouselItem {
64
+ elementRef = inject(ElementRef);
65
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
66
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComCarouselItem, isStandalone: true, selector: "[comCarouselItem]", ngImport: i0 });
67
+ }
68
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselItem, decorators: [{
69
+ type: Directive,
70
+ args: [{
71
+ selector: '[comCarouselItem]',
72
+ }]
73
+ }] });
74
+
75
+ /**
76
+ * Template directive for a custom "previous" navigation button.
77
+ *
78
+ * The template receives a boolean context (`$implicit`) indicating
79
+ * whether the carousel can navigate backward.
80
+ *
81
+ * @example
82
+ * ```html
83
+ * <com-carousel>
84
+ * <div comCarouselItem>Slide 1</div>
85
+ * <ng-template comCarouselPrev let-canGo>
86
+ * <button [disabled]="!canGo">Back</button>
87
+ * </ng-template>
88
+ * </com-carousel>
89
+ * ```
90
+ */
91
+ class ComCarouselPrevTpl {
92
+ templateRef = inject(TemplateRef);
93
+ static ngTemplateContextGuard(_dir, _ctx) {
94
+ return true;
95
+ }
96
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselPrevTpl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
97
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComCarouselPrevTpl, isStandalone: true, selector: "ng-template[comCarouselPrev]", ngImport: i0 });
98
+ }
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselPrevTpl, decorators: [{
100
+ type: Directive,
101
+ args: [{
102
+ selector: 'ng-template[comCarouselPrev]',
103
+ }]
104
+ }] });
105
+
106
+ /**
107
+ * Template directive for a custom "next" navigation button.
108
+ *
109
+ * The template receives a boolean context (`$implicit`) indicating
110
+ * whether the carousel can navigate forward.
111
+ *
112
+ * @example
113
+ * ```html
114
+ * <com-carousel>
115
+ * <div comCarouselItem>Slide 1</div>
116
+ * <ng-template comCarouselNext let-canGo>
117
+ * <button [disabled]="!canGo">Forward</button>
118
+ * </ng-template>
119
+ * </com-carousel>
120
+ * ```
121
+ */
122
+ class ComCarouselNextTpl {
123
+ templateRef = inject(TemplateRef);
124
+ static ngTemplateContextGuard(_dir, _ctx) {
125
+ return true;
126
+ }
127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselNextTpl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
128
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComCarouselNextTpl, isStandalone: true, selector: "ng-template[comCarouselNext]", ngImport: i0 });
129
+ }
130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselNextTpl, decorators: [{
131
+ type: Directive,
132
+ args: [{
133
+ selector: 'ng-template[comCarouselNext]',
134
+ }]
135
+ }] });
136
+
137
+ /**
138
+ * Template directive for custom pagination indicators.
139
+ *
140
+ * Rendered once per "page" in the carousel. The template receives
141
+ * a context with the page index, active state, and total count.
142
+ *
143
+ * @example
144
+ * ```html
145
+ * <com-carousel>
146
+ * <div comCarouselItem>Slide 1</div>
147
+ * <div comCarouselItem>Slide 2</div>
148
+ * <ng-template comCarouselPagination let-idx let-active="active">
149
+ * <span
150
+ * class="inline-block h-3 w-3 rounded-pill transition-colors"
151
+ * [class]="active ? 'bg-primary' : 'bg-muted'"
152
+ * ></span>
153
+ * </ng-template>
154
+ * </com-carousel>
155
+ * ```
156
+ */
157
+ class ComCarouselPaginationTpl {
158
+ templateRef = inject(TemplateRef);
159
+ static ngTemplateContextGuard(_dir, _ctx) {
160
+ return true;
161
+ }
162
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselPaginationTpl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
163
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComCarouselPaginationTpl, isStandalone: true, selector: "ng-template[comCarouselPagination]", ngImport: i0 });
164
+ }
165
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselPaginationTpl, decorators: [{
166
+ type: Directive,
167
+ args: [{
168
+ selector: 'ng-template[comCarouselPagination]',
169
+ }]
170
+ }] });
171
+
172
+ /**
173
+ * Carousel component — a horizontal slider that navigates content screens
174
+ * using percentage-based CSS `transform: translateX()`.
175
+ *
176
+ * Items are projected via `ng-content` and marked with `comCarouselItem`.
177
+ * Navigation is screen-based: each index step moves one full viewport of items.
178
+ * Supports multi-item views, auto-play, loop, touch/swipe, keyboard
179
+ * navigation, and custom templates for navigation and pagination.
180
+ *
181
+ * @tokens `--color-background`, `--color-foreground`, `--color-muted`,
182
+ * `--color-muted-hover`, `--color-primary`, `--color-border-subtle`,
183
+ * `--color-ring`, `--color-disabled`, `--color-disabled-foreground`
184
+ *
185
+ * @example Basic usage
186
+ * ```html
187
+ * <com-carousel>
188
+ * <div comCarouselItem>Slide 1</div>
189
+ * <div comCarouselItem>Slide 2</div>
190
+ * <div comCarouselItem>Slide 3</div>
191
+ * </com-carousel>
192
+ * ```
193
+ *
194
+ * @example Multiple items per view
195
+ * ```html
196
+ * <com-carousel [slidesPerView]="3">
197
+ * @for (product of products(); track product.id) {
198
+ * <com-card comCarouselItem>{{ product.name }}</com-card>
199
+ * }
200
+ * </com-carousel>
201
+ * ```
202
+ *
203
+ * @example Auto-play with loop
204
+ * ```html
205
+ * <com-carousel autoPlay loop [autoPlayInterval]="4000">
206
+ * <img comCarouselItem *ngFor="let img of images" [src]="img.url" [alt]="img.alt" />
207
+ * </com-carousel>
208
+ * ```
209
+ *
210
+ * @example Two-way index binding
211
+ * ```html
212
+ * <com-carousel [(index)]="currentSlide">
213
+ * <div comCarouselItem>A</div>
214
+ * <div comCarouselItem>B</div>
215
+ * </com-carousel>
216
+ * ```
217
+ *
218
+ * @example Custom navigation
219
+ * ```html
220
+ * <com-carousel>
221
+ * <div comCarouselItem>Slide 1</div>
222
+ * <ng-template comCarouselPrev let-canGo>
223
+ * <button [disabled]="!canGo">Back</button>
224
+ * </ng-template>
225
+ * <ng-template comCarouselNext let-canGo>
226
+ * <button [disabled]="!canGo">Forward</button>
227
+ * </ng-template>
228
+ * </com-carousel>
229
+ * ```
230
+ *
231
+ * @example Custom pagination
232
+ * ```html
233
+ * <com-carousel>
234
+ * <div comCarouselItem>Slide 1</div>
235
+ * <ng-template comCarouselPagination let-idx let-active="active">
236
+ * <span [class]="active ? 'bg-primary' : 'bg-muted'" class="h-3 w-3 rounded-pill inline-block"></span>
237
+ * </ng-template>
238
+ * </com-carousel>
239
+ * ```
240
+ */
241
+ class ComCarouselComponent {
242
+ destroyRef = inject(DestroyRef);
243
+ renderer = inject(Renderer2);
244
+ platform = inject(Platform);
245
+ hostEl = inject(ElementRef);
246
+ // ─── Inputs ───
247
+ /** Number of items visible at once. */
248
+ slidesPerView = input(1, ...(ngDevMode ? [{ debugName: "slidesPerView" }] : []));
249
+ /** Wrap from last to first and vice versa. */
250
+ loop = input(false, { ...(ngDevMode ? { debugName: "loop" } : {}), transform: booleanAttribute });
251
+ /** Enable auto-advancing slides. */
252
+ autoPlay = input(false, { ...(ngDevMode ? { debugName: "autoPlay" } : {}), transform: booleanAttribute });
253
+ /** Auto-play interval in milliseconds. */
254
+ autoPlayInterval = input(5000, ...(ngDevMode ? [{ debugName: "autoPlayInterval" }] : []));
255
+ /** Slide transition duration in milliseconds. */
256
+ transitionDuration = input(300, ...(ngDevMode ? [{ debugName: "transitionDuration" }] : []));
257
+ /** Show prev/next navigation buttons. */
258
+ showNavigation = input(true, { ...(ngDevMode ? { debugName: "showNavigation" } : {}), transform: booleanAttribute });
259
+ /** Show pagination dot indicators. */
260
+ showPagination = input(true, { ...(ngDevMode ? { debugName: "showPagination" } : {}), transform: booleanAttribute });
261
+ /** Accessible label for the carousel region. */
262
+ ariaLabel = input('Carousel', { ...(ngDevMode ? { debugName: "ariaLabel" } : {}), alias: 'aria-label' });
263
+ // ─── Model ───
264
+ /** Two-way bindable current screen index. */
265
+ index = model(0, ...(ngDevMode ? [{ debugName: "index" }] : []));
266
+ // ─── Content Children ───
267
+ items = contentChildren(ComCarouselItem, ...(ngDevMode ? [{ debugName: "items" }] : []));
268
+ customPrev = contentChild(ComCarouselPrevTpl, ...(ngDevMode ? [{ debugName: "customPrev" }] : []));
269
+ customNext = contentChild(ComCarouselNextTpl, ...(ngDevMode ? [{ debugName: "customNext" }] : []));
270
+ customPagination = contentChild(ComCarouselPaginationTpl, ...(ngDevMode ? [{ debugName: "customPagination" }] : []));
271
+ // ─── View Children ───
272
+ trackEl = viewChild('track', ...(ngDevMode ? [{ debugName: "trackEl" }] : []));
273
+ // ─── Internal State ───
274
+ paused = signal(false, ...(ngDevMode ? [{ debugName: "paused" }] : []));
275
+ animating = signal(true, ...(ngDevMode ? [{ debugName: "animating" }] : []));
276
+ autoPlayTimerId = null;
277
+ // ─── Computed ───
278
+ totalSlides = computed(() => this.items().length, ...(ngDevMode ? [{ debugName: "totalSlides" }] : []));
279
+ /** Total number of screens (pages). */
280
+ totalScreens = computed(() => Math.max(1, Math.ceil(this.totalSlides() / this.slidesPerView())), ...(ngDevMode ? [{ debugName: "totalScreens" }] : []));
281
+ maxIndex = computed(() => Math.max(0, this.totalScreens() - 1), ...(ngDevMode ? [{ debugName: "maxIndex" }] : []));
282
+ /** Clamp index when items or slidesPerView changes. */
283
+ clampedIndex = linkedSignal(() => {
284
+ const idx = this.index();
285
+ const max = this.maxIndex();
286
+ return Math.max(0, Math.min(idx, max));
287
+ }, ...(ngDevMode ? [{ debugName: "clampedIndex" }] : []));
288
+ canGoPrev = computed(() => this.loop() || this.clampedIndex() > 0, ...(ngDevMode ? [{ debugName: "canGoPrev" }] : []));
289
+ canGoNext = computed(() => this.loop() || this.clampedIndex() < this.maxIndex(), ...(ngDevMode ? [{ debugName: "canGoNext" }] : []));
290
+ pages = computed(() => Array.from({ length: this.totalScreens() }, (_, i) => i), ...(ngDevMode ? [{ debugName: "pages" }] : []));
291
+ /** Percentage-based translateX — each screen step moves by 100%. */
292
+ translateX = computed(() => `translateX(${-this.clampedIndex() * 100}%)`, ...(ngDevMode ? [{ debugName: "translateX" }] : []));
293
+ /** Item width as a percentage string. */
294
+ itemWidth = computed(() => `${100 / this.slidesPerView()}%`, ...(ngDevMode ? [{ debugName: "itemWidth" }] : []));
295
+ ariaLiveMode = computed(() => this.autoPlay() && !this.paused() ? 'off' : 'polite', ...(ngDevMode ? [{ debugName: "ariaLiveMode" }] : []));
296
+ // ─── CVA Classes ───
297
+ containerClasses = computed(() => carouselContainerVariants(), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
298
+ prevButtonClasses = computed(() => carouselNavButtonVariants({ position: 'prev' }), ...(ngDevMode ? [{ debugName: "prevButtonClasses" }] : []));
299
+ nextButtonClasses = computed(() => carouselNavButtonVariants({ position: 'next' }), ...(ngDevMode ? [{ debugName: "nextButtonClasses" }] : []));
300
+ // ─── Template Contexts ───
301
+ prevContext = computed(() => ({
302
+ $implicit: this.canGoPrev(),
303
+ prev: () => this.prev(),
304
+ }), ...(ngDevMode ? [{ debugName: "prevContext" }] : []));
305
+ nextContext = computed(() => ({
306
+ $implicit: this.canGoNext(),
307
+ next: () => this.next(),
308
+ }), ...(ngDevMode ? [{ debugName: "nextContext" }] : []));
309
+ constructor() {
310
+ // Sync clamped index back to the model
311
+ effect(() => {
312
+ const clamped = this.clampedIndex();
313
+ if (this.index() !== clamped) {
314
+ this.index.set(clamped);
315
+ }
316
+ });
317
+ // Apply percentage-based width and ARIA attributes to each projected item
318
+ effect(() => {
319
+ const allItems = this.items();
320
+ const total = allItems.length;
321
+ const width = this.itemWidth();
322
+ allItems.forEach((item, i) => {
323
+ const el = item.elementRef.nativeElement;
324
+ this.renderer.setStyle(el, 'min-width', width);
325
+ this.renderer.setStyle(el, 'max-width', width);
326
+ this.renderer.setStyle(el, 'flex-shrink', '0');
327
+ this.renderer.setStyle(el, 'box-sizing', 'border-box');
328
+ this.renderer.setAttribute(el, 'role', 'group');
329
+ this.renderer.setAttribute(el, 'aria-roledescription', 'slide');
330
+ this.renderer.setAttribute(el, 'aria-label', `${i + 1} of ${total}`);
331
+ });
332
+ });
333
+ // Auto-play effect
334
+ effect(() => {
335
+ const shouldAutoPlay = this.autoPlay();
336
+ const isPaused = this.paused();
337
+ const interval = this.autoPlayInterval();
338
+ this.clearAutoPlay();
339
+ if (shouldAutoPlay && !isPaused) {
340
+ this.autoPlayTimerId = setInterval(() => {
341
+ this.next();
342
+ }, interval);
343
+ }
344
+ });
345
+ this.destroyRef.onDestroy(() => {
346
+ this.clearAutoPlay();
347
+ this.cleanupSwipe();
348
+ });
349
+ // Set up touch/swipe after view init
350
+ if (this.platform.isBrowser) {
351
+ effect(() => {
352
+ const track = this.trackEl();
353
+ if (track) {
354
+ this.setupSwipe(track.nativeElement);
355
+ }
356
+ });
357
+ }
358
+ }
359
+ // ─── Public Methods ───
360
+ /** Navigate to the next screen. */
361
+ next() {
362
+ const current = this.clampedIndex();
363
+ const max = this.maxIndex();
364
+ if (current < max) {
365
+ this.goToPage(current + 1);
366
+ }
367
+ else if (this.loop()) {
368
+ this.goToPage(0);
369
+ }
370
+ }
371
+ /** Navigate to the previous screen. */
372
+ prev() {
373
+ const current = this.clampedIndex();
374
+ if (current > 0) {
375
+ this.goToPage(current - 1);
376
+ }
377
+ else if (this.loop()) {
378
+ this.goToPage(this.maxIndex());
379
+ }
380
+ }
381
+ /** Navigate to a specific page index. */
382
+ goToPage(pageIndex) {
383
+ if (pageIndex >= 0 && pageIndex <= this.maxIndex()) {
384
+ this.animating.set(true);
385
+ this.index.set(pageIndex);
386
+ }
387
+ }
388
+ /** Get CVA classes for a pagination dot at the given index. */
389
+ getDotClasses(pageIndex) {
390
+ return carouselDotVariants({ active: pageIndex === this.clampedIndex() });
391
+ }
392
+ /** Build template context for a custom pagination dot. */
393
+ getPaginationContext(pageIndex) {
394
+ return {
395
+ $implicit: pageIndex,
396
+ active: pageIndex === this.clampedIndex(),
397
+ total: this.totalScreens(),
398
+ index: pageIndex,
399
+ };
400
+ }
401
+ /** Handle keyboard navigation on pagination dots. */
402
+ onPaginationKeydown(event, currentIndex) {
403
+ const total = this.totalScreens();
404
+ let targetIndex = null;
405
+ switch (event.key) {
406
+ case 'ArrowRight':
407
+ case 'ArrowDown':
408
+ targetIndex = (currentIndex + 1) % total;
409
+ break;
410
+ case 'ArrowLeft':
411
+ case 'ArrowUp':
412
+ targetIndex = (currentIndex - 1 + total) % total;
413
+ break;
414
+ case 'Home':
415
+ targetIndex = 0;
416
+ break;
417
+ case 'End':
418
+ targetIndex = total - 1;
419
+ break;
420
+ default:
421
+ return;
422
+ }
423
+ event.preventDefault();
424
+ this.goToPage(targetIndex);
425
+ this.focusDotAt(targetIndex);
426
+ }
427
+ // ─── Host Event Handlers ───
428
+ pauseAutoPlay() {
429
+ if (this.autoPlay()) {
430
+ this.paused.set(true);
431
+ }
432
+ }
433
+ resumeAutoPlay() {
434
+ if (this.autoPlay()) {
435
+ this.paused.set(false);
436
+ }
437
+ }
438
+ // ─── Private: Auto-play ───
439
+ clearAutoPlay() {
440
+ if (this.autoPlayTimerId !== null) {
441
+ clearInterval(this.autoPlayTimerId);
442
+ this.autoPlayTimerId = null;
443
+ }
444
+ }
445
+ // ─── Private: Touch/Swipe ───
446
+ swipeCleanupFns = [];
447
+ pointerStartX = 0;
448
+ pointerStartY = 0;
449
+ isSwiping = false;
450
+ setupSwipe(trackElement) {
451
+ this.cleanupSwipe();
452
+ const unlistenDown = this.renderer.listen(trackElement, 'pointerdown', (e) => {
453
+ this.pointerStartX = e.clientX;
454
+ this.pointerStartY = e.clientY;
455
+ this.isSwiping = true;
456
+ this.animating.set(false);
457
+ });
458
+ const unlistenMove = this.renderer.listen(trackElement, 'pointermove', (e) => {
459
+ if (!this.isSwiping)
460
+ return;
461
+ // Prevent vertical scroll when swiping horizontally
462
+ const dx = Math.abs(e.clientX - this.pointerStartX);
463
+ const dy = Math.abs(e.clientY - this.pointerStartY);
464
+ if (dx > dy && dx > 10) {
465
+ e.preventDefault();
466
+ }
467
+ });
468
+ const unlistenUp = this.renderer.listen(trackElement, 'pointerup', (e) => {
469
+ if (!this.isSwiping)
470
+ return;
471
+ this.isSwiping = false;
472
+ this.animating.set(true);
473
+ const delta = e.clientX - this.pointerStartX;
474
+ const threshold = 50;
475
+ if (delta < -threshold) {
476
+ this.next();
477
+ }
478
+ else if (delta > threshold) {
479
+ this.prev();
480
+ }
481
+ });
482
+ const unlistenCancel = this.renderer.listen(trackElement, 'pointercancel', () => {
483
+ this.isSwiping = false;
484
+ this.animating.set(true);
485
+ });
486
+ this.swipeCleanupFns = [unlistenDown, unlistenMove, unlistenUp, unlistenCancel];
487
+ }
488
+ cleanupSwipe() {
489
+ this.swipeCleanupFns.forEach((fn) => fn());
490
+ this.swipeCleanupFns = [];
491
+ }
492
+ // ─── Private: Focus Management ───
493
+ focusDotAt(index) {
494
+ const host = this.hostEl.nativeElement;
495
+ const dots = host.querySelectorAll('[role="tab"]');
496
+ const dot = dots[index];
497
+ dot?.focus();
498
+ }
499
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
500
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComCarouselComponent, isStandalone: true, selector: "com-carousel", inputs: { slidesPerView: { classPropertyName: "slidesPerView", publicName: "slidesPerView", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, autoPlay: { classPropertyName: "autoPlay", publicName: "autoPlay", isSignal: true, isRequired: false, transformFunction: null }, autoPlayInterval: { classPropertyName: "autoPlayInterval", publicName: "autoPlayInterval", isSignal: true, isRequired: false, transformFunction: null }, transitionDuration: { classPropertyName: "transitionDuration", publicName: "transitionDuration", isSignal: true, isRequired: false, transformFunction: null }, showNavigation: { classPropertyName: "showNavigation", publicName: "showNavigation", isSignal: true, isRequired: false, transformFunction: null }, showPagination: { classPropertyName: "showPagination", publicName: "showPagination", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { index: "indexChange" }, host: { attributes: { "role": "region" }, listeners: { "mouseenter": "pauseAutoPlay()", "mouseleave": "resumeAutoPlay()", "focusin": "pauseAutoPlay()", "focusout": "resumeAutoPlay()" }, properties: { "attr.aria-roledescription": "\"carousel\"", "attr.aria-label": "ariaLabel()" }, classAttribute: "com-carousel" }, queries: [{ propertyName: "items", predicate: ComCarouselItem, isSignal: true }, { propertyName: "customPrev", first: true, predicate: ComCarouselPrevTpl, descendants: true, isSignal: true }, { propertyName: "customNext", first: true, predicate: ComCarouselNextTpl, descendants: true, isSignal: true }, { propertyName: "customPagination", first: true, predicate: ComCarouselPaginationTpl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trackEl", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: `
501
+ <div [class]="containerClasses()">
502
+ <!-- Slide track -->
503
+ <div
504
+ #track
505
+ class="flex"
506
+ [style.transform]="translateX()"
507
+ [style.transition]="animating() ? 'transform ' + transitionDuration() + 'ms ease-out' : 'none'"
508
+ [attr.aria-live]="ariaLiveMode()"
509
+ >
510
+ <ng-content select="[comCarouselItem]" />
511
+ </div>
512
+
513
+ <!-- Navigation: Previous -->
514
+ @if (showNavigation()) {
515
+ @if (customPrev(); as prevTpl) {
516
+ <ng-container
517
+ [ngTemplateOutlet]="prevTpl.templateRef"
518
+ [ngTemplateOutletContext]="prevContext()"
519
+ />
520
+ } @else {
521
+ <button
522
+ type="button"
523
+ [class]="prevButtonClasses()"
524
+ [disabled]="!canGoPrev()"
525
+ [attr.aria-label]="'Previous slide'"
526
+ (click)="prev()"
527
+ >
528
+ <com-icon name="chevron-left" size="md" />
529
+ </button>
530
+ }
531
+ }
532
+
533
+ <!-- Navigation: Next -->
534
+ @if (showNavigation()) {
535
+ @if (customNext(); as nextTpl) {
536
+ <ng-container
537
+ [ngTemplateOutlet]="nextTpl.templateRef"
538
+ [ngTemplateOutletContext]="nextContext()"
539
+ />
540
+ } @else {
541
+ <button
542
+ type="button"
543
+ [class]="nextButtonClasses()"
544
+ [disabled]="!canGoNext()"
545
+ [attr.aria-label]="'Next slide'"
546
+ (click)="next()"
547
+ >
548
+ <com-icon name="chevron-right" size="md" />
549
+ </button>
550
+ }
551
+ }
552
+ </div>
553
+
554
+ <!-- Pagination -->
555
+ @if (showPagination() && totalScreens() > 1) {
556
+ <div
557
+ class="flex items-center justify-center gap-2 py-3"
558
+ role="tablist"
559
+ [attr.aria-label]="'Slide navigation'"
560
+ >
561
+ @for (page of pages(); track page; let i = $index) {
562
+ @if (customPagination(); as paginationTpl) {
563
+ <button
564
+ type="button"
565
+ role="tab"
566
+ [attr.aria-selected]="i === clampedIndex()"
567
+ [attr.aria-label]="'Go to slide ' + (i + 1)"
568
+ [tabindex]="i === clampedIndex() ? 0 : -1"
569
+ (click)="goToPage(i)"
570
+ (keydown)="onPaginationKeydown($event, i)"
571
+ >
572
+ <ng-container
573
+ [ngTemplateOutlet]="paginationTpl.templateRef"
574
+ [ngTemplateOutletContext]="getPaginationContext(i)"
575
+ />
576
+ </button>
577
+ } @else {
578
+ <button
579
+ type="button"
580
+ role="tab"
581
+ [attr.aria-selected]="i === clampedIndex()"
582
+ [attr.aria-label]="'Go to slide ' + (i + 1)"
583
+ [tabindex]="i === clampedIndex() ? 0 : -1"
584
+ [class]="getDotClasses(i)"
585
+ (click)="goToPage(i)"
586
+ (keydown)="onPaginationKeydown($event, i)"
587
+ ></button>
588
+ }
589
+ }
590
+ </div>
591
+ }
592
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ComIcon, selector: "com-icon", inputs: ["name", "img", "color", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
593
+ }
594
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComCarouselComponent, decorators: [{
595
+ type: Component,
596
+ args: [{ selector: 'com-carousel', template: `
597
+ <div [class]="containerClasses()">
598
+ <!-- Slide track -->
599
+ <div
600
+ #track
601
+ class="flex"
602
+ [style.transform]="translateX()"
603
+ [style.transition]="animating() ? 'transform ' + transitionDuration() + 'ms ease-out' : 'none'"
604
+ [attr.aria-live]="ariaLiveMode()"
605
+ >
606
+ <ng-content select="[comCarouselItem]" />
607
+ </div>
608
+
609
+ <!-- Navigation: Previous -->
610
+ @if (showNavigation()) {
611
+ @if (customPrev(); as prevTpl) {
612
+ <ng-container
613
+ [ngTemplateOutlet]="prevTpl.templateRef"
614
+ [ngTemplateOutletContext]="prevContext()"
615
+ />
616
+ } @else {
617
+ <button
618
+ type="button"
619
+ [class]="prevButtonClasses()"
620
+ [disabled]="!canGoPrev()"
621
+ [attr.aria-label]="'Previous slide'"
622
+ (click)="prev()"
623
+ >
624
+ <com-icon name="chevron-left" size="md" />
625
+ </button>
626
+ }
627
+ }
628
+
629
+ <!-- Navigation: Next -->
630
+ @if (showNavigation()) {
631
+ @if (customNext(); as nextTpl) {
632
+ <ng-container
633
+ [ngTemplateOutlet]="nextTpl.templateRef"
634
+ [ngTemplateOutletContext]="nextContext()"
635
+ />
636
+ } @else {
637
+ <button
638
+ type="button"
639
+ [class]="nextButtonClasses()"
640
+ [disabled]="!canGoNext()"
641
+ [attr.aria-label]="'Next slide'"
642
+ (click)="next()"
643
+ >
644
+ <com-icon name="chevron-right" size="md" />
645
+ </button>
646
+ }
647
+ }
648
+ </div>
649
+
650
+ <!-- Pagination -->
651
+ @if (showPagination() && totalScreens() > 1) {
652
+ <div
653
+ class="flex items-center justify-center gap-2 py-3"
654
+ role="tablist"
655
+ [attr.aria-label]="'Slide navigation'"
656
+ >
657
+ @for (page of pages(); track page; let i = $index) {
658
+ @if (customPagination(); as paginationTpl) {
659
+ <button
660
+ type="button"
661
+ role="tab"
662
+ [attr.aria-selected]="i === clampedIndex()"
663
+ [attr.aria-label]="'Go to slide ' + (i + 1)"
664
+ [tabindex]="i === clampedIndex() ? 0 : -1"
665
+ (click)="goToPage(i)"
666
+ (keydown)="onPaginationKeydown($event, i)"
667
+ >
668
+ <ng-container
669
+ [ngTemplateOutlet]="paginationTpl.templateRef"
670
+ [ngTemplateOutletContext]="getPaginationContext(i)"
671
+ />
672
+ </button>
673
+ } @else {
674
+ <button
675
+ type="button"
676
+ role="tab"
677
+ [attr.aria-selected]="i === clampedIndex()"
678
+ [attr.aria-label]="'Go to slide ' + (i + 1)"
679
+ [tabindex]="i === clampedIndex() ? 0 : -1"
680
+ [class]="getDotClasses(i)"
681
+ (click)="goToPage(i)"
682
+ (keydown)="onPaginationKeydown($event, i)"
683
+ ></button>
684
+ }
685
+ }
686
+ </div>
687
+ }
688
+ `, imports: [NgTemplateOutlet, ComIcon], changeDetection: ChangeDetectionStrategy.OnPush, host: {
689
+ 'class': 'com-carousel',
690
+ 'role': 'region',
691
+ '[attr.aria-roledescription]': '"carousel"',
692
+ '[attr.aria-label]': 'ariaLabel()',
693
+ '(mouseenter)': 'pauseAutoPlay()',
694
+ '(mouseleave)': 'resumeAutoPlay()',
695
+ '(focusin)': 'pauseAutoPlay()',
696
+ '(focusout)': 'resumeAutoPlay()',
697
+ }, styles: [":host{display:block}\n"] }]
698
+ }], ctorParameters: () => [], propDecorators: { slidesPerView: [{ type: i0.Input, args: [{ isSignal: true, alias: "slidesPerView", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], autoPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlay", required: false }] }], autoPlayInterval: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlayInterval", required: false }] }], transitionDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "transitionDuration", required: false }] }], showNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNavigation", required: false }] }], showPagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPagination", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }, { type: i0.Output, args: ["indexChange"] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ComCarouselItem), { isSignal: true }] }], customPrev: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComCarouselPrevTpl), { isSignal: true }] }], customNext: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComCarouselNextTpl), { isSignal: true }] }], customPagination: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComCarouselPaginationTpl), { isSignal: true }] }], trackEl: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
699
+
700
+ // Public API for the carousel component
701
+ // Variants
702
+
703
+ /**
704
+ * Generated bundle index. Do not edit.
705
+ */
706
+
707
+ export { ComCarouselComponent, ComCarouselItem, ComCarouselNextTpl, ComCarouselPaginationTpl, ComCarouselPrevTpl, carouselContainerVariants, carouselDotVariants, carouselNavButtonVariants };
708
+ //# sourceMappingURL=ngx-com-components-carousel.mjs.map