ngx-edge-slider 1.0.0
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/ngx-edge-slider-workspace/.editorconfig +16 -0
- package/ngx-edge-slider-workspace/.vscode/extensions.json +4 -0
- package/ngx-edge-slider-workspace/.vscode/launch.json +20 -0
- package/ngx-edge-slider-workspace/.vscode/tasks.json +42 -0
- package/ngx-edge-slider-workspace/README.md +27 -0
- package/ngx-edge-slider-workspace/angular.json +140 -0
- package/ngx-edge-slider-workspace/package-lock.json +13339 -0
- package/ngx-edge-slider-workspace/package.json +39 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/README.md +24 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/ng-package.json +7 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/package.json +12 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/assets/icons/arrow-black.svg +3 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/assets/icons/arrow-gray.svg +3 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.component.html +78 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.component.scss +344 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.component.ts +610 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.interface.ts +34 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.module.ts +18 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.service.ts +9 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/public-api.ts +7 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/tsconfig.lib.json +14 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/tsconfig.lib.prod.json +10 -0
- package/ngx-edge-slider-workspace/projects/ngx-edge-slider/tsconfig.spec.json +14 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/app/app.component.html +11 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/app/app.component.scss +0 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/app/app.component.spec.ts +27 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/app/app.component.ts +57 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/app/app.module.ts +18 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/assets/.gitkeep +0 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/assets/slide2_desktop_1950x.webp +0 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/favicon.ico +0 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/index.html +13 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/main.ts +7 -0
- package/ngx-edge-slider-workspace/projects/test-app/src/styles.scss +1 -0
- package/ngx-edge-slider-workspace/projects/test-app/tsconfig.app.json +14 -0
- package/ngx-edge-slider-workspace/projects/test-app/tsconfig.spec.json +14 -0
- package/ngx-edge-slider-workspace/tsconfig.json +39 -0
- package/package.json +24 -0
package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.component.ts
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import { Component, Input, OnInit, OnDestroy, ChangeDetectionStrategy, EventEmitter, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { SlideBreakpointConfig, SlideConfig } from './ngx-edge-slider.interface';
|
|
3
|
+
import { throttle } from 'lodash';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'lib-ngx-edge-slider',
|
|
7
|
+
templateUrl: './ngx-edge-slider.component.html',
|
|
8
|
+
styleUrls: ['./ngx-edge-slider.component.scss'],
|
|
9
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10
|
+
encapsulation: ViewEncapsulation.Emulated, // Disable view encapsulation
|
|
11
|
+
})
|
|
12
|
+
export class NgxEdgeSliderComponent implements OnInit, OnDestroy {
|
|
13
|
+
@Input() slideTemplate!: TemplateRef<any>;
|
|
14
|
+
@Input() config: Partial<SlideConfig> = {};
|
|
15
|
+
|
|
16
|
+
@Output() onSlideChange = new EventEmitter<number>();
|
|
17
|
+
@Output() onSlideClick = new EventEmitter<number>();
|
|
18
|
+
|
|
19
|
+
currentSlide: number = 0;
|
|
20
|
+
selectedSlide: number = -1;
|
|
21
|
+
slideChanging: boolean = false;
|
|
22
|
+
|
|
23
|
+
slidesPerViewAdjusted = 1;
|
|
24
|
+
visibleSlides: any[] = [];
|
|
25
|
+
|
|
26
|
+
currentPage: number = 1; // Starting page
|
|
27
|
+
visibleDots: number[] = []; // Array holding visible dots (page numbers)
|
|
28
|
+
pager: any;
|
|
29
|
+
|
|
30
|
+
translateStyle = 'translateX(0)'; // CSS transform for translation
|
|
31
|
+
shouldTranslate = false; // Flag to control translation
|
|
32
|
+
isMobile: boolean = false;
|
|
33
|
+
|
|
34
|
+
private isForwardNavigation = true;
|
|
35
|
+
private autoPlayInterval: number | null = null;
|
|
36
|
+
private debounceTimer: any | null = null;
|
|
37
|
+
private clickBlocked = false;
|
|
38
|
+
|
|
39
|
+
// Declare the clicked anchor element and whether dragging is in progress
|
|
40
|
+
public isDragStarted = false;
|
|
41
|
+
private isDragging = false; // Flag to track dragging state
|
|
42
|
+
private startX: number = 0;
|
|
43
|
+
private startY: number = 0;
|
|
44
|
+
|
|
45
|
+
private isVerticalScrolling = false; // Flag to track vertical scrolling
|
|
46
|
+
private maxVerticalDeviation = 20;
|
|
47
|
+
private minSwipeDistance = 10;
|
|
48
|
+
private largeSwipeThreshold: number = 50; // This sets the minimum distance (in pixels) for a swipe to be considered "large." If the swipe distance (deltaX) is greater than this threshold, the default slide step (e.g., slidesToSlide or slidesPerViewAdjusted) will be used. If it's less, only one slide is moved.
|
|
49
|
+
|
|
50
|
+
private onMoveThrottled: any;
|
|
51
|
+
private handleResize = throttle(() => {
|
|
52
|
+
this.updateSlider();
|
|
53
|
+
}, 100);
|
|
54
|
+
|
|
55
|
+
ngOnInit() {
|
|
56
|
+
this.sliderInit();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ngOnDestroy() {
|
|
60
|
+
this.sliderDestroy();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
sliderInit() {
|
|
64
|
+
// Check if slides are provided; if not, exit early
|
|
65
|
+
if (!this.config.slides || this.config.slides.length <= 0) {
|
|
66
|
+
console.warn('EdgeSlider: No slides provided. Initialization aborted.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.onMoveThrottled = throttle(this.onMove.bind(this), 20);
|
|
71
|
+
this.config.slidesToSlide = this.config.slidesToSlide ?? this.slidesPerViewAdjusted;
|
|
72
|
+
|
|
73
|
+
// If the slides array is empty or only has one or two slides, disable looping
|
|
74
|
+
if (this.config.slides?.length <= 2) {
|
|
75
|
+
this.config.loop = 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.config = { ...this.config }; // Copy config for local use
|
|
79
|
+
this.updateSlider();
|
|
80
|
+
|
|
81
|
+
if (this.config.autoPlay) {
|
|
82
|
+
this.startAutoPlay();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Initialize slidesPerView based on screen size
|
|
86
|
+
window.addEventListener('resize', this.handleResize);
|
|
87
|
+
}
|
|
88
|
+
private calculateSlidesPerView(): void {
|
|
89
|
+
this.shouldTranslate = this.config.slides.length > this.slidesPerViewAdjusted;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updateTranslateStyle() {
|
|
93
|
+
// if(this.currentSlide >= (this.config.slides.length - 3))
|
|
94
|
+
// return this.translateStyle = 'none';
|
|
95
|
+
//else {
|
|
96
|
+
return this.translateStyle = this.shouldTranslate
|
|
97
|
+
? `translate${this.config.vertical ? 'Y' : 'X'}(-${(this.currentSlide * 100) / this.slidesPerViewAdjusted}%)`
|
|
98
|
+
: 'none';
|
|
99
|
+
//}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private updateConfigForBreakpoint(): void {
|
|
103
|
+
const width = window.innerWidth;
|
|
104
|
+
const breakpoints = this.config.breakpoints;
|
|
105
|
+
|
|
106
|
+
// Determine which breakpoint to apply based on width
|
|
107
|
+
let breakpointConfig: SlideBreakpointConfig | undefined;
|
|
108
|
+
if (width < 768) {
|
|
109
|
+
breakpointConfig = breakpoints?.mobile;
|
|
110
|
+
this.isMobile = true;
|
|
111
|
+
} else if (width >= 768 && width < 1024) {
|
|
112
|
+
breakpointConfig = breakpoints?.tablet;
|
|
113
|
+
this.isMobile = false;
|
|
114
|
+
} else if (width >= 1024) {
|
|
115
|
+
breakpointConfig = breakpoints?.desktop;
|
|
116
|
+
this.isMobile = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Merge the selected breakpoint configuration into `this.config`
|
|
120
|
+
// Use spread syntax to create a new configuration while keeping defaults
|
|
121
|
+
this.config = new SlideConfig({
|
|
122
|
+
...this.config, // Base config
|
|
123
|
+
...breakpointConfig // Override with breakpoint-specific settings if any
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Adjust slidesPerView and any other property for internal use
|
|
127
|
+
this.slidesPerViewAdjusted = this.config.slidesPerView ?? 1;
|
|
128
|
+
//this.updateVisibleSlides();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
private updateSlider(): void {
|
|
133
|
+
if (!this.config.slides || this.config.slides.length <= 0) {
|
|
134
|
+
console.warn('EdgeSlider: No slides provided. Initialization aborted.');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.updateConfigForBreakpoint(); // Apply initial configuration
|
|
139
|
+
this.updateVisibleSlides();
|
|
140
|
+
this.calculateSlidesPerView();
|
|
141
|
+
this.pager = this.getPaginationPager(this.config.slides.length, this.currentPage, this.slidesPerViewAdjusted);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private startAutoPlay(): void {
|
|
145
|
+
if (!this.config.slides || this.config.slides.length <= 1)
|
|
146
|
+
return;
|
|
147
|
+
|
|
148
|
+
const play = () => {
|
|
149
|
+
this.onNextClick();
|
|
150
|
+
this.autoPlayInterval = requestAnimationFrame(play);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this.autoPlayInterval = requestAnimationFrame(play);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private stopAutoPlay(): void {
|
|
157
|
+
if (this.autoPlayInterval) {
|
|
158
|
+
cancelAnimationFrame(this.autoPlayInterval);
|
|
159
|
+
this.autoPlayInterval = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
private calculateSlideStep(currentIndex: number, isSwipe: boolean, deltaX?: number): number {
|
|
163
|
+
const slidesToSlide = this.config.slidesToSlide || 0;
|
|
164
|
+
const totalSlides = this.config.slides?.length;
|
|
165
|
+
// Use a default slide step for button clicks or large swipes
|
|
166
|
+
if (!isSwipe || Math.abs(deltaX) > this.largeSwipeThreshold) {
|
|
167
|
+
if (slidesToSlide === 0 || slidesToSlide >= this.slidesPerViewAdjusted) {
|
|
168
|
+
return this.slidesPerViewAdjusted;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const slidesLeft = totalSlides - currentIndex - this.slidesPerViewAdjusted;
|
|
172
|
+
return slidesLeft < slidesToSlide ? slidesLeft : slidesToSlide;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// For small swipes, only move by 1 slide
|
|
176
|
+
return totalSlides > 1 ? this.config.slidesToSlide : 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
onNextClick(slideStep?: number, targetIndex?: number): void {
|
|
180
|
+
if (!this.config.slides || this.config.slides.length <= 1 || this.clickBlocked)
|
|
181
|
+
return;
|
|
182
|
+
|
|
183
|
+
this.isForwardNavigation = true;
|
|
184
|
+
this.slideChanging = true;
|
|
185
|
+
|
|
186
|
+
// If a targetIndex is provided, navigate directly to that index.
|
|
187
|
+
if (targetIndex !== undefined) {
|
|
188
|
+
this.currentSlide = targetIndex;
|
|
189
|
+
} else {
|
|
190
|
+
slideStep = slideStep ?? this.calculateSlideStep(this.currentSlide, false);
|
|
191
|
+
|
|
192
|
+
if (this.config.loop === 2) { // Infinite loop
|
|
193
|
+
this.currentSlide = (this.currentSlide + slideStep) % this.config.slides.length;
|
|
194
|
+
} else if (this.config.loop === 1) { // Loop back to start
|
|
195
|
+
const remainingSlides = this.config.slides.length - this.slidesPerViewAdjusted;
|
|
196
|
+
this.currentSlide = (this.currentSlide + slideStep) > remainingSlides ? 0 : this.currentSlide + slideStep;
|
|
197
|
+
} else { // No loop
|
|
198
|
+
const remainingSlides = this.config.slides.length - this.slidesPerViewAdjusted;
|
|
199
|
+
this.currentSlide = Math.min(remainingSlides, this.currentSlide + slideStep);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.emitSlideChange();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
onPreviousClick(slideStep?: number, targetIndex?: number): void {
|
|
207
|
+
if (!this.config.slides || this.config.slides.length <= 1 || this.clickBlocked)
|
|
208
|
+
return;
|
|
209
|
+
|
|
210
|
+
this.isForwardNavigation = false;
|
|
211
|
+
this.slideChanging = true;
|
|
212
|
+
|
|
213
|
+
// If a targetIndex is provided, navigate directly to that index.
|
|
214
|
+
if (targetIndex !== undefined) {
|
|
215
|
+
this.currentSlide = targetIndex;
|
|
216
|
+
} else {
|
|
217
|
+
slideStep = slideStep ?? this.calculateSlideStep(this.currentSlide, false);
|
|
218
|
+
if (this.config.loop === 2) { // Infinite loop
|
|
219
|
+
this.currentSlide = (this.currentSlide - slideStep + this.config.slides.length) % this.config.slides.length;
|
|
220
|
+
} else if (this.config.loop === 1) { // Loop back to end
|
|
221
|
+
this.currentSlide = (this.currentSlide - slideStep) < 0 ? this.config.slides.length - this.slidesPerViewAdjusted : this.currentSlide - slideStep;
|
|
222
|
+
} else { // No loop
|
|
223
|
+
this.currentSlide = slideStep > 0 ? Math.max(0, this.currentSlide - slideStep) : 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.emitSlideChange();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private blockClicks(): void {
|
|
231
|
+
this.clickBlocked = true;
|
|
232
|
+
clearTimeout(this.debounceTimer);
|
|
233
|
+
this.debounceTimer = setTimeout(() => {
|
|
234
|
+
this.clickBlocked = false;
|
|
235
|
+
}, this.config.slideChangeDelay || 500); // Adjust delay as needed
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private updateVisibleSlides(): void {
|
|
239
|
+
if (!this.isForwardNavigation) return; // Only update if navigating forward
|
|
240
|
+
|
|
241
|
+
const startIndex = this.currentSlide;
|
|
242
|
+
const endIndex = Math.min(this.config.slides.length, startIndex + this.slidesPerViewAdjusted);
|
|
243
|
+
|
|
244
|
+
// Slice the range of slides for this "page"
|
|
245
|
+
this.visibleSlides = this.config.slides.slice(startIndex, endIndex);
|
|
246
|
+
|
|
247
|
+
if (this.config.loop > 0 && endIndex - startIndex < this.slidesPerViewAdjusted) {
|
|
248
|
+
const remainingSlides = this.slidesPerViewAdjusted - (endIndex - startIndex);
|
|
249
|
+
this.visibleSlides = this.visibleSlides.concat(this.config.slides.slice(0, remainingSlides));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
handleOnSlideClick(index: number): void {
|
|
255
|
+
if(!this.config.changeToClickedSlide)
|
|
256
|
+
return;
|
|
257
|
+
|
|
258
|
+
if (index < 0 || index >= this.config.slides.length) {
|
|
259
|
+
console.warn(`Invalid index ${index}. It should be between 0 and ${this.config.slides.length - 1}.`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if(index > -1 && index == this.selectedSlide) {
|
|
264
|
+
this.selectedSlide = -1;
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check if the clicked index is forward or backward compared to the current slide.
|
|
269
|
+
if (index > this.selectedSlide) {
|
|
270
|
+
// Clicked on a later slide (move forward)
|
|
271
|
+
this.onNextClick(undefined, index);
|
|
272
|
+
this.selectedSlide = index;
|
|
273
|
+
} else if (index < this.selectedSlide) {
|
|
274
|
+
// Clicked on an earlier slide (move backward)
|
|
275
|
+
this.onPreviousClick(undefined, index);
|
|
276
|
+
this.selectedSlide = index;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
this.onSlideClick.emit(index);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
private emitSlideChange(): void {
|
|
284
|
+
if(this.isForwardNavigation)
|
|
285
|
+
this.paginationNextPage();
|
|
286
|
+
else
|
|
287
|
+
this.paginationPreviousPage();
|
|
288
|
+
|
|
289
|
+
this.updateVisibleSlides();
|
|
290
|
+
this.blockClicks();
|
|
291
|
+
this.updateTranslateStyle();
|
|
292
|
+
|
|
293
|
+
//setTimeout(()=> { this.slideChanging = false; }, 1000); // anim duration time
|
|
294
|
+
this.slideChanging = false;
|
|
295
|
+
this.onSlideChange.emit(this.currentSlide);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private getPaginationPager(totalSlides: number, currentPage: number = 1, slidesPerPage: number = 5) {
|
|
299
|
+
// Calculate total pages based on totalSlides and slidesPerPage
|
|
300
|
+
let totalPages = Math.ceil(totalSlides / slidesPerPage);
|
|
301
|
+
|
|
302
|
+
// Paginate Range: Show at most 4 dots at a time
|
|
303
|
+
let paginateRange = 4;
|
|
304
|
+
|
|
305
|
+
// Ensure currentPage isn't out of range
|
|
306
|
+
if (currentPage < 1) {
|
|
307
|
+
currentPage = 1;
|
|
308
|
+
} else if (currentPage > totalPages) {
|
|
309
|
+
currentPage = totalPages;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let startDot: number, endDot: number;
|
|
313
|
+
|
|
314
|
+
// When there are fewer than 4 pages, show all dots
|
|
315
|
+
if (totalPages <= paginateRange) {
|
|
316
|
+
startDot = 1;
|
|
317
|
+
endDot = totalPages;
|
|
318
|
+
} else if (currentPage <= paginateRange - 2) {
|
|
319
|
+
// First page group (showing the first 4 dots)
|
|
320
|
+
startDot = 1;
|
|
321
|
+
endDot = paginateRange;
|
|
322
|
+
} else if (currentPage >= totalPages - 1) {
|
|
323
|
+
// Last page group (show the last 4 dots)
|
|
324
|
+
startDot = totalPages - paginateRange + 1;
|
|
325
|
+
endDot = totalPages;
|
|
326
|
+
} else {
|
|
327
|
+
// Middle pages, showing 4 dots around the currentPage
|
|
328
|
+
startDot = currentPage - 1;
|
|
329
|
+
endDot = currentPage + 2;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Ensure the dots stay within bounds
|
|
333
|
+
startDot = Math.max(startDot, 1); // Ensure the start dot doesn't go below 1
|
|
334
|
+
endDot = Math.min(endDot, totalPages); // Ensure the end dot doesn't go beyond total pages
|
|
335
|
+
|
|
336
|
+
// Create an array of visible dots (pages to show in the pager)
|
|
337
|
+
let visibleDots = Array.from({ length: endDot - startDot + 1 }, (_, i) => startDot + i);
|
|
338
|
+
|
|
339
|
+
// Return pager object with properties for pagination
|
|
340
|
+
return {
|
|
341
|
+
totalSlides: totalSlides,
|
|
342
|
+
currentPage: currentPage,
|
|
343
|
+
slidesPerPage: slidesPerPage,
|
|
344
|
+
totalPages: totalPages,
|
|
345
|
+
startDot: startDot,
|
|
346
|
+
endDot: endDot,
|
|
347
|
+
visibleDots: visibleDots,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
paginationNextPage() {
|
|
352
|
+
if (this.pager.currentPage < this.pager.totalPages) {
|
|
353
|
+
// Move to the next page
|
|
354
|
+
this.pager.currentPage++;
|
|
355
|
+
|
|
356
|
+
// Update pager with new dot range
|
|
357
|
+
this.pager = this.getPaginationPager(this.config.slides.length, this.pager.currentPage, this.slidesPerViewAdjusted);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
paginationPreviousPage() {
|
|
362
|
+
if (this.pager.currentPage > 1) {
|
|
363
|
+
// Move to the previous page
|
|
364
|
+
this.pager.currentPage--;
|
|
365
|
+
|
|
366
|
+
// Update pager with new dot range
|
|
367
|
+
this.pager = this.getPaginationPager(this.config.slides.length, this.pager.currentPage, this.slidesPerViewAdjusted);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
isSlideActive(index: number): boolean {
|
|
372
|
+
// Check if the currentSlide index matches the given index
|
|
373
|
+
return this.currentSlide === index ||
|
|
374
|
+
(index === 0 && this.currentSlide === this.config.slides?.length) || // Wrap-around for the last slide to the first
|
|
375
|
+
(index === this.config.slides?.length - 1 && this.currentSlide === -1); // Wrap-around for the first slide to the last
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
isCurrentSlide(index: number): boolean {
|
|
379
|
+
return index === this.currentSlide;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
get activeSlideIndex(): number {
|
|
383
|
+
return this.currentSlide;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
get isPreviousDisabled(): boolean {
|
|
387
|
+
return this.config.loop === 0 && this.currentSlide === 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
get isNextDisabled(): boolean {
|
|
391
|
+
const totalSlides = this.config.slides?.length || 0;
|
|
392
|
+
// Check if looping is disabled, and if the currentSlide is the last visible slide
|
|
393
|
+
return this.config.loop === 0 && (this.currentSlide >= totalSlides - this.slidesPerViewAdjusted);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
get slideWidth(): string {
|
|
397
|
+
return `calc(100% / ${this.slidesPerViewAdjusted})`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
get canClickPrevious(): boolean {
|
|
401
|
+
// Check if clicking the previous button is allowed
|
|
402
|
+
if (this.config.loop === 2) {
|
|
403
|
+
// Infinite loop, always allow
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (this.config.loop === 1) {
|
|
408
|
+
// Loop back to the end if at the start
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Non-looping, disable if at the first slide
|
|
413
|
+
return this.currentSlide > 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
get canClickNext(): boolean {
|
|
417
|
+
// Check if clicking the next button is allowed
|
|
418
|
+
if (this.config.loop === 2) {
|
|
419
|
+
// Infinite loop, always allow
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.config.loop === 1) {
|
|
424
|
+
// Loop back to the start if at the end
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Non-looping, disable if at the last visible slide
|
|
429
|
+
const totalSlides = this.config.slides?.length || 0;
|
|
430
|
+
return this.currentSlide < totalSlides - this.slidesPerViewAdjusted;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
trackBySlideId(index: number, slide: any): number {
|
|
434
|
+
return slide.id;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
sliderDestroy(): void {
|
|
438
|
+
if (this.onMoveThrottled) {
|
|
439
|
+
this.onMoveThrottled.cancel(); // Cancel any pending throttled actions
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.stopAutoPlay();
|
|
443
|
+
|
|
444
|
+
this.config = undefined;
|
|
445
|
+
this.slideTemplate = undefined;
|
|
446
|
+
|
|
447
|
+
// Manually reset any other properties
|
|
448
|
+
this.isDragStarted = undefined;
|
|
449
|
+
this.isDragging = undefined;
|
|
450
|
+
|
|
451
|
+
this.startX = undefined;
|
|
452
|
+
this.startY = undefined;
|
|
453
|
+
|
|
454
|
+
this.currentSlide = undefined;
|
|
455
|
+
this.selectedSlide = undefined;
|
|
456
|
+
this.slideChanging = undefined;
|
|
457
|
+
|
|
458
|
+
this.slidesPerViewAdjusted = undefined;
|
|
459
|
+
this.visibleSlides = undefined;
|
|
460
|
+
|
|
461
|
+
this.translateStyle = undefined; // CSS transform for translation
|
|
462
|
+
this.shouldTranslate = undefined; // Flag to control translation
|
|
463
|
+
|
|
464
|
+
this.isForwardNavigation = undefined;
|
|
465
|
+
this.autoPlayInterval = undefined;
|
|
466
|
+
this.debounceTimer = undefined;
|
|
467
|
+
this.clickBlocked = undefined;
|
|
468
|
+
|
|
469
|
+
this.minSwipeDistance = undefined;
|
|
470
|
+
this.maxVerticalDeviation = undefined;
|
|
471
|
+
this.largeSwipeThreshold = undefined;
|
|
472
|
+
|
|
473
|
+
window.removeEventListener('resize', this.handleResize);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
onStart(event: MouseEvent | TouchEvent): void {
|
|
478
|
+
const e = event instanceof MouseEvent ? event : event.touches[0];
|
|
479
|
+
if (this.config.draggable) {
|
|
480
|
+
const target = e.target as HTMLElement;
|
|
481
|
+
|
|
482
|
+
// Check if the element or any of its ancestors has the 'drag-handle' class
|
|
483
|
+
if (target.closest('.drag-handle')) {
|
|
484
|
+
this.isDragStarted = true;
|
|
485
|
+
this.isDragging = false; // Reset dragging state
|
|
486
|
+
this.isVerticalScrolling = false; // Reset vertical scrolling state
|
|
487
|
+
|
|
488
|
+
this.startX = e.clientX || e.pageX;
|
|
489
|
+
this.startY = e.clientY || e.pageY;
|
|
490
|
+
|
|
491
|
+
// Activate the drag handle
|
|
492
|
+
const dragHandle = document.querySelector('.drag-handle');
|
|
493
|
+
if (dragHandle) {
|
|
494
|
+
dragHandle.classList.add('active');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* if (['IMG', 'A', 'P', 'BUTTON', 'DIV', 'SPAN'].includes(target.tagName)) {
|
|
499
|
+
this.isDragStarted = true;
|
|
500
|
+
this.isDragging = true; // Start dragging
|
|
501
|
+
this.startX = e.clientX || e.pageX;
|
|
502
|
+
this.startY = e.clientY || e.pageY; // Track initial Y for scroll detection
|
|
503
|
+
|
|
504
|
+
if (target.tagName === 'A') {
|
|
505
|
+
this.anchorClicked = target as HTMLAnchorElement;
|
|
506
|
+
}
|
|
507
|
+
// Add dragging class to disable pointer events for other content
|
|
508
|
+
document.querySelector('.slide-content')?.classList.add('dragging');
|
|
509
|
+
|
|
510
|
+
// Activate the drag handle
|
|
511
|
+
const dragHandle = document.querySelector('.drag-handle');
|
|
512
|
+
if (dragHandle) {
|
|
513
|
+
dragHandle.classList.add('active');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
*/
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
onMove(event: MouseEvent | TouchEvent): void {
|
|
523
|
+
if (!this.isDragStarted) return;
|
|
524
|
+
|
|
525
|
+
const e = event instanceof MouseEvent ? event : event.touches[0];
|
|
526
|
+
const deltaX = (e.clientX || e.pageX) - this.startX;
|
|
527
|
+
const deltaY = (e.clientY || e.pageY) - this.startY;
|
|
528
|
+
|
|
529
|
+
// Detect vertical scrolling
|
|
530
|
+
if (!this.isDragging && Math.abs(deltaY) > this.maxVerticalDeviation && Math.abs(deltaY) > Math.abs(deltaX)) {
|
|
531
|
+
this.isVerticalScrolling = true; // Allow scrolling
|
|
532
|
+
this.isDragStarted = false; // Stop dragging logic
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
if (Math.abs(deltaX) > this.minSwipeDistance && Math.abs(deltaY) < this.maxVerticalDeviation) {
|
|
538
|
+
event.preventDefault();
|
|
539
|
+
this.isDragging = true;
|
|
540
|
+
|
|
541
|
+
const isSwipeRight = deltaX > 0;
|
|
542
|
+
const slideStep = this.calculateSlideStep(this.currentSlide, true, deltaX);
|
|
543
|
+
|
|
544
|
+
if (isSwipeRight) {
|
|
545
|
+
this.onPreviousClick(slideStep);
|
|
546
|
+
} else {
|
|
547
|
+
this.onNextClick(slideStep);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Reset dragging state after navigation
|
|
551
|
+
this.isDragStarted = false;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Use throttle to improve performance
|
|
557
|
+
onThrottledMove(event: MouseEvent | TouchEvent): void {
|
|
558
|
+
// Block if dragging is in progress
|
|
559
|
+
if (this.isDragging) {
|
|
560
|
+
event.preventDefault();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.onMoveThrottled(event);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
onEnd(event: MouseEvent | TouchEvent): void {
|
|
569
|
+
if (!this.isDragStarted) return;
|
|
570
|
+
|
|
571
|
+
const e = event instanceof MouseEvent ? event : event.changedTouches[0];
|
|
572
|
+
const deltaX = (e.clientX || e.pageX) - this.startX;
|
|
573
|
+
|
|
574
|
+
// Detect and navigate based on swipe distance
|
|
575
|
+
if (!this.isVerticalScrolling && Math.abs(deltaX) > this.minSwipeDistance) {
|
|
576
|
+
const isSwipeRight = deltaX > 0;
|
|
577
|
+
const slideStep = this.calculateSlideStep(this.currentSlide, true, deltaX);
|
|
578
|
+
|
|
579
|
+
if (isSwipeRight) {
|
|
580
|
+
this.onPreviousClick(slideStep);
|
|
581
|
+
} else {
|
|
582
|
+
this.onNextClick(slideStep);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const dragHandle = document.querySelector('.drag-handle');
|
|
587
|
+
if (dragHandle) {
|
|
588
|
+
dragHandle.classList.remove('active');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Reset dragging state
|
|
592
|
+
this.isDragStarted = false;
|
|
593
|
+
this.isDragging = false;
|
|
594
|
+
this.isVerticalScrolling = false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
onLeave(): void {
|
|
599
|
+
this.isDragStarted = false;
|
|
600
|
+
this.isDragging = false; // End dragging
|
|
601
|
+
this.isVerticalScrolling = false; // Reset vertical scrolling flag
|
|
602
|
+
|
|
603
|
+
const dragHandle = document.querySelector('.drag-handle');
|
|
604
|
+
if (dragHandle) {
|
|
605
|
+
dragHandle.classList.remove('active');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
}
|
package/ngx-edge-slider-workspace/projects/ngx-edge-slider/src/lib/ngx-edge-slider.interface.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class SlideConfig {
|
|
2
|
+
title?: string;
|
|
3
|
+
titlePosition?: string;
|
|
4
|
+
|
|
5
|
+
draggable?:boolean = false;
|
|
6
|
+
slides: any[] = [];
|
|
7
|
+
slidesToSlide: number = 1;
|
|
8
|
+
slideChangeDelay: number = 300;
|
|
9
|
+
slidesPerView: number = 1;
|
|
10
|
+
slideWidth?: number;
|
|
11
|
+
changeToClickedSlide?:boolean;
|
|
12
|
+
autoPlay: boolean = false;
|
|
13
|
+
delay: number = 5000;
|
|
14
|
+
loop: number = 0;
|
|
15
|
+
vertical: boolean = false;
|
|
16
|
+
|
|
17
|
+
navEnabled?: boolean;
|
|
18
|
+
navPosition?: string;
|
|
19
|
+
navHoverable?: boolean = false;
|
|
20
|
+
paginationEnabled?: boolean;
|
|
21
|
+
paginationPosition?: string;
|
|
22
|
+
breakpoints?: SlideBreakpoints;
|
|
23
|
+
|
|
24
|
+
constructor(config?: Partial<SlideConfig>) {
|
|
25
|
+
Object.assign(this, config);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SlideBreakpointConfig extends Partial<SlideConfig> { }
|
|
30
|
+
export interface SlideBreakpoints {
|
|
31
|
+
mobile?: SlideBreakpointConfig;
|
|
32
|
+
tablet?: SlideBreakpointConfig;
|
|
33
|
+
desktop?: SlideBreakpointConfig;
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { NgxEdgeSliderComponent } from './ngx-edge-slider.component';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@NgModule({
|
|
8
|
+
declarations: [
|
|
9
|
+
NgxEdgeSliderComponent
|
|
10
|
+
],
|
|
11
|
+
imports: [
|
|
12
|
+
CommonModule
|
|
13
|
+
],
|
|
14
|
+
exports: [
|
|
15
|
+
NgxEdgeSliderComponent
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
export class NgxEdgeSliderModule { }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "../../tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "../../out-tsc/lib",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"inlineSources": true,
|
|
9
|
+
"types": []
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"**/*.spec.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|