PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / trunk
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts vtrunk
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / src / features / carousel / carousel-script.js

carousel-script.js in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts trunk, at src/features/carousel/carousel-script.js

983 lines 24.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 BaseComponent,
3 exposeComponent,
4 triggerEvent,
5 parseJSON,
6 } from "../../utils/dom";
7
8 /**
9 * External dependencies
10 */
11 import Swiper from "swiper";
12
13 import {
14 Navigation,
15 Pagination,
16 Scrollbar,
17 A11y,
18 Autoplay,
19 Thumbs,
20 EffectFade,
21 EffectCube,
22 EffectFlip,
23 EffectCoverflow,
24 EffectCreative,
25 EffectCards,
26 } from "swiper/modules";
27
28 Swiper.use([
29 Navigation,
30 Pagination,
31 Scrollbar,
32 A11y,
33 Autoplay,
34 Thumbs,
35 EffectFade,
36 EffectCube,
37 EffectFlip,
38 EffectCoverflow,
39 EffectCreative,
40 EffectCards,
41 ]);
42
43 /**
44 * Internal dependencies
45 */
46 import { deepMerge, compose, createStyleManager } from "./utils-for-frontend";
47
48 class CarouselScript extends BaseComponent {
49 constructor(element, options = {}) {
50 // Get this._element and this._config
51 super(element, options);
52
53 // Bail if the element is not a DOM node
54 if (!element) {
55 return;
56 }
57
58 // Already initialized.
59 this.cbbCarouselId = element.dataset.cbbCarouselId;
60 if (this.cbbCarouselId && this.slider) {
61 return this;
62 }
63
64 // Store the main element
65 this.sliderElement = this._element;
66
67 // Store the original source
68 this.originalSource = element.cloneNode(true);
69
70 // Get the owner document
71 this.document = element.ownerDocument;
72
73 // Cache the config
74 this.settings = this._config;
75 const { datasetSelector } = this.settings;
76
77 // Get the datasetElement
78 this.datasetElement = datasetSelector
79 ? element.querySelector(datasetSelector) || element
80 : element;
81
82 // Get slider options
83 this.sliderOptions = this.getSliderOptions(options?.sliderOptions);
84
85 // The slider instance
86 this.slider = null;
87
88 // Kick start
89 this.init();
90 }
91
92 static get NAME() {
93 return "carousel";
94 }
95
96 defaultConfig() {
97 return {
98 selector: ".js-carousel-layout",
99 wrapperSelector: ".carousel__inner",
100 itemSelector: ".is-carousel-item",
101 containerClass: "swiper",
102 wrapperClass: "swiper-wrapper",
103 itemClass: "swiper-slide",
104 paginationClass: "swiper-pagination",
105 navigationNextClass: "swiper-button-next",
106 navigationPrevClass: "swiper-button-prev",
107 scrollbarClass: "swiper-scrollbar",
108 preloadClass: "cbb-carousel-preload",
109 instanceId: "cbb-carousel-1",
110 datasetSelector: "",
111 };
112 }
113
114 getSliderOptions(options) {
115 const slideChangeStart = (swiper) =>
116 triggerEvent(window, "cbb.carousel.slideChangeTransitionStart", {
117 swiper,
118 });
119
120 const slideChangeEnd = (swiper) =>
121 triggerEvent(window, "cbb.carousel.slideChangeTransitionEnd", {
122 swiper,
123 });
124
125 // Slider default options
126 let sliderOptions = {
127 // Optional parameters
128 direction: "horizontal",
129 loop: false,
130 grabCursor: true,
131 pagination: true,
132 navigation: false,
133 scrollbar: false,
134
135 // Slides per view
136 slidesPerView: 1,
137 slidesPerGroup: 1,
138 spaceBetween: 0,
139 watchSlidesProgress: true,
140 on: {
141 afterInit(swiper) {
142 triggerEvent(window, "cbb.carousel.afterInit", { swiper });
143 },
144 slideChangeTransitionStart: slideChangeStart,
145 slideChangeTransitionEnd: slideChangeEnd,
146 slideResetTransitionStart: slideChangeStart,
147 slideResetTransitionEnd: slideChangeEnd,
148 },
149 };
150
151 // Parse settings from attribute
152 const carouselSettings = this.datasetElement.dataset?.carouselSettings;
153 const attributeOptions = parseJSON(carouselSettings);
154
155 // Merge with dataset attribute
156 sliderOptions = deepMerge(sliderOptions, attributeOptions);
157
158 // Merge with options
159 if (options) {
160 sliderOptions = deepMerge(sliderOptions, options);
161 }
162
163 // Get slider options
164 return sliderOptions;
165 }
166
167 init() {
168 if (this.cbbCarouselId) {
169 this.restoreSlider();
170 return;
171 }
172
173 // Setup the slider.
174 this.buildSlider();
175 }
176
177 buildSlider() {
178 const { sliderElement, sliderOptions } = this;
179 const {
180 itemSelector,
181 itemClass,
182 wrapperSelector,
183 wrapperClass,
184 containerClass,
185 preloadClass,
186 instanceId,
187 } = this.settings;
188
189 // Query slide items
190 const sliderItems = Array.from(
191 sliderElement.querySelectorAll(itemSelector),
192 );
193
194 // Cache it
195 this.sliderItems = sliderItems;
196
197 // Add item class
198 sliderItems.forEach((item) => this.addClass(item, itemClass));
199
200 // The wrapper element
201 let sliderWrapper = sliderElement.querySelector(wrapperSelector);
202
203 if (sliderWrapper) {
204 this.hasSlideWrapper = true;
205 } else {
206 this.hasSlideWrapper = false;
207
208 // Create wrapper element
209 sliderWrapper = this.document.createElement("div");
210 }
211
212 // Cache it
213 this.sliderWrapper = sliderWrapper;
214
215 // Add swiper class
216 this.addClass(sliderWrapper, wrapperClass);
217
218 const {
219 equalHeight,
220 effect,
221 centeredSlides,
222 centeredSlidesSettings = {},
223 } = sliderOptions;
224
225 // Make all items have equal height
226 if (equalHeight) {
227 this.addClass(sliderWrapper, "is-equal-height");
228 }
229
230 // Get slider style and add custom variables
231 const sliderStyle = this.getSliderStyle();
232
233 // centeredSlides
234 if (
235 effect === "slide" &&
236 centeredSlides &&
237 centeredSlidesSettings?.enable
238 ) {
239 let inactiveSize = parseFloat(centeredSlidesSettings?.inactiveSize);
240
241 if (!isNaN(inactiveSize)) {
242 this.addClass(sliderWrapper, "centered-active-slide");
243 sliderStyle.add("--swiper-inactive-scale", inactiveSize);
244 }
245 }
246
247 if (!this.hasSlideWrapper) {
248 // Attach items
249 sliderWrapper.append(...sliderItems);
250 }
251
252 // Create the container
253 const sliderContainer = Object.assign(this.document.createElement("div"), {
254 className: containerClass,
255 });
256
257 // Cache it
258 this.sliderContainer = sliderContainer;
259
260 // Wrap the slider wrapper inside a container
261 sliderContainer.appendChild(sliderWrapper);
262 sliderElement.appendChild(sliderContainer);
263
264 // Build navigation
265 this.buildNavigation();
266
267 // Build play/pause
268 this.buildPlayPause();
269
270 // Build pagination
271 this.buildPagination();
272
273 // Build scrollbar
274 this.buildScrollbar();
275
276 // Mark it as initialized.
277 this.addClass(sliderElement, "slider-initialized");
278
279 // Remove the preload class
280 this.removeClass(sliderElement, preloadClass);
281
282 // Apply style for all custom properties from sub build functions
283 sliderStyle.apply();
284
285 // Initialize the slider.
286 this.slider = new Swiper(sliderContainer, sliderOptions);
287
288 // Save the instance to the element.
289 sliderElement.setAttribute("data-cbb-carousel-id", instanceId);
290 }
291
292 restoreSlider() {
293 const swiperElement = this._element.querySelector(
294 `.${this.settings.containerClass}`,
295 );
296
297 if (!swiperElement) {
298 this.buildSlider();
299
300 return;
301 }
302
303 /**
304 * Reconnect existing controls
305 */
306 const { cbbCarouselId } = this;
307 const sliderOptions = { ...this.sliderOptions };
308
309 // Navigation
310 if (sliderOptions.navigation) {
311 sliderOptions.navigation = {
312 ...sliderOptions.navigationSettings,
313 nextEl: this.document.getElementById(
314 `${cbbCarouselId}-navigation-next`,
315 ),
316 prevEl: this.document.getElementById(
317 `${cbbCarouselId}-navigation-prev`,
318 ),
319 };
320 }
321
322 // Pagination
323 if (sliderOptions.pagination) {
324 sliderOptions.pagination = this.buildPaginationOptions({
325 ...sliderOptions.paginationSettings,
326 el: this.document.getElementById(`${cbbCarouselId}-pagination`),
327 });
328 }
329
330 // Scrollbar
331 if (sliderOptions.scrollbar) {
332 sliderOptions.scrollbar = {
333 ...sliderOptions.scrollbarSettings,
334 el: this.document.getElementById(`${cbbCarouselId}-scrollbar`),
335 };
336 }
337
338 // Reconnect play/pause
339 if (sliderOptions?.playPause?.enable) {
340 this.sliderPlayPause = this.document.getElementById(
341 `${cbbCarouselId}-play-pause`,
342 );
343
344 if (this.sliderPlayPause) {
345 // Bind events
346 this.bindPlayPause(this.sliderPlayPause);
347 }
348 }
349
350 // Recreate swiper instance only
351 this.slider = new Swiper(swiperElement, sliderOptions);
352 }
353
354 dispose() {
355 super.dispose();
356
357 if (this.slider) {
358 // Swiper's destroy
359 this.slider.destroy();
360
361 // Remove the instance
362 this.slider = null;
363
364 if (!this.settings?.isClone) {
365 // Restore the original source
366 this.sliderElement.replaceWith(this.originalSource);
367 }
368 }
369 }
370
371 /**
372 * Build slider controls
373 */
374 buildSliderControls() {
375 if (!this.sliderControls) {
376 // Create the control wrapper
377 const sliderControls = this.document.createElement("div");
378
379 // Add id
380 sliderControls.setAttribute("id", `${this.settings.instanceId}-controls`);
381
382 // Add class
383 sliderControls.classList.add("cbb-carousel-controls");
384
385 // Cache it
386 this.sliderControls = sliderControls;
387
388 // Add the controls to the slider element
389 this.sliderElement.appendChild(sliderControls);
390 }
391
392 return this.sliderControls;
393 }
394
395 hasCarouselControls(controlType = "") {
396 const {
397 pagination,
398 paginationSettings,
399 direction,
400 navigation,
401 navigationSettings = {},
402 playPause = {},
403 } = this.sliderOptions;
404
405 if (!pagination || direction !== "horizontal") {
406 return false;
407 }
408
409 const withPagination = "with-pagination";
410 const hasNavigation =
411 navigation && navigationSettings?.position === withPagination;
412 const hasPlayPause =
413 playPause?.enable && playPause?.position === withPagination;
414
415 if (!controlType) {
416 return !!(paginationSettings?.position || hasNavigation || hasPlayPause);
417 } else {
418 if (controlType === "navigation") {
419 return hasNavigation;
420 } else if (controlType === "playPause") {
421 return hasPlayPause;
422 }
423 }
424 }
425
426 getSliderControls(controlType) {
427 if (this.hasCarouselControls(controlType)) {
428 return this.buildSliderControls();
429 }
430
431 return this.sliderElement;
432 }
433
434 /**
435 * Build pagination
436 */
437 buildPagination() {
438 if (this.sliderOptions.pagination) {
439 const sliderControls = this.getSliderControls();
440 const {
441 sliderElement,
442 sliderOptions,
443 settings: { instanceId, paginationClass } = {},
444 } = this;
445
446 // Create pagination wrapper
447 const sliderPagination = this.document.createElement("div");
448
449 // Cache it
450 this.sliderPagination = sliderPagination;
451
452 // Add id
453 sliderPagination.setAttribute("id", `${instanceId}-pagination`);
454
455 // Add class
456 sliderPagination.classList.add(paginationClass);
457
458 const { direction, paginationSettings = {} } = sliderOptions;
459
460 const {
461 position,
462 xOffset,
463 type = "bullets",
464 size, // Deprecated
465 bulletWidth = size,
466 bulletHeight = size,
467 activeBulletWidth,
468 activeBulletHeight,
469 spacing,
470 color,
471 inactiveColor,
472 opacity,
473 borderColor,
474 fractionColor,
475 horizontalGap,
476 dynamicBullets = false,
477 } = paginationSettings;
478
479 // Get slider style and add custom variables, apply in the buildSlider
480 const sliderStyle = this.getSliderStyle();
481
482 if (position && direction === "horizontal") {
483 (this.hasCarouselControls()
484 ? this.sliderControls
485 : sliderPagination
486 ).classList.add("pag-custom-position", `pag-is-${position}`);
487
488 sliderStyle.add("--cbb--pag-x-offset", xOffset);
489 }
490
491 if (type === "bullets") {
492 if (bulletWidth === bulletHeight) {
493 sliderStyle.add("--swiper-pagination-bullet-size", bulletWidth);
494 } else {
495 sliderStyle.add("--swiper-pagination-bullet-width", bulletWidth);
496 sliderStyle.add("--swiper-pagination-bullet-height", bulletHeight);
497 }
498
499 if (activeBulletWidth) {
500 this.sliderPagination.classList.add("active-bullet-width");
501
502 sliderStyle.add(
503 "--swiper-pagination-active-bullet-width",
504 activeBulletWidth,
505 );
506 }
507
508 if (activeBulletHeight) {
509 this.sliderPagination.classList.add("active-bullet-height");
510
511 sliderStyle.add(
512 "--swiper-pagination-active-bullet-height",
513 activeBulletHeight,
514 );
515 }
516
517 sliderStyle.addColor("--swiper-pagination-color", color);
518 sliderStyle.addColor(
519 "--swiper-pagination-bullet-inactive-color",
520 inactiveColor,
521 );
522 sliderStyle.addColor("--swiper-pagination-bullet-b-color", borderColor);
523
524 sliderStyle.add("--swiper-pagination-bullet-inactive-opacity", opacity);
525 } else {
526 sliderStyle.addColor(
527 "--swiper-pagination-fraction-color",
528 fractionColor,
529 );
530 }
531
532 if (spacing) {
533 const offsetProperty =
534 direction === "vertical"
535 ? "--swiper-pagination-right"
536 : "--swiper-pagination-bottom";
537 sliderStyle.add(offsetProperty, spacing);
538 }
539
540 sliderStyle.add(
541 "--swiper-pagination-bullet-horizontal-gap",
542 horizontalGap,
543 );
544
545 // Add pagination to the options
546 this.sliderOptions.pagination = this.buildPaginationOptions({
547 el: sliderPagination,
548 type,
549 dynamicBullets,
550 });
551
552 // Add the pagination to the slider element
553 sliderControls.appendChild(sliderPagination);
554 }
555 }
556
557 /**
558 * Build pagination arguments
559 *
560 * @param {Object} options
561 * @returns
562 */
563 buildPaginationOptions(options) {
564 return {
565 ...options,
566 renderBullet(i) {
567 return `<span class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="Go to slide ${
568 i + 1
569 }"><span class="swiper-pagination-bullet__dot"></span></span>`;
570 },
571 clickable: true,
572 };
573 }
574
575 /**
576 * Build navigation
577 */
578 buildNavigation() {
579 if (this.sliderOptions.navigation) {
580 const {
581 sliderElement,
582 settings: { instanceId, navigationNextClass, navigationPrevClass } = {},
583 } = this;
584
585 const {
586 nextIcon = {},
587 prevIcon = {},
588 size,
589 position,
590 xOffset,
591 yOffset,
592 spacing,
593 color,
594 ...otherProps
595 } = this.sliderOptions.navigationSettings;
596
597 const sliderControls = this.getSliderControls("navigation");
598
599 // Create naviation next button
600 const sliderNavigationNext = this.document.createElement("div");
601
602 // Cache it
603 this.sliderNavigationNext = sliderNavigationNext;
604
605 // Add id
606 sliderNavigationNext.setAttribute("id", `${instanceId}-navigation-next`);
607
608 // Add class
609 sliderNavigationNext.classList.add(navigationNextClass);
610
611 if (nextIcon?.value) {
612 sliderNavigationNext.classList.add("has-custom-icon");
613 sliderNavigationNext.insertAdjacentHTML("afterbegin", nextIcon.value);
614 }
615
616 // Create naviation prev button
617 const sliderNavigationPrev = this.document.createElement("div");
618
619 // Cache it
620 this.sliderNavigationPrev = sliderNavigationPrev;
621
622 // Add id
623 sliderNavigationPrev.setAttribute("id", `${instanceId}-navigation-prev`);
624
625 // Add class
626 sliderNavigationPrev.classList.add(navigationPrevClass);
627
628 if (prevIcon?.value) {
629 sliderNavigationPrev.classList.add("has-custom-icon");
630 sliderNavigationPrev.insertAdjacentHTML("afterbegin", prevIcon.value);
631 }
632
633 // Get slider style and add custom variables, apply in the buildSlider
634 const sliderStyle = this.getSliderStyle();
635
636 sliderStyle.add("--swiper-navigation-size", size);
637
638 // Add position settings
639 if (position) {
640 sliderNavigationNext.classList.add(
641 "nav-custom-position",
642 `is-${position}`,
643 );
644 sliderNavigationPrev.classList.add(
645 "nav-custom-position",
646 `is-${position}`,
647 );
648
649 sliderStyle.add("--cbb--nav-x-offset", xOffset);
650 sliderStyle.add("--cbb--nav-y-offset", yOffset);
651 } else {
652 sliderStyle.add("--swiper-navigation-sides-offset", spacing);
653 }
654
655 sliderStyle.addColor("--swiper-navigation-color", color);
656
657 // Add navigation to the options
658 this.sliderOptions.navigation = {
659 ...otherProps,
660 nextEl: sliderNavigationNext,
661 prevEl: sliderNavigationPrev,
662 };
663
664 // Add the naviation button to the slider element
665 sliderControls.appendChild(sliderNavigationNext);
666
667 // Add the naviation button to the slider element
668 sliderControls.appendChild(sliderNavigationPrev);
669
670 // Add class to mark the slider have navigation buttons
671 sliderElement.classList.add("swiper--has-navigation");
672 }
673 }
674
675 /**
676 * Build play/pause
677 */
678 buildPlayPause() {
679 const { playPause = {} } = this.sliderOptions;
680 if (playPause?.enable) {
681 const {
682 size,
683 position = "bottom-right",
684 xOffset,
685 yOffset,
686 color,
687 trackColor,
688 progressColor,
689 } = playPause;
690
691 const sliderControls = this.getSliderControls("playPause");
692
693 // Create play/pause button
694 const sliderPlayPause = this.document.createElement("div");
695
696 // Set the markup
697 sliderPlayPause.innerHTML =
698 '<svg viewBox="0 0 48 48"><circle class="track" cx="24" cy="24" r="20"></circle><circle cx="24" cy="24" r="20"></circle></svg>';
699
700 // Add attributes
701 sliderPlayPause.setAttribute(
702 "id",
703 `${this.settings.instanceId}-play-pause`,
704 );
705 sliderPlayPause.setAttribute("tabindex", 0);
706 sliderPlayPause.setAttribute("role", "button");
707
708 // Add class
709 sliderPlayPause.classList.add(
710 "cbb-play-pause",
711 "cbb-carousel-play-pause",
712 `is-${position}`,
713 );
714
715 // Get style builder for PlayPause
716 const sliderPPStyle = createStyleManager(sliderPlayPause);
717
718 // Inject custom properties
719 sliderPPStyle.add("--cbb--pp-size", size);
720 sliderPPStyle.add("--cbb--pp-x-offset", xOffset);
721 sliderPPStyle.add("--cbb--pp-y-offset", yOffset);
722
723 sliderPPStyle.addColor("--cbb--pp-color", color);
724 sliderPPStyle.addColor("--cbb--progress-track-color", trackColor);
725 sliderPPStyle.addColor("--cbb--progress-color", progressColor);
726
727 // Apply here
728 sliderPPStyle.apply();
729
730 // Bind events
731 this.bindPlayPause(sliderPlayPause);
732
733 // Cache it
734 this.sliderPlayPause = sliderPlayPause;
735
736 // Add the play/pause button to the slider element
737 sliderControls.appendChild(sliderPlayPause);
738 }
739 }
740
741 bindPlayPause(sliderPlayPause) {
742 const { autoplay, on = {} } = this.sliderOptions;
743 const updatePlayPause = (sliderPlayPause, autoplay) => {
744 sliderPlayPause.setAttribute(
745 "aria-pressed",
746 !autoplay ? "true" : "false",
747 );
748 sliderPlayPause.classList.toggle("is-playing", autoplay);
749 sliderPlayPause.classList.toggle("is-paused", !autoplay);
750 sliderPlayPause.setAttribute("aria-label", autoplay ? "Pause" : "Play");
751 };
752 const setProgress = (progress) =>
753 sliderPlayPause.style.setProperty("--cbb--progress", progress.toFixed(5));
754
755 const handlePlayPause = () => {
756 const { running, start, resume, pause } = this.slider.autoplay;
757 if (!running) {
758 start();
759 resume();
760 updatePlayPause(sliderPlayPause, true);
761 this.autoplaying = true;
762 } else {
763 if (!this.autoplaying) {
764 resume();
765 } else {
766 pause();
767 }
768 this.autoplaying = !this.autoplaying;
769 updatePlayPause(sliderPlayPause, this.autoplaying);
770 }
771 };
772
773 // Add event listener
774 sliderPlayPause.addEventListener("click", handlePlayPause);
775 sliderPlayPause.addEventListener("keydown", (e) => {
776 if (!["Enter", " ", "Spacebar"].includes(e.key)) return;
777 e.preventDefault();
778 handlePlayPause();
779 });
780
781 // Play by default
782 this.autoplaying = !!autoplay;
783 updatePlayPause(sliderPlayPause, !!autoplay);
784
785 // Set aria-controls
786 on.afterInit = compose(on.afterInit, (swiper) => {
787 sliderPlayPause.setAttribute(
788 "aria-controls",
789 swiper.wrapperEl.getAttribute("id"),
790 );
791 });
792
793 on.autoplayStop = () => {
794 updatePlayPause(sliderPlayPause, false);
795 setProgress(0);
796 };
797 on.autoplayResume = () => updatePlayPause(sliderPlayPause, true);
798 on.slideChange = compose(on.slideChange, (s) => {
799 if (!this.autoplaying) {
800 s.autoplay.stop();
801 }
802 });
803 on.lock = (s) => sliderPlayPause.classList.add("swiper-button-lock");
804 on.unlock = (s) => sliderPlayPause.classList.remove("swiper-button-lock");
805 on.autoplayTimeLeft = (s, time, progress) => {
806 if (s.isLocked || s.autoplay.paused) {
807 return;
808 }
809
810 const activeSlideEl = s.slides[s.activeIndex];
811 const currentSlideDelay = parseInt(
812 activeSlideEl.getAttribute("data-swiper-autoplay"),
813 10,
814 );
815
816 setProgress(
817 1 - (currentSlideDelay > 0 ? time / currentSlideDelay : progress),
818 );
819 };
820
821 // Set on back to the options
822 this.sliderOptions.on = on;
823 }
824
825 /**
826 * Build scrollbar
827 */
828 buildScrollbar() {
829 if (this.sliderOptions.scrollbar) {
830 const { sliderElement, settings: { instanceId, scrollbarClass } = {} } =
831 this;
832
833 // Create scrollbar element
834 const sliderScrollbar = this.document.createElement("div");
835
836 // Cache it
837 this.sliderScrollbar = sliderScrollbar;
838
839 // Add id
840 sliderScrollbar.setAttribute("id", `${instanceId}-scrollbar`);
841
842 // Add class
843 sliderScrollbar.classList.add(scrollbarClass);
844
845 const { direction, scrollbarSettings = {} } = this.sliderOptions;
846 const {
847 trackSize,
848 spacing,
849 trackColor,
850 sliderColor,
851 trackOpacity,
852 ...otherProps
853 } = scrollbarSettings;
854
855 // Get slider style and add custom variables, apply in the buildSlider
856 const sliderStyle = this.getSliderStyle();
857
858 sliderStyle.add("--swiper-scrollbar-size", trackSize);
859
860 if (spacing) {
861 const offsetProperty =
862 direction === "vertical"
863 ? "--swiper-scrollbar-right"
864 : "--swiper-scrollbar-bottom";
865 sliderStyle.add(offsetProperty, spacing);
866 }
867
868 sliderStyle.addColor("--swiper-scrollbar-track-color", trackColor);
869 sliderStyle.addColor("--swiper-scrollbar-slider-color", sliderColor);
870 sliderStyle.add("--swiper-scrollbar-track-opacity", trackOpacity);
871
872 // Add scrollbar to the options
873 this.sliderOptions.scrollbar = {
874 ...otherProps,
875 el: sliderScrollbar,
876 };
877
878 // Add the scrollbar to the slider element
879 this.sliderContainer.appendChild(sliderScrollbar);
880 }
881 }
882
883 getSliderStyle() {
884 if (!this.sliderStyle) {
885 this.sliderStyle = createStyleManager(this.sliderElement);
886 }
887
888 return this.sliderStyle;
889 }
890
891 addClass(element, classname) {
892 if (!element.classList.contains(classname)) {
893 element.classList.add(classname);
894 }
895 }
896
897 removeClass(element, classname) {
898 if (classname) {
899 element.classList.remove(classname);
900 }
901 }
902 }
903
904 /**
905 * Get carousel manager object
906 *
907 * @returns {Array}
908 */
909 export const getCarouselManager = () => {
910 if (!window?.CBBCarousels) {
911 window.CBBCarousels = {};
912 }
913
914 return window.CBBCarousels;
915 };
916
917 export const addCarousel = (element, args, carouselElements = []) => {
918 const manager = getCarouselManager();
919 const { instanceId } = args;
920 if (!manager[instanceId] || !manager[instanceId]?.slider) {
921 if (carouselElements.length > 1) {
922 const thumbs = buildThumbs(element, carouselElements);
923 if (thumbs) {
924 args.sliderOptions = { thumbs: { swiper: thumbs.slider } };
925 }
926 }
927
928 if (element.classList.contains("wp-block-query")) {
929 args.wrapperSelector = "ul";
930 args.itemSelector = "li";
931 }
932
933 const instance = new CarouselScript(element, args);
934 manager[instance?.cbbCarouselId || instanceId] = instance;
935
936 return instance;
937 }
938
939 return manager[instanceId];
940 };
941
942 const buildThumbs = (element, carouselElements) => {
943 let carouselSettings = element?.dataset?.carouselSettings;
944 if (!carouselSettings) {
945 return null;
946 }
947
948 carouselSettings = parseJSON(carouselSettings);
949
950 const thumbSelector = carouselSettings?.thumbsSettings?.enable
951 ? carouselSettings.thumbsSettings?.selector
952 : "";
953
954 if (!thumbSelector) {
955 return null;
956 }
957
958 const thumbElement = element.ownerDocument.querySelector(thumbSelector);
959 if (!thumbElement) {
960 return null;
961 }
962
963 const thumbsIndex = carouselElements.findIndex((element) =>
964 element.isSameNode(thumbElement),
965 );
966
967 if (thumbsIndex === -1) {
968 return null;
969 }
970
971 return addCarousel(
972 thumbElement,
973 {
974 instanceId: `cbb-carousel-${thumbsIndex + 1}`,
975 },
976 carouselElements,
977 );
978 };
979
980 exposeComponent("CarouselScript", CarouselScript);
981
982 export default CarouselScript;
983