ngx-edge-slider 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,653 @@
1
+ # WingmanColt Angular Slider
2
+
3
+ A modern, fully reactive, plugin‑driven **Angular slider / carousel library** built for **Angular 18+**.
4
+
5
+
6
+ Video :
7
+ https://youtu.be/tGZ5qX70KU0
8
+
9
+ Main Slider (3 rendere sliders on homepage):
10
+ https://obscene.me/
11
+
12
+ Main Slider + Thumb Slider :
13
+ https://obscene.me/shop/product/the-devil
14
+
15
+ WingmanColt is designed for **production‑grade UI systems** where flexibility, performance, and clean architecture matter. It supports **dragging**, **pagination**, **navigation**, **autoplay**, **responsive breakpoints**, and **synced thumbnail sliders**, all powered by a small, predictable core engine.
16
+
17
+ ---
18
+
19
+ ## ✨ Features
20
+
21
+ - ✅ Angular **18+** compatible (Standalone components)
22
+ - ⚡ **RxJS‑driven state** (predictable & debuggable)
23
+ - 🧩 **Plugin architecture** (enable only what you need)
24
+ - 🖱️ Pointer‑based dragging (mouse + touch)
25
+ - 📱 Responsive breakpoints (container‑aware)
26
+ - 🧭 Navigation arrows
27
+ - 🔘 Pagination (dots)
28
+ - ▶️ Autoplay (configurable)
29
+ - 🖼️ **Main + Thumbs slider syncing**
30
+ - 📐 Vertical & horizontal modes
31
+ - 🎯 Click‑to‑select slides
32
+ - ♻️ Safe re‑initialization on data changes
33
+
34
+ ---
35
+
36
+ ## 📦 Installation
37
+
38
+ ```bash
39
+ npm install ngx-edge-slider
40
+ ```
41
+
42
+ > Angular **18 or newer** is required.
43
+
44
+ ---
45
+
46
+ ## 🚀 Quick Start
47
+
48
+ ### 1️⃣ Import the module
49
+
50
+ ```ts
51
+ import { NgxEdgeSliderModule } from "ngx-edge-slider";
52
+
53
+ @Component({
54
+ standalone: true,
55
+ imports: [NgxEdgeSliderModule],
56
+ })
57
+ export class AppComponent {}
58
+ ```
59
+
60
+ Or use as standalone
61
+ ```ts
62
+ @Component({
63
+ selector: 'app-root',
64
+ imports: [CommonModule, NgxEdgeSliderModule],
65
+ templateUrl: './app.html',
66
+ styleUrl: './app.scss',
67
+ encapsulation: ViewEncapsulation.None,
68
+ })
69
+ ```
70
+
71
+
72
+ ---
73
+ ### Import Style
74
+
75
+ ```css
76
+ @import 'ngx-edge-slider/assets/ngx-simple-slider.scss';
77
+ OR
78
+ @import 'ngx-edge-slider/assets/ngx-simple-slider.css';
79
+
80
+ OR USE CDN
81
+
82
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/WingmanColt/ngx-edge-slider@master/projects/ngx-edge-slider/assets/ngx-simple-slider.scss"/>
83
+
84
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/WingmanColt/ngx-edge-slider@master/projects/ngx-edge-slider/assets/ngx-simple-slider.css"/>
85
+
86
+ ```
87
+
88
+
89
+ ---
90
+
91
+ ### 2️⃣ Basic Slider Usage
92
+
93
+ ```html
94
+ <app-simple-slider #MainSlider [config]="sliderConfig" [slideTemplate]="mainSlideTemplate" (slideChange)="onSlideChange($event)"></app-simple-slider>
95
+
96
+ <ng-template #mainSlideTemplate let-slide="slide" let-index="index">
97
+ <div class="slide-content">
98
+ <img [src]="slide.image" alt="Slide {{ index + 1 }}" />
99
+ </div>
100
+ </ng-template>
101
+ ```
102
+
103
+ ```ts
104
+ sliderConfig: SliderConfig = {
105
+ slides: this.slides,
106
+ slidesPerView: 1,
107
+ plugins: {
108
+ draggable: true,
109
+ pagination: true,
110
+ navigation: true,
111
+ },
112
+ };
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 🧱 Slide Templates
118
+
119
+ WingmanColt is **template‑driven**. You fully control slide markup.
120
+
121
+ ```html
122
+ <app-simple-slider [config]="sliderConfig" [slideTemplate]="slideTpl"></app-simple-slider>
123
+
124
+ <ng-template #slideTpl let-slide let-index="index">
125
+ <img [src]="slide.image" />
126
+ </ng-template>
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Full Component with MainSlider, ThumbSlider Pagination, Navigation
132
+
133
+
134
+ ```html
135
+ <div style="padding: 100px">
136
+ <ng-container *ngIf="this.slides.length">
137
+ <div class="slider-container" style="position: relative">
138
+ <app-simple-slider
139
+ #MainSlider
140
+ [config]="sliderConfig"
141
+ class="slider-main-product"
142
+ [slideTemplate]="mainSlideTemplate"
143
+ (slideChange)="onSlideChangeMain($event)"
144
+ >
145
+ </app-simple-slider>
146
+
147
+ <ng-template #mainSlideTemplate let-slide="slide" let-index="index">
148
+ <div class="slide-content">
149
+ <img [src]="slide.image" alt="Slide {{ index + 1 }}" />
150
+ </div>
151
+ </ng-template>
152
+
153
+ <!-- NAVIGATION -->
154
+ <div class="slider-nav" [ngClass]="'nav--' + navPosition">
155
+ <button
156
+ type="button"
157
+ class="nav-btn nav-btn--prev"
158
+ [class.is-hidden]="!(canPrev$ | async)"
159
+ (click)="onPrevClick($event)"
160
+ aria-label="Previous"
161
+ >
162
+ <
163
+ </button>
164
+
165
+ <button
166
+ type="button"
167
+ class="nav-btn nav-btn--next"
168
+ [class.is-hidden]="!(canNext$ | async)"
169
+ (click)="onNextClick($event)"
170
+ aria-label="Next"
171
+ >
172
+ >
173
+ </button>
174
+ </div>
175
+ <ng-container *ngIf="pager$ | async as pager">
176
+ <div class="slider-pagination" *ngIf="pager">
177
+ <div class="thumb-dots-wrapper">
178
+ <div
179
+ *ngFor="let slideIndex of pager.visibleDots; let i = index"
180
+ (click)="goToSlide(slideIndex)"
181
+ >
182
+ <span
183
+ class="thumb-dot"
184
+ [class.active]="slideIndex === pager.visibleDots[pager.activeDotIndex]"
185
+ [class.inactive]="slideIndex !== pager.visibleDots[pager.activeDotIndex]"
186
+ ></span>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </ng-container>
191
+ </div>
192
+ </ng-container>
193
+
194
+ <!-- Thumbs Slider -->
195
+ <ng-container *ngIf="sliderConfigThumbs?.slides?.length">
196
+ <div class="thumbs-wrapper">
197
+ <div
198
+ class="thumb-nav thumb-nav--left"
199
+ [class.activeArrow]="(thumbsState$ | async)?.canPrev"
200
+ (click)="ThumbsSlider?.prev()"
201
+ ></div>
202
+ <app-simple-slider
203
+ #ThumbsSlider
204
+ [config]="sliderConfigThumbs"
205
+ [slideTemplate]="ThumbsSlideTemplate"
206
+ ></app-simple-slider>
207
+
208
+ <ng-template #ThumbsSlideTemplate let-slide="slide" let-index="index">
209
+ <div
210
+ class="slide-content"
211
+ [class.slide--current]="(thumbsState$ | async)?.selectedSlide === index"
212
+ (click)="onThumbClick(index)"
213
+ >
214
+ <img [src]="slide.image" alt="Slide {{ index + 1 }}" />
215
+ </div>
216
+ </ng-template>
217
+
218
+ <div
219
+ class="thumb-nav thumb-nav--right"
220
+ [class.activeArrow]="(thumbsState$ | async)?.canNext"
221
+ (click)="ThumbsSlider?.next()"
222
+ ></div>
223
+ </div>
224
+ </ng-container>
225
+ </div>
226
+
227
+ ```
228
+
229
+ ---
230
+
231
+ ---
232
+
233
+ ## Full TS File
234
+
235
+ ```ts
236
+ import { CommonModule } from '@angular/common';
237
+ import { Component, OnInit, signal, ViewChild, ViewEncapsulation } from '@angular/core';
238
+ import {
239
+ NavPosition,
240
+ NgxEdgeSliderModule,
241
+ Pager,
242
+ SimpleSliderComponent,
243
+ SliderConfig,
244
+ } from 'ngx-edge-slider';
245
+ import { Observable, take } from 'rxjs';
246
+
247
+ @Component({
248
+ selector: 'app-root',
249
+ imports: [CommonModule, NgxEdgeSliderModule],
250
+ templateUrl: './app.html',
251
+ styleUrl: './app.scss',
252
+ encapsulation: ViewEncapsulation.None,
253
+ })
254
+ export class App implements OnInit {
255
+ slidesArray = [
256
+ {
257
+ image: 'https://obscene.me/assets/images/products/27/image-1/image-1.webp',
258
+ caption: 'First Slide',
259
+ },
260
+ {
261
+ image: 'https://obscene.me/assets/images/products/27/image-2/image-2.webp',
262
+ caption: 'Second Slide',
263
+ },
264
+ {
265
+ image: 'https://obscene.me/assets/images/products/27/image-3/image-3.webp',
266
+ caption: 'Third Slide',
267
+ },
268
+ {
269
+ image: 'https://obscene.me/assets/images/products/27/image-4/image-4.webp',
270
+ caption: 'Fourth Slide',
271
+ },
272
+ {
273
+ image: 'https://obscene.me/assets/images/products/27/image-3/image-3.webp',
274
+ caption: 'Third Slide',
275
+ },
276
+ {
277
+ image: 'https://obscene.me/assets/images/products/27/image-1/image-1.webp',
278
+ caption: 'First Slide',
279
+ },
280
+ {
281
+ image: 'https://obscene.me/assets/images/products/27/image-2/image-2.webp',
282
+ caption: 'Second Slide',
283
+ },
284
+ ];
285
+
286
+ private isSyncing = false;
287
+ slides: any[] = [...this.slidesArray];
288
+
289
+ sliderConfig!: SliderConfig;
290
+ sliderConfigThumbs!: SliderConfig;
291
+
292
+ navPosition: NavPosition = 'top-right'; // change this to switch layouts "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "center-sides";
293
+
294
+ @ViewChild('MainSlider') MainSlider!: SimpleSliderComponent;
295
+ @ViewChild('ThumbsSlider') ThumbsSlider?: SimpleSliderComponent;
296
+
297
+ constructor() {}
298
+
299
+ ngOnInit(): void {
300
+ this.sliderConfig = {
301
+ slides: this.slides,
302
+ slidesPerView: 1,
303
+ slidesToSlide: 1,
304
+ isThumbs: false,
305
+ plugins: {
306
+ draggable: true,
307
+ pagination: true,
308
+ navigation: true,
309
+ autoplay: undefined,
310
+ },
311
+ };
312
+
313
+ this.sliderConfigThumbs = {
314
+ slides: this.slides,
315
+ slidesPerView: 4, // desktop default
316
+ slidesToSlide: 1,
317
+ isThumbs: true,
318
+ plugins: {
319
+ pagination: true, // enable pagination
320
+ navigation: true, // optional
321
+ draggable: true, // optional
322
+ autoplay: undefined,
323
+ },
324
+ showOn: { mobile: false, tablet: true, desktop: true },
325
+ breakpoints: {
326
+ mobile: { slidesPerView: 0 }, // not used because showOn.mobile=false
327
+ tablet: { slidesPerView: 4 },
328
+ desktop: { slidesPerView: 5 },
329
+ },
330
+ };
331
+ }
332
+
333
+ /** Sync sliders */
334
+ onSlideChangeMain(index: number) {
335
+ this.syncSliders(index);
336
+ }
337
+ onSlideChangeThumbs(index: number) {
338
+ this.syncSliders(index);
339
+ }
340
+ onThumbClick(index: number) {
341
+ this.syncSliders(index);
342
+ }
343
+
344
+ private syncSliders(index: number) {
345
+ if (this.isSyncing) return;
346
+ this.isSyncing = true;
347
+
348
+ this.MainSlider?.goTo(index);
349
+ this.ThumbsSlider?.goTo(index);
350
+
351
+ this.isSyncing = false;
352
+ }
353
+
354
+ // GET SLIDER STATES
355
+ get mainState$() {
356
+ return this.MainSlider?.state$;
357
+ }
358
+ get thumbsState$() {
359
+ return this.ThumbsSlider?.state$;
360
+ }
361
+
362
+ // NAVIGATION
363
+ get canPrev$(): Observable<boolean> | null {
364
+ if (!this.MainSlider || !this.MainSlider.canPrev$) {
365
+ console.warn('Navigation plugin is not enabled for this slider.');
366
+ return null;
367
+ }
368
+ return this.MainSlider.canPrev$;
369
+ }
370
+
371
+ get canNext$(): Observable<boolean> | null {
372
+ if (!this.MainSlider || !this.MainSlider.canNext$) {
373
+ console.warn('Navigation plugin is not enabled for this slider.');
374
+ return null;
375
+ }
376
+ return this.MainSlider.canNext$;
377
+ }
378
+
379
+ onPrevClick(event?: MouseEvent) {
380
+ event?.stopPropagation();
381
+ event?.preventDefault();
382
+
383
+ this.MainSlider?.canPrev$?.pipe(take(1)).subscribe((can) => {
384
+ if (can) this.MainSlider.prev?.();
385
+ });
386
+ }
387
+
388
+ onNextClick(event?: MouseEvent) {
389
+ event?.stopPropagation();
390
+ event?.preventDefault();
391
+
392
+ this.MainSlider?.canNext$?.pipe(take(1)).subscribe((can) => {
393
+ if (can) this.MainSlider.next?.();
394
+ });
395
+ }
396
+
397
+ // PAGINATION
398
+ get pager$(): Observable<Pager | null> | null {
399
+ if (!this.MainSlider?.pager$) {
400
+ console.warn('Pagination plugin is not enabled for this slider.');
401
+ return null;
402
+ }
403
+ return this.MainSlider.pager$;
404
+ }
405
+
406
+ goToSlide(index: number) {
407
+ this.MainSlider?.goToSlide?.(index);
408
+ }
409
+
410
+ protected readonly title = signal('slider-test');
411
+ }
412
+
413
+ ```
414
+ ----
415
+
416
+ ## 🔧 SliderConfig Reference
417
+
418
+ ```ts
419
+ export interface SliderConfig {
420
+ slides: any[];
421
+ slidesPerView: number;
422
+ slidesToSlide?: number;
423
+ loop?: 0 | 1 | 2;
424
+ vertical?: boolean;
425
+ changeToClickedSlide?: boolean;
426
+ isThumbs?: boolean;
427
+ gap?: number;
428
+
429
+ breakpoints?: {
430
+ mobile?: Partial<SliderConfig>;
431
+ tablet?: Partial<SliderConfig>;
432
+ desktop?: Partial<SliderConfig>;
433
+ };
434
+
435
+ plugins?: {
436
+ draggable?: boolean;
437
+ autoplay?: { delay?: number };
438
+ navigation?: boolean;
439
+ pagination?: boolean;
440
+ };
441
+
442
+ showOn?: {
443
+ mobile?: boolean;
444
+ tablet?: boolean;
445
+ desktop?: boolean;
446
+ };
447
+ }
448
+ ```
449
+
450
+ ---
451
+
452
+ ## 🔌 Plugins
453
+
454
+ Plugins are **opt‑in**. Only enabled plugins are initialized.
455
+
456
+ ### Draggable
457
+
458
+ ```ts
459
+ plugins: {
460
+ draggable: true;
461
+ }
462
+ ```
463
+
464
+ - Mouse + touch dragging
465
+ - Pointer capture outside slider bounds
466
+
467
+ ---
468
+
469
+ ### Navigation
470
+
471
+ ```ts
472
+ plugins: {
473
+ navigation: true;
474
+ }
475
+ ```
476
+
477
+ ```html
478
+ <button (click)="slider.prev()">Prev</button>
479
+ <button (click)="slider.next()">Next</button>
480
+ ```
481
+
482
+ Reactive state:
483
+
484
+ ```ts
485
+ slider.canPrev$;
486
+ slider.canNext$;
487
+ ```
488
+
489
+ ---
490
+
491
+ ### Pagination
492
+
493
+ ```ts
494
+ plugins: {
495
+ pagination: true;
496
+ }
497
+ ```
498
+
499
+ ```ts
500
+ slider.pager$; // Observable<Pager>
501
+ ```
502
+
503
+ Pager structure:
504
+
505
+ ```ts
506
+ interface Pager {
507
+ currentPage: number;
508
+ totalPages: number;
509
+ visibleDots: number[];
510
+ activeDotIndex: number;
511
+ }
512
+ ```
513
+
514
+ ---
515
+
516
+ ### Autoplay
517
+
518
+ ```ts
519
+ plugins: {
520
+ autoplay: {
521
+ delay: 3000;
522
+ }
523
+ }
524
+ ```
525
+
526
+ - Automatically pauses during dragging
527
+ - Resumes safely
528
+
529
+ ---
530
+
531
+ ## 🖼️ Thumbnails Slider (Main + Thumbs)
532
+
533
+ WingmanColt supports **fully synced sliders**.
534
+
535
+ ```html
536
+ <app-simple-slider #MainSlider [config]="mainConfig" (slideChange)="onMainChange($event)"></app-simple-slider>
537
+
538
+ <app-simple-slider #ThumbsSlider [config]="thumbsConfig"></app-simple-slider>
539
+ ```
540
+
541
+ ```ts
542
+ onMainChange(index: number) {
543
+ this.MainSlider.goTo(index);
544
+ this.ThumbsSlider.goTo(index);
545
+ }
546
+ ```
547
+
548
+ Thumbs config example:
549
+
550
+ ```ts
551
+ thumbsConfig = {
552
+ slides,
553
+ slidesPerView: 5,
554
+ isThumbs: true,
555
+ plugins: { draggable: true, navigation: true },
556
+ breakpoints: {
557
+ tablet: { slidesPerView: 4 },
558
+ desktop: { slidesPerView: 5 },
559
+ },
560
+ };
561
+ ```
562
+
563
+ ---
564
+
565
+ ## 📐 Responsive Breakpoints
566
+
567
+ Breakpoints are **container‑aware**, not just viewport‑based.
568
+
569
+ ```ts
570
+ breakpoints: {
571
+ mobile: { slidesPerView: 1 },
572
+ tablet: { slidesPerView: 2 },
573
+ desktop: { slidesPerView: 4 }
574
+ }
575
+ ```
576
+
577
+ Visibility control:
578
+
579
+ ```ts
580
+ showOn: {
581
+ mobile: false,
582
+ tablet: true,
583
+ desktop: true
584
+ }
585
+ ```
586
+
587
+ ---
588
+
589
+ ## 🧠 Architecture Overview
590
+
591
+ - **SliderEngine** – core logic, movement, breakpoints
592
+ - **SliderStore** – RxJS state container
593
+ - **Plugins** – isolated feature modules
594
+ - **SimpleSliderComponent** – UI wrapper
595
+
596
+ This separation allows:
597
+
598
+ - Easy feature expansion
599
+ - Predictable state transitions
600
+ - Minimal DOM coupling
601
+
602
+ ---
603
+
604
+ ## ♻️ Lifecycle & Reinitialization
605
+
606
+ The slider safely re‑initializes when:
607
+
608
+ - Slides array reference changes
609
+ - Breakpoints change
610
+ - Container size changes
611
+
612
+ ```ts
613
+ this.engine.destroy();
614
+ this.engine.init(newConfig);
615
+ ```
616
+
617
+ ---
618
+
619
+ ## 🛠️ Requirements
620
+
621
+ - Angular **18+**
622
+ - RxJS **7+**
623
+ - Browser support for `ResizeObserver`
624
+
625
+ ---
626
+
627
+ ## 🧪 Status
628
+
629
+ WingmanColt is **production‑ready**, actively evolving, and designed for real‑world applications.
630
+
631
+ Planned enhancements:
632
+
633
+ - ⏩ Loop modes
634
+ - 🎞️ Animation presets
635
+ - ♿ Accessibility helpers
636
+ - 🔄 Virtual slides
637
+
638
+ ---
639
+
640
+ ## 📄 License
641
+
642
+ MIT
643
+
644
+ ---
645
+
646
+ ## 👤 Author
647
+
648
+ **WingmanColt**
649
+ Angular & Full‑Stack Engineer
650
+
651
+ ---
652
+
653
+ If you find this library useful, ⭐️ the repository and feel free to contribute.
@@ -0,0 +1,413 @@
1
+ :host {
2
+ display: block;
3
+ position: relative;
4
+ }
5
+
6
+ /* ---------------- Main Slider ---------------- */
7
+ .slider-main-product {
8
+ display: inline-flex;
9
+ height: 520px;
10
+ }
11
+
12
+ .slider-main {
13
+ width: 100%;
14
+ height: 600px;
15
+ justify-content: center;
16
+ display: flex;
17
+ }
18
+
19
+ @media (max-width: 577px) {
20
+ .slider-main {
21
+ height: 100%;
22
+ display: block;
23
+ }
24
+ }
25
+ .slider--main {
26
+ height: 100%;
27
+ overflow: hidden;
28
+ }
29
+ .slider--main .slider__wrapper {
30
+ display: flex;
31
+ will-change: transform;
32
+ }
33
+ .slider--main .slider__wrapper .slide {
34
+ flex: 0 0 100%;
35
+ }
36
+ .slider--main .slider__wrapper .slide .slide-content,
37
+ .slider--main .slider__wrapper .slide picture {
38
+ position: relative;
39
+ width: 100%;
40
+ height: 100%;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+ .slider--main .slider__wrapper .slide .slide-content .drag-handle,
46
+ .slider--main .slider__wrapper .slide picture .drag-handle {
47
+ width: 100%;
48
+ height: 100%;
49
+ cursor: grab;
50
+ z-index: 10;
51
+ pointer-events: all;
52
+ }
53
+ .slider--main .slider__wrapper .slide .slide-content img,
54
+ .slider--main .slider__wrapper .slide picture img {
55
+ max-width: 100%;
56
+ max-height: 100%;
57
+ object-fit: contain;
58
+ display: block;
59
+ transform-origin: center;
60
+ transform: translateZ(0);
61
+ pointer-events: none;
62
+ }
63
+
64
+ /* ---------------- Thumbs Slider ---------------- */
65
+ .slider--thumbs {
66
+ margin-top: 24px;
67
+ margin-bottom: 36px;
68
+ height: 70px !important;
69
+ }
70
+ .slider--thumbs .slider__wrapper {
71
+ display: flex;
72
+ gap: 8px;
73
+ will-change: transform;
74
+ transform: translateZ(0);
75
+ }
76
+ .slider--thumbs .slide {
77
+ flex: 0 0 auto;
78
+ pointer-events: auto;
79
+ }
80
+ .slider--thumbs .slide .slide-content {
81
+ width: 54px;
82
+ height: 68px;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ border: 1px solid #ebebeb;
87
+ cursor: pointer;
88
+ }
89
+ .slider--thumbs .slide .slide-content img {
90
+ max-width: 100%;
91
+ max-height: 100%;
92
+ object-fit: contain;
93
+ display: block;
94
+ transform: scale(0.9);
95
+ }
96
+ .slider--thumbs .slide .slide-content.slide--current {
97
+ border-color: #212121;
98
+ }
99
+
100
+ /* ---------------- Main Slider Container ---------------- */
101
+ .slider {
102
+ width: 100%;
103
+ height: 100%;
104
+ overflow: hidden;
105
+ position: relative;
106
+ touch-action: none;
107
+ cursor: grab;
108
+ user-select: none;
109
+ -webkit-user-drag: none;
110
+ -webkit-tap-highlight-color: transparent;
111
+ }
112
+ .slider.slider-can-drag {
113
+ cursor: grab;
114
+ }
115
+ .slider.slider-is-dragging {
116
+ cursor: grabbing;
117
+ }
118
+ .slider.slider-dragged {
119
+ /* optional, e.g., subtle shadow after drag */
120
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
121
+ }
122
+ .slider.slider-drag-prevent-click {
123
+ pointer-events: none; /* prevent accidental clicks while dragging */
124
+ }
125
+ .slider .slider__wrapper {
126
+ display: flex;
127
+ transition: transform 300ms cubic-bezier(0.25, 0.46, 0.45, 0.84);
128
+ will-change: transform;
129
+ transform: none;
130
+ width: auto;
131
+ height: 100%;
132
+ }
133
+ .slider .slide {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ height: 100%;
138
+ overflow: hidden;
139
+ flex-shrink: 0;
140
+ position: relative;
141
+ transition: transform 300ms cubic-bezier(0.25, 0.46, 0.45, 0.84);
142
+ }
143
+
144
+ @media (min-width: 1441px) {
145
+ .slider--thumbs .slide {
146
+ flex: 0 0 20%;
147
+ }
148
+ }
149
+ @media (max-width: 1024px) {
150
+ .slider--thumbs .slide {
151
+ flex: 0 0 33.333%;
152
+ }
153
+ }
154
+ @media (max-width: 1023px) {
155
+ .slider--thumbs .slide {
156
+ flex: 0 0 50%;
157
+ }
158
+ }
159
+ /* ---------------- Optional Arrows ---------------- */
160
+ .slider-arrow-next,
161
+ .slider-arrow-prev {
162
+ position: absolute;
163
+ top: 50%;
164
+ transform: translateY(-50%);
165
+ width: 32px;
166
+ height: 32px;
167
+ background: rgba(255, 255, 255, 0.8) no-repeat center center;
168
+ cursor: pointer;
169
+ z-index: 10;
170
+ }
171
+ .slider-arrow-next.next,
172
+ .slider-arrow-prev.next {
173
+ right: 10px;
174
+ }
175
+ .slider-arrow-next.prev,
176
+ .slider-arrow-prev.prev {
177
+ left: 10px;
178
+ }
179
+
180
+ /* ---------------- Arrows L/R on Thumbs ---------------- */
181
+ .thumbs-wrapper {
182
+ position: relative;
183
+ }
184
+
185
+ .thumb-nav {
186
+ position: absolute;
187
+ top: 50%;
188
+ transform: translateY(-50%);
189
+ z-index: 20;
190
+ width: 48px;
191
+ height: 48px;
192
+ background: rgba(255, 255, 255, 0.9)
193
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='4.51 0.01 10.99 19.97'%3E%3Cpath fill='%23000' d='m4.508 18.968 1.006 1.006 9.983-9.984L5.514.005 4.508 1.011l8.984 8.984-8.984 8.973Z'/%3E%3C/svg%3E")
194
+ no-repeat center center/11px 18px;
195
+ font-size: 36px;
196
+ line-height: 56px;
197
+ opacity: 0;
198
+ pointer-events: none;
199
+ transition: opacity 200ms ease;
200
+ }
201
+ .thumb-nav.activeArrow {
202
+ opacity: 1;
203
+ pointer-events: all;
204
+ }
205
+ .thumb-nav--left {
206
+ left: -73px;
207
+ transform: translateY(-50%) rotate(180deg) !important;
208
+ }
209
+ .thumb-nav--right {
210
+ right: -65px;
211
+ }
212
+
213
+ .slider-pagination .thumb-dots-wrapper {
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ gap: 7px;
218
+ }
219
+ .slider-pagination .thumb-dots-wrapper .thumb-dot {
220
+ display: inline-block;
221
+ width: 4px;
222
+ height: 4px;
223
+ margin: 0 0 0 8px;
224
+ border-radius: 50%;
225
+ background: #b3b3b3;
226
+ }
227
+ .slider-pagination .thumb-dots-wrapper .thumb-dot.active {
228
+ background: #212121;
229
+ border: 1px solid #212121;
230
+ }
231
+ .slider-pagination .thumb-dots-wrapper .thumb-dot:hover {
232
+ background: #212121;
233
+ border: 1px solid #212121;
234
+ }
235
+ .slider-pagination .thumb-dots-wrapper .thumb-dot.hidden {
236
+ opacity: 0;
237
+ pointer-events: none;
238
+ }
239
+
240
+ .slider-container {
241
+ position: relative;
242
+ z-index: 10;
243
+ }
244
+ .slider-container .slider {
245
+ position: relative;
246
+ z-index: 1;
247
+ }
248
+ .slider-container .slider.slider-is-dragging {
249
+ pointer-events: none;
250
+ }
251
+ .slider-container .slider-nav {
252
+ position: absolute;
253
+ inset: 0;
254
+ pointer-events: none;
255
+ z-index: 20;
256
+ }
257
+ .slider-container .slider-nav .nav-btn {
258
+ pointer-events: auto;
259
+ z-index: 30;
260
+ position: absolute;
261
+ width: 42px;
262
+ height: 42px;
263
+ border-radius: 2px;
264
+ display: inline-flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ border: 0;
268
+ cursor: pointer;
269
+ background: transparent;
270
+ font-size: 32px;
271
+ line-height: 1;
272
+ transition:
273
+ transform 0.15s ease,
274
+ opacity 0.15s ease;
275
+ }
276
+ .slider-container .slider-nav .nav-btn:hover {
277
+ transform: translateY(-1px);
278
+ background: rgba(0, 0, 0, 0.6);
279
+ }
280
+ .slider-container .slider-nav .nav-btn.is-hidden {
281
+ color: rgba(160, 160, 165, 0.5529411765);
282
+ opacity: 0.3;
283
+ cursor: not-allowed;
284
+ pointer-events: auto;
285
+ }
286
+ .slider-container .slider-nav.nav--top-left .nav-btn--prev {
287
+ top: -50px;
288
+ left: -20px;
289
+ }
290
+ .slider-container .slider-nav.nav--top-left .nav-btn--next {
291
+ top: -50px;
292
+ left: 42px;
293
+ }
294
+ .slider-container .slider-nav.nav--top-center .nav-btn--prev {
295
+ top: -50px;
296
+ left: 50%;
297
+ transform: translateX(-50px);
298
+ }
299
+ .slider-container .slider-nav.nav--top-center .nav-btn--next {
300
+ top: -50px;
301
+ left: 50%;
302
+ transform: translateX(10px);
303
+ }
304
+ .slider-container .slider-nav.nav--top-right .nav-btn--prev {
305
+ top: -50px;
306
+ right: 42px;
307
+ }
308
+ .slider-container .slider-nav.nav--top-right .nav-btn--next {
309
+ top: -50px;
310
+ right: -20px;
311
+ }
312
+ .slider-container .slider-nav.nav--bottom-left .nav-btn--prev {
313
+ bottom: 30px;
314
+ left: -20px;
315
+ }
316
+ .slider-container .slider-nav.nav--bottom-left .nav-btn--next {
317
+ bottom: 30px;
318
+ left: 42px;
319
+ }
320
+ .slider-container .slider-nav.nav--bottom-center .nav-btn--prev {
321
+ bottom: 30px;
322
+ left: 50%;
323
+ transform: translateX(-50px);
324
+ }
325
+ .slider-container .slider-nav.nav--bottom-center .nav-btn--next {
326
+ bottom: 30px;
327
+ left: 50%;
328
+ transform: translateX(10px);
329
+ }
330
+ .slider-container .slider-nav.nav--bottom-right .nav-btn--prev {
331
+ bottom: 30px;
332
+ right: 42px;
333
+ }
334
+ .slider-container .slider-nav.nav--bottom-right .nav-btn--next {
335
+ bottom: 30px;
336
+ right: -20px;
337
+ }
338
+ .slider-container .slider-nav.nav--center-sides .nav-btn {
339
+ background: rgba(0, 0, 0, 0.7);
340
+ color: white;
341
+ }
342
+ .slider-container .slider-nav.nav--center-sides .nav-btn--prev {
343
+ top: 50%;
344
+ left: -50px;
345
+ transform: translateY(-50%);
346
+ }
347
+ .slider-container .slider-nav.nav--center-sides .nav-btn--next {
348
+ top: 50%;
349
+ right: -50px;
350
+ transform: translateY(-50%);
351
+ }
352
+ @media (max-width: 1024px) {
353
+ .slider-container .slider-nav .nav--top-left .nav-btn--prev,
354
+ .slider-container .slider-nav .nav--top-center .nav-btn--prev,
355
+ .slider-container .slider-nav .nav--top-right .nav-btn--prev,
356
+ .slider-container .slider-nav .nav--bottom-left .nav-btn--prev,
357
+ .slider-container .slider-nav .nav--bottom-center .nav-btn--prev,
358
+ .slider-container .slider-nav .nav--bottom-right .nav-btn--prev,
359
+ .slider-container .slider-nav .nav--center-sides .nav-btn--prev {
360
+ left: clamp(8px, 5%, 40px);
361
+ right: auto;
362
+ }
363
+ .slider-container .slider-nav .nav--top-left .nav-btn--next,
364
+ .slider-container .slider-nav .nav--top-center .nav-btn--next,
365
+ .slider-container .slider-nav .nav--top-right .nav-btn--next,
366
+ .slider-container .slider-nav .nav--bottom-left .nav-btn--next,
367
+ .slider-container .slider-nav .nav--bottom-center .nav-btn--next,
368
+ .slider-container .slider-nav .nav--bottom-right .nav-btn--next,
369
+ .slider-container .slider-nav .nav--center-sides .nav-btn--next {
370
+ right: clamp(8px, 5%, 20px);
371
+ left: auto;
372
+ }
373
+ }
374
+ @media (max-width: 768px) {
375
+ .slider-container .slider-nav .nav-btn {
376
+ width: 36px;
377
+ height: 36px;
378
+ font-size: 24px;
379
+ }
380
+ }
381
+ @media (max-width: 480px) {
382
+ .slider-container .slider-nav .slider-container .slider-nav.nav--center-sides {
383
+ pointer-events: auto;
384
+ }
385
+ .slider-container .slider-nav .slider-container .slider-nav.nav--center-sides .nav-btn {
386
+ width: 32px;
387
+ height: 32px;
388
+ font-size: 20px;
389
+ pointer-events: auto;
390
+ z-index: 20;
391
+ background: rgba(0, 0, 0, 0.7);
392
+ color: white;
393
+ }
394
+ .slider-container .slider-nav .slider-container .slider__wrapper {
395
+ position: relative;
396
+ }
397
+ .slider-container .slider-nav .slider-container .slider__wrapper .slide-content {
398
+ pointer-events: auto;
399
+ }
400
+ .slider-container .slider-nav .nav-btn {
401
+ width: 32px;
402
+ height: 32px;
403
+ font-size: 20px;
404
+ }
405
+ .slider-container .slider-nav .nav-btn--prev {
406
+ left: 8px !important;
407
+ right: auto !important;
408
+ }
409
+ .slider-container .slider-nav .nav-btn--next {
410
+ right: 8px !important;
411
+ left: auto !important;
412
+ }
413
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-edge-slider",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=18.0.0",
6
6
  "@angular/core": ">=18.0.0",
@@ -23,4 +23,4 @@
23
23
  "default": "./fesm2022/ngx-edge-slider.mjs"
24
24
  }
25
25
  }
26
- }
26
+ }