PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.74
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.74
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 / widgets / Mega_Menu / script.js

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

568 lines 25.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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);