@schukai/monster 4.43.17 → 4.43.18

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.
@@ -14,8 +14,8 @@ import { instanceSymbol } from "../../constants.mjs";
14
14
  import { ATTRIBUTE_PREFIX, ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
15
15
  import { CustomElement, getSlottedElements } from "../../dom/customelement.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- registerCustomElement,
17
+ assembleMethodSymbol,
18
+ registerCustomElement,
19
19
  } from "../../dom/customelement.mjs";
20
20
  import { SliderStyleSheet } from "./stylesheet/slider.mjs";
21
21
  import { fireCustomEvent } from "../../dom/events.mjs";
@@ -28,41 +28,47 @@ export { Slider };
28
28
  /**
29
29
  * @private
30
30
  * @type {symbol}
31
+ * @description Reference to the main slider <ul> container element.
31
32
  */
32
33
  const sliderElementSymbol = Symbol("sliderElement");
33
34
 
34
35
  /**
35
36
  * @private
36
37
  * @type {symbol}
38
+ * @description Reference to the main control <div> wrapper.
37
39
  */
38
40
  const controlElementSymbol = Symbol("controlElement");
39
41
 
40
42
  /**
41
43
  * @private
42
44
  * @type {symbol}
45
+ * @description Reference to the "previous" button element.
43
46
  */
44
47
  const prevElementSymbol = Symbol("prevElement");
45
48
 
46
49
  /**
47
50
  * @private
48
51
  * @type {symbol}
52
+ * @description Reference to the "next" button element.
49
53
  */
50
54
  const nextElementSymbol = Symbol("nextElement");
51
55
 
52
56
  /**
53
57
  * @private
54
58
  * @type {symbol}
59
+ * @description Reference to the thumbnails container element.
55
60
  */
56
61
  const thumbnailElementSymbol = Symbol("thumbnailElement");
57
62
 
58
63
  /**
59
64
  * @private
60
65
  * @type {symbol}
66
+ * @description Stores internal state, configuration, and event handlers.
61
67
  */
62
68
  const configSymbol = Symbol("config");
63
69
 
64
70
  /**
65
- * A Slider
71
+ * A slider/carousel custom element.
66
72
  *
67
73
  * @fragments /fragments/components/layout/slider/
68
74
  *
@@ -72,762 +78,852 @@ const configSymbol = Symbol("config");
72
78
  *
73
79
  * @since 3.74.0
74
80
  * @copyright schukai GmbH
75
- * @summary A beautiful Slider that can make your life easier and also looks good.
76
- * @fires monster-slider-resized
77
- * @fires monster-slider-moved
81
+ * @summary Provides a responsive, touch-enabled slider or carousel component with features like autoplay, thumbnails, and looping.
82
+ * @fires monster-slider-resized - Fired when the slider's dimensions change.
83
+ * @fires monster-slider-moved - Fired when the slider moves to a new slide.
78
84
  */
79
85
  class Slider extends CustomElement {
80
- /**
81
- * This method is called by the `instanceof` operator.
82
- * @return {symbol}
83
- */
84
- static get [instanceSymbol]() {
85
- return Symbol.for("@schukai/monster/components/layout/slider@@instance");
86
- }
87
-
88
- /**
89
- *
90
- * @return {Components.Layout.Slider
91
- */
92
- [assembleMethodSymbol]() {
93
- super[assembleMethodSymbol]();
94
-
95
- this[configSymbol] = {
96
- currentIndex: 0,
97
-
98
- isDragging: false,
99
- draggingPos: 0,
100
- startPos: 0,
101
- autoPlayInterval: null,
102
-
103
- eventHandler: {
104
- mouseOverPause: null,
105
- mouseout: null,
106
- touchstart: null,
107
- touchend: null,
108
- },
109
- };
110
-
111
- // set --monster-slides-width
112
- const slides = this.shadowRoot.querySelector(
113
- `[${ATTRIBUTE_ROLE}="slider"]`,
114
- );
115
-
116
- const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
117
- slides.style.setProperty(
118
- "--monster-slides-width",
119
- `${100 / slidesVisible}%`,
120
- );
121
-
122
- initControlReferences.call(this);
123
- initEventHandler.call(this);
124
- initStructure.call(this);
125
-
126
- return this;
127
- }
128
-
129
- /**
130
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
131
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
132
- *
133
- * The individual configuration values can be found in the table.
134
- *
135
- * @property {Object} templates Template definitions
136
- * @property {string} templates.main Main template
137
- * @property {string} actions.click Callback when clicked
138
- * @property {Object} features Features
139
- * @property {boolean} features.carousel Carousel feature
140
- * @property {boolean} features.autoPlay Auto play feature
141
- * @property {boolean} features.thumbnails Thumbnails feature
142
- * @property {boolean} features.drag Drag feature
143
- * @property {Object} slides Slides configuration, an object with breakpoints and the number of slides to show
144
- * @property {Object} slides.0 Number of slides to show at 0px
145
- * @property {Object} slides.600 Number of slides to show at 600px @since 3.109.0
146
- * @property {Object} slides.1200 Number of slides to show at 1200px @since 3.109.0
147
- * @property {Object} slides.1800 Number of slides to show at 1800px @since 3.109.0
148
- * @property {Object} carousel Carousel configuration
149
- * @property {number} carousel.transition Transition time between a full rotation of the carousel
150
- * @property {Object} autoPlay Auto play configuration
151
- * @property {number} autoPlay.delay Delay between slides
152
- * @property {number} autoPlay.startDelay Start delay
153
- * @property {string} autoPlay.direction Direction of the autoplay
154
- * @property {boolean} autoPlay.mouseOverPause Pause on mouse over
155
- * @property {boolean} autoPlay.touchPause Pause on touch
156
- * @property {Object} classes CSS classes
157
- * @property {boolean} disabled Disabled state
158
- */
159
- get defaults() {
160
- return Object.assign({}, super.defaults, {
161
- templates: {
162
- main: getTemplate(),
163
- },
164
-
165
- classes: {},
166
- disabled: false,
167
-
168
- features: {
169
- carousel: true,
170
- autoPlay: true,
171
- thumbnails: true,
172
- drag: true,
173
- },
174
-
175
- slides: {
176
- 0: 1,
177
- 600: 2,
178
- 1200: 3,
179
- 1800: 4,
180
- },
181
-
182
- carousel: {
183
- transition: 250,
184
- },
185
-
186
- autoPlay: {
187
- delay: 1500,
188
- startDelay: 1000,
189
- direction: "next",
190
- mouseOverPause: true,
191
- touchPause: true,
192
- },
193
- });
194
- }
195
-
196
- /**
197
- * @return {string}
198
- */
199
- static getTag() {
200
- return "monster-slider";
201
- }
202
-
203
- /**
204
- * @return {CSSStyleSheet[]}
205
- */
206
- static getCSSStyleSheet() {
207
- return [SliderStyleSheet];
208
- }
209
-
210
- /**
211
- * moves the slider to the given index
212
- *
213
- * @param index
214
- * @return {void}
215
- */
216
- moveTo(index) {
217
- return moveTo.call(this, index);
218
- }
219
-
220
- /**
221
- * shows the previous slide
222
- *
223
- * @return {void}
224
- */
225
- previous() {
226
- return prev.call(this);
227
- }
228
-
229
- /**
230
- * shows the next slide
231
- *
232
- * @return {void}
233
- */
234
- next() {
235
- return next.call(this);
236
- }
237
-
238
- /**
239
- * stops the auto play
240
- *
241
- * @return {void}
242
- */
243
- stopAutoPlay() {
244
- if (this[configSymbol].autoPlayInterval) {
245
- clearInterval(this[configSymbol].autoPlayInterval);
246
- }
247
- }
248
-
249
- /**
250
- * starts the auto play
251
- *
252
- * @return {void}
253
- */
254
- startAutoPlay() {
255
- initAutoPlay.call(this);
256
- }
86
+ /**
87
+ * This method is called by the `instanceof` operator.
88
+ * @return {symbol}
89
+ */
90
+ static get [instanceSymbol]() {
91
+ return Symbol.for("@schukai/monster/components/layout/slider@@instance");
92
+ }
93
+
94
+ /**
95
+ *
96
+ * @return {Components.Layout.Slider
97
+ */
98
+ [assembleMethodSymbol]() {
99
+ super[assembleMethodSymbol]();
100
+
101
+ this[configSymbol] = {
102
+ currentIndex: 0,
103
+
104
+ isDragging: false,
105
+ draggingPos: 0,
106
+ startPos: 0,
107
+ autoPlayInterval: null,
108
+ resizeObserver: null, // Store the observer for later cleanup
109
+
110
+ eventHandler: {
111
+ mouseOverPause: null,
112
+ mouseout: null,
113
+ touchstart: null,
114
+ touchend: null,
115
+ },
116
+ };
117
+
118
+ // Set the CSS custom property for slide width based on visible slides.
119
+ const slides = this.shadowRoot.querySelector(
120
+ `[${ATTRIBUTE_ROLE}="slider"]`,
121
+ );
122
+
123
+ const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
124
+ slides.style.setProperty(
125
+ "--monster-slides-width",
126
+ `${100 / slidesVisible}%`,
127
+ );
128
+
129
+ initControlReferences.call(this);
130
+ initEventHandler.call(this);
131
+ initStructure.call(this);
132
+
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Called when the element is removed from the DOM.
138
+ * Cleans up intervals, observers, and event listeners to prevent memory leaks.
139
+ */
140
+ disconnectedCallback() {
141
+ // Check if super.disconnectedCallback exists and call it
142
+ if (super.disconnectedCallback) {
143
+ super.disconnectedCallback();
144
+ }
145
+
146
+ this.stopAutoPlay(); // Clear interval
147
+
148
+ // Disconnect the ResizeObserver
149
+ if (this[configSymbol]?.resizeObserver) {
150
+ this[configSymbol].resizeObserver.disconnect();
151
+ this[configSymbol].resizeObserver = null;
152
+ }
153
+
154
+ // Remove autoplay-related event listeners
155
+ if (this[configSymbol]?.eventHandler) {
156
+ const { mouseOverPause, mouseout, touchstart, touchend } =
157
+ this[configSymbol].eventHandler;
158
+
159
+ if (mouseOverPause) {
160
+ this.removeEventListener("mouseover", mouseOverPause);
161
+ this[configSymbol].eventHandler.mouseOverPause = null;
162
+ }
163
+ if (mouseout) {
164
+ this.removeEventListener("mouseout", mouseout);
165
+ this[configSymbol].eventHandler.mouseout = null;
166
+ }
167
+ if (touchstart) {
168
+ this.removeEventListener("touchstart", touchstart);
169
+ this[configSymbol].eventHandler.touchstart = null;
170
+ }
171
+ if (touchend) {
172
+ this.removeEventListener("touchend", touchend);
173
+ this[configSymbol].eventHandler.touchend = null;
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
180
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
181
+ *
182
+ * The individual configuration values can be found in the table.
183
+ *
184
+ * @property {Object} templates Template definitions
185
+ * @property {string} templates.main Main template
186
+ * @property {Object} features Features
187
+ * @property {boolean} features.carousel Carousel feature (infinite looping)
188
+ * @property {boolean} features.autoPlay Auto play feature
189
+ * @property {boolean} features.thumbnails Thumbnails feature
190
+ * @property {boolean} features.drag Drag feature (touch and mouse)
191
+ * @property {Object} slides Slides configuration, an object with breakpoints and the number of slides to show
192
+ * @property {Object} slides.0 Number of slides to show at 0px
193
+ * @property {Object} slides.600 Number of slides to show at 600px @since 3.109.0
194
+ * @property {Object} slides.1200 Number of slides to show at 1200px @since 3.109.0
195
+ * @property {Object} slides.1800 Number of slides to show at 1800px @since 3.109.0
196
+ * @property {Object} carousel Carousel configuration
197
+ * @property {number} carousel.transition Duration (ms) of the carousel 'jump' animation when looping.
198
+ * @property {Object} autoPlay Auto play configuration
199
+ * @property {number} autoPlay.delay Delay in ms between slide transitions
200
+ * @property {number} autoPlay.startDelay Delay in ms before autoplay starts
201
+ * @property {string} autoPlay.direction Direction of the autoplay ("next" or "prev")
202
+ * @property {boolean} autoPlay.mouseOverPause Pause on mouse over
203
+ * @property {boolean} autoPlay.touchPause Pause on touch
204
+ * @property {Object} classes CSS classes
205
+ * @property {boolean} disabled Disabled state
206
+ */
207
+ get defaults() {
208
+ return Object.assign({}, super.defaults, {
209
+ templates: {
210
+ main: getTemplate(),
211
+ },
212
+
213
+ classes: {},
214
+ disabled: false,
215
+
216
+ features: {
217
+ carousel: true,
218
+ autoPlay: true,
219
+ thumbnails: true,
220
+ drag: true,
221
+ },
222
+
223
+ slides: {
224
+ 0: 1,
225
+ 600: 2,
226
+ 1200: 3,
227
+ 1800: 4,
228
+ },
229
+
230
+ carousel: {
231
+ transition: 250,
232
+ },
233
+
234
+ autoPlay: {
235
+ delay: 1500,
236
+ startDelay: 1000,
237
+ direction: "next",
238
+ mouseOverPause: true,
239
+ touchPause: true,
240
+ },
241
+ });
242
+ }
243
+
244
+ /**
245
+ * @return {string}
246
+ */
247
+ static getTag() {
248
+ return "monster-slider";
249
+ }
250
+
251
+ /**
252
+ * @return {CSSStyleSheet[]}
253
+ */
254
+ static getCSSStyleSheet() {
255
+ return [SliderStyleSheet];
256
+ }
257
+
258
+ /**
259
+ * Moves the slider to the given index.
260
+ *
261
+ * @param {number} index - The slide index to move to.
262
+ * @return {void}
263
+ */
264
+ moveTo(index) {
265
+ return moveTo.call(this, index);
266
+ }
267
+
268
+ /**
269
+ * Shows the previous slide.
270
+ *
271
+ * @return {void}
272
+ */
273
+ previous() {
274
+ return prev.call(this);
275
+ }
276
+
277
+ /**
278
+ * Shows the next slide.
279
+ *
280
+ * @return {void}
281
+ */
282
+ next() {
283
+ return next.call(this);
284
+ }
285
+
286
+ /**
287
+ * Stops the auto play.
288
+ *
289
+ * @return {void}
290
+ */
291
+ stopAutoPlay() {
292
+ if (this[configSymbol].autoPlayInterval) {
293
+ clearInterval(this[configSymbol].autoPlayInterval);
294
+ this[configSymbol].autoPlayInterval = null;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Starts the auto play.
300
+ *
301
+ * @return {void}
302
+ */
303
+ startAutoPlay() {
304
+ initAutoPlay.call(this);
305
+ }
257
306
  }
258
307
 
259
308
  /**
260
309
  * @private
261
- * @param name
262
- */
263
- //function initNavigation(name) {
264
- //const element = this.shadowRoot.querySelector("." + name + "");
265
- //const elementHeight = element.offsetHeight;
266
- //element.style.top = `calc(50% - ${elementHeight / 2}px)`;
267
- //}
268
-
269
- /**
270
- * @private
310
+ * @description Initializes the component structure, thumbnails, and autoplay.
271
311
  */
272
312
  function initStructure() {
273
- //initNavigation.call(this, "next");
274
- //initNavigation.call(this, "prev");
313
+ if (this.getOption("features.thumbnails")) {
314
+ initThumbnails.call(this);
315
+ }
275
316
 
276
- if (this.getOption("features.thumbnails")) {
277
- initThumbnails.call(this);
278
- }
317
+ // Clones slides if carousel mode is active
318
+ initCarouselClones.call(this);
279
319
 
280
- initShadows.call(this);
281
-
282
- if (this.getOption("features.autoPlay")) {
283
- initAutoPlay.call(this);
284
- }
320
+ if (this.getOption("features.autoPlay")) {
321
+ initAutoPlay.call(this);
322
+ }
285
323
  }
286
324
 
287
325
  /**
288
326
  * @private
327
+ * @description Generates the thumbnail navigation elements.
289
328
  */
290
329
  function initThumbnails() {
291
- const self = this;
292
- const thumbnails = this.shadowRoot.querySelector(
293
- "[data-monster-role='thumbnails']",
294
- );
295
-
296
- // remove all thumbnails
297
- while (thumbnails.firstChild) {
298
- thumbnails.removeChild(thumbnails.firstChild);
299
- }
300
-
301
- const { originSlides } = getSlidesAndTotal.call(this);
302
-
303
- originSlides.forEach((x, index) => {
304
- const thumbnail = document.createElement("div");
305
- thumbnail.classList.add("thumbnail");
306
- thumbnail.addEventListener("click", () => {
307
- this.moveTo(index);
308
- });
309
-
310
- thumbnails.appendChild(thumbnail);
311
- });
312
-
313
- this.addEventListener("monster-slider-moved", (e) => {
314
- const index = e.detail.index;
315
- const thumbnail = thumbnails.children[index];
316
-
317
- if (!thumbnail) {
318
- return;
319
- }
320
-
321
- Array.from(thumbnails.children).forEach((thumb) => {
322
- thumb.classList.remove("current");
323
- });
324
-
325
- thumbnail.classList.add("current");
326
- });
330
+ const self = this;
331
+ const thumbnails = this.shadowRoot.querySelector(
332
+ "[data-monster-role='thumbnails']",
333
+ );
334
+
335
+ // Clear existing thumbnails before regenerating
336
+ while (thumbnails.firstChild) {
337
+ thumbnails.removeChild(thumbnails.firstChild);
338
+ }
339
+
340
+ const { originSlides } = getSlidesAndTotal.call(this);
341
+
342
+ originSlides.forEach((x, index) => {
343
+ const thumbnail = document.createElement("div");
344
+ thumbnail.classList.add("thumbnail");
345
+ thumbnail.addEventListener("click", () => {
346
+ this.moveTo(index);
347
+ });
348
+
349
+ thumbnails.appendChild(thumbnail);
350
+ });
351
+
352
+ // Listen for move events to update the active thumbnail
353
+ this.addEventListener("monster-slider-moved", (e) => {
354
+ const index = e.detail.index;
355
+ const thumbnail = thumbnails.children[index];
356
+
357
+ if (!thumbnail) {
358
+ return;
359
+ }
360
+
361
+ Array.from(thumbnails.children).forEach((thumb) => {
362
+ thumb.classList.remove("current");
363
+ });
364
+
365
+ thumbnail.classList.add("current");
366
+ });
327
367
  }
328
368
 
329
369
  /**
330
370
  * @private
371
+ * @description Initializes the autoplay functionality and its event handlers.
331
372
  */
332
373
  function initAutoPlay() {
333
- const self = this;
334
- const autoPlay = this.getOption("autoPlay");
335
- const delay = autoPlay.delay;
336
- const startDelay = autoPlay.startDelay;
337
- const direction = autoPlay.direction;
338
-
339
- function start() {
340
- if (self[configSymbol].autoPlayInterval) {
341
- clearInterval(self[configSymbol].autoPlayInterval);
342
- }
343
-
344
- self[configSymbol].autoPlayInterval = setInterval(() => {
345
- const { totalOriginSlides } = getSlidesAndTotal.call(self);
346
-
347
- if (direction === "next") {
348
- if (
349
- !self.getOption("features.carousel") &&
350
- self[configSymbol].currentIndex >= totalOriginSlides - 1
351
- ) {
352
- self[configSymbol].currentIndex = -1;
353
- }
354
- self.next();
355
- } else {
356
- if (
357
- !self.getOption("features.carousel") &&
358
- self[configSymbol].currentIndex <= 0
359
- ) {
360
- self[configSymbol].currentIndex = totalOriginSlides;
361
- }
362
- self.previous();
363
- }
364
- }, delay);
365
- }
366
-
367
- setTimeout(() => {
368
- start();
369
- }, startDelay);
370
-
371
- if (autoPlay.mouseOverPause) {
372
- if (this[configSymbol].eventHandler.mouseOverPause === null) {
373
- this[configSymbol].eventHandler.mouseOverPause = () => {
374
- clearInterval(this[configSymbol].autoPlayInterval);
375
- };
376
-
377
- this.addEventListener(
378
- "mouseover",
379
- this[configSymbol].eventHandler.mouseOverPause,
380
- );
381
- }
382
-
383
- if (this[configSymbol].eventHandler.mouseout === null) {
384
- this[configSymbol].eventHandler.mouseout = () => {
385
- if (this[configSymbol].isDragging) {
386
- return;
387
- }
388
- start();
389
- };
390
-
391
- this.addEventListener(
392
- "mouseout",
393
- this[configSymbol].eventHandler.mouseout,
394
- );
395
- }
396
- }
397
-
398
- if (autoPlay.touchPause) {
399
- if (this[configSymbol].eventHandler.touchstart === null) {
400
- this[configSymbol].eventHandler.touchstart = () => {
401
- clearInterval(this[configSymbol].autoPlayInterval);
402
- };
403
-
404
- this.addEventListener(
405
- "touchstart",
406
- this[configSymbol].eventHandler.touchstart,
407
- );
408
- }
409
-
410
- if (this[configSymbol].eventHandler.touchend === null) {
411
- this[configSymbol].eventHandler.touchend = () => {
412
- if (this[configSymbol].isDragging) {
413
- return;
414
- }
415
- start();
416
- };
417
-
418
- this.addEventListener(
419
- "touchend",
420
- this[configSymbol].eventHandler.touchend,
421
- );
422
- }
423
- }
374
+ const self = this;
375
+
376
+ if (this.getOption("features.autoPlay") === false) {
377
+ return;
378
+ }
379
+
380
+ const autoPlay = this.getOption("autoPlay");
381
+ if (!isObject(autoPlay)) {
382
+ return;
383
+ }
384
+ const delay = autoPlay.delay;
385
+ const startDelay = autoPlay.startDelay;
386
+ const direction = autoPlay.direction;
387
+
388
+ function start() {
389
+ // Clear any existing interval before starting a new one
390
+ if (self[configSymbol].autoPlayInterval) {
391
+ clearInterval(self[configSymbol].autoPlayInterval);
392
+ }
393
+
394
+ self[configSymbol].autoPlayInterval = setInterval(() => {
395
+ const { totalOriginSlides } = getSlidesAndTotal.call(self);
396
+
397
+ if (direction === "next") {
398
+ // Check if carousel looping is disabled and we're at the end
399
+ if (
400
+ !self.getOption("features.carousel") &&
401
+ self[configSymbol].currentIndex >= totalOriginSlides - 1
402
+ ) {
403
+ self[configSymbol].currentIndex = -1;
404
+ }
405
+ self.next();
406
+ } else {
407
+ // Check if carousel looping is disabled and we're at the beginning
408
+ if (
409
+ !self.getOption("features.carousel") &&
410
+ self[configSymbol].currentIndex <= 0
411
+ ) {
412
+ self[configSymbol].currentIndex = totalOriginSlides;
413
+ }
414
+ self.previous();
415
+ }
416
+ }, delay);
417
+ }
418
+
419
+ setTimeout(() => {
420
+ start();
421
+ }, startDelay);
422
+
423
+ // Add listeners for pause-on-hover
424
+ if (autoPlay.mouseOverPause) {
425
+ if (this[configSymbol].eventHandler.mouseOverPause === null) {
426
+ this[configSymbol].eventHandler.mouseOverPause = () => {
427
+ clearInterval(this[configSymbol].autoPlayInterval);
428
+ };
429
+
430
+ this.addEventListener(
431
+ "mouseover",
432
+ this[configSymbol].eventHandler.mouseOverPause,
433
+ );
434
+ }
435
+
436
+ if (this[configSymbol].eventHandler.mouseout === null) {
437
+ this[configSymbol].eventHandler.mouseout = () => {
438
+ if (this[configSymbol].isDragging) {
439
+ return;
440
+ }
441
+ start();
442
+ };
443
+
444
+ this.addEventListener(
445
+ "mouseout",
446
+ this[configSymbol].eventHandler.mouseout,
447
+ );
448
+ }
449
+ }
450
+
451
+ // Add listeners for pause-on-touch
452
+ if (autoPlay.touchPause) {
453
+ if (this[configSymbol].eventHandler.touchstart === null) {
454
+ this[configSymbol].eventHandler.touchstart = () => {
455
+ clearInterval(this[configSymbol].autoPlayInterval);
456
+ };
457
+
458
+ this.addEventListener(
459
+ "touchstart",
460
+ this[configSymbol].eventHandler.touchstart,
461
+ );
462
+ }
463
+
464
+ if (this[configSymbol].eventHandler.touchend === null) {
465
+ this[configSymbol].eventHandler.touchend = () => {
466
+ if (this[configSymbol].isDragging) {
467
+ return;
468
+ }
469
+ start();
470
+ };
471
+
472
+ this.addEventListener(
473
+ "touchend",
474
+ this[configSymbol].eventHandler.touchend,
475
+ );
476
+ }
477
+ }
424
478
  }
425
479
 
480
+ /**
481
+ * @private
482
+ * @description Calculates the number of slides that should be visible based on breakpoints.
483
+ * @return {number}
484
+ */
426
485
  function getVisibleSlidesFromContainerWidth() {
427
- const containerWidth = this.shadowRoot.querySelector(
428
- `[${ATTRIBUTE_ROLE}="slider"]`,
429
- ).offsetWidth;
430
- const slides = this.getOption("slides");
431
- let visibleSlides = 1;
432
-
433
- if (!isObject(slides)) {
434
- return visibleSlides;
435
- }
436
-
437
- for (const key in slides) {
438
- if (containerWidth >= key) {
439
- visibleSlides = slides[key];
440
- }
441
- }
442
-
443
- const { originSlides } = getSlidesAndTotal.call(this);
444
- if (visibleSlides > originSlides.length) {
445
- visibleSlides = originSlides.length - 1;
446
- }
447
-
448
- return visibleSlides;
486
+ const containerWidth = this.shadowRoot.querySelector(
487
+ `[${ATTRIBUTE_ROLE}="slider"]`,
488
+ ).offsetWidth;
489
+ const slides = this.getOption("slides");
490
+ let visibleSlides = 1;
491
+
492
+ if (!isObject(slides)) {
493
+ return visibleSlides;
494
+ }
495
+
496
+ // Find the largest breakpoint that is smaller than the current container width
497
+ for (const key in slides) {
498
+ if (containerWidth >= key) {
499
+ visibleSlides = slides[key];
500
+ }
501
+ }
502
+
503
+ const { originSlides } = getSlidesAndTotal.call(this);
504
+ // Ensure we don't try to show more slides than are available
505
+ if (visibleSlides > originSlides.length) {
506
+ visibleSlides = originSlides.length; // Fixed: was originSlides.length - 1
507
+ }
508
+
509
+ return visibleSlides;
449
510
  }
450
511
 
451
512
  /**
452
513
  * @private
514
+ * @description Clones slides to create the "infinite" loop effect for the carousel.
453
515
  */
454
- function initShadows() {
455
- const { slides, totalSlides } = getSlidesAndTotal.call(this);
456
- const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
457
-
458
- if (totalSlides > slidesVisible) {
459
- let current = slides[0];
460
- let last = slides[totalSlides - 1];
461
- for (let i = 0; i < slidesVisible; i++) {
462
- const clone = current.cloneNode(true);
463
- clone.setAttribute("data-monster-clone-from", i);
464
- last.insertAdjacentElement("afterend", clone);
465
- current = current.nextElementSibling;
466
- last = clone;
467
- }
468
-
469
- current = slides[totalSlides - 1];
470
- let first = slides[0];
471
- for (let i = 0; i < slidesVisible; i++) {
472
- const clone = current.cloneNode(true);
473
- clone.setAttribute("data-monster-clone-from", totalSlides - i);
474
- first.insertAdjacentElement("beforebegin", clone);
475
- current = current.previousElementSibling;
476
- first = clone;
477
- }
478
-
479
- moveTo.call(this, 0);
480
- }
516
+ function initCarouselClones() {
517
+ const { slides, totalSlides } = getSlidesAndTotal.call(this);
518
+ const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
519
+
520
+ // Only clone if there are more slides than are visible
521
+ if (totalSlides > slidesVisible) {
522
+ // Clone slides from the beginning and append them to the end
523
+ let current = slides[0];
524
+ let last = slides[totalSlides - 1];
525
+ for (let i = 0; i < slidesVisible; i++) {
526
+ const clone = current.cloneNode(true);
527
+ clone.setAttribute("data-monster-clone-from", i);
528
+ last.insertAdjacentElement("afterend", clone);
529
+ current = current.nextElementSibling;
530
+ last = clone;
531
+ }
532
+
533
+ // Clone slides from the end and prepend them to the beginning
534
+ current = slides[totalSlides - 1];
535
+ let first = slides[0];
536
+ for (let i = 0; i < slidesVisible; i++) {
537
+ const clone = current.cloneNode(true);
538
+ // Fixed: Index was totalSlides - i, should be totalSlides - 1 - i
539
+ clone.setAttribute("data-monster-clone-from", totalSlides - 1 - i);
540
+ first.insertAdjacentElement("beforebegin", clone);
541
+ current = current.previousElementSibling;
542
+ first = clone;
543
+ }
544
+
545
+ moveTo.call(this, 0);
546
+ }
481
547
  }
482
548
 
483
549
  /**
484
550
  * @private
485
- * @return {{slides: unknown[], totalSlides: number}}
551
+ * @description Gets all slides, original slides, and their counts.
552
+ * @return {{slides: HTMLElement[], totalSlides: number, originSlides: HTMLElement[], totalOriginSlides: number}}
486
553
  */
487
554
  function getSlidesAndTotal() {
488
- const originSlides = Array.from(
489
- getSlottedElements.call(
490
- this,
491
- ":scope:not([data-monster-clone-from])",
492
- null,
493
- ),
494
- );
495
- const totalOriginSlides = originSlides.length;
496
-
497
- const slides = Array.from(getSlottedElements.call(this, ":scope", null));
498
- const totalSlides = slides.length;
499
-
500
- return { originSlides, totalOriginSlides, slides, totalSlides };
555
+ // Get only original slides (excluding clones)
556
+ const originSlides = Array.from(
557
+ getSlottedElements.call(
558
+ this,
559
+ ":scope:not([data-monster-clone-from])",
560
+ null,
561
+ ),
562
+ );
563
+ const totalOriginSlides = originSlides.length;
564
+
565
+ // Get all slides (including clones)
566
+ const slides = Array.from(getSlottedElements.call(this, ":scope", null));
567
+ const totalSlides = slides.length;
568
+
569
+ return { originSlides, totalOriginSlides, slides, totalSlides };
501
570
  }
502
571
 
503
572
  /**
504
573
  * @private
574
+ * @description Moves to the next slide.
505
575
  * @return {number}
506
576
  */
507
577
  function next() {
508
- const nextIndex = this[configSymbol].currentIndex + 1;
578
+ const nextIndex = this[configSymbol].currentIndex + 1;
509
579
 
510
- queueMicrotask(() => {
511
- getWindow().requestAnimationFrame(() => {
512
- getWindow().requestAnimationFrame(() => {
513
- moveTo.call(this, nextIndex);
514
- });
515
- });
516
- });
580
+ // Use requestAnimationFrame to ensure the move happens in the next frame,
581
+ // allowing CSS transitions to apply correctly.
582
+ getWindow().requestAnimationFrame(() => {
583
+ moveTo.call(this, nextIndex);
584
+ });
517
585
 
518
- return 0;
586
+ return 0;
519
587
  }
520
588
 
521
589
  /**
522
590
  * @private
591
+ * @description Moves to the previous slide.
523
592
  * @return {number}
524
593
  */
525
594
  function prev() {
526
- const prevIndex = this[configSymbol].currentIndex - 1;
595
+ const prevIndex = this[configSymbol].currentIndex - 1;
527
596
 
528
- queueMicrotask(() => {
529
- getWindow().requestAnimationFrame(() => {
530
- getWindow().requestAnimationFrame(() => {
531
- moveTo.call(this, prevIndex);
532
- });
533
- });
534
- });
597
+ // Use requestAnimationFrame for smooth transitions
598
+ getWindow().requestAnimationFrame(() => {
599
+ moveTo.call(this, prevIndex);
600
+ });
535
601
 
536
- return 0;
602
+ return 0;
537
603
  }
538
604
 
539
605
  /**
540
606
  * @private
541
- * @param slides
542
- * @param index
607
+ * @description Sets the CSS transform and 'current' class for the slide container.
608
+ * @param {HTMLElement[]} slides - Array of all slide elements.
609
+ * @param {number} index - The target slide index.
543
610
  */
544
611
  function setMoveProperties(slides, index) {
545
- slides.forEach((slide) => {
546
- slide.classList.remove("current");
547
- });
612
+ // Remove 'current' class from all slides
613
+ slides.forEach((slide) => {
614
+ slide.classList.remove("current");
615
+ });
548
616
 
549
- let offset = -(index * 100);
550
- const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
617
+ let offset = -(index * 100);
618
+ const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
551
619
 
552
- offset = offset / slidesVisible;
620
+ offset = offset / slidesVisible;
553
621
 
554
- if (offset !== 0) {
555
- offset += "%";
556
- }
622
+ if (offset !== 0) {
623
+ offset += "%";
624
+ }
557
625
 
558
- this[sliderElementSymbol].style.transform =
559
- `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
626
+ this[sliderElementSymbol].style.transform =
627
+ `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
560
628
 
561
- if (slides[index]) {
562
- slides[index].classList.add("current");
563
- }
564
- this[configSymbol].lastOffset = offset;
629
+ if (slides[index]) {
630
+ slides[index].classList.add("current");
631
+ }
632
+ this[configSymbol].lastOffset = offset;
565
633
  }
566
634
 
567
635
  /**
568
636
  * @private
569
- * @param {number} index
570
- * @param {boolean} animation
637
+ * @description The core logic for moving the slider to a specific index.
638
+ * @param {number} index - The target index (relative to original slides).
639
+ * @param {boolean} [animation=true] - Whether to use CSS transitions for this move.
571
640
  * @fires monster-slider-moved
572
641
  */
573
642
  function moveTo(index, animation) {
574
- const { slides, totalSlides, originSlides, totalOriginSlides } =
575
- getSlidesAndTotal.call(this);
576
-
577
- if (animation === false) {
578
- this[sliderElementSymbol].classList.remove("animate");
579
- } else {
580
- this[sliderElementSymbol].classList.add("animate");
581
- }
582
-
583
- if (this.getOption("features.carousel") === true) {
584
- if (index < 0) {
585
- index = -1;
586
- }
587
-
588
- if (index > totalOriginSlides) {
589
- index = totalOriginSlides;
590
- }
591
- } else {
592
- if (index < 0) {
593
- index = 0;
594
- }
595
-
596
- if (index >= totalOriginSlides) {
597
- index = totalOriginSlides - 1;
598
- }
599
- }
600
-
601
- if (!isInteger(index)) {
602
- return;
603
- }
604
-
605
- const visibleSlides = getVisibleSlidesFromContainerWidth.call(this);
606
- if (totalOriginSlides <= visibleSlides) {
607
- this[prevElementSymbol].classList.add("hidden");
608
- this[nextElementSymbol].classList.add("hidden");
609
- this[thumbnailElementSymbol].classList.add("hidden");
610
- return;
611
- }
612
- this[prevElementSymbol].classList.remove("hidden");
613
- this[nextElementSymbol].classList.remove("hidden");
614
- this[thumbnailElementSymbol].classList.remove("hidden");
615
-
616
- let slidesIndex = index + visibleSlides;
617
- this[configSymbol].currentIndex = index;
618
-
619
- if (slidesIndex < 0) {
620
- slidesIndex = totalSlides - 1 - visibleSlides;
621
- this[configSymbol].currentIndex = totalOriginSlides - 1;
622
- } else if (index > totalOriginSlides) {
623
- slidesIndex = 0;
624
- this[configSymbol].currentIndex = 0;
625
- }
626
-
627
- setMoveProperties.call(this, slides, slidesIndex);
628
-
629
- if (index === totalOriginSlides) {
630
- setTimeout(() => {
631
- getWindow().requestAnimationFrame(() => {
632
- moveTo.call(this, 0, false);
633
- });
634
- }, this.getOption("carousel.transition"));
635
- } else if (index === -1) {
636
- setTimeout(() => {
637
- getWindow().requestAnimationFrame(() => {
638
- moveTo.call(this, totalOriginSlides - 1, false);
639
- });
640
- }, this.getOption("carousel.transition"));
641
- }
642
-
643
- fireCustomEvent(this, "monster-slider-moved", {
644
- index: index,
645
- });
643
+ const { slides, totalSlides, originSlides, totalOriginSlides } =
644
+ getSlidesAndTotal.call(this);
645
+
646
+ // Remove/add 'animate' class to enable/disable CSS transitions
647
+ if (animation === false) {
648
+ this[sliderElementSymbol].classList.remove("animate");
649
+ } else {
650
+ this[sliderElementSymbol].classList.add("animate");
651
+ }
652
+
653
+ // Handle carousel looping logic
654
+ if (this.getOption("features.carousel") === true) {
655
+ if (index < 0) {
656
+ index = -1; // Will trigger the "jump" to the end
657
+ }
658
+
659
+ if (index > totalOriginSlides) {
660
+ index = totalOriginSlides; // Will trigger the "jump" to the start
661
+ }
662
+ } else {
663
+ // Handle non-carousel boundary logic
664
+ if (index < 0) {
665
+ index = 0;
666
+ }
667
+
668
+ if (index >= totalOriginSlides) {
669
+ index = totalOriginSlides - 1;
670
+ }
671
+ }
672
+
673
+ if (!isInteger(index)) {
674
+ return;
675
+ }
676
+
677
+ const visibleSlides = getVisibleSlidesFromContainerWidth.call(this);
678
+
679
+ // Hide controls if all original slides are visible
680
+ if (totalOriginSlides <= visibleSlides) {
681
+ this[prevElementSymbol].classList.add("hidden");
682
+ this[nextElementSymbol].classList.add("hidden");
683
+ this[thumbnailElementSymbol].classList.add("hidden");
684
+ return;
685
+ }
686
+ this[prevElementSymbol].classList.remove("hidden");
687
+ this[nextElementSymbol].classList.remove("hidden");
688
+ this[thumbnailElementSymbol].classList.remove("hidden");
689
+
690
+ // Calculate the actual index in the 'slides' array (which includes clones)
691
+ let slidesIndex = index + visibleSlides;
692
+ this[configSymbol].currentIndex = index;
693
+
694
+ if (slidesIndex < 0) {
695
+ // We are at the "pre-cloned" slides, set index to the end
696
+ slidesIndex = totalSlides - 1 - visibleSlides;
697
+ this[configSymbol].currentIndex = totalOriginSlides - 1;
698
+ } else if (index > totalOriginSlides) {
699
+ // We are at the "post-cloned" slides, set index to the start
700
+ slidesIndex = 0;
701
+ this[configSymbol].currentIndex = 0;
702
+ }
703
+
704
+ setMoveProperties.call(this, slides, slidesIndex);
705
+
706
+ // Handle the "jump" back to the start/end for seamless looping
707
+ if (index === totalOriginSlides) {
708
+ setTimeout(() => {
709
+ getWindow().requestAnimationFrame(() => {
710
+ moveTo.call(this, 0, false); // Jump to first slide without animation
711
+ });
712
+ }, this.getOption("carousel.transition"));
713
+ } else if (index === -1) {
714
+ setTimeout(() => {
715
+ getWindow().requestAnimationFrame(() => {
716
+ moveTo.call(this, totalOriginSlides - 1, false); // Jump to last slide without animation
717
+ });
718
+ }, this.getOption("carousel.transition"));
719
+ }
720
+
721
+ fireCustomEvent(this, "monster-slider-moved", {
722
+ index: this[configSymbol].currentIndex, // Fire with the "real" index
723
+ });
646
724
  }
647
725
 
648
726
  /**
649
727
  * @private
728
+ * @description Initializes all event handlers for navigation, dragging, and resizing.
650
729
  * @return {initEventHandler}
651
730
  * @fires monster-slider-resized
652
731
  */
653
732
  function initEventHandler() {
654
- const self = this;
655
-
656
- const nextElements = this[nextElementSymbol];
657
-
658
- if (nextElements) {
659
- nextElements.addEventListener("click", () => {
660
- self.next();
661
- });
662
- }
663
-
664
- const prevElements = this[prevElementSymbol];
665
-
666
- if (prevElements) {
667
- prevElements.addEventListener("click", () => {
668
- self.previous();
669
- });
670
- }
671
-
672
- if (this.getOption("features.drag")) {
673
- this[sliderElementSymbol].addEventListener("mousedown", (e) =>
674
- startDragging.call(this, e, "mouse"),
675
- );
676
-
677
- this[sliderElementSymbol].addEventListener("touchstart", (e) =>
678
- startDragging.call(this, e, "touch"),
679
- );
680
- }
681
-
682
- const initialSize = {
683
- width: this[sliderElementSymbol]?.offsetWidth || 0,
684
- height: this[sliderElementSymbol]?.offsetHeight || 0,
685
- };
686
-
687
- const resizeObserver = new ResizeObserver((entries) => {
688
- for (let entry of entries) {
689
- const { width, height } = entry.contentRect;
690
- if (width !== initialSize.width || height !== initialSize.height) {
691
- self.stopAutoPlay();
692
-
693
- if (this.getOption("features.thumbnails")) {
694
- initThumbnails.call(this);
695
- }
696
-
697
- const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
698
- this[sliderElementSymbol].style.setProperty(
699
- "--monster-slides-width",
700
- `${100 / slidesVisible}%`,
701
- );
702
-
703
- moveTo.call(self, 0, false);
704
- self.startAutoPlay();
705
-
706
- fireCustomEvent(self, "monster-slider-resized", {
707
- width: width,
708
- height: height,
709
- });
710
- }
711
- }
712
- });
713
-
714
- resizeObserver.observe(this[sliderElementSymbol]);
715
-
716
- return this;
733
+ const self = this;
734
+
735
+ const nextElements = this[nextElementSymbol];
736
+ if (nextElements) {
737
+ nextElements.addEventListener("click", () => {
738
+ self.next();
739
+ });
740
+ }
741
+
742
+ const prevElements = this[prevElementSymbol];
743
+ if (prevElements) {
744
+ prevElements.addEventListener("click", () => {
745
+ self.previous();
746
+ });
747
+ }
748
+
749
+ // Initialize drag-to-move event listeners
750
+ if (this.getOption("features.drag")) {
751
+ this[sliderElementSymbol].addEventListener("mousedown", (e) =>
752
+ startDragging.call(this, e, "mouse"),
753
+ );
754
+
755
+ this[sliderElementSymbol].addEventListener("touchstart", (e) =>
756
+ startDragging.call(this, e, "touch"),
757
+ );
758
+ }
759
+
760
+ const initialSize = {
761
+ width: this[sliderElementSymbol]?.offsetWidth || 0,
762
+ height: this[sliderElementSymbol]?.offsetHeight || 0,
763
+ };
764
+
765
+ // Observe slider size changes to update layout
766
+ const resizeObserver = new ResizeObserver((entries) => {
767
+ for (let entry of entries) {
768
+ const { width, height } = entry.contentRect;
769
+ if (width !== initialSize.width || height !== initialSize.height) {
770
+ self.stopAutoPlay();
771
+
772
+ // Re-init thumbnails if layout changes
773
+ if (this.getOption("features.thumbnails")) {
774
+ initThumbnails.call(this);
775
+ }
776
+
777
+ // Recalculate visible slides and update CSS property
778
+ const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
779
+ this[sliderElementSymbol].style.setProperty(
780
+ "--monster-slides-width",
781
+ `${100 / slidesVisible}%`,
782
+ );
783
+
784
+ // Move to start without animation
785
+ moveTo.call(self, 0, false);
786
+ self.startAutoPlay();
787
+
788
+ fireCustomEvent(self, "monster-slider-resized", {
789
+ width: width,
790
+ height: height,
791
+ });
792
+ }
793
+ }
794
+ });
795
+
796
+ resizeObserver.observe(this[sliderElementSymbol]);
797
+ this[configSymbol].resizeObserver = resizeObserver; // Store for cleanup
798
+
799
+ return this;
717
800
  }
718
801
 
719
802
  /**
720
803
  * @private
721
- * @param e
722
- * @param type
804
+ * @description Handles the "mousedown" or "touchstart" event to begin dragging.
805
+ * @param {Event} e - The mousedown or touchstart event.
806
+ * @param {string} type - The event type ("mouse" or "touch").
723
807
  */
724
808
  function startDragging(e, type) {
725
- const { slides } = getSlidesAndTotal.call(this);
726
-
727
- const widthOfSlider = slides[this[configSymbol].currentIndex]?.offsetWidth;
728
-
729
- this[configSymbol].isDragging = true;
730
- this[configSymbol].startPos = getPositionX(e, type);
731
- this[sliderElementSymbol].classList.add("grabbing");
732
- this[sliderElementSymbol].style.transitionProperty = "none";
733
-
734
- const callbackMousemove = (x) => {
735
- dragging.call(this, x, type);
736
- };
737
-
738
- const callbackMouseUp = () => {
739
- const endEvent = type === "mouse" ? "mouseup" : "touchend";
740
- const moveEvent = type === "mouse" ? "mousemove" : "touchmove";
741
-
742
- document.body.removeEventListener(endEvent, callbackMouseUp);
743
- document.body.removeEventListener(moveEvent, callbackMousemove);
744
-
745
- this[configSymbol].isDragging = false;
746
- this[configSymbol].startPos = 0;
747
- this[sliderElementSymbol].classList.remove("grabbing");
748
- this[sliderElementSymbol].style.transitionProperty = "";
749
-
750
- const lastPos = this[configSymbol].draggingPos;
751
- this[configSymbol].draggingPos = 0;
752
-
753
- let newIndex = this[configSymbol].currentIndex;
754
- const shift = lastPos / widthOfSlider;
755
- const shiftIndex = Math.round(shift);
756
-
757
- newIndex += shiftIndex * -1;
758
- this.moveTo(newIndex);
759
- };
760
-
761
- document.body.addEventListener("mouseup", callbackMouseUp);
762
- document.body.addEventListener("mousemove", callbackMousemove);
809
+ const { slides } = getSlidesAndTotal.call(this);
810
+
811
+ // Get the width of a single slide for calculating drag distance
812
+ const widthOfSlider = slides[this[configSymbol].currentIndex]?.offsetWidth;
813
+
814
+ // Set dragging state and initial position
815
+ this[configSymbol].isDragging = true;
816
+ this[configSymbol].startPos = getPositionX(e, type);
817
+ this[sliderElementSymbol].classList.add("grabbing");
818
+ // Disable transitions during drag for smooth movement
819
+ this[sliderElementSymbol].style.transitionProperty = "none";
820
+
821
+ const callbackMousemove = (x) => {
822
+ dragging.call(this, x, type);
823
+ };
824
+
825
+ const callbackMouseUp = () => {
826
+ const endEvent = type === "mouse" ? "mouseup" : "touchend";
827
+ const moveEvent = type === "mouse" ? "mousemove" : "touchmove";
828
+
829
+ // Clean up global event listeners
830
+ document.body.removeEventListener(endEvent, callbackMouseUp);
831
+ document.body.removeEventListener(moveEvent, callbackMousemove);
832
+
833
+ this[configSymbol].isDragging = false;
834
+ this[configSymbol].startPos = 0;
835
+ this[sliderElementSymbol].classList.remove("grabbing");
836
+ this[sliderElementSymbol].style.transitionProperty = ""; // Re-enable transitions
837
+
838
+ const lastPos = this[configSymbol].draggingPos;
839
+ this[configSymbol].draggingPos = 0;
840
+
841
+ // Calculate how many slides were "swiped" and move to the new index
842
+ let newIndex = this[configSymbol].currentIndex;
843
+ const shift = lastPos / widthOfSlider;
844
+ const shiftIndex = Math.round(shift);
845
+
846
+ newIndex += shiftIndex * -1;
847
+ this.moveTo(newIndex);
848
+ };
849
+
850
+ document.body.addEventListener("mouseup", callbackMouseUp);
851
+ document.body.addEventListener("mousemove", callbackMousemove);
852
+ document.body.addEventListener("touchend", callbackMouseUp);
853
+ document.body.addEventListener("touchmove", callbackMousemove);
763
854
  }
764
855
 
765
856
  /**
766
857
  * @private
767
- * @param e
768
- * @param type
769
- * @return {*|number|number}
858
+ * @description Get the X coordinate from a mouse or touch event.
859
+ * @param {Event} e - The mouse or touch event.
860
+ * @param {string} type - The event type ("mouse" or "touch").
861
+ * @return {number} The clientX position.
770
862
  */
771
863
  function getPositionX(e, type) {
772
- return type === "mouse" ? e.pageX : e.touches[0].clientX;
864
+ return type === "mouse" ? e.pageX : e.touches[0].clientX;
773
865
  }
774
866
 
775
867
  /**
776
868
  * @private
777
- * @param e
778
- * @param type
869
+ * @description Called on mousemove/touchmove to update the slider's transform.
870
+ * @param {Event} e - The mousemove or touchmove event.
871
+ * @param {string} type - The event type ("mouse" or "touch").
779
872
  */
780
873
  function dragging(e, type) {
781
- if (!this[configSymbol].isDragging) return;
782
- this[configSymbol].draggingPos =
783
- getPositionX(e, type) - this[configSymbol].startPos;
874
+ if (!this[configSymbol].isDragging) return;
875
+ this[configSymbol].draggingPos =
876
+ getPositionX(e, type) - this[configSymbol].startPos;
784
877
 
785
- this[sliderElementSymbol].style.transform =
786
- `translateX(calc(${this[configSymbol].lastOffset} + ${this[configSymbol].draggingPos}px))`;
878
+ // Update position based on drag delta
879
+ this[sliderElementSymbol].style.transform =
880
+ `translateX(calc(${this[configSymbol].lastOffset} + ${this[configSymbol].draggingPos}px))`;
787
881
  }
788
882
 
789
883
  /**
790
884
  * @private
885
+ * @description Caches references to key elements in the shadow DOM.
791
886
  * @return {void}
792
887
  */
793
888
  function initControlReferences() {
794
- this[controlElementSymbol] = this.shadowRoot.querySelector(
795
- `[${ATTRIBUTE_ROLE}="control"]`,
796
- );
889
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
890
+ `[${ATTRIBUTE_ROLE}="control"]`,
891
+ );
797
892
 
798
- this[sliderElementSymbol] = this.shadowRoot.querySelector(
799
- `[${ATTRIBUTE_ROLE}="slider"]`,
800
- );
893
+ this[sliderElementSymbol] = this.shadowRoot.querySelector(
894
+ `[${ATTRIBUTE_ROLE}="slider"]`,
895
+ );
801
896
 
802
- this[prevElementSymbol] = this.shadowRoot.querySelector(
803
- `[${ATTRIBUTE_ROLE}="prev"]`,
804
- );
897
+ this[prevElementSymbol] = this.shadowRoot.querySelector(
898
+ `[${ATTRIBUTE_ROLE}="prev"]`,
899
+ );
805
900
 
806
- this[nextElementSymbol] = this.shadowRoot.querySelector(
807
- `[${ATTRIBUTE_ROLE}="next"]`,
808
- );
901
+ this[nextElementSymbol] = this.shadowRoot.querySelector(
902
+ `[${ATTRIBUTE_ROLE}="next"]`,
903
+ );
809
904
 
810
- this[thumbnailElementSymbol] = this.shadowRoot.querySelector(
811
- `[${ATTRIBUTE_ROLE}="thumbnails"]`,
812
- );
905
+ this[thumbnailElementSymbol] = this.shadowRoot.querySelector(
906
+ `[${ATTRIBUTE_ROLE}="thumbnails"]`,
907
+ );
813
908
  }
814
909
 
815
910
  /**
816
911
  * @private
912
+ * @description Returns the HTML template string for the component's shadow DOM.
817
913
  * @return {string}
818
914
  */
819
915
  function getTemplate() {
820
- // language=HTML
821
- return `
916
+ // language=HTML
917
+ return `
822
918
  <div data-monster-role="control" part="control">
823
- <div class="prev" data-monster-role="prev" part="prev" part="prev">
919
+ <div class="prev" data-monster-role="prev" part="prev">
824
920
  <slot name="prev"></slot>
825
921
  </div>
826
922
  <div data-monster-role="slider" part="slides">
827
923
  <slot></slot>
828
924
  </div>
829
925
  <div data-monster-role="thumbnails" part="thumbnails"></div>
830
- <div class="next" data-monster-role="next" part="next" part="next">
926
+ <div class="next" data-monster-role="next" part="next">
831
927
  <slot name="next"></slot>
832
928
  </div>
833
929
  </div>`;