@schukai/monster 3.73.9 → 3.74.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.
@@ -0,0 +1,668 @@
1
+ /**
2
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
11
+ */
12
+
13
+ import {instanceSymbol} from "../../constants.mjs";
14
+ import {addAttributeToken} from "../../dom/attributes.mjs";
15
+ import {
16
+ ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_PREFIX,
17
+ ATTRIBUTE_ROLE,
18
+ } from "../../dom/constants.mjs";
19
+ import {CustomControl} from "../../dom/customcontrol.mjs";
20
+ import {CustomElement, getSlottedElements} from "../../dom/customelement.mjs";
21
+ import {
22
+ assembleMethodSymbol,
23
+ registerCustomElement,
24
+ } from "../../dom/customelement.mjs";
25
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
26
+ import {isFunction} from "../../types/is.mjs";
27
+ import {SliderStyleSheet} from "./stylesheet/slider.mjs";
28
+ import {fireCustomEvent} from "../../dom/events.mjs";
29
+
30
+ import {getWindow} from "../../dom/util.mjs";
31
+
32
+ export {Slider};
33
+
34
+ /**
35
+ * @private
36
+ * @type {symbol}
37
+ */
38
+ const sliderElementSymbol = Symbol("sliderElement");
39
+
40
+ /**
41
+ * @private
42
+ * @type {symbol}
43
+ */
44
+ const controlElementSymbol = Symbol("controlElement");
45
+
46
+ /**
47
+ * @private
48
+ * @type {symbol}
49
+ */
50
+ const prevElementSymbol = Symbol("prevElement");
51
+
52
+ /**
53
+ * @private
54
+ * @type {symbol}
55
+ */
56
+ const nextElementSymbol = Symbol("nextElement");
57
+
58
+ /**
59
+ * @private
60
+ * @type {symbol}
61
+ */
62
+ const configSymbol = Symbol("config");
63
+
64
+ /**
65
+ * @private
66
+ * @type {string}
67
+ */
68
+ const ATTRIBUTE_CLON_FROM = ATTRIBUTE_PREFIX + "clone-from"
69
+
70
+ /**
71
+ * A Slider
72
+ *
73
+ * @fragments /fragments/components/form/slider/
74
+ *
75
+ * @example /examples/components/form/slider-simple
76
+ *
77
+ * @since 3.74.0
78
+ * @copyright schukai GmbH
79
+ * @summary A beautiful Slider that can make your life easier and also looks good.
80
+ */
81
+ class Slider extends CustomElement {
82
+ /**
83
+ * This method is called by the `instanceof` operator.
84
+ * @returns {symbol}
85
+ */
86
+ static get [instanceSymbol]() {
87
+ return Symbol.for("@schukai/monster/components/layout/slider@@instance");
88
+ }
89
+
90
+ /**
91
+ *
92
+ * @return {Components.Layout.Slider
93
+ */
94
+ [assembleMethodSymbol]() {
95
+ super[assembleMethodSymbol]();
96
+
97
+ this[configSymbol] = {
98
+ currentIndex: 0,
99
+
100
+ isDragging: false,
101
+ draggingPos: 0,
102
+ startPos: 0,
103
+ // currentTranslate: 0,
104
+ // prevTranslate: 0,
105
+ //
106
+ // startPositions: null,
107
+ // currentPositions: null,
108
+
109
+ autoPlayInterval: null,
110
+ }
111
+
112
+ initControlReferences.call(this);
113
+ initEventHandler.call(this);
114
+ initStructure.call(this);
115
+
116
+ return this;
117
+ }
118
+
119
+ /**
120
+ * To set the options via the html tag the attribute `data-monster-options` must be used.
121
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
122
+ *
123
+ * The individual configuration values can be found in the table.
124
+ *
125
+ * @property {Object} templates Template definitions
126
+ * @property {string} templates.main Main template
127
+ * @property {Object} labels Label definitions
128
+ * @property {Object} actions Callbacks
129
+ * @property {string} actions.click="throw Error" Callback when clicked
130
+ * @property {Object} features Features
131
+ * @property {Object} classes CSS classes
132
+ * @property {boolean} disabled=false Disabled state
133
+ */
134
+ get defaults() {
135
+ return Object.assign({}, super.defaults, {
136
+ templates: {
137
+ main: getTemplate(),
138
+ },
139
+ labels: {},
140
+ classes: {},
141
+ disabled: false,
142
+
143
+ features: {
144
+ carousel: false,
145
+ autoPlay: true,
146
+ thumbnails: true,
147
+ },
148
+
149
+ autoPlay: {
150
+ delay: 1500,
151
+ startDelay: 1000,
152
+ direction: "next",
153
+ mouseOverPause: true,
154
+ touchPause: true,
155
+ drag: true,
156
+ },
157
+
158
+ actions: {
159
+ click: () => {
160
+ throw new Error("the click action is not defined");
161
+ },
162
+ }
163
+ });
164
+ }
165
+
166
+ /**
167
+ * @return {string}
168
+ */
169
+ static getTag() {
170
+ return "monster-slider";
171
+ }
172
+
173
+ /**
174
+ * @return {CSSStyleSheet[]}
175
+ */
176
+ static getCSSStyleSheet() {
177
+ return [SliderStyleSheet];
178
+ }
179
+
180
+ /**
181
+ * moves the slider to the given index
182
+ *
183
+ * @param index
184
+ * @returns {void}
185
+ */
186
+ moveTo(index) {
187
+ return moveTo.call(this, index)
188
+ }
189
+
190
+ /**
191
+ * shows the previous slide
192
+ *
193
+ * @return {void}
194
+ */
195
+ previous() {
196
+ return prev.call(this);
197
+ }
198
+
199
+ /**
200
+ * shows the next slide
201
+ *
202
+ * @return {void}
203
+ */
204
+ next() {
205
+ return next.call(this);
206
+ }
207
+
208
+ /**
209
+ * stops the auto play
210
+ *
211
+ * @return {void}
212
+ */
213
+ stopAutoPlay() {
214
+ if (this[configSymbol].autoPlayInterval) {
215
+ clearInterval(this[configSymbol].autoPlayInterval);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * starts the auto play
221
+ *
222
+ * @return {void}
223
+ */
224
+ startAutoPlay() {
225
+ initAutoPlay.call(this);
226
+ }
227
+
228
+ }
229
+
230
+ /**
231
+ * @private
232
+ * @param name
233
+ */
234
+ function initNavigation(name) {
235
+ const element = this.shadowRoot.querySelector("." + name + "");
236
+ const elementHeight = element.offsetHeight;
237
+ element.style.top = `calc(50% - ${elementHeight / 2}px)`;
238
+ }
239
+
240
+ /**
241
+ * @private
242
+ */
243
+ function initStructure() {
244
+ initNavigation.call(this, "next");
245
+ initNavigation.call(this, "prev");
246
+
247
+ if (this.getOption("features.thumbnails")) {
248
+ initThumbnails.call(this);
249
+ }
250
+
251
+ if (this.getOption("features.carousel")) {
252
+ initCarousel.call(this);
253
+ }
254
+
255
+ if (this.getOption("features.autoPlay")) {
256
+ initAutoPlay.call(this);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * @private
262
+ */
263
+ function initThumbnails() {
264
+ const self = this;
265
+ const thumbnails = this.shadowRoot.querySelector("[data-monster-role='thumbnails']");
266
+ const slides = Array.from(getSlottedElements.call(this, ":scope", null));
267
+
268
+ slides.forEach((slide, index) => {
269
+ const thumbnail = document.createElement("div");
270
+ thumbnail.classList.add("thumbnail");
271
+ thumbnail.addEventListener("click", () => {
272
+ if (self.getOption("features.carousel")) {
273
+ this.moveTo(index + 1);
274
+ } else {
275
+ this.moveTo(index);
276
+ }
277
+ });
278
+
279
+ thumbnails.appendChild(thumbnail);
280
+ })
281
+
282
+ this.addEventListener("monster-slider-moved", (e) => {
283
+ const index = e.detail.index;
284
+ const thumbnail = thumbnails.children[index];
285
+
286
+ if (!thumbnail) {
287
+ return;
288
+ }
289
+
290
+ Array.from(thumbnails.children).forEach((thumb) => {
291
+ thumb.classList.remove("current");
292
+ })
293
+
294
+ thumbnail.classList.add("current");
295
+ })
296
+ }
297
+
298
+ /**
299
+ * @private
300
+ */
301
+ function initAutoPlay() {
302
+ const self = this;
303
+ const autoPlay = this.getOption("autoPlay");
304
+ const delay = autoPlay.delay;
305
+ const startDelay = autoPlay.startDelay;
306
+ const direction = autoPlay.direction;
307
+
308
+ function start() {
309
+
310
+ if (self[configSymbol].autoPlayInterval) {
311
+ clearInterval(self[configSymbol].autoPlayInterval);
312
+ }
313
+
314
+ self[configSymbol].autoPlayInterval = setInterval(() => {
315
+ if (direction === "next") {
316
+ if(self.next() === -1) {
317
+ if (self.getOption("features.carousel")) {
318
+ clearInterval(self[configSymbol].autoPlayInterval);
319
+ }
320
+ }
321
+ } else {
322
+ if (self.previous() === -1) {
323
+ if (self.getOption("features.carousel")) {
324
+ clearInterval(self[configSymbol].autoPlayInterval);
325
+ }
326
+ }
327
+ }
328
+ }, delay);
329
+ }
330
+
331
+ setTimeout(() => {
332
+ start();
333
+ }, startDelay);
334
+
335
+ if (autoPlay.mouseOverPause) {
336
+ this.addEventListener("mouseover", () => {
337
+ clearInterval(this[configSymbol].autoPlayInterval);
338
+ });
339
+
340
+ this.addEventListener("mouseout", () => {
341
+ if (this[configSymbol].isDragging) {
342
+ return;
343
+ }
344
+ start();
345
+ });
346
+ }
347
+
348
+ if (autoPlay.touchPause) {
349
+ this.addEventListener("touchstart", () => {
350
+ clearInterval(this[configSymbol].autoPlayInterval);
351
+ });
352
+
353
+ this.addEventListener("touchend", () => {
354
+ start();
355
+ });
356
+ }
357
+
358
+ }
359
+
360
+ /**
361
+ * @private
362
+ */
363
+ function initCarousel() {
364
+
365
+ const {slides, totalSlides} = getSlidesAndTotal.call(this);
366
+ if (this.getOption("features.carousel") && totalSlides > 2) {
367
+
368
+ const firstElement = slides[0].cloneNode(true);
369
+ firstElement.setAttribute(ATTRIBUTE_CLON_FROM, 1);
370
+
371
+ const lastElement = slides[totalSlides - 1].cloneNode(true);
372
+ lastElement.setAttribute(ATTRIBUTE_CLON_FROM, totalSlides);
373
+ slides[totalSlides - 1].insertAdjacentElement("afterend", firstElement);
374
+
375
+ slides[0].insertAdjacentElement("beforebegin", lastElement);
376
+
377
+ moveTo.call(this, 1);
378
+
379
+ }
380
+ }
381
+
382
+ /**
383
+ * @private
384
+ * @returns {{slides: unknown[], totalSlides: number}}
385
+ */
386
+ function getSlidesAndTotal() {
387
+ const slides = Array.from(getSlottedElements.call(this, ":scope", null));
388
+ const totalSlides = slides.length;
389
+ return {slides, totalSlides};
390
+ }
391
+
392
+ /**
393
+ * @private
394
+ * @returns {number}
395
+ */
396
+ function next() {
397
+
398
+ const {slides, totalSlides} = getSlidesAndTotal.call(this);
399
+ const nextIndex = this[configSymbol].currentIndex + 1;
400
+
401
+ if (nextIndex >= totalSlides) {
402
+ return -1;
403
+ }
404
+
405
+ queueMicrotask(() => {
406
+ getWindow().requestAnimationFrame(() => {
407
+ getWindow().requestAnimationFrame(() => {
408
+ moveTo.call(this, nextIndex);
409
+ });
410
+ });
411
+ });
412
+
413
+ return 0;
414
+ }
415
+
416
+ /**
417
+ * @private
418
+ * @returns {number}
419
+ */
420
+ function prev() {
421
+
422
+ const prevIndex = this[configSymbol].currentIndex - 1;
423
+
424
+ if (prevIndex < 0) {
425
+ return -1;
426
+ }
427
+
428
+ moveTo.call(this, prevIndex);
429
+ return 0;
430
+ }
431
+
432
+ /**
433
+ * @private
434
+ * @param slides
435
+ * @param index
436
+ */
437
+ function setMoveProperties(slides, index) {
438
+
439
+ this[configSymbol].currentIndex = index
440
+
441
+ slides.forEach((slide) => {
442
+ slide.classList.remove("current");
443
+ })
444
+
445
+ let offset = -(index * 100);
446
+ if (offset !== 0) {
447
+ offset += "%";
448
+ }
449
+
450
+ this[sliderElementSymbol].style.transform = `translateX(calc(${offset} + ${this[configSymbol].draggingPos}px))`;
451
+ slides[index].classList.add("current");
452
+
453
+ }
454
+
455
+ /**
456
+ * @private
457
+ * @param index
458
+ * @fires monster-slider-moved
459
+ */
460
+ function moveTo(index) {
461
+
462
+ const {slides, totalSlides} = getSlidesAndTotal.call(this);
463
+
464
+ if (index < 0) {
465
+ index = totalSlides - 1
466
+ } else if (index >= totalSlides) {
467
+ index = 0
468
+ }
469
+
470
+ const slider = this[sliderElementSymbol];
471
+
472
+ setMoveProperties.call(this, slides, index);
473
+
474
+ const style = getComputedStyle(this[sliderElementSymbol]);
475
+ const duration = style.transitionDuration;
476
+ const durationMilis = parseFloat(duration) * 1000;
477
+
478
+ let slideIndex = index;
479
+ let eventFired = false;
480
+
481
+ if (this.getOption("features.carousel")) {
482
+ slideIndex = index - 1;
483
+
484
+ if (slides[index].hasAttribute(ATTRIBUTE_CLON_FROM)) {
485
+ const from = parseInt(slides[index].getAttribute(ATTRIBUTE_CLON_FROM));
486
+
487
+ getWindow().requestAnimationFrame(() => {
488
+ getWindow().requestAnimationFrame(() => {
489
+ setTimeout(() => {
490
+ slider.style.transitionProperty = "none";
491
+
492
+ setMoveProperties.call(this, slides, from);
493
+ slideIndex = from - 1;
494
+
495
+ getWindow().requestAnimationFrame(() => {
496
+ getWindow().requestAnimationFrame(() => {
497
+ slider.style.transitionProperty = "";
498
+
499
+ fireCustomEvent(this, "monster-slider-moved", {
500
+ index: slideIndex,
501
+ })
502
+
503
+ eventFired = true;
504
+ });
505
+ });
506
+ }, durationMilis);
507
+ });
508
+ });
509
+ }
510
+ }
511
+
512
+ if (!eventFired) {
513
+ fireCustomEvent(this, "monster-slider-moved", {
514
+ index: slideIndex,
515
+ })
516
+ }
517
+ }
518
+
519
+
520
+ /**
521
+ * @private
522
+ * @return {initEventHandler}
523
+ */
524
+ function initEventHandler() {
525
+ const self = this;
526
+
527
+ const nextElements = this[nextElementSymbol];
528
+
529
+ if (nextElements) {
530
+ nextElements.addEventListener("click", () => {
531
+ self.next();
532
+ });
533
+ }
534
+
535
+ const prevElements = this[prevElementSymbol];
536
+
537
+ if (prevElements) {
538
+ prevElements.addEventListener("click", () => {
539
+ self.previous();
540
+ });
541
+ }
542
+
543
+ this[sliderElementSymbol].addEventListener('mousedown', (e) => startDragging.call(this, e, 'mouse'));
544
+ this[sliderElementSymbol].addEventListener('touchstart', (e) => startDragging.call(this, e, 'touch'));
545
+ // this[sliderElementSymbol].addEventListener('touchend', () => stopDragging.call(this, 'touch'));
546
+ // this[sliderElementSymbol].addEventListener('touchmove', (e) => dragging.call(this, e, 'touch'));
547
+ return this;
548
+ }
549
+
550
+ /**
551
+ * @private
552
+ * @param e
553
+ * @param type
554
+ */
555
+ function startDragging(e, type) {
556
+
557
+ this[configSymbol].isDragging = true;
558
+ this[configSymbol].startPos = getPositionX(e, type);
559
+ this[sliderElementSymbol].classList.add('grabbing');
560
+ this[sliderElementSymbol].style.transitionProperty = "none";
561
+
562
+ const callbackMousemove = (x) => {
563
+ dragging.call(this, x, type);
564
+ }
565
+
566
+ const callbackMouseUp = () => {
567
+
568
+ const endEvent = type === 'mouse' ? 'mouseup' : 'touchend';
569
+ const moveEvent = type === 'mouse' ? 'mousemove' : 'touchmove';
570
+
571
+ document.body.removeEventListener(endEvent, callbackMouseUp);
572
+ document.body.removeEventListener(moveEvent, callbackMousemove);
573
+
574
+ this[configSymbol].isDragging = false;
575
+ this[configSymbol].startPos = 0;
576
+ this[sliderElementSymbol].classList.remove('grabbing');
577
+ this[sliderElementSymbol].style.transitionProperty = "";
578
+
579
+ const lastPos = this[configSymbol].draggingPos;
580
+ const widthOfSlider = this[sliderElementSymbol].offsetWidth;
581
+ this[configSymbol].draggingPos = 0;
582
+
583
+ let newIndex = this[configSymbol].currentIndex;
584
+
585
+ const x = lastPos / widthOfSlider;
586
+ if (x > 0.5) {
587
+ newIndex--;
588
+ } else if (x < -0.5) {
589
+ newIndex++;
590
+ }
591
+
592
+ this.moveTo(newIndex);
593
+
594
+ }
595
+
596
+ document.body.addEventListener('mouseup', callbackMouseUp);
597
+ document.body.addEventListener('mousemove', callbackMousemove);
598
+
599
+ }
600
+
601
+ /**
602
+ * @private
603
+ * @param e
604
+ * @param type
605
+ * @returns {*|number|number}
606
+ */
607
+ function getPositionX(e, type) {
608
+ return type === 'mouse' ? e.pageX : e.touches[0].clientX;
609
+ }
610
+
611
+ /**
612
+ * @private
613
+ * @param e
614
+ * @param type
615
+ */
616
+ function dragging(e, type) {
617
+ if (!this[configSymbol].isDragging) return;
618
+ this[configSymbol].draggingPos = getPositionX(e, type) - this[configSymbol].startPos;
619
+ const {slides, totalSlides} = getSlidesAndTotal.call(this);
620
+ setMoveProperties.call(this, slides, this[configSymbol].currentIndex);
621
+ }
622
+
623
+ /**
624
+ * @private
625
+ * @return {void}
626
+ */
627
+ function initControlReferences() {
628
+
629
+ this[controlElementSymbol] = this.shadowRoot.querySelector(
630
+ `[${ATTRIBUTE_ROLE}="control"]`,
631
+ );
632
+
633
+ this[sliderElementSymbol] = this.shadowRoot.querySelector(
634
+ `[${ATTRIBUTE_ROLE}="slider"]`,
635
+ );
636
+
637
+ this[prevElementSymbol] = this.shadowRoot.querySelector(
638
+ `[${ATTRIBUTE_ROLE}="prev"]`,
639
+ );
640
+
641
+ this[nextElementSymbol] = this.shadowRoot.querySelector(
642
+ `[${ATTRIBUTE_ROLE}="next"]`,
643
+ );
644
+ }
645
+
646
+ /**
647
+ * @private
648
+ * @return {string}
649
+ */
650
+ function getTemplate() {
651
+ // language=HTML
652
+ return `
653
+ <div data-monster-role="control" part="control">
654
+ <div class="prev" data-monster-role="prev" part="prev">
655
+ <slot name="prev"></slot>
656
+ </div>
657
+ <div data-monster-role="slider">
658
+ <slot></slot>
659
+ </div>
660
+ <div data-monster-role="thumbnails"></div>
661
+ <div class="next" data-monster-role="next" part="next">
662
+ <slot name="next"></slot>
663
+ </div>
664
+ </div>`;
665
+ }
666
+
667
+
668
+ registerCustomElement(Slider);