PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
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 51.1.47 51.1.49 All 37 releases
king-addons / includes / assets / libraries / grid / media.js

media.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.78, at includes/assets/libraries/grid/media.js

1,515 lines 90.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // noinspection JSUnresolvedReference,DuplicatedCode
2
3 "use strict";
4 (function ($) {
5 $(window).on("elementor/frontend/init", () => {
6
7 const gridHooks = [
8 "frontend/element_ready/king-addons-media-grid.default"
9 ];
10
11 const gridHandler = ($scope) => {
12 elementorFrontend.elementsHandler.addHandler(
13 elementorModules.frontend.handlers.Base.extend({
14 onInit() {
15 const $body = $("body");
16 const isEditor = $body.hasClass("elementor-editor-active");
17 const $grid = this.$element.find(".king-addons-grid");
18 if (!$grid.length) return;
19
20 /* ──────────────────────────────────────
21 * UTILITY FUNCTIONS
22 * ────────────────────────────────────── */
23
24 // Simple debounce helper.
25 const debounce = (func, threshold = 100, execAsap) => {
26 let timeout;
27 return function (...args) {
28 const context = this;
29 const delayed = () => {
30 if (!execAsap) func.apply(context, args);
31 timeout = null;
32 };
33 if (timeout) clearTimeout(timeout);
34 else if (execAsap) func.apply(context, args);
35 timeout = setTimeout(delayed, threshold);
36 };
37 };
38
39 // Attach "smartresize" to jQuery.
40 $.fn.smartresize = function (fn) {
41 return fn ? this.on("resize", debounce(fn)) : this.trigger("smartresize");
42 };
43
44 // Parse grid settings.
45 const settingsData = $grid.attr("data-settings");
46 const settings = settingsData ? JSON.parse(settingsData) : false;
47
48 /* ──────────────────────────────────────
49 * INITIAL EVENT HANDLERS
50 * ────────────────────────────────────── */
51
52 // Handle WooCommerce orderby form.
53 if ($scope.find(".king-addons-grid-orderby form").length) {
54 const $orderbyForm = $scope.find(".king-addons-grid-orderby form");
55 $scope.find(".orderby").on("change", () => $orderbyForm.trigger("submit"));
56 }
57
58 // Adjust result count text if WooCommerce count element exists.
59 if (settings && $scope.find(".woocommerce-result-count").length) {
60 adjustResultCount($scope, $grid, settings, isEditor);
61 }
62
63 // Helper to schedule a delayed isotope layout.
64 const scheduleIsotopeLayout = (delay) =>
65 setTimeout(() => isotopeLayout(settings), delay);
66
67 if (settings) {
68 // Perform initial layout.
69 isotopeLayout(settings);
70 scheduleIsotopeLayout(100);
71 if (isEditor) {
72 scheduleIsotopeLayout(500);
73 scheduleIsotopeLayout(1000);
74 }
75 $(window).on("load", () => scheduleIsotopeLayout(100));
76 $(document).ready(() => scheduleIsotopeLayout(100));
77 $(window).smartresize(() => scheduleIsotopeLayout(200));
78
79 // If dynamic grid settings exist, set up filtering and “load more” experiments.
80 if (settings.grid_settings) {
81 loadMoreExperiment();
82 filtersExperiment();
83 }
84 isotopeFilters(settings);
85
86 let initialItems = 0;
87 $grid.on("arrangeComplete", (e, filteredItems) => {
88 animateGridItems(filteredItems, settings, $grid);
89 initialItems = filteredItems.length;
90 });
91
92 // Once images load, set grid opacity and equalize item heights.
93 $grid.imagesLoaded(() => {
94 if ($grid.css("opacity") !== "1") $grid.css("opacity", "1");
95 setTimeout(() => $grid.addClass("grid-images-loaded"), 500);
96 setEqualHeight(settings);
97 });
98
99 // Pagination: load-more or infinite-scroll.
100 if (["load-more", "infinite-scroll"].includes(settings.pagination_type)) {
101 if ($scope.find(".king-addons-grid-pagination").length && !isEditor) {
102 setupInfiniteScroll(settings);
103 } else {
104 $scope.find(".king-addons-load-more-btn").on("click", () =>
105 alert(
106 "Load More is Disabled in the Editor! Please Preview this Page to see it in action"
107 )
108 );
109 }
110 }
111 } else {
112 // Fallback: initialize Slick slider.
113 $grid.animate({opacity: "1"}, 1000);
114 initSlickSlider();
115 }
116
117 // Additional features.
118 if ($grid.find(".king-addons-grid-item-add-to-cart").length) {
119 setupAddToCart();
120 }
121 postSharing();
122 mediaHoverLink();
123 if (
124 !$scope.hasClass("elementor-widget-king-addons-woo-category-grid-pro") &&
125 !$scope.hasClass("elementor-widget-king-addons-category-grid-pro")
126 ) {
127 lightboxPopup(settings);
128 }
129 postLikes(settings);
130 lazyLoadObserver();
131
132 /* ──────────────────────────────────────
133 * FUNCTION DEFINITIONS
134 * ────────────────────────────────────── */
135
136 // Adjust WooCommerce result count.
137 function adjustResultCount($scope, $grid, s, isEditor) {
138 const $resultCount = $scope.find(".woocommerce-result-count");
139 let text = $resultCount.text();
140 const scopeId = $scope.data("id");
141 const storageKey = `king-addons-cached-items-length-${scopeId}`;
142 let cachedLength = localStorage.getItem(storageKey);
143 if (!cachedLength || isEditor) {
144 cachedLength = $scope.find(".king-addons-grid-item").length;
145 localStorage.setItem(storageKey, cachedLength);
146 }
147 const itemsPerPage = s.query_posts_per_page || parseInt(cachedLength, 10);
148 if (isNaN(itemsPerPage) || itemsPerPage <= 0) return;
149 let currentPage = 1;
150 const $currentPage = $scope.find(".king-addons-grid-current-page");
151 if ($currentPage.length) {
152 currentPage = parseInt($currentPage.text().trim(), 10) || 1;
153 }
154 const totalMatch = text.match(/of (\d+) results/);
155 const totalItems = totalMatch ? parseInt(totalMatch[1].trim(), 10) : itemsPerPage;
156 if (isNaN(totalItems) || totalItems <= 0) return;
157 const startItem = (currentPage - 1) * itemsPerPage + 1;
158 const endItem = Math.min(startItem + itemsPerPage - 1, totalItems);
159 text = text.replace(/\d+\u2013\d+/, `${startItem}\u2013${endItem}`);
160 $resultCount.text(text);
161 }
162
163 // Layout the grid using Isotope (or its custom wrapper).
164 function isotopeLayout(s, $response = "") {
165 const layout = s.layout;
166 const columnsDesktop = parseInt(s.columns_desktop, 10);
167 let items = $response ? $response : $grid.find(".king-addons-grid-item"),
168 contWidth = $grid.width() + s.gutter_hr - 0.3,
169 viewportWidth = $(window).outerWidth(),
170 columns,
171 gutterHr,
172 gutterVr;
173
174 // Breakpoints.
175 const {mobile, mobile_extra, tablet, tablet_extra, laptop, widescreen} =
176 elementorFrontend.config.responsive.breakpoints;
177 const activeBps = elementorFrontend.config.responsive.activeBreakpoints;
178 let cMobile = 1,
179 cMobileExtra,
180 cTablet = 2,
181 cTabletExtra,
182 cLaptop,
183 cWideScreen;
184 $scope
185 .attr("class")
186 .split(" ")
187 .forEach((cl) => {
188 if (/mobile\d/.test(cl)) cMobile = parseInt(cl.slice(-1), 10);
189 if (/mobile_extra\d/.test(cl)) cMobileExtra = parseInt(cl.slice(-1), 10);
190 if (/tablet\d/.test(cl)) cTablet = parseInt(cl.slice(-1), 10);
191 if (/tablet_extra\d/.test(cl)) cTabletExtra = parseInt(cl.slice(-1), 10);
192 if (/widescreen\d/.test(cl)) cWideScreen = parseInt(cl.slice(-1), 10);
193 if (/laptop\d/.test(cl)) cLaptop = parseInt(cl.slice(-1), 10);
194 });
195
196 if (viewportWidth <= mobile.value && activeBps.mobile) {
197 columns = cMobile;
198 gutterHr = s.gutter_hr_mobile;
199 gutterVr = s.gutter_vr_mobile;
200 } else if (viewportWidth <= mobile_extra.value && activeBps.mobile_extra) {
201 columns = cMobileExtra || cTablet;
202 gutterHr = s.gutter_hr_mobile_extra;
203 gutterVr = s.gutter_vr_mobile_extra;
204 } else if (viewportWidth <= tablet.value && activeBps.tablet) {
205 columns = cTablet;
206 gutterHr = s.gutter_hr_tablet;
207 gutterVr = s.gutter_vr_tablet;
208 } else if (viewportWidth <= tablet_extra.value && activeBps.tablet_extra) {
209 columns = cTabletExtra || cTablet;
210 gutterHr = s.gutter_hr_tablet_extra;
211 gutterVr = s.gutter_vr_tablet_extra;
212 } else if (viewportWidth <= laptop.value && activeBps.laptop) {
213 columns = cLaptop || columnsDesktop;
214 gutterHr = s.gutter_hr_laptop;
215 gutterVr = s.gutter_vr_laptop;
216 } else if (viewportWidth <= widescreen.value) {
217 columns = columnsDesktop;
218 gutterHr = s.gutter_hr;
219 gutterVr = s.gutter_vr;
220 } else {
221 columns = cWideScreen || columnsDesktop;
222 gutterHr = s.gutter_hr_widescreen;
223 gutterVr = s.gutter_vr_widescreen;
224 }
225 if (columns > 8) columns = 8;
226 items.outerWidth(Math.floor(contWidth / columns - gutterHr));
227 items.css("margin-bottom", `${gutterVr}px`);
228
229 if (layout === "list") {
230 handleListLayout(s, items);
231 }
232
233 const isoLayoutMode = layout === "list" ? "fitRows" : layout;
234 const transDuration = s.filters_animation === "default" ? 400 : 0;
235
236 $grid.isotopekng({
237 layoutMode: isoLayoutMode,
238 masonry: {gutter: gutterHr},
239 fitRows: {gutter: gutterHr},
240 transitionDuration: transDuration,
241 percentPosition: true,
242 });
243 }
244
245 // Handle "list" layout adjustments.
246 function handleListLayout(s, items) {
247 const imageHeight = items.find(".king-addons-grid-image-wrap").outerHeight();
248 items.find(".king-addons-grid-item-below-content").css("min-height", `${imageHeight}px`);
249
250 if ($body.width() < 480) {
251 items.find(".king-addons-grid-media-wrap").css({float: "none", width: "100%"});
252 items.find(".king-addons-grid-item-below-content").css({
253 float: "none",
254 width: "100%",
255 "min-height": "0",
256 });
257 } else {
258 const {media_align: align, media_width: mWidth, media_distance: mDistance} = s;
259 if (align === "zigzag") {
260 handleZigzag(items, mWidth, mDistance);
261 } else {
262 items.find(".king-addons-grid-media-wrap").css({
263 float: align,
264 width: `${mWidth}%`,
265 [`margin-${align === "left" ? "right" : "left"}`]: `${mDistance}px`,
266 });
267 items.find(".king-addons-grid-item-below-content").css({
268 float: align,
269 width: `calc((100% - ${mWidth}%) - ${mDistance}px)`,
270 });
271 }
272 }
273 }
274
275 // Alternate ("zigzag") layout for list items.
276 function handleZigzag(items, mWidth, mDistance) {
277 items.filter(":even").each(function () {
278 $(this).find(".king-addons-grid-media-wrap").css({
279 float: "left",
280 width: `${mWidth}%`,
281 "margin-right": `${mDistance}px`,
282 });
283 $(this).find(".king-addons-grid-item-below-content").css({
284 float: "left",
285 width: `calc((100% - ${mWidth}%) - ${mDistance}px)`,
286 });
287 });
288 items.filter(":odd").each(function () {
289 $(this).find(".king-addons-grid-media-wrap").css({
290 float: "right",
291 width: `${mWidth}%`,
292 "margin-left": `${mDistance}px`,
293 });
294 $(this).find(".king-addons-grid-item-below-content").css({
295 float: "right",
296 width: `calc((100% - ${mWidth}%) - ${mDistance}px)`,
297 });
298 });
299 }
300
301 // Equalize grid item heights for "fitRows" layout.
302 function setEqualHeight(s) {
303 if (s.layout !== "fitRows") {
304 return;
305 }
306
307 const $items = $grid.children("article");
308 if (!$items.length) {
309 return;
310 }
311
312 $items.css("height", "");
313
314 const itemWidth = $items.first().outerWidth(true);
315 if (!itemWidth) {
316 return;
317 }
318
319 const columns = Math.max(1, Math.round($grid.outerWidth() / itemWidth));
320 const itemsArray = $items.toArray();
321
322 for (let rowStart = 0; rowStart < itemsArray.length; rowStart += columns) {
323 const rowItems = itemsArray.slice(rowStart, rowStart + columns);
324 const maxH = Math.max(...rowItems.map((el) => $(el).outerHeight()));
325 rowItems.forEach((el) => {
326 $(el).css("height", `${maxH}px`);
327 });
328 }
329
330 if (s.stick_last_element_to_bottom === "yes") {
331 $scope.addClass("king-addons-grid-last-element-yes");
332 }
333 }
334
335 // Lazy-load images via an IntersectionObserver.
336 function lazyLoadObserver() {
337 setTimeout(() => {
338 const observer = new IntersectionObserver((entries) => {
339 entries.forEach((entry) => {
340 if (
341 entry.isIntersecting &&
342 entry.target.src.includes("icon-256x256")
343 ) {
344 setTimeout(() => {
345 entry.target.src =
346 entry.target.parentElement.dataset.src;
347 $(entry.target).toggleClass("king-addons-hidden-image");
348 $(window).trigger("resize");
349 }, 100);
350 }
351 });
352 });
353 $scope
354 .find(".king-addons-grid-image-wrap img:first-of-type")
355 .each(function () {
356 observer.observe(this);
357 });
358 }, 100);
359 }
360
361 // Update filter counts and set up filtering behavior.
362 function isotopeFilters(s, ev = "load") {
363 if (s.filters_count === "yes") {
364 $scope.find(".king-addons-grid-filters a, .king-addons-grid-filters span").each(function () {
365 const $el = $(this);
366 if (s.grid_settings && ev === "load") {
367 const thisTaxonomy =
368 $el.attr("data-filter") !== "*" ? $el.data("ajax-filter")[0] : "*";
369 const thisFilter =
370 $el.attr("data-filter") !== "*" ? $el.data("ajax-filter")[1] : "*";
371 $.ajax({
372 type: "POST",
373 url: KingAddonsGridData.ajaxUrl,
374 data: {
375 action: s.grid_settings
376 ? "king_addons_get_filtered_count"
377 : "king_addons_get_filtered_count",
378 king_addons_offset: 0,
379 king_addons_filter: thisFilter,
380 king_addons_taxonomy: thisTaxonomy,
381 grid_settings: s.grid_settings,
382 },
383 success: (response) => {
384 $el.find("sup").text(response.data.query_found);
385 },
386 });
387 } else {
388 if ($el.attr("data-filter") === "*") {
389 $el.find("sup").text(
390 $scope.find(".king-addons-grid-filters").next().find("article").length
391 );
392 } else {
393 $el.find("sup").text($scope.find($el.attr("data-filter")).length);
394 }
395 }
396 });
397 }
398
399 if (s.filters_linkable === "yes") return;
400
401 // Deep linking support.
402 if (s.deeplinking === "yes") {
403 let deepLink = window.location.hash.replace("#filter:", ".");
404 if (window.location.hash.match("#filter:all")) deepLink = "*";
405 const activeFilter = $scope
406 .find(`.king-addons-grid-filters span[data-filter="${deepLink}"]`)
407 .not(".king-addons-back-filter");
408 $scope.find(".king-addons-grid-filters span").removeClass("king-addons-active-filter");
409 activeFilter.addClass("king-addons-active-filter");
410 $grid.isotopekng({filter: deepLink});
411 s.lightbox.selector =
412 deepLink === "*" ? ".king-addons-grid-image-wrap" : `${deepLink} .king-addons-grid-image-wrap`;
413 lightboxPopup(s);
414 }
415
416 // Hide empty filters.
417 if (s.filters_hide_empty === "yes" && !s.grid_settings) {
418 $scope.find(".king-addons-grid-filters span").each(function () {
419 const filterClass = $(this).attr("data-filter");
420 if (filterClass !== "*" && $grid.find(filterClass).length === 0) {
421 $(this).parent("li").addClass("king-addons-hidden-element");
422 } else {
423 $(this).parent("li").removeClass("king-addons-hidden-element");
424 }
425 });
426 }
427
428 // Default filter.
429 if (
430 !$scope.hasClass("elementor-widget-king-addons-woo-category-grid-pro") &&
431 !$scope.hasClass("elementor-widget-king-addons-category-grid-pro") &&
432 s.filters_default_filter
433 ) {
434 setTimeout(() => {
435 const filterEl = $scope
436 .find(".king-addons-grid-filters")
437 .find(`span[data-filter*="-${s.filters_default_filter}"]`);
438 if (filterEl.length) filterEl[0].click();
439 }, 100);
440 }
441
442 // Filter click behavior.
443 if (!s.grid_settings) {
444 $scope.find(".king-addons-grid-filters span").on("click", function () {
445 const filterClass = $(this).data("filter");
446 $scope.find(".king-addons-grid-filters span").removeClass("king-addons-active-filter");
447 $(this).addClass("king-addons-active-filter");
448 if (s.deeplinking === "yes") {
449 const filterHash =
450 filterClass === "*" ? "#filter:all" : `#filter:${filterClass.replace(".", "")}`;
451 window.location.href =
452 window.location.pathname + window.location.search + filterHash;
453 }
454 if (["infinite-scroll", "load-more"].includes(s.pagination_type)) {
455 if ($grid.find($(this).attr("data-filter")).length === 0) {
456 $grid.infiniteScroll("loadNextPage");
457 }
458 }
459 if (s.filters_animation !== "default") {
460 $scope.find(".king-addons-grid-item-inner").css({
461 opacity: "0",
462 transition: "none",
463 });
464 if (s.filters_animation === "fade-slide") {
465 $scope.find(".king-addons-grid-item-inner").css("top", "20px");
466 } else if (s.filters_animation === "zoom") {
467 $scope.find(".king-addons-grid-item-inner").css("transform", "scale(0.01)");
468 }
469 }
470 $grid.isotopekng({filter: filterClass});
471 s.lightbox.selector =
472 filterClass === "*" ? ".king-addons-grid-image-wrap" : `${filterClass} .king-addons-grid-image-wrap`;
473 $grid.data("lightGallery").destroy(true);
474 $grid.lightGallery(s.lightbox);
475 });
476 }
477 }
478
479 // Experiment: dynamic filtering via AJAX.
480 function filtersExperiment() {
481 const countAction = $scope.hasClass("elementor-widget-king-addons-woo-grid")
482 ? "king_addons_get_woocommerce_filtered_count"
483 : "king_addons_get_filtered_count";
484 const contentAction = $scope.hasClass("elementor-widget-king-addons-woo-grid")
485 ? "king_addons_filter_woocommerce_products"
486 : "king_addons_filter_grid_posts";
487
488 $scope.find(".king-addons-grid-filters").on("click", "span", function (e) {
489 e.preventDefault();
490 e.stopPropagation();
491 // e.stopImmediatePropagation();
492
493 const filterClass = $(this).data("filter");
494 const thisTaxonomy = filterClass !== "*" ? $(this).data("ajax-filter")[0] : "*";
495 const thisFilter = filterClass !== "*" ? $(this).data("ajax-filter")[1] : "*";
496 const loader = `<div class="king-addons-grid-loader-wrap"><div class="king-addons-ring"><div></div><div></div><div></div><div></div></div></div>`;
497
498 $scope.find(".king-addons-grid-filters span").removeClass("king-addons-active-filter");
499 $(this).addClass("king-addons-active-filter");
500
501 $grid.isotopekng("destroy");
502 $grid.html(loader);
503
504 $.ajax({
505 type: "POST",
506 url: KingAddonsGridData.ajaxUrl,
507 data: {
508 action: countAction,
509 king_addons_offset:
510 +settings.grid_settings.query_offset +
511 $scope.find(".king-addons-grid-item").length,
512 king_addons_filter: thisFilter,
513 king_addons_taxonomy: thisTaxonomy,
514 grid_settings: settings.grid_settings,
515 },
516 success: function () {
517 $.ajax({
518 type: "POST",
519 url: KingAddonsGridData.ajaxUrl,
520 data: {
521 action: contentAction,
522 king_addons_item_length:
523 +settings.grid_settings.query_offset +
524 $scope.find(".king-addons-grid-item").length,
525 king_addons_filter: thisFilter,
526 king_addons_taxonomy: thisTaxonomy,
527 grid_settings: settings.grid_settings,
528 },
529 success: function (resp) {
530 setTimeout(() => {
531 $grid.addClass("king-addons-zero-opacity");
532 $grid.html($(resp));
533 isotopeLayout(settings, $(resp));
534 setTimeout(() => {
535 isotopeLayout(settings);
536 window.dispatchEvent(new Event("resize"));
537 window.dispatchEvent(new Event("scroll"));
538 }, 500);
539 mediaHoverLink();
540 $grid.removeClass("king-addons-zero-opacity");
541 }, 800);
542 },
543 });
544 },
545 });
546 });
547 }
548
549 // Experiment: load-more via AJAX.
550 function loadMoreExperiment() {
551 const countAction = $scope.hasClass("elementor-widget-king-addons-woo-grid")
552 ? "king_addons_get_woocommerce_filtered_count"
553 : "king_addons_get_filtered_count";
554 const contentAction = $scope.hasClass("elementor-widget-king-addons-woo-grid")
555 ? "king_addons_filter_woocommerce_products"
556 : "king_addons_filter_grid_posts";
557 const $pagination = $scope.find(".king-addons-grid-pagination");
558
559 $scope.find(".king-addons-load-more-btn").on("click", function (e) {
560 e.preventDefault();
561 e.stopPropagation();
562 // e.stopImmediatePropagation();
563
564 let thisTaxonomy = "*";
565 let thisFilter = "*";
566 const $activeFilter = $scope.find(".king-addons-active-filter");
567 if ($activeFilter.length && $activeFilter.data("filter") !== "*") {
568 thisTaxonomy = $activeFilter.data("ajax-filter")[0];
569 thisFilter = $activeFilter.data("ajax-filter")[1];
570 }
571
572 $pagination.find(".king-addons-load-more-btn").hide();
573 $pagination.find(".king-addons-pagination-loading").css("display", "inline-block");
574
575 $.ajax({
576 type: "POST",
577 url: KingAddonsGridData.ajaxUrl,
578 data: {
579 action: countAction,
580 king_addons_offset:
581 +settings.grid_settings.query_offset +
582 $grid.find(".king-addons-grid-item").length,
583 king_addons_filter: thisFilter,
584 king_addons_taxonomy: thisTaxonomy,
585 grid_settings: settings.grid_settings,
586 },
587 success: function (res) {
588 $.ajax({
589 type: "POST",
590 url: KingAddonsGridData.ajaxUrl,
591 data: {
592 action: contentAction,
593 king_addons_offset:
594 +settings.grid_settings.query_offset +
595 $grid.find(".king-addons-grid-item").length,
596 king_addons_filter: thisFilter,
597 king_addons_taxonomy: thisTaxonomy,
598 grid_settings: settings.grid_settings,
599 },
600 success: function (resp) {
601 const $items = $(resp);
602 $grid.infiniteScroll("appendItems", $items);
603 $grid.isotopekng("appended", $items);
604 $items.imagesLoaded().progress(() => {
605 isotopeLayout(settings);
606 setTimeout(() => isotopeLayout(settings), 100);
607 setTimeout(() => $grid.addClass("grid-images-loaded"), 500);
608 });
609 $pagination.find(".king-addons-pagination-loading").hide();
610 if (res.data.page_count > 1) {
611 $pagination.find(".king-addons-load-more-btn").fadeIn();
612 } else {
613 $pagination.find(".king-addons-pagination-finish").fadeIn(1000);
614 $pagination.delay(2000).fadeOut(1000);
615 setTimeout(() => $pagination.find(".king-addons-pagination-loading").hide(), 500);
616 }
617 lightboxPopup(settings);
618 $grid.data("lightGallery").destroy(true);
619 $grid.lightGallery(settings.lightbox);
620 mediaHoverLink();
621 postSharing();
622 lazyLoadObserver();
623 window.dispatchEvent(new Event("resize"));
624 window.dispatchEvent(new Event("scroll"));
625 },
626 });
627 },
628 });
629 });
630 }
631
632 // Setup Infinite Scroll.
633 function setupInfiniteScroll(s) {
634 const $pagination = $scope.find(".king-addons-grid-pagination");
635 const scopeClass = `.elementor-element-${$scope.attr("data-id")}`;
636 let navClass = false,
637 threshold = false;
638 if (s.pagination_type === "infinite-scroll") {
639 threshold = 300;
640 navClass = `${scopeClass} .king-addons-load-more-btn`;
641 }
642 $grid.infiniteScroll({
643 path: `${scopeClass} .king-addons-grid-pagination a`,
644 hideNav: navClass,
645 append: false,
646 history: false,
647 scrollThreshold: threshold,
648 status: `${scopeClass} .page-load-status`,
649 onInit() {
650 this.on("load", () => $grid.removeClass("grid-images-loaded"));
651 },
652 });
653 $grid.on("request.infiniteScroll", () => {
654 $pagination.find(".king-addons-load-more-btn").hide();
655 $pagination.find(".king-addons-pagination-loading").css("display", "inline-block");
656 });
657 let pagesLoaded = 0;
658 $grid.on("load.infiniteScroll", (event, response) => {
659 pagesLoaded++;
660 const $items = $(response).find(scopeClass).find(".king-addons-grid-item");
661 if ($scope.find(".woocommerce-result-count").length) {
662 let updatedCount = $scope.find(".woocommerce-result-count").text();
663 updatedCount = updatedCount.replace(
664 /\d\u2013\d+/,
665 `1–${$scope.find(".king-addons-grid-item").length + $items.length}`
666 );
667 $scope.find(".woocommerce-result-count").text(updatedCount);
668 }
669 $grid.infiniteScroll("appendItems", $items);
670 $grid.isotopekng("appended", $items);
671 $items.imagesLoaded().progress(() => {
672 isotopeLayout(s);
673 setTimeout(() => {
674 isotopeLayout(s);
675 isotopeFilters(s);
676 }, 10);
677 setTimeout(() => $grid.addClass("grid-images-loaded"), 500);
678 });
679 $pagination.find(".king-addons-pagination-loading").hide();
680 if (pagesLoaded < s.pagination_max_pages) {
681 if (s.pagination_type === "load-more") {
682 $pagination.find(".king-addons-load-more-btn").fadeIn();
683 if ($scope.find(".king-addons-grid-filters").length) {
684 const activeF = $scope.find(".king-addons-active-filter");
685 if (activeF.length && activeF.attr("data-filter") !== "*") {
686 const filterClass = activeF.attr("data-filter").slice(1);
687 let foundOne = false;
688 $items.each(function () {
689 if ($(this).hasClass(filterClass)) {
690 foundOne = true;
691 return false;
692 }
693 });
694 if (!foundOne) $grid.infiniteScroll("loadNextPage");
695 }
696 }
697 }
698 } else {
699 $pagination.find(".king-addons-pagination-finish").fadeIn(1000);
700 $pagination.delay(2000).fadeOut(1000);
701 setTimeout(() => $pagination.find(".king-addons-pagination-loading").hide(), 500);
702 }
703 lightboxPopup(s);
704 $grid.data("lightGallery").destroy(true);
705 $grid.lightGallery(s.lightbox);
706 mediaHoverLink();
707 postSharing();
708 lazyLoadObserver();
709 setTimeout(() => {
710 setEqualHeight(s);
711 window.dispatchEvent(new Event("resize"));
712 }, 500);
713 });
714 $pagination.find(".king-addons-load-more-btn").on("click", () => {
715 $grid.infiniteScroll("loadNextPage");
716 return false;
717 });
718 }
719
720 // Initialize the Slick slider.
721 function initSlickSlider() {
722 const settingsSlick = JSON.parse($grid.attr("data-slick") || "{}");
723 $grid.slick({
724 appendDots: $scope.find(".king-addons-grid-slider-dots"),
725 rows: settingsSlick.sliderRows,
726 customPaging: () => `<span class="king-addons-grid-slider-dot"></span>`,
727 slidesToShow: getSliderColumns("desktop"),
728 responsive: getSliderResponsiveOptions(settingsSlick),
729 });
730 handleSlickArrows();
731 handleSlickDots();
732 }
733
734 // Get the number of slider columns.
735 function getSliderColumns(type) {
736 const match = $scope.attr("class").match(/king-addons-grid-slider-columns-(\d)/);
737 return type === "desktop" ? (match ? parseInt(match[1], 10) : 2) : 2;
738 }
739
740 // Get responsive slider options.
741 function getSliderResponsiveOptions(sl) {
742 const className = $scope.attr("class");
743 const colDesktop = getSliderColumns("desktop");
744 const colWide = extractColumns(className, "widescreen") || colDesktop;
745 const colLaptop = extractColumns(className, "laptop") || colDesktop;
746 const colTablet = extractColumns(className, "tablet") || 2;
747 const colTabletExtra = extractColumns(className, "tablet_extra") || colTablet;
748 const colMobileExtra = extractColumns(className, "mobile_extra") || colTablet;
749 const colMobile = extractColumns(className, "mobile") || 1;
750 const slidesToScroll = sl.sliderSlidesToScroll;
751 const adjust = (cols) => (slidesToScroll > cols ? 1 : slidesToScroll);
752 return [
753 {breakpoint: 10000, settings: {slidesToShow: colWide, slidesToScroll: adjust(colWide)}},
754 {
755 breakpoint: 2399,
756 settings: {slidesToShow: colDesktop, slidesToScroll: adjust(colDesktop)}
757 },
758 {
759 breakpoint: 1221,
760 settings: {slidesToShow: colLaptop, slidesToScroll: adjust(colLaptop)}
761 },
762 {
763 breakpoint: 1200,
764 settings: {slidesToShow: colTabletExtra, slidesToScroll: adjust(colTabletExtra)}
765 },
766 {
767 breakpoint: 1024,
768 settings: {slidesToShow: colTablet, slidesToScroll: adjust(colTablet)}
769 },
770 {
771 breakpoint: 880,
772 settings: {slidesToShow: colMobileExtra, slidesToScroll: adjust(colMobileExtra)}
773 },
774 {
775 breakpoint: 768,
776 settings: {slidesToShow: colMobile, slidesToScroll: adjust(colMobile)}
777 },
778 ];
779 }
780
781 // Extract a number of columns from a class name.
782 function extractColumns(className, type) {
783 const match = className.match(new RegExp(`columns--${type}(\\d)`));
784 return match ? parseInt(match[1], 10) : false;
785 }
786
787 // Adjust the slider arrows based on available space.
788 function handleSlickArrows() {
789 const $prevArrow = $scope.find(".king-addons-grid-slider-prev-arrow");
790 const $nextArrow = $scope.find(".king-addons-grid-slider-next-arrow");
791 if (!$prevArrow.length || !$nextArrow.length) return;
792 const positionSum = $prevArrow.position().left * -2;
793 $(window).on("load", checkArrows);
794 $(window).smartresize(checkArrows);
795
796 function checkArrows() {
797 if (
798 $(window).width() <=
799 $scope.outerWidth() +
800 $prevArrow.outerWidth() +
801 $nextArrow.outerWidth() +
802 positionSum
803 ) {
804 $prevArrow.addClass("king-addons-adjust-slider-prev-arrow");
805 $nextArrow.addClass("king-addons-adjust-slider-next-arrow");
806 } else {
807 $prevArrow.removeClass("king-addons-adjust-slider-prev-arrow");
808 $nextArrow.removeClass("king-addons-adjust-slider-next-arrow");
809 }
810 }
811 }
812
813 // Adjust slider dots if needed.
814 function handleSlickDots() {
815 if (
816 $scope.find(".slick-dots").length &&
817 $scope.hasClass("king-addons-grid-slider-dots-horizontal")
818 ) {
819 resizeDots();
820 $(window).smartresize(() => setTimeout(resizeDots, 300));
821 }
822
823 function resizeDots() {
824 const $dots = $scope.find(".slick-dots li");
825 const marginRight = parseInt($dots.find("span").css("margin-right"), 10);
826 const width = $dots.outerWidth() * $dots.length - marginRight;
827 $scope.find(".slick-dots").css("width", width);
828 }
829 }
830
831 // Setup "Add to Cart" behavior.
832 function setupAddToCart() {
833 const $addCartIcon = $grid.find(".king-addons-grid-item-add-to-cart i");
834 let addCartIconClass = $addCartIcon.attr("class") || "";
835 if (addCartIconClass) {
836 addCartIconClass = addCartIconClass.substring(
837 addCartIconClass.indexOf("fa-")
838 );
839 }
840 $body.on("adding_to_cart", (ev, btn) => btn.fadeTo("slow", 0));
841 $body.on("added_to_cart", (ev, fragments, hash, btn) => {
842 const productId = btn.data("product_id");
843 btn.next().fadeTo(700, 1).css("display", "inline-block");
844 btn.css("display", "none");
845 if (btn.data("atc-popup") === "sidebar") {
846 $(".king-addons-mini-cart-toggle-wrap a").each(function () {
847 const $miniCart = $(this)
848 .closest(".king-addons-mini-cart-inner")
849 .find(".king-addons-mini-cart");
850 if ($miniCart.css("display") === "none") $(this).trigger("click");
851 });
852 } else if (btn.data("atc-popup") === "popup") {
853 addToCartPopup(btn, productId);
854 }
855 if (addCartIconClass) {
856 btn.find("i").removeClass(addCartIconClass).addClass("fa-check");
857 setTimeout(() => {
858 btn.find("i").removeClass("fa-check").addClass(addCartIconClass);
859 }, 3500);
860 }
861 });
862
863 function addToCartPopup(btn, productId) {
864 const $popupItem = btn.closest(".king-addons-grid-item");
865 const popupText = $popupItem.find(".king-addons-grid-item-title").text();
866 const popupLink = btn.next().attr("href");
867 const popupImageSrc = $popupItem.find(".king-addons-grid-image-wrap").data("src");
868 const popupAnimation = btn.data("atc-animation");
869 const fadeOutIn = btn.data("atc-fade-out-in");
870 const animTime = btn.data("atc-animation-time");
871 let animationClass = "king-addons-added-to-cart-default",
872 removeAnimationClass = "king-addons-added-to-cart-popup-hide";
873 const popupImage = popupImageSrc
874 ? `<div class="king-addons-added-tc-popup-img"><img src="${popupImageSrc}" alt="" /></div>`
875 : "";
876 switch (popupAnimation) {
877 case "slide-left":
878 animationClass = "king-addons-added-to-cart-slide-in-left";
879 removeAnimationClass = "king-addons-added-to-cart-slide-out-left";
880 break;
881 case "scale-up":
882 animationClass = "king-addons-added-to-cart-scale-up";
883 removeAnimationClass = "king-addons-added-to-cart-scale-down";
884 break;
885 case "skew":
886 animationClass = "king-addons-added-to-cart-skew";
887 removeAnimationClass = "king-addons-added-to-cart-skew-off";
888 break;
889 case "fade":
890 animationClass = "king-addons-added-to-cart-fade";
891 removeAnimationClass = "king-addons-added-to-cart-fade-out";
892 break;
893 }
894 if (!$scope.find(`#king-addons-added-to-cart-${productId}`).length) {
895 $scope
896 .find(".king-addons-grid")
897 .append(
898 `<div id="king-addons-added-to-cart-${productId}" class="king-addons-added-to-cart-popup ${animationClass}">
899 ${popupImage}
900 <div class="king-addons-added-tc-title">
901 <p>${popupText} ${KingAddonsGridData.addedToCartText}</p>
902 <p><a href="${popupLink}">${KingAddonsGridData.viewCart}</a></p>
903 </div>
904 </div>`
905 );
906 setTimeout(() => {
907 $scope
908 .find(`#king-addons-added-to-cart-${productId}`)
909 .addClass(removeAnimationClass);
910 setTimeout(() => {
911 $scope.find(`#king-addons-added-to-cart-${productId}`).remove();
912 }, animTime * 1000);
913 }, fadeOutIn * 1000);
914 }
915 }
916 }
917
918 // Setup post sharing behavior.
919 function postSharing() {
920 if (!$scope.find(".king-addons-sharing-trigger").length) return;
921 const $sharingTrigger = $scope.find(".king-addons-sharing-trigger");
922 const $sharingInner = $scope.find(".king-addons-post-sharing-inner");
923 let sharingWidth = 5;
924 $sharingInner.first().find("a").each(function () {
925 sharingWidth += $(this).outerWidth() + parseInt($(this).css("margin-right"), 10);
926 });
927 const direction = $sharingTrigger.attr("data-direction");
928 if (direction === "left" || direction === "right") {
929 $sharingInner.css("width", `${sharingWidth}px`);
930 if (direction === "left") {
931 $sharingInner.css(
932 "left",
933 -(parseInt($sharingInner.find("a").css("margin-right"), 10) + sharingWidth) + "px"
934 );
935 } else {
936 $sharingInner.css({left: $sharingTrigger.css("margin-right")});
937 }
938 } else if (direction === "top") {
939 const margin = parseInt($sharingInner.find("a").css("margin-right"), 10);
940 $sharingInner.find("a").css({"margin-right": "0", "margin-top": `${margin}px`});
941 $sharingInner.css({
942 top: `-${margin}px`,
943 left: "50%",
944 transform: "translate(-50%, -100%)",
945 });
946 } else if (direction === "bottom") {
947 const margin = parseInt($sharingInner.find("a").css("margin-right"), 10);
948 $sharingInner.find("a").css({"margin-right": "0", "margin-bottom": `${margin}px`});
949 $sharingInner.css({
950 bottom: `-${margin}px`,
951 left: "50%",
952 transform: "translate(-50%, 100%)",
953 });
954 }
955 if ($sharingTrigger.attr("data-action") === "click") {
956 $sharingTrigger.on("click", function () {
957 const $inner = $(this).next();
958 if ($inner.css("visibility") === "hidden") {
959 $inner.css("visibility", "visible").find("a").css({opacity: "1", top: "0"});
960 setTimeout(() => $inner.find("a").addClass("king-addons-no-transition-delay"), $inner.find("a").length * 100);
961 } else {
962 $inner.find("a").removeClass("king-addons-no-transition-delay").css({
963 opacity: "0",
964 top: "-5px",
965 });
966 setTimeout(() => $inner.css("visibility", "hidden"), $inner.find("a").length * 100);
967 }
968 });
969 } else {
970 $sharingTrigger.on("mouseenter", function () {
971 const $inner = $(this).next();
972 $inner.css("visibility", "visible").find("a").css({opacity: "1", top: "0"});
973 setTimeout(() => $inner.find("a").addClass("king-addons-no-transition-delay"), $inner.find("a").length * 100);
974 });
975 $scope.find(".king-addons-grid-item-sharing").on("mouseleave", function () {
976 const $inner = $(this).find(".king-addons-post-sharing-inner");
977 $inner.find("a").removeClass("king-addons-no-transition-delay").css({
978 opacity: "0",
979 top: "-5px",
980 });
981 setTimeout(() => $inner.css("visibility", "hidden"), $inner.find("a").length * 100);
982 });
983 }
984 }
985
986 // Setup media hover behavior and overlay clickable links.
987 function mediaHoverLink() {
988 const $wrap = $grid.find(".king-addons-grid-image-wrap");
989 if ($wrap.data("img-on-hover") === "yes") {
990 $grid.find(".king-addons-grid-media-wrap").hover(
991 function () {
992 const $secondImg = $(this).find("img:nth-of-type(2)");
993 if ($secondImg.attr("src")) {
994 $(this).find("img:first-of-type").addClass("king-addons-hidden-img");
995 $secondImg.removeClass("king-addons-hidden-img");
996 }
997 },
998 function () {
999 const $secondImg = $(this).find("img:nth-of-type(2)");
1000 if ($secondImg.attr("src")) {
1001 $secondImg.addClass("king-addons-hidden-img");
1002 $(this).find("img:first-of-type").removeClass("king-addons-hidden-img");
1003 }
1004 }
1005 );
1006 }
1007 if ($wrap.attr("data-overlay-link") === "yes" && !isEditor) {
1008 $wrap.css("cursor", "pointer").on("click", function (e) {
1009 const cn = e.target.className;
1010 if (
1011 cn.indexOf("inner-block") !== -1 ||
1012 cn.indexOf("king-addons-cv-inner") !== -1 ||
1013 cn.indexOf("king-addons-grid-media-hover") !== -1
1014 ) {
1015 e.preventDefault();
1016 let itemUrl = $(this)
1017 .find(".king-addons-grid-media-hover-bg")
1018 .attr("data-url")
1019 .replace("#new_tab", "");
1020 if ($grid.find(".king-addons-grid-item-title a").attr("target") === "_blank") {
1021 window.open(itemUrl, "_blank").focus();
1022 } else {
1023 window.location.href = itemUrl;
1024 }
1025 }
1026 });
1027 }
1028 }
1029
1030 function lightboxPopup(settings) {
1031 // If there are no .king-addons-grid-item-lightbox elements, exit early
1032 if (!$scope.find('.king-addons-grid-item-lightbox').length) return;
1033
1034 const lightboxElements = $scope.find('.king-addons-grid-item-lightbox');
1035 const firstOverlay = lightboxElements.find('.king-addons-grid-lightbox-overlay').first();
1036
1037 /**
1038 * Returns the gallery lightbox item wrapper for a slide index.
1039 *
1040 * @param {number} slideIndex LightGallery slide index.
1041 * @return {jQuery} Matching image wrap element.
1042 */
1043 const getGalleryLightboxWrapByIndex = (slideIndex) => {
1044 return $grid.find('.king-addons-grid-image-wrap').eq(slideIndex);
1045 };
1046
1047 /**
1048 * Returns the active LightGallery slide index.
1049 *
1050 * @return {number} Active slide index.
1051 */
1052 const getActiveLightboxSlideIndex = () => {
1053 const $current = jQuery('.lg-outer .lg-item.lg-current');
1054 if ($current.length && $current.attr('data-lg-index') !== undefined) {
1055 return parseInt($current.attr('data-lg-index'), 10);
1056 }
1057
1058 return $current.length ? $current.index() : 0;
1059 };
1060
1061 /**
1062 * Returns a LightGallery slide element by index.
1063 *
1064 * @param {number} slideIndex LightGallery slide index.
1065 * @return {jQuery} Matching slide element.
1066 */
1067 const getLightboxSlideByIndex = (slideIndex) => {
1068 return jQuery('.lg-outer .lg-item').eq(slideIndex);
1069 };
1070
1071 /**
1072 * Returns the currently visible LightGallery slide.
1073 *
1074 * @return {jQuery} Active slide element.
1075 */
1076 const getActiveLightboxSlide = () => {
1077 return jQuery('.lg-outer .lg-item.lg-current');
1078 };
1079
1080 /**
1081 * Resolves the target slide index from a LightGallery event.
1082 *
1083 * onAfterSlide passes (previousIndex, currentIndex), not the active index as the first arg.
1084 *
1085 * @param {Event|undefined} event LightGallery jQuery event object.
1086 * @param {number|undefined} firstArg First event argument.
1087 * @param {number|undefined} secondArg Second event argument.
1088 * @return {number} Resolved slide index.
1089 */
1090 const resolveLightboxSlideIndex = (event, firstArg, secondArg) => {
1091 if (event && event.type === 'onAfterSlide' && typeof secondArg === 'number') {
1092 return secondArg;
1093 }
1094
1095 if (event && event.type === 'onAferAppendSlide' && typeof firstArg === 'number') {
1096 return firstArg;
1097 }
1098
1099 if (typeof firstArg === 'number') {
1100 return firstArg;
1101 }
1102
1103 return getActiveLightboxSlideIndex();
1104 };
1105
1106 /**
1107 * Updates responsive max-height for the active lightbox video.
1108 *
1109 * @return {void}
1110 */
1111 const resizeActiveLightboxVideo = () => {
1112 const $slide = getActiveLightboxSlide();
1113 if (!$slide.hasClass('king-addons-lg-video-slide')) {
1114 return;
1115 }
1116
1117 const toolbarHeight = jQuery('.lg-toolbar').outerHeight() || 50;
1118 const thumbHeight = jQuery('.lg-outer.lg-thumb-open .lg-thumb-outer').outerHeight() || 0;
1119 const availableHeight = Math.max(240, window.innerHeight - toolbarHeight - thumbHeight - 40);
1120 const availableWidth = Math.max(320, window.innerWidth - 40);
1121
1122 $slide.find('video').css({
1123 maxHeight: `${availableHeight}px`,
1124 maxWidth: `${availableWidth}px`,
1125 });
1126 };
1127
1128 const debouncedResizeLightboxVideo = debounce(resizeActiveLightboxVideo, 150);
1129
1130 /**
1131 * Toggles image-only toolbar controls for the active slide.
1132 *
1133 * @param {number} slideIndex LightGallery slide index.
1134 * @return {void}
1135 */
1136 const toggleImageLightboxControls = (slideIndex) => {
1137 const $wrap = getGalleryLightboxWrapByIndex(slideIndex);
1138 const controls = jQuery('#lg-actual-size, #lg-zoom-in, #lg-zoom-out, #lg-download');
1139
1140 if ($wrap.hasClass('king-addons-grid-video-lightbox-item')) {
1141 controls.addClass('king-addons-hidden-element');
1142 } else {
1143 controls.removeClass('king-addons-hidden-element');
1144 }
1145 };
1146
1147 /**
1148 * Injects an HTML5 video player into the active LightGallery slide.
1149 *
1150 * @param {number} slideIndex LightGallery slide index.
1151 * @return {void}
1152 */
1153 const renderHtml5VideoLightboxSlide = (slideIndex) => {
1154 const $wrap = getGalleryLightboxWrapByIndex(slideIndex);
1155 const $slide = getLightboxSlideByIndex(slideIndex);
1156
1157 if (!$wrap.length || !$wrap.hasClass('king-addons-grid-video-lightbox-item')) {
1158 jQuery('.lg-outer').removeClass('king-addons-lg-has-video-slide');
1159 $slide.removeClass('king-addons-lg-video-slide lg-has-video lg-video-playing');
1160 toggleImageLightboxControls(slideIndex);
1161 return;
1162 }
1163
1164 const videoUrl = $wrap.attr('data-kng-video-url');
1165 if (!videoUrl || !$slide.length) {
1166 return;
1167 }
1168
1169 const posterUrl = $wrap.attr('data-poster') || $wrap.attr('data-src') || '';
1170
1171 jQuery('.lg-outer .lg-item video').each(function () {
1172 if (typeof this.pause === 'function') {
1173 this.pause();
1174 }
1175 });
1176
1177 jQuery('.lg-outer').addClass('king-addons-lg-has-video-slide');
1178 $slide.addClass('king-addons-lg-video-slide lg-has-video lg-video-playing lg-complete');
1179 $slide.find('.lg-img-wrap, .lg-image, .lg-video-cont, .lg-object').remove();
1180
1181 if (!$slide.find('.king-addons-lg-html5-video').length) {
1182 const videoType = /\.webm(\?|$)/i.test(videoUrl) ? 'video/webm' : 'video/mp4';
1183 const $videoCont = jQuery(
1184 '<div class="lg-video-cont lg-has-html5 king-addons-lg-html5-video">' +
1185 '<div class="lg-video king-addons-lg-video-inner"></div></div>'
1186 );
1187 const $video = jQuery('<video>', {
1188 class: 'lg-video-object lg-html5',
1189 controls: true,
1190 autoplay: true,
1191 playsinline: true,
1192 preload: 'metadata',
1193 });
1194
1195 if (posterUrl) {
1196 $video.attr('poster', posterUrl);
1197 }
1198
1199 $video.append(jQuery('<source>', { src: videoUrl, type: videoType }));
1200 $videoCont.find('.king-addons-lg-video-inner').append($video);
1201 $slide.empty().append($videoCont);
1202 }
1203
1204 toggleImageLightboxControls(slideIndex);
1205 resizeActiveLightboxVideo();
1206
1207 const videoEl = $slide.find('video').get(0);
1208 if (videoEl && typeof videoEl.play === 'function') {
1209 videoEl.play().catch(function () {});
1210 }
1211 };
1212
1213 /**
1214 * Handles LightGallery slide changes for mixed image and video items.
1215 *
1216 * @param {Event|undefined} event LightGallery event object.
1217 * @param {number|undefined} index Target slide index.
1218 * @return {void}
1219 */
1220 const handleLightboxVideoSlide = (event, firstArg, secondArg) => {
1221 const slideIndex = resolveLightboxSlideIndex(event, firstArg, secondArg);
1222 renderHtml5VideoLightboxSlide(slideIndex);
1223 };
1224
1225 // Assign data-src and data-iframe for items not marked with .lg-initialized
1226 lightboxElements.each(function () {
1227 const el = jQuery(this);
1228 const srcAttr = el.find('.inner-block > span').attr('data-src');
1229 const article = el.closest('article').not('.slick-cloned');
1230 const $wrap = article.find('.king-addons-grid-image-wrap');
1231 const isVideoItem = $wrap.hasClass('king-addons-grid-video-lightbox-item') || article.hasClass('king-addons-grid-item-video');
1232
1233 if (isVideoItem) {
1234 $wrap.removeAttr('data-iframe');
1235
1236 if (!$wrap.attr('data-poster') && $wrap.attr('data-src')) {
1237 $wrap.attr('data-poster', $wrap.attr('data-src'));
1238 }
1239 } else if (!isEditor && srcAttr) {
1240 $wrap.attr('data-src', srcAttr);
1241 }
1242
1243 // Check if it should be an iframe (not hosted in wp-content)
1244 const finalSrc = $wrap.attr('data-src');
1245 if (finalSrc && finalSrc.indexOf('wp-content') === -1 && !isVideoItem) {
1246 $wrap.attr('data-iframe', 'true');
1247 }
1248
1249 // Mark article as initialized
1250 article.addClass('lg-initialized');
1251 });
1252
1253 // Initialize or re-initialize lightGallery
1254 if (!$grid.data('lightGallery')) {
1255 $grid.lightGallery(settings.lightbox);
1256 } else {
1257 $grid.data('lightGallery').destroy(true);
1258 $grid.lightGallery(settings.lightbox);
1259 }
1260
1261 // Rewrite thumbnails only once after opening the lightbox
1262 let thumbsRewritten = false;
1263 $grid.on('onAfterOpen.lg', function () {
1264 if (thumbsRewritten) return;
1265 thumbsRewritten = true;
1266
1267 jQuery('.lg-outer').find('.lg-thumb-item').each(function () {
1268 const img = jQuery(this).find('img');
1269 const original = img.attr('src');
1270 const extIndex = original.lastIndexOf('.');
1271 const dashIndex = original.lastIndexOf('-');
1272 const dashChunk = original.substring(dashIndex, extIndex);
1273
1274 // If it looks like a typical WordPress crop (e.g., -150x150), replace it
1275 if (/\d{3,}x\d{3,}/.test(dashChunk)) {
1276 img.attr('src', original.replace(dashChunk, '-150x150'));
1277 } else {
1278 // Otherwise, just insert -150x150 before the extension
1279 img.attr('src', [
1280 original.slice(0, extIndex),
1281 '-150x150',
1282 original.slice(extIndex)
1283 ].join(''));
1284 }
1285 });
1286 });
1287
1288 $grid.on('onAfterOpen.lg onAfterSlide.lg onAferAppendSlide.lg', function (event, firstArg, secondArg) {
1289 setTimeout(function () {
1290 handleLightboxVideoSlide(event, firstArg, secondArg);
1291 }, 120);
1292 });
1293
1294 $grid.on('onAfterOpen.lg', function () {
1295 jQuery(window).on('resize.kingAddonsGalleryVideo orientationchange.kingAddonsGalleryVideo', debouncedResizeLightboxVideo);
1296 });
1297
1298 // Toggle certain controls on slide events
1299 $scope.find('.king-addons-grid').on('onBeforeSlide.lg', function () {
1300 jQuery('.lg-outer video, .lg-video-object').each(function () {
1301 if (typeof this.pause === 'function') {
1302 this.pause();
1303 }
1304 });
1305 });
1306
1307 $scope.find('.king-addons-grid').on('onBeforeClose.lg', function () {
1308 jQuery(window).off('resize.kingAddonsGalleryVideo orientationchange.kingAddonsGalleryVideo');
1309 jQuery('.lg-outer').removeClass('king-addons-lg-has-video-slide');
1310
1311 jQuery('.lg-outer video, .lg-video-object').each(function () {
1312 if (typeof this.pause === 'function') {
1313 this.pause();
1314 }
1315 });
1316 });
1317
1318 $scope.find('.king-addons-grid').on('onAferAppendSlide.lg onAfterSlide.lg', function (event, firstArg, secondArg) {
1319 const slideIndex = resolveLightboxSlideIndex(event, firstArg, secondArg);
1320 toggleImageLightboxControls(slideIndex);
1321
1322 // If autoplay is disabled, hide the autoplay button
1323 if (settings.lightbox.autoplay === '') {
1324 jQuery('.lg-autoplay-button').css({
1325 width: 0,
1326 height: 0,
1327 overflow: 'hidden'
1328 });
1329 }
1330 });
1331
1332 // If overlay exists, move it and set click handler
1333 if (firstOverlay.length) {
1334 $scope.find('.king-addons-grid-media-hover-bg').after(firstOverlay.remove());
1335 $scope.find('.king-addons-grid-lightbox-overlay').on('click', function () {
1336 if (!isEditor) {
1337 jQuery(this).closest('article').find('.king-addons-grid-image-wrap').trigger('click');
1338 } else {
1339 alert('Lightbox is Disabled in the Editor! Please Preview this Page to see it in action.');
1340 }
1341 });
1342 } else {
1343 // Otherwise, set click on .inner-block > span
1344 lightboxElements.find('.inner-block > span').on('click', function () {
1345 if (!isEditor) {
1346 jQuery(this).closest('article').find('.king-addons-grid-image-wrap').trigger('click');
1347 } else {
1348 alert('Lightbox is Disabled in the Editor! Please Preview this Page to see it in action.');
1349 }
1350 });
1351 }
1352 }
1353
1354
1355 // Handle post likes.
1356 function postLikes() {
1357 if (!$scope.find(".king-addons-post-like-button").length) return;
1358 $scope.on("click", ".king-addons-post-like-button", function (e) {
1359 e.preventDefault();
1360 const $btn = $(this);
1361 if (!$btn.attr("data-post-id")) return false;
1362 $.ajax({
1363 type: "POST",
1364 url: $btn.attr("data-ajax"),
1365 data: {
1366 action: "king_addons_likes_init",
1367 post_id: $btn.attr("data-post-id"),
1368 nonce: $btn.attr("data-nonce"),
1369 },
1370 beforeSend: () => $btn.fadeTo(500, 0.5),
1371 success: function (response) {
1372 let iconClass = $btn.attr("data-icon");
1373 let countHTML = response.count;
1374 if (!countHTML.replace(/<\/?[^>]+(>|$)/g, "")) {
1375 countHTML = `<span class="king-addons-post-like-count">${$btn.attr("data-text")}</span>`;
1376 $btn.addClass("king-addons-likes-zero");
1377 } else {
1378 $btn.removeClass("king-addons-likes-zero");
1379 }
1380 if ($btn.hasClass("king-addons-already-liked")) {
1381 $btn.prop("title", "Like").removeClass("king-addons-already-liked");
1382 $btn.html(`<i class="${iconClass.replace("fas", "far")}"></i>${countHTML}`);
1383 } else {
1384 $btn.prop("title", "Unlike").addClass("king-addons-already-liked");
1385 $btn.html(`<i class="${iconClass.replace("far", "fas")}"></i>${countHTML}`);
1386 }
1387 $btn.fadeTo(500, 1);
1388 },
1389 });
1390 return false;
1391 });
1392 }
1393
1394 // Animate grid items after layout.
1395 function animateGridItems(filteredItems, s, $grid) {
1396 let initStager = 0,
1397 filterStager = 0,
1398 deepLinkStager = 0;
1399 if (!$grid.hasClass("grid-images-loaded")) $grid.css("opacity", "1");
1400 filteredItems.forEach((item) => {
1401 initStager += s.animation_delay;
1402 $(item.element)
1403 .find(".king-addons-grid-item-inner")
1404 .css({
1405 opacity: "1",
1406 top: "0",
1407 transform: "scale(1)",
1408 transition: `all ${s.animation_duration}s ease-in ${initStager}s`,
1409 });
1410 filterStager += s.filters_animation_delay;
1411 if ($grid.hasClass("grid-images-loaded")) {
1412 $(item.element)
1413 .find(".king-addons-grid-item-inner")
1414 .css({
1415 transition: `all ${s.filters_animation_duration}s ease-in ${filterStager}s`,
1416 });
1417 }
1418 let deepLink = window.location.hash;
1419 if (deepLink.includes("#filter:") && !deepLink.includes("#filter:*")) {
1420 deepLink = deepLink.replace("#filter:", "");
1421 if ($(item.element).hasClass(deepLink)) {
1422 deepLinkStager += s.filters_animation_delay;
1423 $(item.element)
1424 .find(".king-addons-grid-item-inner")
1425 .css({"transition-delay": `${deepLinkStager}s`});
1426 }
1427 }
1428 });
1429 }
1430
1431 /**
1432 * Re-layout fitRows grid after editor control changes.
1433 *
1434 * @return {void}
1435 */
1436 this.refreshFitRowsLayout = () => {
1437 if (!settings) {
1438 return;
1439 }
1440
1441 const $gridEl = this.$element.find(".king-addons-grid");
1442 if (!$gridEl.length) {
1443 return;
1444 }
1445
1446 $gridEl.children("article").css("height", "");
1447 isotopeLayout(settings);
1448
1449 if (settings.layout === "fitRows") {
1450 setEqualHeight(settings);
1451 }
1452 };
1453
1454 this.isEditor = isEditor;
1455 this.gridSettings = settings;
1456 this.scheduleEditorLayoutRefresh = debounce(() => {
1457 this.refreshFitRowsLayout();
1458 }, 500);
1459
1460 if (isEditor && settings) {
1461 setTimeout(() => this.refreshFitRowsLayout(), 800);
1462 }
1463 },
1464
1465 /**
1466 * Re-layout the grid in the editor after layout-related setting changes.
1467 *
1468 * @param {string} propertyName Changed Elementor control name.
1469 * @return {void}
1470 */
1471 onElementChange(propertyName) {
1472 if (!this.isEditor || !this.gridSettings || !this.scheduleEditorLayoutRefresh) {
1473 return;
1474 }
1475
1476 if (!this.shouldRefreshGridLayout(propertyName)) {
1477 return;
1478 }
1479
1480 this.scheduleEditorLayoutRefresh();
1481 },
1482
1483 /**
1484 * Determines whether a setting change should trigger a grid relayout.
1485 *
1486 * @param {string} propertyName Changed Elementor control name.
1487 * @return {boolean}
1488 */
1489 shouldRefreshGridLayout(propertyName) {
1490 const prefixes = [
1491 "layout_select",
1492 "layout_fitrows_media_height",
1493 "layout_fitrows_image_fit",
1494 "layout_fitrows_image_fit_position",
1495 "layout_columns",
1496 "layout_gutter_hr",
1497 "layout_gutter_vr",
1498 ];
1499
1500 return prefixes.some(
1501 (prefix) => propertyName === prefix || propertyName.startsWith(`${prefix}_`)
1502 );
1503 },
1504 }),
1505 {$element: $scope}
1506 );
1507 }
1508
1509 gridHooks.forEach((hook) => {
1510 elementorFrontend.hooks.addAction(hook, gridHandler);
1511 });
1512
1513 });
1514 })(jQuery);
1515