PageSourceSearch

https://www.principalam.com/modules/custom/pgi_global_header/js/global_header_custom.js?tm0qq5

js principalam.com collected 2026-09-28 08:19:50 UTC 14,726 bytes, 312 lines download raw bytes

1/**
2 * @file
3 * Global header custom scripts.
4 *
5 */
6(function (Drupal) {
7  'use strict';
8  Drupal.behaviors.pgi_global_header = {
9    attach: function (context, settings) {
10      // Cache DOM queries that are used multiple times
11      const domCache = {
12        overlay: document.querySelector('.pgi_global_overlay'),
13        closeButton: document.querySelector('.pgi-coveo-search-close'),
14        coveoSearchContainer: document.querySelector('.utility_search_droplet'),
15        searchDropdownTrigger: document.querySelector('.pgi-search-dropdown-trigger'),
16        dropDownSignIn: document.querySelector('pds-global-header-dropdown-item.mobile-utility-signin')?.shadowRoot?.querySelector('.pds-c-global-header-dropdown-item__label'),
17        mobileNavExternalLink: document.querySelector('pds-global-header')?.querySelector('[slot="mobile-nav-menu-footer"]')?.querySelector('pds-list-item pds-link.external-link-utility-nav-mobile'),
18        regionListItem: document.querySelector('pds-list-item.region-switcher-list-item'),
19        globalHeader: document.querySelector('.block-pgi-global-header'),
20        utilityHeader: document.querySelector('.block-pgi-utility-header pds-layout-container'),
21        globalHeaderDropdowns: document.querySelectorAll('.global-header-dropdown'),
22        mobileUtilitySignIn: document.querySelector('pds-global-header-dropdown-item.mobile-utility-signin'),
23      };
24      domCache.regionButtonElement = domCache.regionListItem?.querySelector('pds-button');
25      domCache.region_button = domCache.regionButtonElement?.shadowRoot?.querySelector('button');
26      if (!domCache.utilityHeader && !domCache.globalHeader) return;
27      // Handle main navigation dropdown open event
28      function handleDropdownOpen(event) {
29        domCache.overlay?.setAttribute('aria-hidden', false);
30        // Close all other open dropdown items except the one that just opened
31        document.querySelectorAll('pds-global-header-dropdown-item').forEach((dropdownItem) => {
32          if (dropdownItem.open && dropdownItem !== event.target) {
33            dropdownItem.toggleOpen();
34          }
35        });
36      }
37
38      // Handle main navigation dropdown close event
39      function handleDropdownClose() {
40        domCache.overlay?.setAttribute('aria-hidden', true);
41      }
42      // Set up event listeners for PDS component dropdown events
43      document.addEventListener('pds-global-header-dropdown-item-open', handleDropdownOpen);
44      document.addEventListener('pds-global-header-dropdown-item-close', handleDropdownClose);
45      // Create a synthetic Escape key event for programmatic dispatch
46      const escapeEventOptions = {
47        key: 'Escape',
48        keyCode: 27,
49        which: 27,
50        code: 'Escape',
51        bubbles: true,
52        cancelable: true,
53        composed: true,
54      };
55      function createEscapeEvent() {
56        return new KeyboardEvent('keydown', escapeEventOptions);
57      }
58      // Handle clicks on the Coveo search close button
59      function handleCoveoCloseClick(event) {
60        event.preventDefault();
61        event.currentTarget.dispatchEvent(createEscapeEvent());
62      }
63
64      // Handle keyboard events on the close button (Enter/Space)
65      function handleCloseButtonKeydown(event) {
66        if (event.key === 'Enter' || event.key === ' ') {
67          event.preventDefault();
68          handleCoveoCloseClick(event);
69        }
70      }
71      // Set up event listeners for the Coveo close button
72      function setupCloseButtonListeners() {
73        if (domCache.closeButton) {
74          domCache.closeButton.addEventListener('click', handleCoveoCloseClick);
75          domCache.closeButton.addEventListener('keydown', handleCloseButtonKeydown);
76        }
77      }
78      // Handle keydown events on the search dropdown trigger
79      function handleSearchDropdownTriggerKeydown(event) {
80        if (event.key === 'Escape') {
81          domCache.coveoSearchContainer?.dispatchEvent(createEscapeEvent());
82        }
83      }
84      // Focus the search input inside the Coveo search container
85      function searchFocus() {
86        const searchInput = domCache.coveoSearchContainer?.querySelector('.coveo-omnibox .search_focus');
87        if (searchInput) {
88          searchInput.setAttribute('aria-labelledby', 'searchHeading');
89          searchInput.setAttribute('placeholder', '');
90          setTimeout(() => searchInput.focus(), 600);
91        }
92      }
93      // Set up event listeners for the search dropdown trigger
94      function setupSearchDropdownTriggerListeners() {
95        if (domCache.searchDropdownTrigger) {
96          domCache.searchDropdownTrigger.addEventListener('keydown', handleSearchDropdownTriggerKeydown);
97          domCache.searchDropdownTrigger.addEventListener('click', searchFocus);
98        }
99      }
100      // Handle viewport changes using media query
101      const mobileQuery = window.matchMedia('(width < 1024px)');
102      mobileQuery.addEventListener('change', (mediaQuery) => {
103        if (mediaQuery.matches) {
104          const closeEvent = new CustomEvent(
105            'pds-global-header-dropdown-item-close',
106            {
107              bubbles: true,
108              cancelable: true,
109            },
110          );
111          document.dispatchEvent(closeEvent);
112        }
113      });
114      function setHeaderBlockPosition() {
115        // Set overflow to visible for the utility menu dropdown item.
116        const utilityMenuDropdown = document.querySelector('pds-global-header-utility-menu')?.shadowRoot?.querySelector('pds-global-header-dropdown-item')?.shadowRoot?.querySelector('.pds-c-global-header-dropdown-item__menu');
117        if (utilityMenuDropdown) utilityMenuDropdown.setAttribute('style', 'overflow: visible;');
118        const regionHeader = document.querySelector('.region-header');
119        if (!regionHeader) return;
120
121        // Hide both headers visually during load. The headers start hidden
122        // via CSS (visibility:hidden). Both are revealed together after
123        // PDS elements are upgraded and heights are correctly applied.
124        const globalHeaderBlock = regionHeader.querySelector('.block-pgi-global-header');
125        const utilityLayoutContainer = regionHeader.querySelector('.block-pgi-utility-header pds-layout-container');
126
127        const applyHeights = () => {
128          const blocks = regionHeader.querySelectorAll('.block-pgi-utility-header, .block-pgi-global-header');
129          let cumulativeHeight = 0;
130          blocks.forEach(block => {
131            if (block.classList.contains('block-pgi-utility-header')) {
132              const layoutContainer = block.querySelector('pds-layout-container');
133              if (layoutContainer) {
134                layoutContainer.style.top = `${cumulativeHeight}px`;
135                cumulativeHeight += layoutContainer.getBoundingClientRect().height;
136                return;
137              }
138            } else {
139              block.style.top = `${cumulativeHeight}px`;
140              cumulativeHeight += block.getBoundingClientRect().height;
141            }
142          });
143          if (cumulativeHeight > 0) {
144            regionHeader.style.height = `${cumulativeHeight}px`;
145            // Clear the CSS min-height placeholder once JS has the real height,
146            // so it no longer overrides on mobile where utility header is hidden.
147            regionHeader.style.minHeight = '0';
148          }
149        };
150
151        let initialMeasurementDone = false;
152        let rafId;
153
154        const observer = new ResizeObserver(() => {
155          if (!initialMeasurementDone) return;
156          cancelAnimationFrame(rafId);
157          rafId = requestAnimationFrame(applyHeights);
158        });
159
160        regionHeader.querySelectorAll('.block-pgi-utility-header, .block-pgi-global-header').forEach(block => {
161          observer.observe(block);
162        });
163        const pdsHeader = regionHeader.querySelector('pds-global-header');
164        if (pdsHeader) observer.observe(pdsHeader);
165
166        Promise.all([
167          customElements.whenDefined('pds-global-header'),
168          customElements.whenDefined('pds-layout-container'),
169        ]).then(() => {
170          requestAnimationFrame(() => {
171            requestAnimationFrame(() => {
172              applyHeights();
173              initialMeasurementDone = true;
174              // Reveal both headers simultaneously after correct positioning.
175              if (globalHeaderBlock) globalHeaderBlock.classList.add('pgi-header-ready');
176              if (utilityLayoutContainer) utilityLayoutContainer.classList.add('pgi-utility-ready');
177            });
178          });
179        });
180
181        // Debounced resize listener for window resize events.
182        let resizeTimer;
183        window.addEventListener('resize', () => {
184          clearTimeout(resizeTimer);
185          resizeTimer = setTimeout(applyHeights, 100);
186        });
187      }
188      // Adjust the sign-in icon and label in the dropdown
189      function setupSignInIcon() {
190        if (domCache.dropDownSignIn) {
191          const setupIcon = () => {
192            // Save original text
193            const dropDownSignIn = domCache.dropDownSignIn;
194            const originalText = dropDownSignIn.textContent.trim();
195
196            // Create pds-icon-user element
197            const iconElement = document.createElement('pds-icon-user');
198            iconElement.setAttribute('color', 'inverted-default');
199            iconElement.style.marginRight = "8px";
200            dropDownSignIn.textContent = "";
201            // Apply inline-flex styling to the shadow DOM label element
202            dropDownSignIn.style.display = "inline-flex";
203            dropDownSignIn.style.flexDirection = "unset";
204            dropDownSignIn.style.alignItems = "center";
205            dropDownSignIn.style.whiteSpace = "nowrap";
206
207            // Append icon and text directly to label
208            dropDownSignIn.appendChild(iconElement);
209            dropDownSignIn.appendChild(document.createTextNode(originalText));
210          };
211
212          if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
213            setTimeout(setupIcon, 100);
214          } else {
215            setupIcon();
216          }
217        }
218      }
219      // Apply styles to external link in mobile nav
220      function externalLinkStyles(link) {
221        const applyStyles = () => {
222          if (link?.shadowRoot) {
223            const shadowRoot = link.shadowRoot;
224            shadowRoot.querySelector('.pds-c-link__wrapper')?.setAttribute('style', 'padding: calc(var(--pds-base-unit) * 2) calc(var(--pds-base-unit) * 3.5); border-left: calc(var(--pds-base-unit) * .5) solid var(--pds-component-global-header-dropdown-item-border); width: 100%;');
225            shadowRoot.querySelector('.pds-c-link')?.setAttribute('style', 'display: flex; width: 100%; text-decoration: none;');
226            shadowRoot.querySelector('.pds-c-link .pds-c-link__text')?.setAttribute('style', 'width: 100%; color: #FFFFFF;');
227            link.querySelector('pds-icon-external-link')?.shadowRoot?.querySelector('svg')?.setAttribute('style', 'stroke: #FFFFFF !important;');
228          }
229        };
230
231        if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
232          setTimeout(applyStyles, 100);
233        } else {
234          applyStyles();
235        }
236      }
237
238      // Set scroll-margin-top for pds-footnote-item:target to avoid header overlap
239      // This function calculates the height of the global and utility headers
240      // and sets the scroll-margin-top CSS property accordingly.
241      const setFootnoteScrollMargin = () => {
242        if (!domCache.globalHeader && !domCache.utilityHeader) return;
243        const globalHeaderHeight = domCache.globalHeader ? domCache.globalHeader.offsetHeight : 0;
244        const utilityHeaderHeight = domCache.utilityHeader ? domCache.utilityHeader.offsetHeight : 0;
245        if (domCache.utilityHeader) {
246          document.documentElement.style.setProperty('--pds-utility-header-height', `${utilityHeaderHeight}px`);
247        }
248        if (domCache.globalHeader) {
249          document.documentElement.style.setProperty('--pds-global-header-height', `${globalHeaderHeight}px`);
250        }
251      };
252      // Apply box shadow to the global header dropdown menu
253      function setupGlobalHeaderDropdownBoxShadow() {
254        if (domCache.globalHeaderDropdowns) {
255          domCache.globalHeaderDropdowns.forEach(globalHeaderDropdown => {
256            function setBoxShadow() {
257              const menu = globalHeaderDropdown.shadowRoot?.querySelector('.pds-c-global-header-dropdown-item__menu');
258              if (menu) {
259                menu.style.boxShadow = 'inset 0 calc(var(--pds-base-unit) * -.125) 0 var(--pds-component-global-header-dropdown-menu-border)';
260              }
261            }
262            if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
263              setTimeout(setBoxShadow, 0);
264            } else {
265              setBoxShadow();
266            }
267          });
268        }
269        if (domCache.mobileUtilitySignIn) {
270          const setMobileSignInBoxShadow = () => {
271            const menu = domCache.mobileUtilitySignIn.shadowRoot?.querySelector('.pds-c-global-header-dropdown-item__menu');
272            if (menu) {
273              menu.style.boxShadow = 'inset 0 0 0 var(--pds-component-global-header-dropdown-menu-border)';
274            }
275          };
276          if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
277            setTimeout(setMobileSignInBoxShadow, 0);
278          } else {
279            setMobileSignInBoxShadow();
280          }
281        }
282      }
283      // On mobile view, close the mobile tray when tabbing out of the search submit button.
284      // Only execute this code on mobile view (width < 1024px)
285      if (window.matchMedia('(max-width: 1023px)').matches) {
286        const coveoOmniboxButton = document.querySelector('.coveo-omnibox');
287        if (coveoOmniboxButton) {
288          coveoOmniboxButton.addEventListener('keydown', function (event) {
289            if (event.key === 'Tab') {
290              if (document.activeElement && document.activeElement.tagName === 'BUTTON' && document.activeElement.type === 'submit') {
291                document.querySelector('pds-global-header-utility-menu').shadowRoot.querySelector('pds-global-header-mobile-tray').removeAttribute('open');
292              }
293            }
294          });
295        }
296      }
297      // setHeaderBlockPosition installs a ResizeObserver that self-corrects
298      // whenever PDS components change size, so no deferred Promise needed.
299      setHeaderBlockPosition();
300      setupCloseButtonListeners();
301      setupSearchDropdownTriggerListeners();
302      setupSignInIcon();
303      if (domCache.mobileNavExternalLink) {
304        externalLinkStyles(domCache.mobileNavExternalLink);
305      }
306      setFootnoteScrollMargin();
307      setupGlobalHeaderDropdownBoxShadow();
308
309    }
310  };
311
312})(Drupal);

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.