1// This class encapsulates the functionality of the header submenu component 2// It handles initialization, item capitalization, active states, and slick carousel behavior. 3// It also manages the visibility of navigation buttons based on the width of the submenu items. 4class SubMenu { 5 /** 6 * Initializes the SubMenu instance with the provided DOM component. 7 * @param {HTMLElement} component - The root element of the submenu component. 8 */ 9 constructor(component) { 10 this.component = component; 11 this.listPage = this.component.querySelector('.list-page'); 12 this.blockPages = this.component.querySelector('.block-pages'); 13 this.init(); 14 } 15 16 /** 17 * Initializes all the core functionality of the submenu. 18 * Sets up the submenu layout, styling, active states, and slick carousel. 19 */ 20 init() { 21 this.loadComponent(); 22 this.itemSectionToUpper(); 23 this.itemSubsectionsActive(); 24 this.displaying(); 25 this.calculateWidthToScroll(); 26 27 // Recalculate layout on window resize 28 window.addEventListener('resize', () => { 29 this.calculateWidthToScroll(); 30 }); 31 } 32 33 /** 34 * Capitalizes the first letter of each principal-item link text. 35 */ 36 itemSectionToUpper() { 37 const links = this.component.querySelectorAll('.principal-item a'); 38 links.forEach(link => { 39 const text = link.textContent.trim(); 40 if (text.length > 0) { 41 const capitalized = text.charAt(0).toUpperCase() + text.slice(1); 42 link.textContent = capitalized; 43 } 44 }); 45 } 46 47 /** 48 * Highlights the active submenu item based on the current page URL. 49 * Adds 'subitem-active' class and disables the link. 50 */ 51 itemSubsectionsActive() { 52 const currentPath = window.location.pathname.replace(/\.html$/, "").replace(/\/$/, ""); 53 const links = this.component.querySelectorAll('.list-page .item.rp-body-16'); 54 55 links.forEach(link => { 56 const titleUrl = link.dataset.titleurl; 57 if (titleUrl) { 58 const normalized = titleUrl.replace(/\.html$/, "").replace(/\/$/, ""); 59 // Activate link if it matches current path 60 if (normalized === currentPath) { 61 link.classList.add('subitem-active'); 62 link.setAttribute('href', '#'); // prevent navigation since it's the current page 63 } 64 } 65 }); 66 } 67 68 /** 69 * Hides the entire submenu component if there are no submenu items. 70 */ 71 displaying() { 72 const items = this.component.querySelectorAll('.item'); 73 // No items to display, hide the component completely 74 if (items.length <= 0) { 75 this.component.style.display = 'none'; 76 } 77 } 78 79 /** 80 * Calculates and sets the minimum width for the submenu based on item widths. 81 * Also handles the visibility of navigation buttons. 82 */ 83 loadComponent() { 84 const items = this.component.querySelectorAll('.item'); 85 if (items.length <= 0) { return; } 86 let widthItems = 0; 87 88 // Force a large minWidth temporarily to avoid layout issues during calculation 89 if (window.innerWidth > 1023) { 90 this.listPage.style.minWidth = '10000px'; 91 } 92 93 this.listPage.classList.add('load'); 94 95 // Sum width of all items including spacing 96 items.forEach(item => { 97 widthItems += Math.ceil(item.getBoundingClientRect().width + 16); 98 }); 99 100 // Set the actual width after calculating all items 101 if (window.innerWidth > 1023) { 102 this.listPage.style.minWidth = `${widthItems}px`; 103 } 104 105 // Reset transform for fresh start 106 this.listPage.style.transform = 'translateX(0)'; 107 this.listPage.dataset.currentTab = 0; 108 109 const widthContainer = this.blockPages.getBoundingClientRect().width; 110 const showNext = widthContainer < widthItems; 111 112 // Update navigation buttons visibility 113 this.setButton(false, showNext); 114 } 115 116 /** 117 * Toggles the visibility state of the previous and next navigation buttons. 118 * @param {boolean} showPrev - Whether to show the previous button. 119 * @param {boolean} showNext - Whether to show the next button. 120 */ 121 setButton(showPrev, showNext) { 122 const btnPrev = this.component.querySelector('.tab-prev'); 123 const btnNext = this.component.querySelector('.tab-next'); 124 125 if (btnPrev) btnPrev.classList.toggle('btn-disabled', !showPrev); 126 if (btnNext) btnNext.classList.toggle('btn-disabled', !showNext); 127 } 128 129 /** 130 * Calculates the total width of the submenu items and decides when to hide/show navigation buttons. 131 * Adjusts button visibility based on the container width. 132 */ 133 calculateWidthToScroll() { 134 setTimeout(() => { 135 if (!this.blockPages) { return; } // Exit if blockPages is not found 136 const items = this.component.querySelectorAll('.list-page a.item'); 137 const buttons = this.blockPages.querySelectorAll('button[type="button"]'); 138 const containerWidth = this.blockPages.getBoundingClientRect().width; 139 140 // Calculate total width of items including margins 141 const totalWidth = Array.from(items).reduce((sum, el) => sum + el.getBoundingClientRect().width + 24, 0); 142 143 if (window.innerWidth < 640) { 144 buttons.forEach(btn => btn.style.display = 'none'); 145 } else { 146 buttons.forEach(btn => btn.style.display = 'block'); 147 } 148 149 if (totalWidth < containerWidth - 30) { 150 // If items fit in container, hide arrows 151 buttons.forEach(btn => btn.classList.add('slick-hidden')); 152 } else { 153 // Otherwise, show at least the next button 154 this.component.querySelector('.tab-prev')?.classList.add('slick-hidden'); 155 this.component.querySelector('.tab-next')?.classList.remove('slick-hidden'); 156 } 157 158 if (totalWidth > containerWidth) { 159 this.horizontalScrollSub(); 160 } else { 161 buttons.forEach(btn => btn.style.display = 'none'); 162 } 163 164 }, 100); 165 } 166 167 /** 168 * Initializes Slick Carousel on the submenu list and handles slide navigation behavior. 169 * Also manages the visibility of next/previous buttons on slide change. 170 */ 171 horizontalScrollSub() { 172 const $listPage = $(this.listPage); 173 174 if (!$listPage.hasClass('slick-initialized')) { 175 // Initialize slick carousel
176 $listPage.slick({ 177 dots: false, 178 infinite: false, 179 speed: 300, 180 swipeToSlide: true, 181 touchMove: true, 182 slidesToShow: 1, 183 slidesToScroll: 1, 184 centerMode: false, 185 variableWidth: true, 186 cssEase: 'linear', 187 prevArrow: $(this.component).find('.tab-prev.icon-rp-Arrow_left'), 188 nextArrow: $(this.component).find('.tab-next.icon-rp-Arrow_right') 189 }); 190 191 // Handle visibility of buttons after slide change 192 $listPage.on('afterChange', (event, slick, currentSlide) => { 193 const itemMax = slick.slideCount - 1; 194 let itemToHide = itemMax - 4; 195 196 if( itemToHide === -1 ) { 197 itemToHide = itemMax - 1; 198 } 199 200 const btnPrev = $(this.component).find('.tab-prev.icon-rp-Arrow_left'); 201 const btnNext = $(this.component).find('.tab-next.icon-rp-Arrow_right'); 202 203 // Hide next button near the end 204 if (currentSlide >= itemToHide) { 205 btnNext.addClass('d-none'); 206 btnPrev.removeClass('d-none'); 207 208 } else if (currentSlide < 1) { 209 // Hide previous button at the start 210 btnPrev.addClass('d-none'); 211 212 } else { 213 // Show both buttons in the middle slides 214 btnPrev.removeClass('d-none'); 215 btnNext.removeClass('d-none'); 216 } 217 }); 218 219 // Start with prev button hidden 220 $(this.component).find('.tab-prev.icon-rp-Arrow_left').addClass('d-none'); 221 } 222 } 223} 224 225document.addEventListener('DOMContentLoaded', () => { 226 const subMenuComponents = document.querySelectorAll('.submenu-component .submenu-guia-component'); 227 // No submenu components found, exit early 228 if( subMenuComponents.length === 0 ) { return; } 229 subMenuComponents.forEach(subMenuComponent => { 230 new SubMenu(subMenuComponent); 231 }); 232});
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.