PageSourceSearch

https://www.syneoshealth.com/themes/custom/bootstrap_syneos_sass/js/navigation.js?v=11.4.3

js syneoshealth.com collected 2026-10-02 06:26:03 UTC 8,917 bytes, 223 lines download raw bytes

1(function ($, Drupal, once) {
2  "use strict"
3
4  Drupal.behaviors.custom = {
5    attach: function (context, settings) {
6
7      // ===== Mega Menu: state detection + background scroll lock + dynamic height =====
8      const navRoot = document.getElementById('tbm-main') || document.querySelector('#tbm-main');
9      const mobileMenuQuery = window.matchMedia('(max-width: 1140px)');
10
11      // Helper: detect if mega menu is open (top-level item open or mobile overlay shown)
12      const isMenuOpen = () => {
13        if (!navRoot) return false;
14        return (
15          !!navRoot.querySelector('.tbm-item.level-1.open') ||
16          navRoot.classList.contains('tbm--mobile-show')
17        );
18      };
19
20      // Background scroll lock (allow scroll only inside the open submenu/panels)
21      const scrollLock = (() => {
22        let locked = false;
23        let y = 0;
24
25        const allowTarget = (t) => {
26          if (!navRoot) return false;
27          // Mobile overlay scroll container.
28          const mobileCollapse = navRoot.querySelector(':scope > .tbm-collapse');
29          if (navRoot.classList.contains('tbm--mobile-show') && mobileCollapse && mobileCollapse.contains(t)) return true;
30          // Open top submenu
31          const topOpen = navRoot.querySelector('.tbm-item.level-1.open > .tbm-submenu');
32          if (topOpen && topOpen.contains(t)) return true;
33          // Inline accordion containers and panels
34          const panels = navRoot.querySelectorAll('.tbm-inline-accordion, .tbm-inline-accordion .accordion-collapse');
35          for (const p of panels) if (p.contains(t)) return true;
36          return false;
37        };
38
39        const onBlock = (e) => {
40          if (!allowTarget(e.target)) {
41            e.preventDefault();
42          }
43        };
44
45        return {
46          lock() {
47            if (locked) return;
48            locked = true;
49            y = window.scrollY || document.documentElement.scrollTop || 0;
50            document.documentElement.classList.add('noscroll');
51            document.body.classList.add('noscroll');
52            // iOS/Safari friendly: fix body to prevent jump
53            document.body.style.position = 'fixed';
54            document.body.style.top = `-${y}px`;
55            document.body.style.width = '100%';
56            document.addEventListener('wheel', onBlock, { passive: false });
57            document.addEventListener('touchmove', onBlock, { passive: false });
58          },
59          unlock() {
60            if (!locked) return;
61            locked = false;
62            document.documentElement.classList.remove('noscroll');
63            document.body.classList.remove('noscroll');
64            document.body.style.position = '';
65            document.body.style.top = '';
66            document.body.style.width = '';
67            document.removeEventListener('wheel', onBlock, { passive: false });
68            document.removeEventListener('touchmove', onBlock, { passive: false });
69            window.scrollTo(0, y);
70          }
71        };
72      })();
73
74      const setMobileCollapseHeight = () => {
75        if (!navRoot) return;
76
77        const collapse = navRoot.querySelector(':scope > .tbm-collapse');
78        if (!collapse) return;
79
80        if (!navRoot.classList.contains('tbm--mobile-show') || !mobileMenuQuery.matches) {
81          collapse.style.removeProperty('min-height');
82          collapse.style.removeProperty('max-height');
83          collapse.style.removeProperty('overflow-y');
84          collapse.style.removeProperty('-webkit-overflow-scrolling');
85          return;
86        }
87
88        const collapseTop = collapse.getBoundingClientRect().top;
89        const available = Math.max(0, window.innerHeight - collapseTop);
90
91        collapse.style.minHeight = available + 'px';
92        collapse.style.maxHeight = available + 'px';
93        collapse.style.overflowY = 'auto';
94        collapse.style.webkitOverflowScrolling = 'touch';
95      };
96
97      // Compute available viewport height from the top of the OPEN top submenu ("cage").
98      // Only size the cage. Inner accordions/panels inherit and can grow naturally.
99      const setScrollableMaxHeight = () => {
100        if (!navRoot) return;
101
102        const cage = navRoot.querySelector('.tbm-item.level-1.open > .tbm-submenu');
103        if (!cage) return;
104
105        // Proactively clear any stale inline styles that may have been set previously
106        cage.querySelectorAll('.tbm-inline-accordion, .tbm-inline-accordion .accordion-collapse').forEach(el => {
107          el.style.maxHeight = '';
108          el.style.overflowY = '';
109          el.style.webkitOverflowScrolling = '';
110        });
111
112        const cageTop = cage.getBoundingClientRect().top;
113        const available = Math.max(0, window.innerHeight - cageTop);
114
115        cage.style.maxHeight = available + 'px';
116        cage.style.overflowY = 'auto';
117        cage.style.webkitOverflowScrolling = 'touch';
118
119        // As a safety, ensure no inline accordion/panel keeps a stale max-height
120        // that could collapse it. Clear on all under the open menu in a microtask
121        // to win over any other async writers.
122        setTimeout(() => {
123          const scope = cage.closest('#tbm-main') || navRoot;
124          if (!scope) return;
125          scope.querySelectorAll('.tbm-inline-accordion, .tbm-inline-accordion .accordion-collapse').forEach(el => {
126            el.style.removeProperty('max-height');
127            el.style.removeProperty('overflow-y');
128            el.style.removeProperty('-webkit-overflow-scrolling');
129          });
130        }, 0);
131      };
132
133      // Observe class changes to toggle lock and refresh heights
134      if (navRoot) {
135        const onState = () => {
136          const open = isMenuOpen();
137          if (open) {
138            scrollLock.lock();
139            setMobileCollapseHeight();
140            setScrollableMaxHeight();
141          } else {
142            setMobileCollapseHeight();
143            scrollLock.unlock();
144          }
145        };
146
147        // Ensure we only attach one observer
148        once('tbm-observer', navRoot).forEach(function () {
149          const mo = new MutationObserver(onState);
150          mo.observe(navRoot, { attributes: true, subtree: true, attributeFilter: ['class'] });
151          // Initial sync
152          onState();
153
154          // Recompute on resize/orientation and on Bootstrap collapse events
155          window.addEventListener('resize', () => {
156            if (isMenuOpen()) {
157              setMobileCollapseHeight();
158              setScrollableMaxHeight();
159            }
160          });
161          window.addEventListener('orientationchange', () => {
162            if (isMenuOpen()) {
163              setMobileCollapseHeight();
164              setScrollableMaxHeight();
165            }
166          });
167          document.addEventListener('shown.bs.collapse', (e) => {
168            if (navRoot.contains(e.target) && isMenuOpen()) {
169              setMobileCollapseHeight();
170              setScrollableMaxHeight();
171            }
172          });
173          document.addEventListener('hidden.bs.collapse', (e) => {
174            if (navRoot.contains(e.target) && isMenuOpen()) {
175              setMobileCollapseHeight();
176              setScrollableMaxHeight();
177            }
178          });
179        });
180
181        once('tbm-mobile-back-area', navRoot).forEach(function () {
182          $(navRoot).on('click', '.tbm-item.level-1.open > .tbm-link-container', function (e) {
183            if (!mobileMenuQuery.matches || e.target !== this) return;
184
185            const toggle = this.querySelector(':scope > .tbm-submenu-toggle');
186            if (!toggle) return;
187
188            e.preventDefault();
189            toggle.click();
190          });
191        });
192      }
193
194      // Helper: clear any fixed height on the cage and inner accordions, then reapply viewport-bounded max-height to the cage.
195      const topSubmenu = () => $('.tbm-item.level-1.open > .tbm-submenu');
196      const refreshCage = () => {
197        const $top = topSubmenu();
198        if (!$top.length) return;
199        // Clear cage inline height
200        $top.css('height', '');
201        const cageEl = $top.get(0);
202        if (cageEl) {
203          // Clear any previous inline max-height/overflow set on inner accordions/panels
204          cageEl.querySelectorAll('.tbm-inline-accordion, .tbm-inline-accordion .accordion-collapse').forEach(el => {
205            el.style.maxHeight = '';
206            el.style.overflowY = '';
207            el.style.webkitOverflowScrolling = '';
208          });
209        }
210        setScrollableMaxHeight();
211      };
212
213      // When inline accordions open/close, recompute the cage height after transition.
214      once('accordion-events', document).forEach(function () {
215        $(document).on('shown.bs.collapse hidden.bs.collapse', '.tbm-inline-accordion .accordion-collapse', function (e) {
216          if (navRoot && navRoot.contains(e.target) && isMenuOpen()) {
217            refreshCage();
218          }
219        });
220      });
221    }
222  }
223})(jQuery, Drupal, once)

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.