@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.
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/source/components/layout/slider.mjs +731 -635
|
@@ -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
|
-
|
|
18
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
274
|
-
|
|
313
|
+
if (this.getOption("features.thumbnails")) {
|
|
314
|
+
initThumbnails.call(this);
|
|
315
|
+
}
|
|
275
316
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
317
|
+
// Clones slides if carousel mode is active
|
|
318
|
+
initCarouselClones.call(this);
|
|
279
319
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
578
|
+
const nextIndex = this[configSymbol].currentIndex + 1;
|
|
509
579
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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
|
-
|
|
595
|
+
const prevIndex = this[configSymbol].currentIndex - 1;
|
|
527
596
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
});
|
|
597
|
+
// Use requestAnimationFrame for smooth transitions
|
|
598
|
+
getWindow().requestAnimationFrame(() => {
|
|
599
|
+
moveTo.call(this, prevIndex);
|
|
600
|
+
});
|
|
535
601
|
|
|
536
|
-
|
|
602
|
+
return 0;
|
|
537
603
|
}
|
|
538
604
|
|
|
539
605
|
/**
|
|
540
606
|
* @private
|
|
541
|
-
* @
|
|
542
|
-
* @param
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
612
|
+
// Remove 'current' class from all slides
|
|
613
|
+
slides.forEach((slide) => {
|
|
614
|
+
slide.classList.remove("current");
|
|
615
|
+
});
|
|
548
616
|
|
|
549
|
-
|
|
550
|
-
|
|
617
|
+
let offset = -(index * 100);
|
|
618
|
+
const slidesVisible = getVisibleSlidesFromContainerWidth.call(this);
|
|
551
619
|
|
|
552
|
-
|
|
620
|
+
offset = offset / slidesVisible;
|
|
553
621
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
622
|
+
if (offset !== 0) {
|
|
623
|
+
offset += "%";
|
|
624
|
+
}
|
|
557
625
|
|
|
558
|
-
|
|
559
|
-
|
|
626
|
+
this[sliderElementSymbol].style.transform =
|
|
627
|
+
`translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
|
|
560
628
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
* @
|
|
570
|
-
* @param {
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
* @
|
|
722
|
-
* @param
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
* @
|
|
768
|
-
* @param
|
|
769
|
-
* @
|
|
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
|
-
|
|
864
|
+
return type === "mouse" ? e.pageX : e.touches[0].clientX;
|
|
773
865
|
}
|
|
774
866
|
|
|
775
867
|
/**
|
|
776
868
|
* @private
|
|
777
|
-
* @
|
|
778
|
-
* @param
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
874
|
+
if (!this[configSymbol].isDragging) return;
|
|
875
|
+
this[configSymbol].draggingPos =
|
|
876
|
+
getPositionX(e, type) - this[configSymbol].startPos;
|
|
784
877
|
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
889
|
+
this[controlElementSymbol] = this.shadowRoot.querySelector(
|
|
890
|
+
`[${ATTRIBUTE_ROLE}="control"]`,
|
|
891
|
+
);
|
|
797
892
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
893
|
+
this[sliderElementSymbol] = this.shadowRoot.querySelector(
|
|
894
|
+
`[${ATTRIBUTE_ROLE}="slider"]`,
|
|
895
|
+
);
|
|
801
896
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
897
|
+
this[prevElementSymbol] = this.shadowRoot.querySelector(
|
|
898
|
+
`[${ATTRIBUTE_ROLE}="prev"]`,
|
|
899
|
+
);
|
|
805
900
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
901
|
+
this[nextElementSymbol] = this.shadowRoot.querySelector(
|
|
902
|
+
`[${ATTRIBUTE_ROLE}="next"]`,
|
|
903
|
+
);
|
|
809
904
|
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
|
|
821
|
-
|
|
916
|
+
// language=HTML
|
|
917
|
+
return `
|
|
822
918
|
<div data-monster-role="control" part="control">
|
|
823
|
-
<div class="prev" data-monster-role="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"
|
|
926
|
+
<div class="next" data-monster-role="next" part="next">
|
|
831
927
|
<slot name="next"></slot>
|
|
832
928
|
</div>
|
|
833
929
|
</div>`;
|