ngx-edge-slider 2.2.2 → 2.2.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.
@@ -0,0 +1,824 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, ViewChild, NgModule, Pipe } from '@angular/core';
3
+ import { BehaviorSubject, Subject, fromEvent, merge, auditTime, takeUntil } from 'rxjs';
4
+ import * as i7 from '@angular/common';
5
+ import { CommonModule } from '@angular/common';
6
+ import * as i1 from '@angular/platform-browser';
7
+
8
+ const DEFAULT_CONFIG = {
9
+ slides: [],
10
+ slidesPerView: 1,
11
+ slidesToSlide: 1,
12
+ loop: 0,
13
+ vertical: false,
14
+ changeToClickedSlide: false,
15
+ isThumbs: false,
16
+ plugins: {
17
+ draggable: false,
18
+ pagination: false,
19
+ navigation: false,
20
+ autoplay: undefined,
21
+ },
22
+ gap: 0,
23
+ showOn: { mobile: true, tablet: true, desktop: true },
24
+ };
25
+
26
+ const INITIAL_SLIDER_STATE = {
27
+ currentSlide: 0,
28
+ selectedSlide: -1,
29
+ slidesPerView: 1,
30
+ visibleSlides: [],
31
+ gap: 0,
32
+ maxStartIndex: 0,
33
+ canPrev: false,
34
+ canNext: false,
35
+ translate: "translateX(0px)",
36
+ transition: "transform 300ms ease",
37
+ pager: null,
38
+ isAnimating: false,
39
+ isVisible: false,
40
+ isDragging: false,
41
+ };
42
+
43
+ class SliderStore {
44
+ state$ = new BehaviorSubject({
45
+ currentSlide: 0,
46
+ selectedSlide: -1,
47
+ slidesPerView: 1,
48
+ visibleSlides: [],
49
+ translate: "translateX(0)",
50
+ transition: "transform 300ms ease",
51
+ pager: null,
52
+ isVisible: true,
53
+ isAnimating: false,
54
+ isDragging: false,
55
+ });
56
+ view$ = this.state$.asObservable();
57
+ get snapshot() {
58
+ return this.state$.value;
59
+ }
60
+ update(patch) {
61
+ this.state$.next({ ...this.snapshot, ...patch });
62
+ }
63
+ reset() {
64
+ this.state$.next(INITIAL_SLIDER_STATE);
65
+ }
66
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
67
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderStore });
68
+ }
69
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderStore, decorators: [{
70
+ type: Injectable
71
+ }] });
72
+
73
+ class SliderEngine {
74
+ store;
75
+ config;
76
+ plugins = [];
77
+ syncThumbsEngine;
78
+ // RX lifecycle
79
+ destroy$ = new Subject();
80
+ // Container-based breakpoints
81
+ containerEl;
82
+ ro;
83
+ measuredSlideSizePx;
84
+ constructor(store) {
85
+ this.store = store;
86
+ }
87
+ /* ---------------- Public API ---------------- */
88
+ init(config, plugins = []) {
89
+ this.config = { ...DEFAULT_CONFIG, ...config, plugins: { ...(config.plugins ?? {}) } };
90
+ this.plugins = plugins;
91
+ this.plugins.forEach((p) => p.init?.(this));
92
+ this.setupViewportSignals();
93
+ this.applyBreakpoint();
94
+ this.clampIndicesAfterLayoutChange();
95
+ this.recalculate();
96
+ }
97
+ /** Call this from the component once you have the slider host element */
98
+ attachContainer(el) {
99
+ this.containerEl = el;
100
+ // Observe container size changes (robust in grids/sidebars/tabs)
101
+ this.ro?.disconnect();
102
+ this.ro = new ResizeObserver(() => {
103
+ // We reuse the same debounced layout handler
104
+ this.onLayoutSignal();
105
+ });
106
+ this.ro.observe(el);
107
+ this.measureSlideSize();
108
+ // Apply immediately based on real container width
109
+ this.onLayoutSignal();
110
+ }
111
+ measureSlideSize() {
112
+ if (!this.containerEl)
113
+ return;
114
+ const firstSlide = this.containerEl.querySelector(".slide");
115
+ if (!firstSlide)
116
+ return;
117
+ const rect = firstSlide.getBoundingClientRect();
118
+ if (rect.width > 0)
119
+ this.measuredSlideSizePx = rect.width;
120
+ }
121
+ destroy() {
122
+ // Stop Rx streams
123
+ this.destroy$.next();
124
+ this.destroy$.complete();
125
+ // Disconnect ResizeObserver
126
+ this.ro?.disconnect();
127
+ this.ro = undefined;
128
+ this.containerEl = undefined;
129
+ // Plugin cleanup
130
+ this.plugins.forEach((p) => p.destroy?.());
131
+ this.plugins = [];
132
+ // Reset store
133
+ this.store.reset();
134
+ }
135
+ next() {
136
+ this.goTo(this.store.snapshot.currentSlide + this.slideStep());
137
+ this.plugins.forEach((p) => p.onNext?.());
138
+ }
139
+ previous() {
140
+ this.goTo(this.store.snapshot.currentSlide - this.slideStep());
141
+ this.plugins.forEach((p) => p.onPrevious?.());
142
+ }
143
+ getContainerEl() {
144
+ return this.containerEl;
145
+ }
146
+ selectSlide(index) {
147
+ const slidesPerView = this.store.snapshot.slidesPerView;
148
+ // 1️⃣ Mark selected
149
+ this.store.update({ selectedSlide: index });
150
+ // 2️⃣ Calculate page start
151
+ let pageStart = Math.floor(index / slidesPerView) * slidesPerView;
152
+ // 3️⃣ Clamp to maxStartIndex
153
+ pageStart = Math.min(pageStart, this.maxStartIndex);
154
+ // 4️⃣ If synced with thumbs, also clamp to their max index
155
+ if (this.syncThumbsEngine) {
156
+ const thumbMaxIndex = this.syncThumbsEngine.getMaxStartIndex();
157
+ pageStart = Math.min(pageStart, thumbMaxIndex);
158
+ }
159
+ // 5️⃣ Move main slider
160
+ if (pageStart !== this.store.snapshot.currentSlide) {
161
+ this.goToSlide(pageStart);
162
+ }
163
+ // 6️⃣ Notify plugins
164
+ this.plugins.forEach((p) => p.onSlideClick?.(index));
165
+ }
166
+ /* ---------------- Drag ---------------- */
167
+ handleDragStart(event) {
168
+ this.store.update({ isDragging: true });
169
+ this.plugins.forEach((p) => p.onDragStart?.(event));
170
+ }
171
+ handleDragMove(event) {
172
+ this.plugins.forEach((p) => p.onDragMove?.(event));
173
+ }
174
+ handleDragEnd() {
175
+ this.store.update({ isDragging: false });
176
+ this.plugins.forEach((p) => p.onDragEnd?.());
177
+ }
178
+ goTo(index) {
179
+ this.goToSlide(index); // reuse your existing private method
180
+ }
181
+ /* ---------------- Internals ---------------- */
182
+ goToSlide(index) {
183
+ const clamped = Math.max(0, Math.min(index, this.maxStartIndex));
184
+ // console.log("[Engine] goToSlide index:", index, "clamped:", clamped);
185
+ this.store.update({ currentSlide: clamped });
186
+ this.recalculate();
187
+ // NOTE: calling onSlideClick here is a bit semantically odd (since it’s not always a click),
188
+ // but I preserved your behavior.
189
+ this.plugins.forEach((p) => p.onSlideClick?.(index));
190
+ }
191
+ recalculate() {
192
+ if (this.store.snapshot.isDragging)
193
+ return; // skip during drag
194
+ const slides = this.config.slides ?? [];
195
+ const maxStartIndex = this.getMaxStartIndex();
196
+ const current = this.store.snapshot.currentSlide;
197
+ this.store.update({
198
+ visibleSlides: slides,
199
+ translate: this.translate(current),
200
+ pager: this.buildPager(),
201
+ maxStartIndex,
202
+ canPrev: current > 0,
203
+ canNext: current < maxStartIndex,
204
+ });
205
+ }
206
+ translate(index) {
207
+ const axis = this.config.vertical ? "Y" : "X";
208
+ const containerSize = this.containerEl
209
+ ? this.config.vertical
210
+ ? this.containerEl.clientHeight
211
+ : this.containerEl.clientWidth
212
+ : this.config.vertical
213
+ ? window.innerHeight
214
+ : window.innerWidth;
215
+ const gap = this.store.snapshot.gap ?? 0;
216
+ const spv = this.store.snapshot.slidesPerView;
217
+ const slideSize = this.config.isThumbs && this.measuredSlideSizePx
218
+ ? this.measuredSlideSizePx
219
+ : spv > 0
220
+ ? (containerSize - gap * (spv - 1)) / spv
221
+ : containerSize;
222
+ // move by "pageStart * (slideSize + gap)"
223
+ const offset = index * (slideSize + gap);
224
+ return `translate${axis}(-${offset}px)`;
225
+ }
226
+ /** Debounced layout signals for resize/orientation/container changes */
227
+ setupViewportSignals() {
228
+ // If you want "only after resizing stops", replace auditTime with debounceTime(120)
229
+ const resize$ = fromEvent(window, "resize", { passive: true });
230
+ const orientation$ = fromEvent(window, "orientationchange", { passive: true });
231
+ merge(resize$, orientation$)
232
+ .pipe(auditTime(80), // good compromise: responsive without over-recalc
233
+ takeUntil(this.destroy$))
234
+ .subscribe(() => this.onLayoutSignal());
235
+ }
236
+ onLayoutSignal() {
237
+ const beforeSlidesPerView = this.store.snapshot.slidesPerView;
238
+ const beforeVisible = this.store.snapshot.isVisible;
239
+ this.applyBreakpoint();
240
+ const afterSlidesPerView = this.store.snapshot.slidesPerView;
241
+ const afterVisible = this.store.snapshot.isVisible;
242
+ if (beforeSlidesPerView !== afterSlidesPerView || beforeVisible !== afterVisible) {
243
+ this.clampIndicesAfterLayoutChange();
244
+ }
245
+ this.measureSlideSize();
246
+ this.recalculate();
247
+ }
248
+ getBreakpointWidth() {
249
+ if (this.containerEl) {
250
+ // Prefer clientWidth (layout), fallback to rect width
251
+ return this.containerEl.clientWidth || this.containerEl.getBoundingClientRect().width || window.innerWidth;
252
+ }
253
+ return window.innerWidth;
254
+ }
255
+ applyBreakpoint() {
256
+ const width = this.getBreakpointWidth();
257
+ const bp = this.config.breakpoints;
258
+ let override;
259
+ let device = "desktop";
260
+ if (width < 768) {
261
+ override = bp?.mobile;
262
+ device = "mobile";
263
+ }
264
+ else if (width < 1024) {
265
+ override = bp?.tablet;
266
+ device = "tablet";
267
+ }
268
+ else {
269
+ override = bp?.desktop;
270
+ device = "desktop";
271
+ }
272
+ // Merge overrides
273
+ this.config = { ...this.config, ...override };
274
+ this.store.update({
275
+ slidesPerView: this.config.slidesPerView,
276
+ gap: this.config.gap ?? 0, // ✅ NEW
277
+ isVisible: this.config.showOn?.[device] ?? true,
278
+ });
279
+ }
280
+ clampIndicesAfterLayoutChange() {
281
+ const max = this.getMaxStartIndex();
282
+ const current = this.store.snapshot.currentSlide;
283
+ const selected = this.store.snapshot.selectedSlide;
284
+ const clampedCurrent = Math.max(0, Math.min(current, max));
285
+ const clampedSelected = selected === -1 ? -1 : Math.max(0, Math.min(selected, (this.config.slides?.length ?? 0) - 1));
286
+ this.store.update({
287
+ currentSlide: clampedCurrent,
288
+ selectedSlide: clampedSelected,
289
+ });
290
+ }
291
+ buildPager() {
292
+ const totalSlides = this.config.slides.length;
293
+ const currentSlide = this.store.snapshot.selectedSlide !== -1 ? this.store.snapshot.selectedSlide : this.store.snapshot.currentSlide;
294
+ const maxVisibleDots = 5;
295
+ let start = Math.max(0, currentSlide - Math.floor(maxVisibleDots / 2));
296
+ let end = start + maxVisibleDots;
297
+ if (end > totalSlides) {
298
+ end = totalSlides;
299
+ start = Math.max(0, end - maxVisibleDots);
300
+ }
301
+ const visibleDots = Array.from({ length: end - start }, (_, i) => start + i);
302
+ const activeDotIndex = visibleDots.indexOf(currentSlide);
303
+ return {
304
+ currentPage: currentSlide,
305
+ totalPages: totalSlides,
306
+ visibleDots,
307
+ activeDotIndex,
308
+ };
309
+ }
310
+ slideStep() {
311
+ return this.store.snapshot.slidesPerView;
312
+ }
313
+ get maxStartIndex() {
314
+ const total = this.config.slides?.length ?? 0;
315
+ return Math.max(0, total - this.store.snapshot.slidesPerView);
316
+ }
317
+ getMaxStartIndex() {
318
+ const total = this.config.slides?.length ?? 0;
319
+ const spv = this.store.snapshot.slidesPerView || 1;
320
+ return Math.max(0, total - spv);
321
+ }
322
+ /* -------- Plugin-safe API -------- */
323
+ getState() {
324
+ return this.store.snapshot;
325
+ }
326
+ getStateObservable() {
327
+ return this.store.view$;
328
+ }
329
+ setState(patch) {
330
+ this.store.update(patch);
331
+ }
332
+ getConfig() {
333
+ return this.config;
334
+ }
335
+ /** Link this slider to thumbs */
336
+ syncWithThumbs(thumbsEngine) {
337
+ this.syncThumbsEngine = thumbsEngine;
338
+ }
339
+ /* -------- Read-only helpers -------- */
340
+ getSlidesPerView() {
341
+ return this.store.snapshot.slidesPerView;
342
+ }
343
+ getCurrentSlide() {
344
+ return this.store.snapshot.currentSlide;
345
+ }
346
+ getSelectedSlide() {
347
+ return this.store.snapshot.selectedSlide;
348
+ }
349
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderEngine, deps: [{ token: SliderStore }], target: i0.ɵɵFactoryTarget.Injectable });
350
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderEngine });
351
+ }
352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderEngine, decorators: [{
353
+ type: Injectable
354
+ }], ctorParameters: () => [{ type: SliderStore }] });
355
+
356
+ class SliderAutoplayPlugin {
357
+ engine;
358
+ timerId;
359
+ delay = 4000; // default
360
+ init(engine) {
361
+ this.engine = engine;
362
+ this.start();
363
+ }
364
+ setConfig(config) {
365
+ if (!config)
366
+ return;
367
+ if (typeof config.delay === "number") {
368
+ this.delay = config.delay;
369
+ }
370
+ // restart autoplay with new config
371
+ this.start();
372
+ }
373
+ onDragStart() {
374
+ this.stop();
375
+ }
376
+ onDragEnd() {
377
+ this.start();
378
+ }
379
+ start() {
380
+ this.stop();
381
+ this.timerId = setInterval(() => {
382
+ this.engine.next();
383
+ }, this.delay);
384
+ }
385
+ stop() {
386
+ if (this.timerId) {
387
+ clearInterval(this.timerId);
388
+ this.timerId = null;
389
+ }
390
+ }
391
+ destroy() {
392
+ this.stop();
393
+ }
394
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderAutoplayPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
395
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderAutoplayPlugin });
396
+ }
397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderAutoplayPlugin, decorators: [{
398
+ type: Injectable
399
+ }] });
400
+
401
+ class SliderDraggablePlugin {
402
+ engine;
403
+ startX = 0;
404
+ deltaX = 0;
405
+ hasDragged = false;
406
+ isDragging = false;
407
+ dragThreshold = 50;
408
+ // Expose drag state to template
409
+ get isDraggingPointer() {
410
+ return this.isDragging;
411
+ }
412
+ init(engine) {
413
+ this.engine = engine;
414
+ }
415
+ onDragStart(event) {
416
+ this.startX = event.clientX;
417
+ this.deltaX = 0;
418
+ this.hasDragged = false;
419
+ this.isDragging = true;
420
+ // Set engine dragging state
421
+ this.engine.setState({ isDragging: true });
422
+ }
423
+ onDragMove(event) {
424
+ if (!this.isDragging)
425
+ return;
426
+ const moveX = event.clientX - this.startX;
427
+ this.deltaX += moveX;
428
+ this.hasDragged = true;
429
+ const currentTranslate = this.extractTranslate(this.engine.getState().translate);
430
+ const newTranslate = currentTranslate + moveX;
431
+ this.engine.setState({ translate: `translateX(${newTranslate}px)` });
432
+ this.startX = event.clientX;
433
+ }
434
+ onDragEnd() {
435
+ if (this.hasDragged) {
436
+ if (this.deltaX > this.dragThreshold) {
437
+ this.engine.previous();
438
+ }
439
+ else if (this.deltaX < -this.dragThreshold) {
440
+ this.engine.next();
441
+ }
442
+ else {
443
+ // Small drag → snap back
444
+ this.engine.recalculate();
445
+ }
446
+ }
447
+ this.isDragging = false;
448
+ this.hasDragged = false;
449
+ this.deltaX = 0;
450
+ this.engine.setState({ isDragging: false });
451
+ }
452
+ /** Extract numeric value from 'translateX(-123px)' */
453
+ extractTranslate(transform) {
454
+ if (!transform)
455
+ return 0;
456
+ const match = transform.match(/-?\d+(\.\d+)?/);
457
+ return match ? parseFloat(match[0]) : 0;
458
+ }
459
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderDraggablePlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
460
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderDraggablePlugin });
461
+ }
462
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderDraggablePlugin, decorators: [{
463
+ type: Injectable
464
+ }] });
465
+
466
+ class SliderNavigationPlugin {
467
+ engine;
468
+ isPagerMode = false;
469
+ _canPrev$ = new BehaviorSubject(false);
470
+ _canNext$ = new BehaviorSubject(false);
471
+ sub;
472
+ get canPrev$() {
473
+ return this._canPrev$.asObservable();
474
+ }
475
+ get canNext$() {
476
+ return this._canNext$.asObservable();
477
+ }
478
+ init(engine, isPagerMode = false) {
479
+ this.engine = engine;
480
+ this.isPagerMode = isPagerMode;
481
+ // Subscribe to state changes
482
+ this.sub = this.engine.getStateObservable?.()?.subscribe(() => this.updateArrows());
483
+ // ✅ Force initial check after a short delay (to catch async slides)
484
+ setTimeout(() => this.updateArrows());
485
+ }
486
+ destroy() {
487
+ this.sub?.unsubscribe();
488
+ }
489
+ next() {
490
+ if (!this.engine)
491
+ return;
492
+ if (this.isPagerMode) {
493
+ const state = this.engine.getState();
494
+ const cfg = this.engine.getConfig();
495
+ const totalSlides = cfg?.slides?.length ?? 0;
496
+ const perView = state?.slidesPerView ?? cfg?.slidesPerView ?? 1;
497
+ const step = cfg?.slidesToSlide ?? 1;
498
+ const maxIndex = Math.max(0, totalSlides - perView);
499
+ const current = state?.currentSlide ?? 0;
500
+ const newIndex = Math.min(maxIndex, current + step);
501
+ this.engine.goTo(newIndex);
502
+ }
503
+ else {
504
+ this.engine.next();
505
+ }
506
+ }
507
+ prev() {
508
+ if (!this.engine)
509
+ return;
510
+ if (this.isPagerMode) {
511
+ const state = this.engine.getState();
512
+ const cfg = this.engine.getConfig();
513
+ const step = cfg?.slidesToSlide ?? 1;
514
+ const current = state?.currentSlide ?? 0;
515
+ const newIndex = Math.max(0, current - step);
516
+ this.engine.goTo(newIndex);
517
+ }
518
+ else {
519
+ this.engine.previous();
520
+ }
521
+ }
522
+ updateArrows() {
523
+ if (!this.engine)
524
+ return;
525
+ const state = this.engine.getState();
526
+ const cfg = this.engine.getConfig();
527
+ const totalSlides = cfg?.slides?.length ?? 0;
528
+ const perView = state?.slidesPerView ?? 1;
529
+ const canPrev = totalSlides > 0 && (state?.currentSlide ?? 0) > 0;
530
+ const canNext = totalSlides > 0 && (state?.currentSlide ?? 0) < Math.max(0, totalSlides - perView);
531
+ if (this._canPrev$.value !== canPrev)
532
+ this._canPrev$.next(canPrev);
533
+ if (this._canNext$.value !== canNext)
534
+ this._canNext$.next(canNext);
535
+ }
536
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderNavigationPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
537
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderNavigationPlugin });
538
+ }
539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderNavigationPlugin, decorators: [{
540
+ type: Injectable
541
+ }] });
542
+
543
+ class SliderPaginationPlugin {
544
+ engine;
545
+ maxVisibleDots = 10;
546
+ sub;
547
+ _pager$ = new BehaviorSubject(null);
548
+ pager$ = this._pager$.asObservable();
549
+ init(engine) {
550
+ this.engine = engine;
551
+ this.sub = this.engine.getStateObservable().subscribe(() => {
552
+ this.updatePager();
553
+ });
554
+ // initial
555
+ setTimeout(() => this.updatePager());
556
+ }
557
+ destroy() {
558
+ this.sub?.unsubscribe();
559
+ this._pager$.next(null);
560
+ }
561
+ /** Go to a slide by slide index (NOT dot index) */
562
+ goToSlide(slideIndex) {
563
+ this.engine.selectSlide(slideIndex);
564
+ }
565
+ updatePager() {
566
+ const state = this.engine.getState();
567
+ const totalSlides = state.visibleSlides?.length ?? 0;
568
+ if (totalSlides <= 0) {
569
+ this._pager$.next(null);
570
+ return;
571
+ }
572
+ // ✅ important: selectedSlide is -1 initially; fallback to currentSlide
573
+ const current = state.selectedSlide !== -1 ? state.selectedSlide : state.currentSlide;
574
+ const maxDots = Math.max(1, this.maxVisibleDots);
575
+ // ✅ clamp to >= 0
576
+ const safeCurrent = Math.max(0, Math.min(current, totalSlides - 1));
577
+ const pageStart = Math.max(0, Math.floor(safeCurrent / maxDots) * maxDots);
578
+ const pageEnd = Math.min(pageStart + maxDots, totalSlides);
579
+ const visibleDots = Array.from({ length: pageEnd - pageStart }, (_, i) => pageStart + i);
580
+ const activeDotIndex = visibleDots.indexOf(safeCurrent);
581
+ this._pager$.next({ visibleDots, activeDotIndex });
582
+ }
583
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderPaginationPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
584
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderPaginationPlugin });
585
+ }
586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SliderPaginationPlugin, decorators: [{
587
+ type: Injectable
588
+ }] });
589
+
590
+ class SimpleSliderComponent {
591
+ engine;
592
+ store;
593
+ draggable;
594
+ pagination;
595
+ navigation;
596
+ autoplay;
597
+ config;
598
+ slideTemplate;
599
+ navigationTemplate;
600
+ paginationTemplate;
601
+ slideChange = new EventEmitter();
602
+ isDraggingPointer = false;
603
+ dragStartX = 0;
604
+ dragStartY = 0;
605
+ lastIndex = -1;
606
+ lastSlidesRef = null;
607
+ destroy$ = new Subject();
608
+ state$;
609
+ sliderHost;
610
+ constructor(engine, store, draggable, pagination, navigation, autoplay) {
611
+ this.engine = engine;
612
+ this.store = store;
613
+ this.draggable = draggable;
614
+ this.pagination = pagination;
615
+ this.navigation = navigation;
616
+ this.autoplay = autoplay;
617
+ }
618
+ ngOnInit() {
619
+ this.state$ = this.store.view$;
620
+ this.state$.pipe(takeUntil(this.destroy$)).subscribe((state) => {
621
+ if (state.currentSlide !== this.lastIndex) {
622
+ this.lastIndex = state.currentSlide;
623
+ this.slideChange.emit(state.currentSlide);
624
+ }
625
+ });
626
+ this.lastSlidesRef = this.config?.slides ?? null;
627
+ this.resolvePlugins(this.config);
628
+ }
629
+ ngAfterViewInit() {
630
+ if (this.sliderHost?.nativeElement) {
631
+ this.engine.attachContainer(this.sliderHost.nativeElement);
632
+ }
633
+ }
634
+ ngOnChanges(changes) {
635
+ if (!changes["config"] || !this.config)
636
+ return;
637
+ const newSlidesRef = this.config.slides ?? null;
638
+ if (newSlidesRef !== this.lastSlidesRef) {
639
+ this.lastSlidesRef = newSlidesRef;
640
+ this.engine.destroy();
641
+ this.resolvePlugins(this.config);
642
+ }
643
+ }
644
+ ngOnDestroy() {
645
+ this.destroySlider();
646
+ }
647
+ goTo(index) {
648
+ this.engine.selectSlide(index); // add this line
649
+ //this.slideChange.emit(index);
650
+ }
651
+ destroySlider() {
652
+ this.destroy$.next();
653
+ this.destroy$.complete();
654
+ this.engine.destroy();
655
+ } // destroys plugins and clears store
656
+ onNext() {
657
+ this.engine.next();
658
+ }
659
+ onPrevious() {
660
+ this.engine.previous();
661
+ }
662
+ selectSlide(index) {
663
+ this.engine.selectSlide(index);
664
+ }
665
+ onRecalculate() {
666
+ this.engine.recalculate();
667
+ }
668
+ // Navigation
669
+ /** Expose navigation observables safely */
670
+ get canPrev$() {
671
+ if (!this.navigation) {
672
+ console.warn("Navigation plugin is not enabled.");
673
+ return null;
674
+ }
675
+ return this.navigation.canPrev$;
676
+ }
677
+ get canNext$() {
678
+ if (!this.navigation) {
679
+ console.warn("Navigation plugin is not enabled.");
680
+ return null;
681
+ }
682
+ return this.navigation.canNext$;
683
+ }
684
+ /** Expose next/prev methods */
685
+ next() {
686
+ this.navigation?.next();
687
+ }
688
+ prev() {
689
+ this.navigation?.prev();
690
+ }
691
+ /** Expose pagination observable */
692
+ get pager$() {
693
+ if (!this.pagination) {
694
+ console.warn("Pagination plugin is not enabled for this slider.");
695
+ return null;
696
+ }
697
+ return this.pagination.pager$;
698
+ }
699
+ /** Expose a goToSlide method */
700
+ goToSlide(index) {
701
+ this.pagination.goToSlide(index);
702
+ }
703
+ // Handle Pointers
704
+ onPointerDown(event) {
705
+ // Only start drag if not clicking a nav button
706
+ if (event.target.closest(".nav-btn")) {
707
+ return; // ignore
708
+ }
709
+ this.dragStartX = event.clientX;
710
+ this.dragStartY = event.clientY;
711
+ this.isDraggingPointer = false;
712
+ // Do not capture yet; wait until movement exceeds threshold
713
+ this.engine.handleDragStart(event);
714
+ }
715
+ onPointerMove(event) {
716
+ const dx = Math.abs(event.clientX - this.dragStartX);
717
+ const dy = Math.abs(event.clientY - this.dragStartY);
718
+ if (!this.isDraggingPointer && (dx > 5 || dy > 5)) {
719
+ this.isDraggingPointer = true;
720
+ // Now start capturing pointer so dragging works outside the slider bounds
721
+ event.currentTarget.setPointerCapture(event.pointerId);
722
+ }
723
+ if (this.isDraggingPointer) {
724
+ this.engine.handleDragMove(event);
725
+ }
726
+ }
727
+ onPointerUp(event) {
728
+ if (!event)
729
+ return;
730
+ if (!this.isDraggingPointer) {
731
+ const target = event.target;
732
+ // Only select the slide if click is not on an interactive child
733
+ const slideEl = target.closest(".slide");
734
+ if (slideEl && !target.closest("button, video, a")) {
735
+ const indexAttr = slideEl.getAttribute("data-index");
736
+ const index = indexAttr ? parseInt(indexAttr, 10) : null;
737
+ if (index !== null)
738
+ this.engine.selectSlide(index);
739
+ }
740
+ }
741
+ this.engine.handleDragEnd();
742
+ this.isDraggingPointer = false;
743
+ // Release pointer capture if it was captured
744
+ try {
745
+ event.currentTarget.releasePointerCapture(event.pointerId);
746
+ }
747
+ catch { }
748
+ }
749
+ resolvePlugins(config) {
750
+ const runtimePlugins = [];
751
+ const cfg = config.plugins ?? {}; // <-- safe fallback
752
+ if (cfg.draggable)
753
+ runtimePlugins.push(this.draggable);
754
+ if (cfg.pagination)
755
+ runtimePlugins.push(this.pagination);
756
+ if (cfg.navigation)
757
+ runtimePlugins.push(this.navigation);
758
+ if (cfg.autoplay) {
759
+ this.autoplay.setConfig(cfg.autoplay);
760
+ runtimePlugins.push(this.autoplay);
761
+ }
762
+ this.engine.init({ ...config, plugins: undefined }, runtimePlugins);
763
+ if (cfg.navigation) {
764
+ setTimeout(() => this.navigation?.updateArrows?.());
765
+ }
766
+ }
767
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SimpleSliderComponent, deps: [{ token: SliderEngine }, { token: SliderStore }, { token: SliderDraggablePlugin }, { token: SliderPaginationPlugin }, { token: SliderNavigationPlugin }, { token: SliderAutoplayPlugin }], target: i0.ɵɵFactoryTarget.Component });
768
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SimpleSliderComponent, isStandalone: true, selector: "app-simple-slider", inputs: { config: "config", slideTemplate: "slideTemplate", navigationTemplate: "navigationTemplate", paginationTemplate: "paginationTemplate" }, outputs: { slideChange: "slideChange" }, providers: [SliderEngine, SliderStore, SliderDraggablePlugin, SliderPaginationPlugin, SliderNavigationPlugin, SliderAutoplayPlugin], viewQueries: [{ propertyName: "sliderHost", first: true, predicate: ["sliderHost"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container *ngIf=\"state$ | async as state\">\r\n <div\r\n class=\"slider\"\r\n #sliderHost\r\n [class.slider-vertical]=\"state.vertical\"\r\n [class.slider-draggable]=\"state.isDragging\"\r\n [class.slider--main]=\"!config.isThumbs\"\r\n [class.slider--thumbs]=\"config.isThumbs\"\r\n *ngIf=\"state.visibleSlides?.length > 0 && state.isVisible\"\r\n (pointerdown)=\"onPointerDown($event)\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerup)=\"onPointerUp($event)\"\r\n (pointercancel)=\"onPointerUp($event)\">\r\n <div\r\n class=\"slider__wrapper\"\r\n [ngStyle]=\"{\r\n transform: state.translate,\r\n transition: draggable.isDraggingPointer ? 'none' : 'transform 0.3s ease',\r\n display: 'flex',\r\n 'flex-direction': state.vertical ? 'column' : 'row',\r\n 'justify-content': state.visibleSlides?.length < state.slidesPerView ? 'center' : 'flex-start',\r\n gap: (state.slidesPerView > 1 ? state.gap || 0 : 0) + 'px',\r\n }\">\r\n <div\r\n *ngFor=\"let slide of state.visibleSlides; let i = index\"\r\n class=\"slide\"\r\n [attr.data-index]=\"i\"\r\n [class.slide--current]=\"state.selectedSlide === i\"\r\n [style.flex]=\"\r\n !config.isThumbs\r\n ? '0 0 calc(' +\r\n 100 / state.slidesPerView +\r\n '% - ' +\r\n ((state.gap || 0) * (state.slidesPerView - 1)) / state.slidesPerView +\r\n 'px)'\r\n : '0 0 ' + thumbSize + 'px'\r\n \"\r\n [style.width]=\"config.isThumbs ? thumbSize + 'px' : null\"\r\n [style.height]=\"config.isThumbs ? thumbSize + 'px' : null\"\r\n (click)=\"engine.selectSlide(i)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"slideTemplate ? slideTemplate : defaultSlideTemplate; context: { slide: slide, index: i }\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i7.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i7.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i7.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
769
+ }
770
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SimpleSliderComponent, decorators: [{
771
+ type: Component,
772
+ args: [{ selector: "app-simple-slider", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [CommonModule], providers: [SliderEngine, SliderStore, SliderDraggablePlugin, SliderPaginationPlugin, SliderNavigationPlugin, SliderAutoplayPlugin], template: "<ng-container *ngIf=\"state$ | async as state\">\r\n <div\r\n class=\"slider\"\r\n #sliderHost\r\n [class.slider-vertical]=\"state.vertical\"\r\n [class.slider-draggable]=\"state.isDragging\"\r\n [class.slider--main]=\"!config.isThumbs\"\r\n [class.slider--thumbs]=\"config.isThumbs\"\r\n *ngIf=\"state.visibleSlides?.length > 0 && state.isVisible\"\r\n (pointerdown)=\"onPointerDown($event)\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerup)=\"onPointerUp($event)\"\r\n (pointercancel)=\"onPointerUp($event)\">\r\n <div\r\n class=\"slider__wrapper\"\r\n [ngStyle]=\"{\r\n transform: state.translate,\r\n transition: draggable.isDraggingPointer ? 'none' : 'transform 0.3s ease',\r\n display: 'flex',\r\n 'flex-direction': state.vertical ? 'column' : 'row',\r\n 'justify-content': state.visibleSlides?.length < state.slidesPerView ? 'center' : 'flex-start',\r\n gap: (state.slidesPerView > 1 ? state.gap || 0 : 0) + 'px',\r\n }\">\r\n <div\r\n *ngFor=\"let slide of state.visibleSlides; let i = index\"\r\n class=\"slide\"\r\n [attr.data-index]=\"i\"\r\n [class.slide--current]=\"state.selectedSlide === i\"\r\n [style.flex]=\"\r\n !config.isThumbs\r\n ? '0 0 calc(' +\r\n 100 / state.slidesPerView +\r\n '% - ' +\r\n ((state.gap || 0) * (state.slidesPerView - 1)) / state.slidesPerView +\r\n 'px)'\r\n : '0 0 ' + thumbSize + 'px'\r\n \"\r\n [style.width]=\"config.isThumbs ? thumbSize + 'px' : null\"\r\n [style.height]=\"config.isThumbs ? thumbSize + 'px' : null\"\r\n (click)=\"engine.selectSlide(i)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"slideTemplate ? slideTemplate : defaultSlideTemplate; context: { slide: slide, index: i }\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
773
+ }], ctorParameters: () => [{ type: SliderEngine }, { type: SliderStore }, { type: SliderDraggablePlugin }, { type: SliderPaginationPlugin }, { type: SliderNavigationPlugin }, { type: SliderAutoplayPlugin }], propDecorators: { config: [{
774
+ type: Input
775
+ }], slideTemplate: [{
776
+ type: Input
777
+ }], navigationTemplate: [{
778
+ type: Input
779
+ }], paginationTemplate: [{
780
+ type: Input
781
+ }], slideChange: [{
782
+ type: Output
783
+ }], sliderHost: [{
784
+ type: ViewChild,
785
+ args: ["sliderHost", { static: false }]
786
+ }] } });
787
+
788
+ class NgxEdgeSliderModule {
789
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NgxEdgeSliderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
790
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: NgxEdgeSliderModule, imports: [SimpleSliderComponent], exports: [SimpleSliderComponent] });
791
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NgxEdgeSliderModule, imports: [SimpleSliderComponent] });
792
+ }
793
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NgxEdgeSliderModule, decorators: [{
794
+ type: NgModule,
795
+ args: [{
796
+ imports: [SimpleSliderComponent],
797
+ exports: [SimpleSliderComponent],
798
+ }]
799
+ }] });
800
+
801
+ class SafeUrlPipe {
802
+ sanitizer;
803
+ constructor(sanitizer) {
804
+ this.sanitizer = sanitizer;
805
+ }
806
+ transform(url) {
807
+ return this.sanitizer.bypassSecurityTrustResourceUrl(url);
808
+ }
809
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SafeUrlPipe, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Pipe });
810
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: SafeUrlPipe, name: "safeUrl" });
811
+ }
812
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SafeUrlPipe, decorators: [{
813
+ type: Pipe,
814
+ args: [{ name: "safeUrl" }]
815
+ }], ctorParameters: () => [{ type: i1.DomSanitizer }] });
816
+
817
+ // components
818
+
819
+ /**
820
+ * Generated bundle index. Do not edit.
821
+ */
822
+
823
+ export { DEFAULT_CONFIG, INITIAL_SLIDER_STATE, NgxEdgeSliderModule, SafeUrlPipe, SimpleSliderComponent, SliderAutoplayPlugin, SliderDraggablePlugin, SliderEngine, SliderNavigationPlugin, SliderPaginationPlugin, SliderStore };
824
+ //# sourceMappingURL=WingmanColt-ngx-edge-slider.mjs.map