1try { 2/** 3 * @package Helix Ultimate Framework 4 * @author JoomShaper https://www.joomshaper.com 5 * @copyright Copyright (c) 2010 - 2023 JoomShaper 6 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or Later 7 */ 8 9// Preloader 10jQuery(window).on('load', function () { 11 if (jQuery('.sp-loader-with-logo').length > 0) { 12 13 move(); 14 } 15 jQuery('.sp-pre-loader').fadeOut(500, function () { 16 jQuery(this).remove(); 17 }); 18}); 19 20/** 21 * Move the progress bar 22 */ 23function move() { 24 var elem = document.getElementById('line-load'); 25 var width = 1; 26 var id = setInterval(frame, 10); 27 function frame() { 28 if (width >= 100) { 29 clearInterval(id); 30 } else { 31 width++; 32 elem.style.width = width + '%'; 33 } 34 } 35 36} 37 38jQuery(function ($) { 39 40 /** 41 * Helix settings data 42 * 43 */ 44 var settings = Joomla.getOptions('data') || {}; 45 46 /** 47 * sticky header 48 * 49 * @param {string} className the header className 50 */ 51 var handleStickiness = function (className, offsetTop) { 52 if ($('body:not(.layout-edit-iframe)').hasClass(className)) { 53 var $header = $('#sp-header'); 54 var headerHeight = $header.outerHeight(); 55 var $stickyHeaderPlaceholder = $('.sticky-header-placeholder'); 56 let $stickyOffset = '100'; 57 58 if (settings.header !== undefined && settings.header.stickyOffset !== undefined) { 59 $stickyOffset = settings.header.stickyOffset || '100'; 60 } 61 62 var stickyHeader = function () { 63 var scrollTop = $(window).scrollTop(); 64 if (scrollTop >= offsetTop + Number($stickyOffset)) { 65 $header.addClass('header-sticky'); 66 $stickyHeaderPlaceholder.height(headerHeight); 67 } else { 68 if ($header.hasClass('header-sticky')) { 69 $header.removeClass('header-sticky'); 70 $stickyHeaderPlaceholder.height('inherit'); 71 } 72 } 73 }; 74 75 stickyHeader(); 76 $(window).scroll(function () { 77 stickyHeader(); 78 }); 79 80 if ($('body').hasClass('layout-boxed')) { 81 var windowWidth = $header.parent().outerWidth(); 82 $header.css({ 'max-width': windowWidth, left: 'auto' }); 83 } 84 } else { 85 var $header = $('#sp-header'); 86 if ($header.hasClass('header-sticky')) { 87 $header.removeClass('header-sticky'); 88 } 89 $(window).off('scroll'); 90 } 91 }; 92 93 /** 94 * Calculate the header offset based on the 95 * backend preview iframe site and real site. 96 * 97 * @return integer The offset value 98 */ 99 function getHeaderOffset() { 100 /** 101 * Real site header offset top 102 * 103 */ 104 let $header = $('#sp-header'); 105 let stickyHeaderTop = $header.offset().top; 106 107 /** 108 * Backend edit preview iframe header offset top 109 * 110 */ 111 let $backHeader = $('body.back-panel').find('#sp-header'); 112 let backPanelStickyHeaderTop = null; 113 114 /** 115 * If the class .back-panel exists, 116 * that means this is from backend preview frame, 117 * then get the offset top, as it varies with the original one. 118 * 119 */ 120 if ($backHeader.length > 0) { 121 backPanelStickyHeaderTop = $backHeader.offset().top; 122 } 123 124 // By Default the header offset is the original header top 125 let headerOffset = stickyHeaderTop; 126 127 /** 128 * If back panel sticky header top has value rather than null 129 * that means the device type is changes from desktop to table or mobile or vice-versa. 130 * If value found then subtract the settings topbar height from the offset top. 131 * 132 */ 133 if (backPanelStickyHeaderTop !== null) { 134 headerOffset = backPanelStickyHeaderTop - settings.topbarHeight; 135 headerOffset = headerOffset < 0 ? stickyHeaderTop : headerOffset; 136 } 137 138 return headerOffset; 139 } 140 141 const headerExist = $('#sp-header'); 142 if (headerExist.length > 0) { 143 handleStickiness('sticky-header', getHeaderOffset()); 144 } 145 146 // go to top 147 $(window).scroll(function () { 148 if ($(this).scrollTop() > 100) { 149 $('.sp-scroll-up').fadeIn(); 150 } else { 151 $('.sp-scroll-up').fadeOut(400); 152 } 153 }); 154 155 $('.sp-scroll-up').click(function () { 156 $('html, body').animate( 157 { 158 scrollTop: -60, 159 }, 160 600 161 ); 162 return false; 163 }); 164 165 //mega menu 166 $('.sp-megamenu-wrapper').parent().parent().css('position', 'static').parent().css('position', 'relative'); 167 $('.sp-menu-full').each(function () { 168 $(this).parent().addClass('menu-justify'); 169 }); 170 171 172 173 $('#offcanvas-toggler, .offcanvas-toggler-secondary, .offcanvas-toggler-full').on('click', function (event) { 174 event.preventDefault(); 175 openOffcanvas(); 176 }); 177 178 // Close handlers 179 $('.close-offcanvas, .offcanvas-overlay').on('click', function (event) { 180 event.preventDefault(); 181 closeOffcanvas(); 182 }); 183 184 // Open function 185 function openOffcanvas() { 186 $('.offcanvas-init').addClass('offcanvas-active full-offcanvas'); 187 $(document.body).css('overflow', 'hidden'); 188 189 // Make offcanvas interactive 190 $('.offcanvas-menu') 191 .removeAttr('inert') 192 .attr('tabindex', '0'); 193 194 // Set focus to close button (better for accessibility) 195 setTimeout(() => { 196 $('.close-offcanvas').focus(); 197 }, 100); 198 199 // Add keyboard trap 200 $(document).on('keydown.offcanvas', handleOffcanvasKeyboard); 201 } 202 203 // Close function 204 function closeOffcanvas() { 205 $('.offcanvas-init').removeClass('offcanvas-active full-offcanvas'); 206 $(document.body).css('overflow', ''); 207 208 // Make offcanvas non-interactive 209 $('.offcanvas-menu') 210 .attr('inert', '') 211 .attr('tabindex', '-1'); 212 213 // Return focus to the trigger 214 $('#offcanvas-toggler').focus(); 215 216 // Remove keyboard trap 217 $(document).off('keydown.offcanvas'); 218 } 219 220 // Keyboard trap handler on offcanvas 221 function handleOffcanvasKeyboard(e) { 222 if (!$('.offcanvas-init').hasClass('offcanvas-active')) return; 223 224 const $offcanvas = $('.offcanvas-menu'); 225 const focusable = 'a[href], button, input, textarea, select, [tabindex="0"]'; 226 const $focusable = $offcanvas.find(focusable).filter(':visible'); 227 228 // Escape key closes 229 if (e.key === 'Escape') { 230 e.preventDefault(); 231 closeOffcanvas(); 232 return; 233 } 234 235 // Tab key containment 236 if (e.key === 'Tab') { 237 const $first = $focusable.first(); 238 const $last = $focusable.last(); 239 240 if (!$offcanvas[0].contains(document.activeElement)) { 241 e.preventDefault(); 242 $first.focus(); 243 return; 244 } 245 246 if (e.shiftKey && document.activeElement === $first[0]) { 247 e.preventDefault(); 248 $last.focus(); 249 } else if (!e.shiftKey && document.activeElement === $last[0]) { 250 e.preventDefault(); 251 $first.focus(); 252 } 253 } 254 } 255 256 // Prevent focus on inert elements 257 $(document).on('focusin', function (e) { 258 if ($(e.target).closest('[inert]').length) { 259 e.preventDefault(); 260 $('#offcanvas-toggler').focus(); 261 } 262 }); 263 264 // Modal Menu
265 if ($('#modal-menu').length > 0) { 266 let $modalToggler = $('#modal-menu-toggler'); 267 let $modalMenu = $('#modal-menu'); 268 let $body = $('body'); 269 270 $modalToggler.on('click', function (e) { 271 e.preventDefault(); 272 $modalMenu.toggleClass('active'); 273 $body.toggleClass('modal-menu-active'); 274 $(this).toggleClass('active'); 275 }); 276 277 // modal menu close with escape 278 $(document).keyup(function (e) { 279 if (e.key == 'Escape') { 280 $modalMenu.removeClass('active'); 281 $modalToggler.removeClass('active'); 282 $body.removeClass('modal-menu-active'); 283 } 284 }); 285 } 286 287 // Tooltip 288 const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"], .hasTooltip')); 289 tooltipTriggerList.map(function (tooltipTriggerEl) { 290 return new bootstrap.Tooltip(tooltipTriggerEl, { 291 html: true 292 }); 293 }); 294 295 // Popover 296 const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"], .hasPopover')); 297 popoverTriggerList.map(function (popoverTriggerEl) { 298 return new bootstrap.Popover(popoverTriggerEl); 299 }); 300 301 // Article Ajax voting 302 $('.article-ratings .rating-star').on('click', function (event) { 303 event.preventDefault(); 304 var $parent = $(this).closest('.article-ratings'); 305 306 var request = { 307 option: 'com_ajax', 308 template: template, 309 action: 'rating', 310 rating: $(this).data('number'), 311 article_id: $parent.data('id'), 312 format: 'json', 313 }; 314 315 $.ajax({ 316 type: 'POST', 317 data: request, 318 beforeSend: function () { 319 $parent.find('.fa-spinner').show(); 320 }, 321 success: function (response) { 322 var data = $.parseJSON(response); 323 $parent.find('.ratings-count').text(data.message); 324 $parent.find('.fa-spinner').hide(); 325 326 if (data.status) { 327 $parent.find('.rating-symbol').html(data.ratings); 328 } 329 330 setTimeout(function () { 331 $parent.find('.ratings-count').text('(' + data.rating_count + ')'); 332 }, 3000); 333 }, 334 }); 335 }); 336 337 // Cookie consent 338 $('.sp-cookie-allow').on('click', function (event) { 339 event.preventDefault(); 340 341 var date = new Date(); 342 date.setTime(date.getTime() + 30 * 24 * 60 * 60 * 1000); 343 var expires = '; expires=' + date.toGMTString(); 344 document.cookie = 'spcookie_status=ok' + expires + '; path=/'; 345 346 $(this).closest('.sp-cookie-consent').fadeOut(); 347 }); 348 349 $('.btn-group label:not(.active)').click(function () { 350 var label = $(this); 351 var input = $('#' + label.attr('for')); 352 353 if (!input.prop('checked')) { 354 label.closest('.btn-group').find('label').removeClass('active btn-success btn-danger btn-primary'); 355 if (input.val() === '') { 356 label.addClass('active btn-primary'); 357 } else if (input.val() == 0) { 358 label.addClass('active btn-danger'); 359 } else { 360 label.addClass('active btn-success'); 361 } 362 input.prop('checked', true); 363 input.trigger('change'); 364 } 365 var parent = $(this).parents('#attrib-helix_ultimate_blog_options'); 366 if (parent) { 367 showCategoryItems(parent, input.val()); 368 } 369 }); 370 $('.btn-group input[checked=checked]').each(function () { 371 if ($(this).val() == '') { 372 $('label[for=' + $(this).attr('id') + ']').addClass('active btn btn-primary'); 373 } else if ($(this).val() == 0) { 374 $('label[for=' + $(this).attr('id') + ']').addClass('active btn btn-danger'); 375 } else { 376 $('label[for=' + $(this).attr('id') + ']').addClass('active btn btn-success'); 377 } 378 var parent = $(this).parents('#attrib-helix_ultimate_blog_options'); 379 if (parent) { 380 parent.find('*[data-showon]').each(function () { 381 $(this).hide(); 382 }); 383 } 384 }); 385 386 function showCategoryItems(parent, value) { 387 var controlGroup = parent.find('*[data-showon]'); 388 389 controlGroup.each(function () { 390 var data = $(this).attr('data-showon'); 391 data = typeof data !== 'undefined' ? JSON.parse(data) : []; 392 if (data.length > 0) { 393 if (typeof data[0].values !== 'undefined' && data[0].values.includes(value)) { 394 $(this).slideDown(); 395 } else { 396 $(this).hide(); 397 } 398 } 399 }); 400 } 401 402 $(window).on('scroll', function () { 403 var scrollBar = $('.sp-reading-progress-bar'); 404 if (scrollBar.length > 0) { 405 var s = $(window).scrollTop(), 406 d = $(document).height(), 407 c = $(window).height(); 408 var scrollPercent = (s / (d - c)) * 100; 409 const position = scrollBar.data('position'); 410 if (position === 'top') { 411 // var sticky = $('.header-sticky'); 412 // if( sticky.length > 0 ){ 413 // sticky.css({ top: scrollBar.height() }) 414 // }else{ 415 // sticky.css({ top: 0 }) 416 // } 417 } 418 scrollBar.css({ width: `${scrollPercent}%` }); 419 } 420 }); 421 422 // Error Alert close issue fix for Joomla 3 423 var observer = new MutationObserver(function (mutations) { 424 $('#system-message-container .alert .close').attr('data-bs-dismiss', 'alert'); 425 }); 426 var target = document.querySelector('#system-message-container'); 427 observer.observe(target, { 428 attributes: true 429 }); 430 431 432}); 433 434 435// Handle accessibility on off-canvas dropdown menus 436jQuery(function ($) { 437 const menuSelector = '.menu-deeper.menu-parent'; 438 const togglerSelector = '.menu-toggler'; 439 const childSelector = '.menu-child'; 440 441 // Toggle submenu open/close 442 function toggleSubmenu($item, open = null) { 443 const $submenu = $item.children(childSelector); 444 const isOpen = $item.hasClass('menu-parent-open'); 445 446 if (open === null) open = !isOpen; 447 448 if (open) { 449 $item.addClass('menu-parent-open').attr('aria-expanded', 'true'); 450 $submenu.slideDown(150); 451 } else { 452 $item.removeClass('menu-parent-open').attr('aria-expanded', 'false');
453 $submenu.slideUp(150); 454 } 455 } 456 457 // Prevent event bubbling from toggler to link 458 $(document).on('click', togglerSelector, function (event) { 459 event.preventDefault(); 460 event.stopPropagation(); 461 const $item = $(this).closest(menuSelector); 462 const isOpen = $item.hasClass('menu-parent-open'); 463 toggleSubmenu($item, !isOpen); 464 }); 465 466 // Handle menu link click or Enter/Space key 467 $(document).on('click keydown', `${menuSelector} > a`, function (event) { 468 const isToggleKey = event.type === 'click' || event.key === 'Enter' || event.key === ' '; 469 const isKeyboard = event.key === 'Enter' || event.key === ' '; 470 471 if (isToggleKey) { 472 const $item = $(this).closest(menuSelector); 473 const $submenu = $item.children(childSelector); 474 475 // If submenu exists 476 if ($submenu.length) { 477 // If it's Space key (prevent scrolling), toggle submenu only 478 if (event.key === ' ') { 479 event.preventDefault(); 480 toggleSubmenu($item); 481 } 482 // If it's Enter or click â follow link (do not preventDefault) 483 // but also toggle submenu if desired 484 else if (event.key === 'Enter') { 485 toggleSubmenu($item, true); 486 487 } else if (event.type === 'click') { 488 toggleSubmenu($item, true); 489 } 490 } 491 } 492 }); 493 494 // Handle arrow key navigation 495 $(document).on('keydown', `${menuSelector} > a`, function (event) { 496 const $currentItem = $(this).closest('li'); 497 const $siblings = $currentItem.parent().children('li:visible'); 498 const index = $siblings.index($currentItem); 499 500 if (event.key === 'ArrowDown') { 501 event.preventDefault(); 502 // If submenu is open, focus first child link 503 const $submenu = $currentItem.children(childSelector); 504 if ($submenu.length && $currentItem.hasClass('menu-parent-open')) { 505 const $firstChildLink = $submenu.children('li:visible').find('a').first(); 506 if ($firstChildLink.length) { 507 $firstChildLink.focus(); 508 return; 509 } 510 } 511 // Otherwise, move to next sibling 512 $siblings.eq((index + 1) % $siblings.length).find('a').first().focus(); 513 } else if (event.key === 'ArrowUp') { 514 event.preventDefault(); 515 // Move to previous sibling 516 $siblings.eq((index - 1 + $siblings.length) % $siblings.length).find('a').first().focus(); 517 } else if (event.key === 'ArrowRight') { 518 // Open nested submenu if present 519 const $submenu = $currentItem.children(childSelector); 520 if ($submenu.length) { 521 event.preventDefault(); 522 toggleSubmenu($currentItem, true); 523 const $firstChildLink = $submenu.children('li:visible').find('a').first(); 524 if ($firstChildLink.length) { 525 $firstChildLink.focus(); 526 } 527 } 528 } else if (event.key === 'ArrowLeft' || event.key === 'Escape') { 529 event.preventDefault(); 530 // Close only the current open dropdown 531 toggleSubmenu($currentItem, false); 532 $currentItem.children('a').first().focus(); 533 } 534 }); 535 536 // Handle arrow key navigation inside submenu 537 $(document).on('keydown', `${childSelector} > li > a`, function (event) { 538 const $currentItem = $(this).closest('li'); 539 const $siblings = $currentItem.parent().children('li:visible'); 540 const index = $siblings.index($currentItem); 541 542 if (event.key === 'ArrowDown') { 543 event.preventDefault(); 544 $siblings.eq((index + 1) % $siblings.length).find('a').first().focus(); 545 } else if (event.key === 'ArrowUp') { 546 event.preventDefault(); 547 $siblings.eq((index - 1 + $siblings.length) % $siblings.length).find('a').first().focus(); 548 } else if (event.key === 'ArrowRight') { 549 // Open nested submenu if present 550 const $submenu = $currentItem.children(childSelector); 551 if ($submenu.length) { 552 event.preventDefault(); 553 toggleSubmenu($currentItem, true); 554 const $firstChildLink = $submenu.children('li:visible').find('a').first(); 555 if ($firstChildLink.length) { 556 $firstChildLink.focus(); 557 } 558 } 559 } else if (event.key === 'ArrowLeft' || event.key === 'Escape') { 560 event.preventDefault(); 561 // Close submenu and focus parent 562 const $parentMenu = $currentItem.parents(menuSelector).first(); 563 toggleSubmenu($parentMenu, false); 564 $parentMenu.children('a').first().focus(); 565 } 566 }); 567 568 // Close all menus when clicking outside 569 $(document).on('click', function (event) { 570 if (!$(event.target).closest('.menu-deeper').length) { 571 $(menuSelector).removeClass('menu-parent-open').attr('aria-expanded', 'false');
572 $(childSelector).slideUp(150); 573 } 574 }); 575 576 //mark all menu-parents as aria-haspopup 577 $(menuSelector).attr('aria-haspopup', 'true').attr('aria-expanded', 'false'); 578}); 579 580// Handle accessibility on megamenu and profile dropdowns 581jQuery(function ($) { 582 const menuSelectors = '.sp-megamenu-parent > li, .sp-profile-wrapper'; 583 584 $(menuSelectors).each(function () { 585 const $menuItem = $(this); 586 const $trigger = $menuItem.children('a, button'); 587 const $dropdown = $menuItem.children('.sp-dropdown, .sp-profile-dropdown'); 588 589 if ($dropdown.length) { 590 setupDropdownEvents($menuItem, $trigger, $dropdown); 591 } 592 }); 593 594 bindNestedDropdowns('body'); 595 596 function setupDropdownEvents($menuItem, $trigger, $dropdown) { 597 // Show on focus or mouseenter 598 $trigger.on('focus mouseenter', function () { 599 openMenu($menuItem, $dropdown); 600 }); 601 602 $menuItem.on('mouseenter', function () { 603 openMenu($menuItem, $dropdown); 604 }); 605 606 // Hide on focusout or mouseleave 607 $menuItem.on('mouseleave focusout', function () { 608 setTimeout(function () { 609 if (!$menuItem.find(':focus').length && !$menuItem.is(':hover')) { 610 closeMenu($menuItem, $dropdown); 611 } 612 }, 100); 613 }); 614 615 // Keyboard trigger 616 $trigger.on('keydown', function (event) { 617 switch (event.key) { 618 case ' ': 619 event.preventDefault(); 620 openMenu($menuItem, $dropdown); 621 break; 622 case 'ArrowDown': 623 event.preventDefault(); 624 openMenu($menuItem, $dropdown); 625 focusFirstItem($dropdown); 626 break; 627 case 'Escape': 628 event.preventDefault(); 629 closeMenu($menuItem, $dropdown); 630 $trigger.focus(); 631 break; 632 } 633 }); 634 635 // Prevent click from toggling menu 636 $trigger.on('click', function (event) { 637 if ($dropdown.length) { 638 openMenu($menuItem, $dropdown); 639 } 640 }); 641 642 } 643 644 function bindNestedDropdowns(containerSelector) { 645 $(containerSelector).find(' .sp-has-child').each(function () { 646 const $subItem = $(this); 647 const $trigger = $subItem.children('a, button'); 648 const $subDropdown = $subItem.children('.sp-dropdown'); 649 650 if ($subDropdown.length) { 651 setupDropdownEvents($subItem, $trigger, $subDropdown); 652 bindNestedDropdowns($subDropdown); 653 } 654 }); 655 } 656 657 function openMenu($item, $dropdown) { 658 $dropdown.show(); 659 // Only force display for .sp-profile-dropdown 660 if ($dropdown.hasClass('sp-profile-dropdown')) { 661 $dropdown.attr('style', 'display: block !important'); 662 } 663 bindKeyboardNavigation($dropdown); 664 } 665 666 function closeMenu($item, $dropdown) { 667 $dropdown.hide(); 668 // Reset style for .sp-profile-dropdown 669 if ($dropdown.hasClass('sp-profile-dropdown')) { 670 $dropdown.removeAttr('style'); 671 } 672 } 673 674 function focusFirstItem($dropdown) { 675 const $focusable = $dropdown.find('a, button').filter(':visible'); 676 if ($focusable.length) { 677 $focusable.first().focus(); 678 } 679 } 680 681 function bindKeyboardNavigation($dropdown) { 682 const $items = $dropdown.find('a, button').filter(':visible'); 683 684 $items.off('keydown').on('keydown', function (event) { 685 const currentIndex = $items.index(this); 686 let newIndex = -1; 687 688 if (event.key === 'ArrowDown') { 689 event.preventDefault(); 690 newIndex = (currentIndex + 1) % $items.length; 691 } else if (event.key === 'ArrowUp') { 692 event.preventDefault(); 693 newIndex = (currentIndex - 1 + $items.length) % $items.length; 694 } else if (event.key === 'Escape') { 695 event.preventDefault(); 696 697 // Reset style for .sp-profile-dropdown 698 if ($dropdown.hasClass('sp-profile-dropdown')) { 699 $dropdown.removeAttr('style'); 700 } 701 702 const $currentDropdown = $(this).closest('.sp-dropdown'); 703 const $parentItem = $currentDropdown.parent(' .sp-has-child, .sp-megamenu-parent > li'); 704 705 // Check if this is the root-level dropdown 706 const isRoot = $parentItem.parent().is('.sp-megamenu-parent, .sp-megamenu-parent > ul, nav'); 707 708 if (isRoot) { 709 // Close all menus 710 $(menuSelectors).each(function () { 711 const $item = $(this); 712 closeMenu($item, $item.children('.sp-dropdown')); 713 }); 714 } else { 715 // Close only current submenu and focus its trigger 716 const $trigger = $parentItem.children('a, button'); 717 closeMenu($parentItem, $currentDropdown); 718 if ($trigger.length) { 719 $trigger.focus(); 720 } 721 } 722 return; 723 } 724 725 if (newIndex > -1) { 726 $items.eq(newIndex).focus(); 727 } 728 }); 729 } 730 731 // Close all menus on outside click
732 $(document).on('click', function (event) { 733 if (!$(event.target).closest(menuSelectors).length) { 734 $(menuSelectors).each(function () { 735 const $item = $(this); 736 closeMenu($item, $item.children('.sp-dropdown')); 737 }); 738 } 739 }); 740 741}); 742 743 744 745} catch (e) { 746console.error('Error in file:/templates/shaper_helixultimate/js/main.js; Error:' + e.message); 747};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.