1/** 2 * @file 3 * video-carousel.js 4 * 5 * Behavior for the Video Carousel component (.video-carousel). 6 * Follows the Slick + jQuery pattern established in the theme's carousel.js. 7 * 8 * Slick is initialized on .field__items â each .field__item becomes a slide. 9 */ 10 11(function ($, Drupal) { 12 'use strict'; 13 14 Drupal.behaviors.videoCarousel = { 15 attach(context) { 16 $('.video-carousel', context).each((i, el) => { 17 if ($(el).data('vc-initialized')) return; 18 $(el).data('vc-initialized', true); 19 new VideoCarousel($(el)); 20 }); 21 } 22 }; 23 24 class VideoCarousel { 25 constructor($el) { 26 this.$el = $el; 27 this.$track = $el.find('.field--name-field-video-carousel-cards .field__items'); 28 this.$prev = $el.find('.video-carousel__prev'); 29 this.$next = $el.find('.video-carousel__next'); 30 this.$counter = $el.find('.video-carousel__counter'); 31 this.$progressBar = $el.find('.video-carousel__progress-bar'); 32 33 if (!this.$track.length) { 34 console.warn('[VideoCarousel] Track element not found.', $el); 35 return; 36 } 37 38 this.initSlick(); 39 this.initModal(); 40 } 41 42 // Update "X of Y" counter and progress bar fill. 43 updateFooter(currentIndex, slick) { 44 const total = slick.slideCount; 45 const visible = slick.options.slidesToShow; 46 const pages = Math.max(1, total - visible + 1); 47 const currentPage = Math.min(currentIndex + 1, pages); 48 49 this.$counter.text(currentPage + ' of ' + pages); 50 this.$progressBar.css('width', (currentPage / pages * 100) + '%'); 51 52 this.$el.toggleClass('video-carousel--single-page', pages === 1); 53 } 54 55 // Slick carousel. 56 initSlick() { 57 this.$track.on('init', (event, slick) => { 58 this.updateFooter(0, slick); 59 }); 60 61 this.$track.on('beforeChange', (event, slick, currentSlide, nextSlide) => { 62 this.updateFooter(nextSlide, slick); 63 }); 64 65 this.$track.slick({ 66 slidesToShow: 3, 67 slidesToScroll: 1, 68 infinite: false, 69 dots: false, 70 arrows: true, 71 speed: 600, 72 accessibility: true, 73 focusOnChange: false, 74 prevArrow: this.$prev, 75 nextArrow: this.$next, 76 responsive: [ 77 { 78 breakpoint: 1024, 79 settings: { 80 slidesToShow: 2, 81 } 82 }, 83 { 84 breakpoint: 768, 85 settings: { 86 slidesToShow: 1, 87 centerMode: true, 88 centerPadding: '10%', 89 } 90 } 91 ] 92 }); 93 } 94 95 // Modal / lightbox. 96 initModal() { 97 this.$modal = $(this.buildModal()); 98 $('body').append(this.$modal); 99 100 const $content = this.$modal.find('.vc-modal__content'); 101 const $close = this.$modal.find('.vc-modal__close'); 102 103 this.$el.on('click', '.video-carousel-card__play', (e) => { 104 const $card = $(e.currentTarget).closest('.video-carousel-card'); 105 const $videoField = $card.find('.video-carousel-card__video'); 106 if (!$videoField.length) return; 107 108 const $clone = $videoField.clone().removeAttr('hidden').css({ 109 position: 'static', 110 width: '100%', 111 height: '100%' 112 }); 113 114 $content.empty().append($clone); 115 this.openModal(); 116 }); 117 118 $close.on('click', () => this.closeModal()); 119 this.$modal.on('click', (e) => { 120 if ($(e.target).is(this.$modal)) this.closeModal(); 121 }); 122 123 $(document).on('keydown.vcModal', (e) => { 124 if (e.key === 'Escape' && this.$modal.attr('aria-hidden') === 'false') { 125 this.closeModal(); 126 } 127 }); 128 } 129 130 openModal() { 131 this.$modal.attr('aria-hidden', 'false').addClass('is-open'); 132 $('body').css('overflow', 'hidden'); 133 this.$modal.find('.vc-modal__close').trigger('focus'); 134 } 135 136 closeModal() { 137 this.$modal.attr('aria-hidden', 'true').removeClass('is-open'); 138 $('body').css('overflow', ''); 139 this.$modal.find('.vc-modal__content').empty(); 140 } 141 142 buildModal() { 143 return ` 144 <div class="vc-modal" aria-hidden="true" role="dialog" aria-modal="true" aria-label="${Drupal.t('Video player')}"> 145 <div class="vc-modal__inner"> 146 <button class="vc-modal__close" aria-label="${Drupal.t('Close video')}">
147 <span aria-hidden="true">×</span> 148 </button> 149 <div class="vc-modal__content"></div> 150 </div> 151 </div> 152 `; 153 } 154 } 155 156})(jQuery, Drupal);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.