@schukai/monster 4.45.5 → 4.46.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.
@@ -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";
@@ -83,226 +83,226 @@ const configSymbol = Symbol("config");
83
83
  * @fires monster-slider-moved - Fired when the slider moves to a new slide.
84
84
  */
85
85
  class Slider extends CustomElement {
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
- }
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
+ }
306
306
  }
307
307
 
308
308
  /**
@@ -310,16 +310,16 @@ class Slider extends CustomElement {
310
310
  * @description Initializes the component structure, thumbnails, and autoplay.
311
311
  */
312
312
  function initStructure() {
313
- if (this.getOption("features.thumbnails")) {
314
- initThumbnails.call(this);
315
- }
313
+ if (this.getOption("features.thumbnails")) {
314
+ initThumbnails.call(this);
315
+ }
316
316
 
317
- // Clones slides if carousel mode is active
318
- initCarouselClones.call(this);
317
+ // Clones slides if carousel mode is active
318
+ initCarouselClones.call(this);
319
319
 
320
- if (this.getOption("features.autoPlay")) {
321
- initAutoPlay.call(this);
322
- }
320
+ if (this.getOption("features.autoPlay")) {
321
+ initAutoPlay.call(this);
322
+ }
323
323
  }
324
324
 
325
325
  /**
@@ -327,43 +327,43 @@ function initStructure() {
327
327
  * @description Generates the thumbnail navigation elements.
328
328
  */
329
329
  function initThumbnails() {
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
- });
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
+ });
367
367
  }
368
368
 
369
369
  /**
@@ -371,110 +371,110 @@ function initThumbnails() {
371
371
  * @description Initializes the autoplay functionality and its event handlers.
372
372
  */
373
373
  function initAutoPlay() {
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
- }
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
+ }
478
478
  }
479
479
 
480
480
  /**
@@ -483,30 +483,30 @@ function initAutoPlay() {
483
483
  * @return {number}
484
484
  */
485
485
  function getVisibleSlidesFromContainerWidth() {
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;
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;
510
510
  }
511
511
 
512
512
  /**
@@ -514,36 +514,36 @@ function getVisibleSlidesFromContainerWidth() {
514
514
  * @description Clones slides to create the "infinite" loop effect for the carousel.
515
515
  */
516
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
- }
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
+ }
547
547
  }
548
548
 
549
549
  /**
@@ -552,21 +552,21 @@ function initCarouselClones() {
552
552
  * @return {{slides: HTMLElement[], totalSlides: number, originSlides: HTMLElement[], totalOriginSlides: number}}
553
553
  */
554
554
  function getSlidesAndTotal() {
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 };
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 };
570
570
  }
571
571
 
572
572
  /**
@@ -575,15 +575,15 @@ function getSlidesAndTotal() {
575
575
  * @return {number}
576
576
  */
577
577
  function next() {
578
- const nextIndex = this[configSymbol].currentIndex + 1;
578
+ const nextIndex = this[configSymbol].currentIndex + 1;
579
579
 
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
- });
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
+ });
585
585
 
586
- return 0;
586
+ return 0;
587
587
  }
588
588
 
589
589
  /**
@@ -592,14 +592,14 @@ function next() {
592
592
  * @return {number}
593
593
  */
594
594
  function prev() {
595
- const prevIndex = this[configSymbol].currentIndex - 1;
595
+ const prevIndex = this[configSymbol].currentIndex - 1;
596
596
 
597
- // Use requestAnimationFrame for smooth transitions
598
- getWindow().requestAnimationFrame(() => {
599
- moveTo.call(this, prevIndex);
600
- });
597
+ // Use requestAnimationFrame for smooth transitions
598
+ getWindow().requestAnimationFrame(() => {
599
+ moveTo.call(this, prevIndex);
600
+ });
601
601
 
602
- return 0;
602
+ return 0;
603
603
  }
604
604
 
605
605
  /**
@@ -609,27 +609,27 @@ function prev() {
609
609
  * @param {number} index - The target slide index.
610
610
  */
611
611
  function setMoveProperties(slides, index) {
612
- // Remove 'current' class from all slides
613
- slides.forEach((slide) => {
614
- slide.classList.remove("current");
615
- });
612
+ // Remove 'current' class from all slides
613
+ slides.forEach((slide) => {
614
+ slide.classList.remove("current");
615
+ });
616
616
 
617
- let offset = -(index * 100);
618
- const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
617
+ let offset = -(index * 100);
618
+ const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
619
619
 
620
- offset = offset / slidesVisible;
620
+ offset = offset / slidesVisible;
621
621
 
622
- if (offset !== 0) {
623
- offset += "%";
624
- }
622
+ if (offset !== 0) {
623
+ offset += "%";
624
+ }
625
625
 
626
- this[sliderElementSymbol].style.transform =
627
- `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
626
+ this[sliderElementSymbol].style.transform =
627
+ `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
628
628
 
629
- if (slides[index]) {
630
- slides[index].classList.add("current");
631
- }
632
- this[configSymbol].lastOffset = offset;
629
+ if (slides[index]) {
630
+ slides[index].classList.add("current");
631
+ }
632
+ this[configSymbol].lastOffset = offset;
633
633
  }
634
634
 
635
635
  /**
@@ -640,87 +640,87 @@ function setMoveProperties(slides, index) {
640
640
  * @fires monster-slider-moved
641
641
  */
642
642
  function moveTo(index, animation) {
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
- });
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
+ });
724
724
  }
725
725
 
726
726
  /**
@@ -730,73 +730,73 @@ function moveTo(index, animation) {
730
730
  * @fires monster-slider-resized
731
731
  */
732
732
  function initEventHandler() {
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;
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;
800
800
  }
801
801
 
802
802
  /**
@@ -806,51 +806,51 @@ function initEventHandler() {
806
806
  * @param {string} type - The event type ("mouse" or "touch").
807
807
  */
808
808
  function startDragging(e, type) {
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);
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);
854
854
  }
855
855
 
856
856
  /**
@@ -861,7 +861,7 @@ function startDragging(e, type) {
861
861
  * @return {number} The clientX position.
862
862
  */
863
863
  function getPositionX(e, type) {
864
- return type === "mouse" ? e.pageX : e.touches[0].clientX;
864
+ return type === "mouse" ? e.pageX : e.touches[0].clientX;
865
865
  }
866
866
 
867
867
  /**
@@ -871,13 +871,13 @@ function getPositionX(e, type) {
871
871
  * @param {string} type - The event type ("mouse" or "touch").
872
872
  */
873
873
  function dragging(e, type) {
874
- if (!this[configSymbol].isDragging) return;
875
- this[configSymbol].draggingPos =
876
- getPositionX(e, type) - this[configSymbol].startPos;
874
+ if (!this[configSymbol].isDragging) return;
875
+ this[configSymbol].draggingPos =
876
+ getPositionX(e, type) - this[configSymbol].startPos;
877
877
 
878
- // Update position based on drag delta
879
- this[sliderElementSymbol].style.transform =
880
- `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))`;
881
881
  }
882
882
 
883
883
  /**
@@ -886,25 +886,25 @@ function dragging(e, type) {
886
886
  * @return {void}
887
887
  */
888
888
  function initControlReferences() {
889
- this[controlElementSymbol] = this.shadowRoot.querySelector(
890
- `[${ATTRIBUTE_ROLE}="control"]`,
891
- );
889
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
890
+ `[${ATTRIBUTE_ROLE}="control"]`,
891
+ );
892
892
 
893
- this[sliderElementSymbol] = this.shadowRoot.querySelector(
894
- `[${ATTRIBUTE_ROLE}="slider"]`,
895
- );
893
+ this[sliderElementSymbol] = this.shadowRoot.querySelector(
894
+ `[${ATTRIBUTE_ROLE}="slider"]`,
895
+ );
896
896
 
897
- this[prevElementSymbol] = this.shadowRoot.querySelector(
898
- `[${ATTRIBUTE_ROLE}="prev"]`,
899
- );
897
+ this[prevElementSymbol] = this.shadowRoot.querySelector(
898
+ `[${ATTRIBUTE_ROLE}="prev"]`,
899
+ );
900
900
 
901
- this[nextElementSymbol] = this.shadowRoot.querySelector(
902
- `[${ATTRIBUTE_ROLE}="next"]`,
903
- );
901
+ this[nextElementSymbol] = this.shadowRoot.querySelector(
902
+ `[${ATTRIBUTE_ROLE}="next"]`,
903
+ );
904
904
 
905
- this[thumbnailElementSymbol] = this.shadowRoot.querySelector(
906
- `[${ATTRIBUTE_ROLE}="thumbnails"]`,
907
- );
905
+ this[thumbnailElementSymbol] = this.shadowRoot.querySelector(
906
+ `[${ATTRIBUTE_ROLE}="thumbnails"]`,
907
+ );
908
908
  }
909
909
 
910
910
  /**
@@ -913,8 +913,8 @@ function initControlReferences() {
913
913
  * @return {string}
914
914
  */
915
915
  function getTemplate() {
916
- // language=HTML
917
- return `
916
+ // language=HTML
917
+ return `
918
918
  <div data-monster-role="control" part="control">
919
919
  <div class="prev" data-monster-role="prev" part="prev">
920
920
  <slot name="prev"></slot>