PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 All 39 releases
king-addons / includes / widgets / Woo_Product_Images_Gallery / script.js

script.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.83, at includes/widgets/Woo_Product_Images_Gallery/script.js

338 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Woo Product Images Gallery widget behavior.
3 *
4 * Provides gallery navigation, optional lightbox, and optional zoom.
5 */
6 (function ($) {
7 "use strict";
8
9 const INIT_FLAG = "kaWooGalleryInit";
10 const KEYUP_FLAG = "kaWooGalleryKeyupBound";
11
12 // Global active lightbox controller (single active at a time).
13 window.kaWooGalleryActive = window.kaWooGalleryActive || null;
14
15 const lockScroll = () => {
16 document.body.classList.add('ka-woo-gallery--lock');
17 };
18
19 const unlockScroll = () => {
20 document.body.classList.remove('ka-woo-gallery--lock');
21 };
22
23 const enableZoom = (slide) => {
24 if (!slide || slide.dataset.zoomBound === 'yes') return;
25 if (window.matchMedia('(hover: none)').matches) return; // avoid on touch-only
26 const full = slide.dataset.full;
27 if (!full) return;
28 const lens = document.createElement('div');
29 lens.className = 'ka-woo-gallery__zoom-lens';
30 lens.style.backgroundImage = `url(${full})`;
31 slide.appendChild(lens);
32 slide.dataset.zoomBound = 'yes';
33
34 const move = (event) => {
35 const rect = slide.getBoundingClientRect();
36 const x = event.clientX - rect.left;
37 const y = event.clientY - rect.top;
38 const xPercent = Math.max(0, Math.min(100, (x / rect.width) * 100));
39 const yPercent = Math.max(0, Math.min(100, (y / rect.height) * 100));
40 lens.style.left = `${x - lens.offsetWidth / 2}px`;
41 lens.style.top = `${y - lens.offsetHeight / 2}px`;
42 lens.style.backgroundPosition = `${xPercent}% ${yPercent}%`;
43 lens.classList.add('is-visible');
44 };
45
46 slide.addEventListener('mousemove', move);
47 slide.addEventListener('mouseenter', () => lens.classList.add('is-visible'));
48 slide.addEventListener('mouseleave', () => lens.classList.remove('is-visible'));
49 };
50
51 const initGallery = (wrapper) => {
52 if (!wrapper || !wrapper.dataset) return;
53 if (wrapper.dataset[INIT_FLAG] === "1") return;
54 wrapper.dataset[INIT_FLAG] = "1";
55
56 const slides = wrapper.querySelectorAll('.ka-woo-gallery__slide');
57 const thumbs = wrapper.querySelectorAll('.ka-woo-gallery__thumb');
58 if (!slides.length) return;
59
60 const layout = wrapper.dataset.layout || 'slider';
61 const mobileLayout = wrapper.dataset.mobileLayout || '';
62 const loop = wrapper.dataset.loop === 'yes';
63 const autoplay = wrapper.dataset.autoplay === 'yes';
64 const speed = parseInt(wrapper.dataset.speed || '4000', 10);
65 const captions = wrapper.dataset.captions === 'yes';
66 const lightboxEnabled = wrapper.dataset.lightbox === 'yes';
67 const zoomEnabled = wrapper.classList.contains('ka-woo-gallery--zoom');
68 const lightboxSkin = wrapper.dataset.lightboxSkin || 'dark';
69 const lightboxCounterEnabled = wrapper.dataset.lightboxCounter === 'yes';
70 const lightboxThumbsEnabled = wrapper.dataset.lightboxThumbs === 'yes';
71 let activeIndex = 0;
72 let timer = null;
73
74 const overlayId = `ka-woo-gallery-lightbox-${Math.random().toString(36).slice(2)}`;
75
76 const lightbox = document.createElement('div');
77 lightbox.className = 'ka-woo-gallery__lightbox';
78 lightbox.id = overlayId;
79 lightbox.innerHTML = `
80 <button type="button" class="ka-woo-gallery__lightbox-close" aria-label="Close">&times;</button>
81 <div class="ka-woo-gallery__lightbox-body">
82 <div class="ka-woo-gallery__lightbox-top">
83 <div class="ka-woo-gallery__lightbox-counter"></div>
84 <div class="ka-woo-gallery__lightbox-nav">
85 <button class="ka-woo-gallery__lb-prev" aria-label="Previous">&#10094;</button>
86 <button class="ka-woo-gallery__lb-next" aria-label="Next">&#10095;</button>
87 </div>
88 </div>
89 <div class="ka-woo-gallery__lightbox-media">
90 <img alt="" />
91 <div class="ka-woo-gallery__lightbox-caption"></div>
92 </div>
93 <div class="ka-woo-gallery__lightbox-thumbs"></div>
94 </div>
95 `;
96 if (lightboxEnabled) {
97 document.body.appendChild(lightbox);
98 lightbox.classList.add('ka-woo-gallery__lightbox--' + lightboxSkin);
99 }
100
101 const lightboxImg = lightbox.querySelector('img');
102 const lightboxCaption = lightbox.querySelector('.ka-woo-gallery__lightbox-caption');
103 const lightboxThumbs = lightbox.querySelector('.ka-woo-gallery__lightbox-thumbs');
104 const lightboxCounter = lightbox.querySelector('.ka-woo-gallery__lightbox-counter');
105 const lbPrev = lightbox.querySelector('.ka-woo-gallery__lb-prev');
106 const lbNext = lightbox.querySelector('.ka-woo-gallery__lb-next');
107 const lbClose = lightbox.querySelector('.ka-woo-gallery__lightbox-close');
108 const lightboxThumbButtons = [];
109
110 const updateCounter = () => {
111 if (!lightboxCounterEnabled || !lightboxCounter) return;
112 lightboxCounter.textContent = `${activeIndex + 1} / ${slides.length}`;
113 };
114
115 const syncLightboxThumbs = () => {
116 if (!lightboxThumbsEnabled || !lightboxThumbButtons.length) return;
117 lightboxThumbButtons.forEach((btn, i) => btn.classList.toggle('is-active', i === activeIndex));
118 };
119
120 const setActive = (index) => {
121 activeIndex = index;
122 // The mobile layout overrides the desktop one in both directions:
123 // a slider can become a grid on a phone, and a grid can become a
124 // slider. Only the first half was handled.
125 const isMobile = window.matchMedia('(max-width: 767px)').matches;
126 let isGrid = layout === 'grid' || layout === 'masonry';
127 if (isMobile && 'grid' === mobileLayout) {
128 isGrid = true;
129 } else if (isMobile && 'slider' === mobileLayout) {
130 isGrid = false;
131 }
132 slides.forEach((slide, i) => {
133 slide.style.display = i === activeIndex || isGrid ? 'block' : 'none';
134 });
135 thumbs.forEach((thumb, i) => {
136 thumb.classList.toggle('is-active', i === activeIndex);
137 });
138 dots.forEach((dot, i) => {
139 dot.classList.toggle('is-active', i === activeIndex);
140 });
141 if (lightboxEnabled && captions && lightboxCaption) {
142 const cap = slides[activeIndex]?.dataset.caption || '';
143 lightboxCaption.textContent = cap;
144 lightboxCaption.style.display = cap ? 'block' : 'none';
145 }
146 updateCounter();
147 syncLightboxThumbs();
148 if (zoomEnabled) {
149 enableZoom(slides[activeIndex]);
150 }
151 };
152
153 thumbs.forEach((thumb, index) => {
154 thumb.addEventListener('click', () => setActive(index));
155 });
156
157 const prev = wrapper.querySelector('.ka-woo-gallery__arrow--prev');
158 const next = wrapper.querySelector('.ka-woo-gallery__arrow--next');
159 const dots = wrapper.querySelectorAll('.ka-woo-gallery__dot');
160
161 const goTo = (delta) => {
162 const total = slides.length;
163 let target = activeIndex + delta;
164 if (loop) {
165 target = (target + total) % total;
166 } else {
167 target = Math.min(Math.max(target, 0), total - 1);
168 }
169 setActive(target);
170 };
171
172 if (prev) prev.addEventListener('click', () => goTo(-1));
173 if (next) next.addEventListener('click', () => goTo(1));
174 dots.forEach((dot, idx) => dot.addEventListener('click', () => setActive(idx)));
175
176 // Lightbox
177 const openLightbox = (index) => {
178 if (!lightboxEnabled || !lightboxImg) return;
179 const slide = slides[index];
180 if (!slide) return;
181 const src = slide.dataset.full || '';
182 if (!src) return;
183 activeIndex = index;
184 lightboxImg.src = src;
185 if (captions && lightboxCaption) {
186 const cap = slide.dataset.caption || '';
187 lightboxCaption.textContent = cap;
188 lightboxCaption.style.display = cap ? 'block' : 'none';
189 }
190 updateCounter();
191 syncLightboxThumbs();
192 lightbox.classList.add('is-open');
193 lockScroll();
194
195 // Set global active controller
196 window.kaWooGalleryActive = {
197 isOpen: () => lightbox.classList.contains("is-open"),
198 close: closeLightbox,
199 prev: () => {
200 goTo(-1);
201 openLightbox(activeIndex);
202 },
203 next: () => {
204 goTo(1);
205 openLightbox(activeIndex);
206 },
207 };
208 };
209
210 const closeLightbox = () => {
211 lightbox.classList.remove('is-open');
212 unlockScroll();
213 if (window.kaWooGalleryActive && window.kaWooGalleryActive.close === closeLightbox) {
214 window.kaWooGalleryActive = null;
215 }
216 };
217
218 if (lightboxEnabled) {
219 lightbox.addEventListener('click', (event) => {
220 const isBody = event.target.classList.contains('ka-woo-gallery__lightbox');
221 const isClose = event.target === lbClose;
222 if (isBody || isClose) {
223 closeLightbox();
224 }
225 });
226 lightbox.querySelector('.ka-woo-gallery__lightbox-body')?.addEventListener('click', (e) => e.stopPropagation());
227
228 if (lightboxThumbsEnabled && lightboxThumbs) {
229 slides.forEach((slide, idx) => {
230 const imgEl = slide.querySelector('img');
231 const src = slide.dataset.full || imgEl?.src || '';
232 const btn = document.createElement('button');
233 btn.type = 'button';
234 btn.className = 'ka-woo-gallery__lightbox-thumb';
235 btn.innerHTML = `<span style="background-image:url('${src}')"></span>`;
236 btn.addEventListener('click', (e) => {
237 e.stopPropagation();
238 setActive(idx);
239 openLightbox(idx);
240 });
241 lightboxThumbButtons.push(btn);
242 lightboxThumbs.appendChild(btn);
243 });
244 }
245
246 if (!lightboxCounterEnabled && lightboxCounter) {
247 lightboxCounter.style.display = 'none';
248 }
249
250 if (!lightboxThumbsEnabled && lightboxThumbs) {
251 lightboxThumbs.style.display = 'none';
252 }
253 }
254
255 slides.forEach((slide, idx) => {
256 slide.addEventListener('click', () => {
257 if (!lightboxEnabled) return;
258 openLightbox(idx);
259 });
260 if (zoomEnabled) {
261 enableZoom(slide);
262 }
263 });
264
265 if (lbPrev) lbPrev.addEventListener('click', (e) => { e.stopPropagation(); goTo(-1); openLightbox(activeIndex); });
266 if (lbNext) lbNext.addEventListener('click', (e) => { e.stopPropagation(); goTo(1); openLightbox(activeIndex); });
267
268 // Global key handler is bound once for all instances below.
269
270 // Autoplay
271 const startAutoplay = () => {
272 if (!autoplay || slides.length <= 1) return;
273 stopAutoplay();
274 timer = setInterval(() => {
275 const nextIndex = loop ? (activeIndex + 1) % slides.length : Math.min(activeIndex + 1, slides.length - 1);
276 setActive(nextIndex);
277 }, speed > 500 ? speed : 4000);
278 };
279 const stopAutoplay = () => {
280 if (timer) {
281 clearInterval(timer);
282 timer = null;
283 }
284 };
285 wrapper.addEventListener('mouseenter', stopAutoplay);
286 wrapper.addEventListener('mouseleave', startAutoplay);
287
288 setActive(0);
289 startAutoplay();
290 };
291
292 const bindGlobalKeyupOnce = () => {
293 if (window[KEYUP_FLAG]) {
294 return;
295 }
296 window[KEYUP_FLAG] = true;
297
298 document.addEventListener("keyup", (e) => {
299 const active = window.kaWooGalleryActive;
300 if (!active || typeof active.isOpen !== "function" || !active.isOpen()) {
301 return;
302 }
303 if (e.key === "Escape") {
304 active.close();
305 }
306 if (e.key === "ArrowLeft") {
307 active.prev();
308 }
309 if (e.key === "ArrowRight") {
310 active.next();
311 }
312 });
313 };
314
315 const initAll = (root) => {
316 const ctx = root && root.querySelectorAll ? root : document;
317 bindGlobalKeyupOnce();
318 ctx.querySelectorAll(".ka-woo-gallery").forEach(initGallery);
319 };
320
321 document.addEventListener("DOMContentLoaded", () => initAll(document));
322
323 $(window).on("elementor/frontend/init", function () {
324 elementorFrontend.hooks.addAction(
325 "frontend/element_ready/woo_product_images_gallery.default",
326 function ($scope) {
327 initAll($scope && $scope[0] ? $scope[0] : document);
328 }
329 );
330 });
331 })(jQuery);
332
333
334
335
336
337
338