PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.14
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.14
51.1.87 51.1.86 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 All 41 releases
king-addons / includes / widgets / Mega_Menu / script.js

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

497 lines 21.8 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 // Responsive logo alignment
101 const applyLogoAlignment = () => {
102 $mainMenu.each(function () {
103 const $menu = $(this);
104 const breakpoint = parseInt($menu.data('mobile-breakpoint') || '1024', 10);
105 if (window.innerWidth > breakpoint) {
106 $menu.find('.king-addons-mega-menu-logo').css('justify-content', '');
107 } else {
108 const $logo = $menu.find('.king-addons-mega-menu-logo');
109 if ($logo.hasClass('king-addons-logo-mobile-alignment-left')) {
110 $logo.css('justify-content', 'flex-start');
111 } else if ($logo.hasClass('king-addons-logo-mobile-alignment-center')) {
112 $logo.css('justify-content', 'center');
113 } else if ($logo.hasClass('king-addons-logo-mobile-alignment-right')) {
114 $logo.css('justify-content', 'flex-end');
115 }
116 }
117 });
118 };
119 $(window).on('resize', applyLogoAlignment);
120 applyLogoAlignment();
121
122 // Mobile menu toggle logic
123 const $mobileToggle = $scope.find('.king-addons-mobile-menu-toggle');
124 const $mobileMenu = $scope.find('.king-addons-mobile-menu');
125 const $mobileClose = $scope.find('.king-addons-mobile-menu-close');
126
127 $mobileToggle.on('click', function (e) {
128 e.preventDefault();
129 $mobileMenu.slideToggle(250);
130 $mobileToggle.toggleClass('active');
131 });
132
133 $mobileClose.on('click', function (e) {
134 e.preventDefault();
135 $mobileMenu.slideUp(250);
136 $mobileToggle.removeClass('active');
137 });
138
139 // Mobile menu dropdown logic
140 $mobileMenu.find('.menu-item-has-children > a').on('click', function (e) {
141 // Only handle in mobile menu
142 if (window.innerWidth > 1024) return;
143 e.preventDefault();
144 const $parent = $(this).parent();
145 const $submenu = $parent.children('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content');
146 // Toggle current submenu
147 $submenu.slideToggle(250);
148 $parent.toggleClass('king-addons-mobile-menu__item--open');
149 // Optionally close other open submenus (accordion behavior)
150 $parent.siblings('.menu-item-has-children').removeClass('king-addons-mobile-menu__item--open')
151 .children('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content').slideUp(250);
152 });
153
154 // Initialize center logo positioning
155 kingAddonsCenterLogoPositioning($mainMenu);
156
157 // Initialize Lottie animations for logos (with library loading wait)
158 waitForLottieAndInitialize($mainMenu);
159 }
160 );
161 });
162
163 /**
164 * 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
165 */
166 function kingAddonsMobileMenuTransformReset() {
167 const isMobile = window.innerWidth <= 1024;
168 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');
169 subMenus.forEach(function(subMenu) {
170 if (isMobile) {
171 subMenu.style.setProperty('transform', 'none', 'important');
172 subMenu.style.setProperty('left', '0', 'important');
173 } else {
174 subMenu.style.removeProperty('transform');
175 subMenu.style.removeProperty('left');
176 }
177 });
178 }
179
180 function kingAddonsMobileMenuHideOnDesktop() {
181 if (window.innerWidth > 1024) {
182 document.querySelectorAll('.king-addons-mobile-menu').forEach(function(menu) {
183 if (menu.style.display !== 'none') {
184 if (window.jQuery) {
185 jQuery(menu).slideUp(0);
186 } else {
187 menu.style.display = 'none';
188 }
189 }
190 // Close all open dropdowns inside mobile menu
191 menu.querySelectorAll('.menu-item-has-children.king-addons-mobile-menu__item--open').forEach(function(item) {
192 item.classList.remove('king-addons-mobile-menu__item--open');
193 });
194 menu.querySelectorAll('ul.sub-menu, ul.king-addons-submenu, .king-addons-template-content').forEach(function(sub) {
195 if (window.jQuery) {
196 jQuery(sub).slideUp(0);
197 } else {
198 sub.style.display = 'none';
199 }
200 });
201 });
202 document.querySelectorAll('.king-addons-mobile-menu-toggle.active').forEach(function(toggle) {
203 toggle.classList.remove('active');
204 });
205 }
206 }
207
208 window.addEventListener('resize', kingAddonsMobileMenuTransformReset);
209 document.addEventListener('DOMContentLoaded', kingAddonsMobileMenuTransformReset);
210 window.addEventListener('resize', kingAddonsMobileMenuHideOnDesktop);
211 document.addEventListener('DOMContentLoaded', kingAddonsMobileMenuHideOnDesktop);
212
213 /**
214 * Ensure dropdown (template, sub-menu, custom submenu) fits inside viewport on desktop
215 * Observer is temporarily disconnected to avoid infinite loop
216 */
217 function kingAddonsDropdownFitToScreen(dropdown) {
218 if (window.innerWidth <= 1024) return;
219 // Temporarily disconnect observer to avoid loop
220 if (dropdown._kingAddonsObserver) dropdown._kingAddonsObserver.disconnect();
221 dropdown.style.left = '';
222 dropdown.style.right = '';
223 dropdown.style.transform = '';
224 const rect = dropdown.getBoundingClientRect();
225 const viewportWidth = window.innerWidth;
226 let changed = false;
227 if (rect.right > viewportWidth) {
228 const overflowRight = rect.right - viewportWidth;
229 dropdown.style.left = (dropdown.offsetLeft - overflowRight) + 'px';
230 changed = true;
231 }
232 if (rect.left < 0) {
233 dropdown.style.left = (dropdown.offsetLeft - rect.left) + 'px';
234 changed = true;
235 }
236 // Reconnect observer after change
237 if (dropdown._kingAddonsObserver) {
238 dropdown._kingAddonsObserver.observe(dropdown, { attributes: true, attributeFilter: ['class', 'style'] });
239 }
240 }
241
242 /**
243 * Observe dropdowns and fix their position as soon as they become visible (desktop only)
244 */
245 function kingAddonsObserveDropdowns() {
246 if (window.innerWidth <= 1024) return;
247 const dropdowns = document.querySelectorAll('.king-addons-template-content, .sub-menu, .king-addons-submenu');
248 dropdowns.forEach(function(dropdown) {
249 // Avoid multiple observers
250 if (dropdown._kingAddonsObserved) return;
251 dropdown._kingAddonsObserved = true;
252 const observer = new MutationObserver(function(mutations) {
253 mutations.forEach(function(mutation) {
254 if (
255 mutation.attributeName === 'class' || mutation.attributeName === 'style'
256 ) {
257 // Check if dropdown is visible
258 const style = window.getComputedStyle(dropdown);
259 if (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0') {
260 kingAddonsDropdownFitToScreen(dropdown);
261 }
262 }
263 });
264 });
265 dropdown._kingAddonsObserver = observer;
266 observer.observe(dropdown, { attributes: true, attributeFilter: ['class', 'style'] });
267 });
268 }
269 window.addEventListener('DOMContentLoaded', kingAddonsObserveDropdowns);
270 window.addEventListener('resize', kingAddonsObserveDropdowns);
271
272 /**
273 * Handle center logo positioning by inserting logo between menu items
274 * Supports responsive positioning (desktop/tablet/mobile)
275 */
276 function kingAddonsCenterLogoPositioning($mainMenu) {
277 const centerLogo = $mainMenu.attr('data-center-logo');
278 if (centerLogo !== 'true') return;
279
280 // Get responsive logo positions
281 const logoPositionDesktop = $mainMenu.attr('data-logo-position-desktop') || 'left';
282 const logoPositionTablet = $mainMenu.attr('data-logo-position-tablet') || logoPositionDesktop;
283 const logoPositionMobile = $mainMenu.attr('data-logo-position-mobile') || logoPositionDesktop;
284
285 // Determine current device and position
286 let currentPosition = logoPositionDesktop;
287 if (window.innerWidth <= 767) {
288 currentPosition = logoPositionMobile;
289 } else if (window.innerWidth <= 1024) {
290 currentPosition = logoPositionTablet;
291 }
292
293 // Remove any existing center logo
294 $mainMenu.find('.king-addons-center-logo-inserted').remove();
295 $mainMenu.removeClass('king-addons-center-logo-active');
296
297 // Only proceed if current position is center
298 if (currentPosition !== 'center') return;
299
300 const splitAt = parseInt($mainMenu.attr('data-split-at')) || 2;
301 const $menuItems = $mainMenu.find('.king-addons-menu-items > li');
302 const $logoPlaceholder = $mainMenu.find('.king-addons-center-logo-placeholder');
303
304 if ($menuItems.length > splitAt && $logoPlaceholder.length) {
305 // Clone logo and insert it after the split position
306 const $logoClone = $logoPlaceholder.children().first().clone();
307 $logoClone.addClass('king-addons-center-logo-inserted');
308
309 // Insert logo after the specified menu item
310 $menuItems.eq(splitAt - 1).after($logoClone);
311
312 // Add special class to menu for center layout
313 $mainMenu.addClass('king-addons-center-logo-active');
314 }
315 }
316
317 /**
318 * Check if Lottie library is available under different global names
319 */
320 function getLottieLibrary() {
321 // Check common global names for lottie library
322 if (typeof lottie !== 'undefined') {
323 return lottie;
324 }
325 if (typeof window.lottie !== 'undefined') {
326 return window.lottie;
327 }
328 if (typeof window.Lottie !== 'undefined') {
329 return window.Lottie;
330 }
331 if (typeof window.bodymovin !== 'undefined') {
332 return window.bodymovin;
333 }
334 return null;
335 }
336
337 /**
338 * Wait for Lottie library to load and then initialize animations
339 */
340 function waitForLottieAndInitialize($mainMenu) {
341 let attempts = 0;
342 const maxAttempts = 50; // 5 seconds max wait
343
344 function checkLottie() {
345 const lottieLib = getLottieLibrary();
346 if (lottieLib) {
347 console.log('Lottie library found, initializing animations');
348 kingAddonsInitializeLottieLogos($mainMenu);
349 } else if (attempts < maxAttempts) {
350 attempts++;
351 setTimeout(checkLottie, 100);
352 } else {
353 console.log('Lottie library not loaded after 5 seconds');
354 // Try one more time with alternative approach
355 tryAlternativeLottieInit($mainMenu);
356 }
357 }
358
359 checkLottie();
360 }
361
362 /**
363 * Alternative initialization approach for edge cases
364 */
365 function tryAlternativeLottieInit($mainMenu) {
366 // Wait for window load event
367 if (document.readyState === 'complete') {
368 setTimeout(function() {
369 const lottieLib = getLottieLibrary();
370 if (lottieLib) {
371 console.log('Lottie library found on window load, initializing');
372 kingAddonsInitializeLottieLogos($mainMenu);
373 }
374 }, 500);
375 } else {
376 window.addEventListener('load', function() {
377 setTimeout(function() {
378 const lottieLib = getLottieLibrary();
379 if (lottieLib) {
380 console.log('Lottie library found after window load, initializing');
381 kingAddonsInitializeLottieLogos($mainMenu);
382 }
383 }, 500);
384 });
385 }
386 }
387
388 /**
389 * Initialize Lottie animations for logos
390 */
391 function kingAddonsInitializeLottieLogos($mainMenu) {
392 const $lottieElements = $mainMenu.find('.king-addons-lottie-animations');
393
394 if ($lottieElements.length === 0) return;
395
396 $lottieElements.each(function() {
397 const $element = $(this);
398 const settings = $element.data('settings');
399 const jsonUrl = $element.data('json-url');
400
401 if (!jsonUrl) return;
402
403 // Skip if already initialized
404 if ($element.data('lottie-initialized')) return;
405
406 try {
407 // Check if lottie is available
408 const lottieLib = getLottieLibrary();
409 if (lottieLib) {
410 const animationSettings = {
411 container: this,
412 renderer: settings.lottie_renderer || 'svg',
413 loop: settings.loop === 'yes',
414 autoplay: settings.autoplay === 'yes',
415 path: jsonUrl,
416 rendererSettings: {
417 preserveAspectRatio: 'xMidYMid slice'
418 }
419 };
420
421 const animation = lottieLib.loadAnimation(animationSettings);
422
423 // Store animation instance
424 $element.data('lottie-animation', animation);
425 $element.data('lottie-initialized', true);
426
427 // Set speed if specified
428 if (settings.speed) {
429 animation.setSpeed(parseFloat(settings.speed));
430 }
431
432 // Set direction if reversed
433 if (settings.reverse === 'true') {
434 animation.setDirection(-1);
435 }
436
437 // Handle triggers
438 if (settings.trigger === 'hover') {
439 $element.on('mouseenter', function() {
440 animation.play();
441 });
442 $element.on('mouseleave', function() {
443 animation.pause();
444 });
445 } else if (settings.trigger === 'viewport') {
446 // Simple viewport detection
447 const observer = new IntersectionObserver((entries) => {
448 entries.forEach(entry => {
449 if (entry.isIntersecting) {
450 animation.play();
451 } else {
452 animation.pause();
453 }
454 });
455 });
456 observer.observe(this);
457 }
458
459 console.log('Lottie animation initialized for:', jsonUrl);
460 } else {
461 console.log('Lottie library not available');
462 }
463 } catch (error) {
464 console.log('Lottie initialization failed:', error);
465 }
466 });
467 }
468
469 // Handle center logo repositioning on window resize
470 $(window).on('resize', function() {
471 $('.king-addons-mega-menu[data-center-logo="true"]').each(function() {
472 kingAddonsCenterLogoPositioning($(this));
473 });
474 });
475
476 // Additional Lottie initialization fallback
477 // This handles cases where Lottie loads after our initial check
478 $(document).ready(function() {
479 // Wait a bit for all scripts to load, then try Lottie initialization again
480 setTimeout(function() {
481 $('.king-addons-mega-menu').each(function() {
482 const $menu = $(this);
483 const $lottieElements = $menu.find('.king-addons-lottie-animations');
484
485 // Only initialize elements that haven't been initialized yet
486 $lottieElements.each(function() {
487 const $element = $(this);
488 const lottieLib = getLottieLibrary();
489 if (!$element.data('lottie-initialized') && lottieLib) {
490 kingAddonsInitializeLottieLogos($menu);
491 }
492 });
493 });
494 }, 1000);
495 });
496
497 })(jQuery);