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
← All changes | includes/widgets/Mega_Menu/script.js +568 -0 51.1.251.1.83 View file →
@@ -1,0 +1,568 @@
1 +(function ($) {
2 + $(window).on('elementor/frontend/init', function () {
3 + elementorFrontend.hooks.addAction(
4 + 'frontend/element_ready/king-addons-mega-menu.default',
5 + function ($scope) {
6 + const $mainMenu = $scope.find('.king-addons-mega-menu');
7 + if (!$mainMenu.length) return;
8 +
9 + // Ensure data-dropdown-animation attribute is set
10 + let animation = $mainMenu.attr('data-dropdown-animation');
11 + if (!animation) {
12 + animation = $mainMenu.data('dropdown-animation') || 'fade';
13 + $mainMenu.attr('data-dropdown-animation', animation);
14 + }
15 +
16 + // Remove previous animation classes and add the current one
17 + $mainMenu.removeClass(function (i, c) {
18 + return (c.match(/king-addons-animation-[^\s]+/g) || []).join(' ');
19 + });
20 + if ([
21 + 'fade-up',
22 + 'fade-down',
23 + 'fade-left',
24 + 'fade-right',
25 + 'zoom-in',
26 + 'zoom-out',
27 + 'fade',
28 + 'slide',
29 + 'none'
30 + ].includes(animation)) {
31 + $mainMenu.addClass('king-addons-animation-' + animation);
32 + }
33 +
34 + // Dropdown open/close logic (do not set any left/right/transform styles)
35 + $mainMenu.find('.king-addons-menu-items > li').each(function () {
36 + const $menuItem = $(this);
37 + const $dropdowns = $menuItem.find('> ul.sub-menu, > .king-addons-template-content, > .king-addons-submenu');
38 + if ($dropdowns.length) {
39 + $menuItem.off('mouseenter mouseleave');
40 + $menuItem.on('mouseenter', function () {
41 + if (window.innerWidth > 1024) {
42 + $dropdowns.each(function () {
43 + kingAddonsDropdownFitToScreen(this);
44 + });
45 + }
46 + });
47 + $menuItem.on('mouseleave', function () {
48 + $dropdowns.removeClass('king-addons-dropdown-open');
49 + });
50 + }
51 + });
52 +
53 + // Full-width dropdown logic (only set width, do not set left/right/transform)
54 + $mainMenu.filter('.king-addons-dropdown-width-full').each(function () {
55 + const $menu = $(this);
56 + const $window = $(window);
57 + const setDropdownWidth = () => {
58 + const windowWidth = $window.width();
59 + $menu.find('.king-addons-menu-items > li > ul.sub-menu, .king-addons-menu-items > li > .king-addons-template-content').css('width', windowWidth + 'px');
60 + };
61 + setDropdownWidth();
62 + $window.on('resize', setDropdownWidth);
63 + });
64 +
65 + // Accessibility: keyboard navigation
66 + $mainMenu.find('.king-addons-menu-items > li > a').on('keydown', function (e) {
67 + if (e.key === 'Enter' || e.key === ' ') {
68 + e.preventDefault();
69 + $(this).trigger('click');
70 + const $firstLink = $(this).siblings('.sub-menu, .king-addons-template-content').find('a').first();
71 + if ($firstLink.length) {
72 + $firstLink.focus();
73 + }
74 + }
75 + });
76 +
77 + // Escape key closes dropdowns
78 + $(document).on('keydown.kingAddonsMegaMenu', function (e) {
79 + if (e.key === 'Escape') {
80 + $mainMenu.find('.king-addons-menu-items li').removeClass('focus');
81 + $mainMenu.find('.sub-menu, .king-addons-template-content').css({ opacity: '', visibility: '' });
82 + }
83 + });
84 +
85 + // ARIA attributes
86 + $mainMenu.find('.king-addons-menu-items li').each(function () {
87 + const $li = $(this);
88 + if ($li.has('.sub-menu, .king-addons-template-content').length) {
89 + $li.attr('aria-haspopup', 'true');
90 + $li.find('> a').attr('aria-expanded', 'false');
91 + $li.on('mouseenter focus', function () {
92 + $li.find('> a').attr('aria-expanded', 'true');
93 + });
94 + $li.on('mouseleave blur', function () {
95 + $li.find('> a').attr('aria-expanded', 'false');
96 + });
97 + }
98 + });
99 +
100 +
101 +
102 + // Mobile menu toggle logic
103 + const $mobileToggle = $scope.find('.king-addons-mobile-menu-toggle');
104 + const $mobileMenu = $scope.find('.king-addons-mobile-menu');
105 + const $mobileClose = $scope.find('.king-addons-mobile-menu-close');
106 +
107 + $mobileToggle.on('click', function (e) {
108 + e.preventDefault();
109 + $mobileMenu.slideToggle(250);
110 + $mobileToggle.toggleClass('active');
111 + });
112 +
113 + $mobileClose.on('click', function (e) {
114 + e.preventDefault();
115 + $mobileMenu.slideUp(250);
116 + $mobileToggle.removeClass('active');
117 + });
118 +
119 + // Mobile menu dropdown logic
120 + $mobileMenu.find('.menu-item-has-children > a').on('click', function (e) {
121 + // Only handle in mobile menu
122 + if (window.innerWidth > 1024) return;
123 + e.preventDefault();
124 + const $parent = $(this).parent();
125 + const $submenu = $parent.children('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content');
126 + // Toggle current submenu
127 + $submenu.slideToggle(250);
128 + $parent.toggleClass('king-addons-mobile-menu__item--open');
129 + // Optionally close other open submenus (accordion behavior)
130 + $parent.siblings('.menu-item-has-children').removeClass('king-addons-mobile-menu__item--open')
131 + .children('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content').slideUp(250);
132 + });
133 +
134 + // Initialize center logo positioning
135 + kingAddonsCenterLogoPositioning($mainMenu);
136 +
137 + // Initialize Lottie animations for logos (with library loading wait)
138 + waitForLottieAndInitialize($mainMenu);
139 + }
140 + );
141 + });
142 +
143 + /**
144 + * Force transform: none !important and left: 0 !important for all .sub-menu, .king-addons-submenu, and .king-addons-template-content in mega menu on mobile width
145 + */
146 + function kingAddonsMobileMenuTransformReset() {
147 + const isMobile = window.innerWidth <= 1024;
148 + const subMenus = document.querySelectorAll('.king-addons-mega-menu .sub-menu, .king-addons-mega-menu .king-addons-submenu, .king-addons-mega-menu .king-addons-template-content');
149 + subMenus.forEach(function(subMenu) {
150 + if (isMobile) {
151 + subMenu.style.setProperty('transform', 'none', 'important');
152 + subMenu.style.setProperty('left', '0', 'important');
153 + } else {
154 + subMenu.style.removeProperty('transform');
155 + subMenu.style.removeProperty('left');
156 + }
157 + });
158 + }
159 +
160 + function kingAddonsMobileMenuHideOnDesktop() {
161 + if (window.innerWidth > 1024) {
162 + document.querySelectorAll('.king-addons-mobile-menu').forEach(function(menu) {
163 + if (menu.style.display !== 'none') {
164 + if (window.jQuery) {
165 + jQuery(menu).slideUp(0);
166 + } else {
167 + menu.style.display = 'none';
168 + }
169 + }
170 + // Close all open dropdowns inside mobile menu
171 + menu.querySelectorAll('.menu-item-has-children.king-addons-mobile-menu__item--open').forEach(function(item) {
172 + item.classList.remove('king-addons-mobile-menu__item--open');
173 + });
174 + menu.querySelectorAll('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content').forEach(function(sub) {
175 + if (window.jQuery) {
176 + jQuery(sub).slideUp(0);
177 + } else {
178 + sub.style.display = 'none';
179 + }
180 + });
181 + });
182 + document.querySelectorAll('.king-addons-mobile-menu-toggle.active').forEach(function(toggle) {
183 + toggle.classList.remove('active');
184 + });
185 + }
186 + }
187 +
188 + window.addEventListener('resize', kingAddonsMobileMenuTransformReset);
189 + document.addEventListener('DOMContentLoaded', kingAddonsMobileMenuTransformReset);
190 + window.addEventListener('resize', kingAddonsMobileMenuHideOnDesktop);
191 + document.addEventListener('DOMContentLoaded', kingAddonsMobileMenuHideOnDesktop);
192 +
193 + /**
194 + * Ensure dropdown (template, sub-menu, custom submenu) fits inside viewport on desktop
195 + * Observer is temporarily disconnected to avoid infinite loop
196 + */
197 + function kingAddonsDropdownFitToScreen(dropdown) {
198 + if (window.innerWidth <= 1024) return;
199 + // Temporarily disconnect observer to avoid loop
200 + if (dropdown._kingAddonsObserver) dropdown._kingAddonsObserver.disconnect();
201 + dropdown.style.left = '';
202 + dropdown.style.right = '';
203 + dropdown.style.transform = '';
204 + const rect = dropdown.getBoundingClientRect();
205 + const viewportWidth = window.innerWidth;
206 + let changed = false;
207 + if (rect.right > viewportWidth) {
208 + const overflowRight = rect.right - viewportWidth;
209 + dropdown.style.left = (dropdown.offsetLeft - overflowRight) + 'px';
210 + changed = true;
211 + }
212 + if (rect.left < 0) {
213 + dropdown.style.left = (dropdown.offsetLeft - rect.left) + 'px';
214 + changed = true;
215 + }
216 + // Reconnect observer after change
217 + if (dropdown._kingAddonsObserver) {
218 + dropdown._kingAddonsObserver.observe(dropdown, { attributes: true, attributeFilter: ['class', 'style'] });
219 + }
220 + }
221 +
222 + /**
223 + * Observe dropdowns and fix their position as soon as they become visible (desktop only)
224 + */
225 + function kingAddonsObserveDropdowns() {
226 + if (window.innerWidth <= 1024) return;
227 + const dropdowns = document.querySelectorAll('.king-addons-template-content, .sub-menu, .king-addons-submenu');
228 + dropdowns.forEach(function(dropdown) {
229 + // Avoid multiple observers
230 + if (dropdown._kingAddonsObserved) return;
231 + dropdown._kingAddonsObserved = true;
232 + const observer = new MutationObserver(function(mutations) {
233 + mutations.forEach(function(mutation) {
234 + if (
235 + mutation.attributeName === 'class' || mutation.attributeName === 'style'
236 + ) {
237 + // Check if dropdown is visible
238 + const style = window.getComputedStyle(dropdown);
239 + if (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0') {
240 + kingAddonsDropdownFitToScreen(dropdown);
241 + }
242 + }
243 + });
244 + });
245 + dropdown._kingAddonsObserver = observer;
246 + observer.observe(dropdown, { attributes: true, attributeFilter: ['class', 'style'] });
247 + });
248 + }
249 + window.addEventListener('DOMContentLoaded', kingAddonsObserveDropdowns);
250 + window.addEventListener('resize', kingAddonsObserveDropdowns);
251 +
252 + /**
253 + * Handle center logo positioning by inserting logo between menu items
254 + * Supports responsive positioning (desktop/tablet/mobile)
255 + */
256 + function kingAddonsCenterLogoPositioning($mainMenu) {
257 + const centerLogo = $mainMenu.attr('data-center-logo');
258 + if (centerLogo !== 'true') return;
259 +
260 + // Get responsive logo positions
261 + const logoPositionDesktop = $mainMenu.attr('data-logo-position-desktop') || 'left';
262 + const logoPositionTablet = $mainMenu.attr('data-logo-position-tablet') || logoPositionDesktop;
263 + const logoPositionMobile = $mainMenu.attr('data-logo-position-mobile') || logoPositionDesktop;
264 +
265 + // Determine current device and position
266 + let currentPosition = logoPositionDesktop;
267 + if (window.innerWidth <= 767) {
268 + currentPosition = logoPositionMobile;
269 + } else if (window.innerWidth <= 1024) {
270 + currentPosition = logoPositionTablet;
271 + }
272 +
273 + // Remove any existing center logo
274 + $mainMenu.find('.king-addons-center-logo-inserted').remove();
275 + $mainMenu.removeClass('king-addons-center-logo-active');
276 +
277 + // Only proceed if current position is center
278 + if (currentPosition !== 'center') return;
279 + if (window.innerWidth <= 1024) return;
280 +
281 + const splitAt = parseInt($mainMenu.attr('data-split-at')) || 2;
282 + const $menuItems = $mainMenu.find('.king-addons-menu-items > li');
283 + const $logoPlaceholder = $mainMenu.find('.king-addons-center-logo-placeholder');
284 +
285 + if ($menuItems.length > splitAt && $logoPlaceholder.length) {
286 + // Clone logo and insert it after the split position
287 + const $logoClone = $logoPlaceholder.children().first().clone(true, true);
288 +
289 + // Clean any previously injected Lottie DOM from the clone
290 + // (cloning after Lottie init copies <svg>/<canvas> which would produce duplicates)
291 + $logoClone.find('.king-addons-lottie-animations').each(function() {
292 + const $container = jQuery(this);
293 + // Remove any rendered content and runtime flags
294 + $container.empty();
295 + $container.removeData('lottie-initialized');
296 + $container.removeData('lottie-animation');
297 + });
298 + $logoClone.addClass('king-addons-center-logo-inserted');
299 +
300 + // Insert logo after the specified menu item
301 + $menuItems.eq(splitAt - 1).after($logoClone);
302 +
303 + // Add special class to menu for center layout
304 + $mainMenu.addClass('king-addons-center-logo-active');
305 +
306 + // Ensure Lottie animations are initialized for cloned logo, if any
307 + try {
308 + // Initialize Lottie only for not-yet-initialized elements to avoid duplicates
309 + $mainMenu.find('.king-addons-lottie-animations').each(function() {
310 + const $el = jQuery(this);
311 + if (!$el.data('lottie-initialized')) {
312 + kingAddonsInitializeLottieLogos($mainMenu);
313 + return false; // one pass is enough; internal init loops through all
314 + }
315 + });
316 + } catch (e) {}
317 + }
318 + }
319 +
320 + /**
321 + * Check if Lottie library is available under different global names
322 + */
323 + function getLottieLibrary() {
324 + // Check common global names for lottie library
325 + if (typeof lottie !== 'undefined') {
326 + return lottie;
327 + }
328 + if (typeof window.lottie !== 'undefined') {
329 + return window.lottie;
330 + }
331 + if (typeof window.Lottie !== 'undefined') {
332 + return window.Lottie;
333 + }
334 + if (typeof window.bodymovin !== 'undefined') {
335 + return window.bodymovin;
336 + }
337 + return null;
338 + }
339 +
340 + /**
341 + * Wait for Lottie library to load and then initialize animations
342 + */
343 + function waitForLottieAndInitialize($mainMenu) {
344 + let attempts = 0;
345 + const maxAttempts = 50; // 5 seconds max wait
346 +
347 + function checkLottie() {
348 + const lottieLib = getLottieLibrary();
349 + if (lottieLib) {
350 + // console.log('Lottie library found, initializing animations');
351 + kingAddonsInitializeLottieLogos($mainMenu);
352 + } else if (attempts < maxAttempts) {
353 + attempts++;
354 + setTimeout(checkLottie, 100);
355 + } else {
356 + // console.log('Lottie library not loaded after 5 seconds');
357 + // Try one more time with alternative approach
358 + tryAlternativeLottieInit($mainMenu);
359 + }
360 + }
361 +
362 + checkLottie();
363 + }
364 +
365 + /**
366 + * Alternative initialization approach for edge cases
367 + */
368 + function tryAlternativeLottieInit($mainMenu) {
369 + // Wait for window load event
370 + if (document.readyState === 'complete') {
371 + setTimeout(function() {
372 + const lottieLib = getLottieLibrary();
373 + if (lottieLib) {
374 + // console.log('Lottie library found on window load, initializing');
375 + kingAddonsInitializeLottieLogos($mainMenu);
376 + }
377 + }, 500);
378 + } else {
379 + window.addEventListener('load', function() {
380 + setTimeout(function() {
381 + const lottieLib = getLottieLibrary();
382 + if (lottieLib) {
383 + // console.log('Lottie library found after window load, initializing');
384 + kingAddonsInitializeLottieLogos($mainMenu);
385 + }
386 + }, 500);
387 + });
388 + }
389 + }
390 +
391 + /**
392 + * Initialize Lottie animations for logos
393 + */
394 + function kingAddonsInitializeLottieLogos($mainMenu) {
395 + const $lottieElements = $mainMenu.find('.king-addons-lottie-animations');
396 +
397 + if ($lottieElements.length === 0) return;
398 +
399 + $lottieElements.each(function() {
400 + const $element = $(this);
401 + const settings = $element.data('settings');
402 + const jsonUrl = $element.data('json-url');
403 +
404 + if (!jsonUrl) return;
405 +
406 + // Skip if already initialized
407 + if ($element.data('lottie-initialized')) return;
408 +
409 + try {
410 + // Check if lottie is available
411 + const lottieLib = getLottieLibrary();
412 + if (lottieLib) {
413 + const animationSettings = {
414 + container: this,
415 + renderer: settings.lottie_renderer || 'svg',
416 + loop: settings.loop === 'yes',
417 + autoplay: settings.autoplay === 'yes',
418 + path: jsonUrl,
419 + rendererSettings: {
420 + preserveAspectRatio: 'xMidYMid slice'
421 + }
422 + };
423 +
424 + const animation = lottieLib.loadAnimation(animationSettings);
425 +
426 + // Store animation instance
427 + $element.data('lottie-animation', animation);
428 + $element.data('lottie-initialized', true);
429 +
430 + // Set speed if specified
431 + if (settings.speed) {
432 + animation.setSpeed(parseFloat(settings.speed));
433 + }
434 +
435 + // Set direction if reversed
436 + if (settings.reverse === 'true') {
437 + animation.setDirection(-1);
438 + }
439 +
440 + // Handle triggers
441 + if (settings.trigger === 'hover') {
442 + const isInNavigation = $element.closest('.king-addons-mega-menu').length > 0;
443 + $element.on('mouseenter', function() {
444 + animation.play();
445 + });
446 + $element.on('mouseleave', function() {
447 + // Do not pause on mouseleave while a King Addons popup is open
448 + if (isInNavigation && document.querySelector('.king-addons-pb-template-popup.king-addons-pb-popup-open')) {
449 + animation.play();
450 + return;
451 + }
452 + animation.pause();
453 + });
454 + } else if (settings.trigger === 'viewport') {
455 + // For navigation logos, viewport trigger should be more permissive
456 + // Check if element is in navigation context
457 + const isInNavigation = $element.closest('.king-addons-mega-menu').length > 0;
458 +
459 + if (isInNavigation) {
460 + // For navigation logos, just start playing and keep playing
461 + // Navigation is typically always visible
462 + animation.play();
463 + } else {
464 + // For non-navigation elements, use standard viewport detection
465 + const observer = new IntersectionObserver((entries) => {
466 + entries.forEach(entry => {
467 + if (entry.isIntersecting) {
468 + animation.play();
469 + } else {
470 + animation.pause();
471 + }
472 + });
473 + });
474 + observer.observe(this);
475 + }
476 + }
477 +
478 + // console.log('Lottie animation initialized for:', jsonUrl);
479 + } else {
480 + // console.log('Lottie library not available');
481 + }
482 + } catch (error) {
483 + // console.log('Lottie initialization failed:', error);
484 + }
485 + });
486 + }
487 +
488 + // Handle center logo repositioning on window resize
489 + $(window).on('resize', function() {
490 + $('.king-addons-mega-menu[data-center-logo="true"]').each(function() {
491 + const $menu = $(this);
492 + kingAddonsCenterLogoPositioning($menu);
493 + // Ensure cloned logo does not carry previous rendered SVG/canvas
494 + $menu.find('.king-addons-center-logo-inserted .king-addons-lottie-animations').each(function() {
495 + const $container = jQuery(this);
496 + if (!$container.data('lottie-initialized')) {
497 + $container.empty();
498 + }
499 + });
500 + // Re-initialize Lottie only for missing instances
501 + try {
502 + $menu.find('.king-addons-lottie-animations').each(function() {
503 + const $el = jQuery(this);
504 + if (!$el.data('lottie-initialized')) {
505 + kingAddonsInitializeLottieLogos($menu);
506 + return false;
507 + }
508 + });
509 + } catch (e) {}
510 + });
511 + });
512 +
513 + // Additional Lottie initialization fallback
514 + // This handles cases where Lottie loads after our initial check
515 + $(document).ready(function() {
516 + // Wait a bit for all scripts to load, then try Lottie initialization again
517 + setTimeout(function() {
518 + $('.king-addons-mega-menu').each(function() {
519 + const $menu = $(this);
520 + const $lottieElements = $menu.find('.king-addons-lottie-animations');
521 +
522 + // Only initialize elements that haven't been initialized yet
523 + $lottieElements.each(function() {
524 + const $element = $(this);
525 + const lottieLib = getLottieLibrary();
526 + if (!$element.data('lottie-initialized') && lottieLib) {
527 + kingAddonsInitializeLottieLogos($menu);
528 + }
529 + });
530 + });
531 + }, 1000);
532 + });
533 +
534 + // Resume navigation logo Lottie animations when a King Addons popup opens
535 + function kingAddonsKeepNavLottieRunning() {
536 + const resumeAll = () => {
537 + document.querySelectorAll('.king-addons-mega-menu .king-addons-lottie-animations').forEach(function(el) {
538 + const $el = jQuery(el);
539 + const anim = $el.data('lottie-animation');
540 + if (anim) {
541 + anim.play();
542 + }
543 + });
544 + };
545 + // Resume when class changes on popups (open/close)
546 + const popups = document.querySelectorAll('.king-addons-pb-template-popup');
547 + popups.forEach(function(popup) {
548 + const observer = new MutationObserver(function(mutations) {
549 + mutations.forEach(function(mutation) {
550 + if (mutation.attributeName === 'class') {
551 + resumeAll();
552 + }
553 + });
554 + });
555 + observer.observe(popup, { attributes: true, attributeFilter: ['class'] });
556 + });
557 + // Resume on resize (desktop breakpoint changes)
558 + window.addEventListener('resize', function() {
559 + resumeAll();
560 + });
561 + // Resume on scroll (in case of transient viewport observers)
562 + window.addEventListener('scroll', function() {
563 + resumeAll();
564 + }, { passive: true });
565 + }
566 + document.addEventListener('DOMContentLoaded', kingAddonsKeepNavLottieRunning);
567 +
568 +})(jQuery);