PageSourceSearch

https://www.datarails.com/wp-content/themes/dr/assets/js/navigation.js?ver=1.76.7323

js datarails.com collected 2026-09-24 17:34:56 UTC 18,247 bytes, 547 lines download raw bytes

1/**
2 * @Author: Roni Laukkarinen
3 * @Date:   2021-04-23 13:10:51
4 * @Last Modified by:   Roni Laukkarinen
5 * @Last Modified time: 2022-01-03 15:27:02
6 */
7// TODO: Refactor file
8/* eslint-disable default-case, eqeqeq, no-restricted-globals, no-undef, no-var, vars-on-top, max-len, prefer-destructuring, no-redeclare, no-plusplus, no-use-before-define, no-unused-vars, block-scoped-var, func-names */
9/*
10An accessible menu for WordPress
11
12https://github.com/theme-smith/accessible-nav-wp
13Kirsten Smith ([email protected])
14Licensed GPL v.2 (http://www.gnu.org/licenses/gpl-2.0.html)
15
16This work derived from:
17https://github.com/WordPress/twentysixteen (GPL v.2)
18https://github.com/wpaccessibility/a11ythemepatterns/tree/master/menu-keyboard-arrow-nav (GPL v.2)
19*/
20
21/*!
22 * Check if an element is out of the viewport
23 * (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
24 * @param  {Node}  elem The element to check
25 * @return {Object}     A set of booleans for each side of the element
26 * @source https://gomakethings.com/how-to-check-if-any-part-of-an-element-is-out-of-the-viewport-with-vanilla-js/
27 */
28var isOutOfViewport = function (elem) {
29  // Get element's bounding
30  var bounding = elem.getBoundingClientRect();
31
32  // Check if it's out of the viewport on each side
33  var out = {};
34  out.top = bounding.top < 0;
35  out.left = bounding.left < 0;
36  out.bottom = bounding.bottom > (window.innerHeight || document.documentElement.clientHeight);
37  out.right = bounding.right > (window.innerWidth || document.documentElement.clientWidth);
38  out.any = out.top || out.left || out.bottom || out.right;
39
40  return out;
41};
42
43// Navigation.js start
44(function ($) {
45  // Responsive nav width
46  var responsivenav = 1051;
47  var html;
48  var body;
49  var container;
50  var button;
51  var menu;
52  var menuWrapper;
53  var links;
54  var subMenus;
55  var i;
56  var len;
57  var focusableElements;
58  var firstFocusableElement;
59  var lastFocusableElement;
60
61  // Hover intent
62  const menuItems = document.querySelectorAll('.menu-item');
63  // const hoverIntentTimeout = 1000;
64
65  menuItems.forEach((li) => {
66    // Find sub menus
67    var subMenusUnderMenuItem = li.querySelectorAll('.sub-menu');
68
69    // Loop through sub menus
70    subMenusUnderMenuItem.forEach((subMenu) => {
71      // First let's check if submenu exists
72      if (typeof subMenusUnderMenuItem !== 'undefined') {
73        // Check if the sub menu is out of viewport or not
74        var isOut = isOutOfViewport(subMenu);
75
76        // At least one side of the element is out of viewport
77        if (isOut.any) {
78          subMenu.classList.add('is-out-of-viewport');
79        }
80      }
81    });
82
83    li.addEventListener('mouseover', function () {
84      this.classList.add('hover-intent');
85      this.parentNode.classList.add('hover-intent');
86
87      document.addEventListener('keydown', (keydownMouseoverEvent) => {
88        if (keydownMouseoverEvent.key === 'Escape') {
89          li.classList.remove('hover-intent');
90          li.parentNode.classList.remove('hover-intent');
91          li.parentNode.parentNode.classList.remove('hover-intent');
92        }
93      });
94    });
95
96    li.addEventListener('mouseleave', function () {
97      this.classList.remove('hover-intent');
98      this.parentNode.classList.remove('hover-intent');
99    }); 
100  });
101
102  // Define menu items
103  var menuContainer = $('.site-header');
104  var menuToggle = menuContainer.find('.menu-toggle');
105
106  var siteHeaderMenu = menuContainer.find('.nav-primary');
107  var siteNavigation = menuContainer.find('#primary-menu');
108
109  // Close focused dropdowns when pressing esc
110  $('.menu-item a, .dropdown button').on('keyup', function (e) {
111    // Checking are menu items open or not
112    const isSubMenuDropdownOpen = $(this).parent().parent().parent()
113      .find('.sub-menu')
114      .prev('.dropdown-toggle')
115      .attr('aria-expanded');
116    const isMainMenuDropdownOpen = $(this).closest('.menu-item').find('.dropdown-toggle').attr('aria-expanded');
117    const areWeInDropdown = $(this).parent().parent().hasClass('sub-menu');
118
119    if (isSubMenuDropdownOpen === 'true' || isMainMenuDropdownOpen === 'true') {
120      if ($('.dropdown').find(':focus').length !== 0) {
121        // Close menu using Esc key but only if dropdown is open
122        if (e.code === 'Escape') {
123          // Close the dropdown menu
124          var thisDropdown = $(this).parent().parent().parent();
125
126          var screenReaderSpan = thisDropdown.find('.screen-reader-text');
127          var dropdownToggle = thisDropdown.find('.dropdown-toggle');
128          thisDropdown.find('.sub-menu').removeClass('toggled-on');
129          thisDropdown.find('.dropdown-toggle').removeClass('toggled-on');
130          thisDropdown.find('.dropdown').removeClass('toggled-on');
131          dropdownToggle.attr('aria-expanded', 'false');
132          // jscs:enable
133          screenReaderSpan.text(dr_screenReaderText.expand);
134          // Move focus back to previous dropdown select
135          // But only if we are not already in the toggle of the first dropdown menu
136          if (areWeInDropdown === true) {
137            thisDropdown.find('.dropdown-toggle:first').trigger('focus');
138          }
139        }
140      }
141    }
142
143    if (window.innerWidth > responsivenav) {
144      // Close previous dropdown if we are on main level
145      var prevDropdown = $(this).parent().prev();
146
147      var screenReaderSpanPrev = prevDropdown.find('.screen-reader-text');
148      var dropdownTogglePrev = prevDropdown.find('.dropdown-toggle');
149      prevDropdown.find('.sub-menu').removeClass('toggled-on');
150      prevDropdown.find('.dropdown-toggle').removeClass('toggled-on');
151      prevDropdown.find('.dropdown').removeClass('toggled-on');
152      dropdownTogglePrev.attr('aria-expanded', 'false');
153      screenReaderSpanPrev.text(dr_screenReaderText.expand);
154
155      // Close next dropdown if we are on main level
156      var nextDropdown = $(this).parent().next();
157
158      var screenReaderSpanNext = nextDropdown.find('.screen-reader-text');
159      var dropdownToggleNext = nextDropdown.find('.dropdown-toggle');
160      nextDropdown.find('.sub-menu').removeClass('toggled-on');
161      nextDropdown.find('.dropdown-toggle').removeClass('toggled-on');
162      nextDropdown.find('.dropdown').removeClass('toggled-on');
163      dropdownToggleNext.attr('aria-expanded', 'false');
164      screenReaderSpanNext.text(dr_screenReaderText.expand);
165    }
166  });
167
168  // Adds aria attribute
169  siteHeaderMenu.find('.menu-item-has-children').attr('aria-haspopup', 'true');
170
171  // Add default dropdown-toggle label 
172  $('.submenu-expand').each(function () {
173    $(this).attr('aria-label', `${dr_screenReaderText.expand_for} ${$(this).prev().text()}`);
174  });
175
176  // Toggles the sub-menu when dropdown toggle button accessed
177  siteHeaderMenu.find('.submenu-expand').on('click', function (e) {
178    var dropdownMenu = $(this).nextAll('.sub-menu');
179
180    $(this).prev().toggleClass('toggled-on');
181    $(this).toggleClass('toggled-on');
182    dropdownMenu.toggleClass('toggled-on');
183
184    // jscs:disable
185    $(this).attr(
186      'aria-expanded',
187      $(this).attr('aria-expanded') === 'false' ? 'true' : 'false',
188    );
189    // jscs:enable
190    // Change screen reader open/close labels
191
192    $(this).attr(
193      'aria-label',
194      $(this).attr('aria-label') === `${dr_screenReaderText.collapse_for} ${$(this).prev().text()}`
195        ? `${dr_screenReaderText.expand_for} ${$(this).prev().text()}`
196        : `${dr_screenReaderText.collapse_for} ${$(this).prev().text()}`,
197    );
198  });
199
200  // Adds a class to sub-menus for styling
201  $('.sub-menu .menu-item-has-children')
202    .parent('.sub-menu')
203    .addClass('has-sub-menu');
204
205  // Keyboard navigation
206  $('.menu-item a, button.submenu-expand').on('keydown', function (e) {
207    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].indexOf(e.code) == -1) {
208      return;
209    }
210
211    switch (e.code) {
212    case 'ArrowLeft': // Left key
213      e.stopPropagation();
214
215      if ($(this).hasClass('dropdown-toggle')) {
216        $(this).prev('a').trigger('focus');
217      } else if (
218        $(this).parent().prev().children('button.dropdown-toggle').length
219      ) {
220        $(this).parent().prev().children('button.dropdown-toggle')
221          .trigger('focus');
222      } else {
223        $(this).parent().prev().children('a')
224          .trigger('focus');
225      }
226
227      if ($(this).is('ul ul ul.sub-menu.toggled-on li:first-child a')) {
228        $(this)
229          .parents('ul.sub-menu.toggled-on li')
230          .children('button.dropdown-toggle')
231          .trigger('focus');
232      }
233
234      break;
235
236    case 'ArrowRight': // Right key
237      e.stopPropagation();
238
239      if ($(this).next('button.dropdown-toggle').length) {
240        $(this).next('button.dropdown-toggle').trigger('focus');
241      } else if ($(this).parent().next().find('input').length) {
242        $(this).parent().next().find('input')
243          .trigger('focus');
244      } else {
245        $(this).parent().next().children('a')
246          .trigger('focus');
247      }
248
249      if ($(this).is('ul.sub-menu .dropdown-toggle.toggled-on')) {
250        $(this).parent().find('ul.sub-menu li:first-child a').trigger('focus');
251      }
252
253      break;
254
255    case 'ArrowDown': // Down key
256      e.stopPropagation();
257
258      if ($(this).next().length) {
259        $(this).next().find('li:first-child a').first()
260          .trigger('focus');
261      } else if ($(this).parent().next().find('input').length) {
262        $(this).parent().next().find('input')
263          .trigger('focus');
264      } else {
265        $(this).parent().next().children('a')
266          .trigger('focus');
267      }
268
269      if (
270        $(this).is('ul.sub-menu a')
271                    && $(this).next('button.dropdown-toggle').length
272      ) {
273        $(this).parent().next().children('a')
274          .trigger('focus');
275      }
276
277      if (
278        $(this).is('ul.sub-menu .dropdown-toggle')
279                    && $(this).parent().next().children('.dropdown-toggle').length
280      ) {
281        $(this).parent().next().children('.dropdown-toggle')
282          .trigger('focus');
283      }
284
285      break;
286
287    case 'ArrowUp': // Up key
288      e.stopPropagation();
289
290      if ($(this).parent().prev().length) {
291        $(this).parent().prev().children('a')
292          .trigger('focus');
293      } else {
294        $(this)
295          .parents('ul')
296          .first()
297          .prev('.dropdown-toggle.toggled-on')
298          .trigger('focus');
299      }
300
301      if (
302        $(this).is('ul.sub-menu .dropdown-toggle')
303                    && $(this).parent().prev().children('.dropdown-toggle').length
304      ) {
305        $(this).parent().prev().children('.dropdown-toggle')
306          .trigger('focus');
307      }
308
309      break;
310    }
311  });
312
313  container = document.getElementById('nav');
314  if (!container) {
315    return;
316  }
317
318  button = document.querySelector('.menu-toggle');
319  if (typeof button === 'undefined') {
320    return;
321  }
322
323  // Set vars.
324  html = document.getElementsByTagName('html')[0];
325  body = document.getElementsByTagName('body')[0];
326  menu = container.getElementsByTagName('ul')[0];
327  menuWrapper = document.querySelector('.nav-menu');
328
329  function mobileNav() {
330    var mobileNavInstance;
331
332    // Toggles the menu button
333    if (!menuToggle.length) {
334      return;
335    }
336
337    // Do not set aria-expanded false on desktop
338    if (window.innerWidth < responsivenav) {
339      menuToggle.add(siteNavigation).attr('aria-expanded', 'false');
340    }
341
342    menuToggle.on('click', function () {
343      // $(this).add(menuWrapper).toggleClass('toggled-on');
344      // Change screen reader expanded state
345      $(this).attr(
346        'aria-expanded',
347        $(this).attr('aria-expanded') === 'false' ? 'true' : 'false',
348      );
349
350      // Change screen reader open/close labels
351      $('#nav-toggle-label').text(
352        // eslint-disable-next-line no-undef
353        $('#nav-toggle-label').text() === dr_screenReaderText.expand_toggle
354          ? dr_screenReaderText.collapse_toggle
355          : dr_screenReaderText.expand_toggle,
356      );
357
358      $(this).attr(
359        'aria-label',
360        $(this).attr('aria-label') === dr_screenReaderText.expand_toggle
361          ? dr_screenReaderText.collapse_toggle
362          : dr_screenReaderText.expand_toggle,
363      );
364
365      // jscs:disable
366      $(this)
367        .add(siteNavigation)
368        .attr(
369          'aria-expanded',
370          $(this).add(siteNavigation).attr('aria-expanded') === 'false'
371            ? 'true'
372            : 'false',
373        );
374      // jscs:enable
375    });
376
377    // Hide menu toggle button if menu is empty and return early.
378    if (typeof menu === 'undefined') {
379      button.style.display = 'none';
380      return;
381    }
382
383    // Do not set aria-expanded false on desktop
384    if (window.innerWidth < responsivenav) {
385      menu.setAttribute('aria-expanded', 'false');
386    }
387
388    // if (menu.className.indexOf('nav-menu') === -1) {
389    //     menu.className += ' nav-menu';
390    // }
391
392    // Focus trap for mobile navigation
393    if (window.innerWidth < responsivenav) {
394      firstFocusableElement = null;
395      lastFocusableElement = null;
396
397      // Select nav items
398      var navElements = container.querySelectorAll([
399        '.nav-primary a[href]',
400        '.nav-primary button',
401      ]);
402
403      // Listen for key events on nav elements and the toggle button
404      // to trigger focus trap
405      for (var ii = 0; ii < navElements.length; ii++) {
406        navElements[ii].addEventListener('keydown', focusTrap);
407      }
408    }
409
410    // Function to hide the HubSpot chat widget
411    function hideChatWidget() {
412      const chatWidget = document.querySelector('#hubspot-messages-iframe-container');
413      if (chatWidget) {
414        chatWidget.style.display = 'none';
415      }
416    }
417
418    // Function to show the HubSpot chat widget
419    function showChatWidget() {
420      const chatWidget = document.querySelector('#hubspot-messages-iframe-container');
421      if (chatWidget) {
422        chatWidget.style.display = '';
423      }
424    }
425
426    // Click event handler for menu toggle button
427    button.onclick = function () {
428      if (container.className.indexOf('is-active') !== -1) {
429        closeMenu(); // Close menu.
430        showChatWidget(); // Show chat widget when menu is closed.
431      } else {
432        html.className += ' disable-scroll';
433        body.className += ' js-nav-active';
434        container.className += ' is-active';
435        button.className += ' is-active';
436        button.setAttribute('aria-expanded', 'true');
437        menu.setAttribute('aria-expanded', 'true');
438
439        // Add focus trap when menu open
440        button.addEventListener('keydown', focusTrap, false);
441
442        hideChatWidget(); // Hide chat widget when menu is open.
443      }
444    };
445
446    // Close menu using Esc key.
447    document.addEventListener('keyup', (event) => {
448      if (event.code == 'Escape' || event.code == 'Esc') {
449        if (container.className.indexOf('is-active') !== -1) {
450          closeMenu(); // Close menu.
451        }
452      }
453    });
454
455    // Close menu clicking menu wrapper area.
456    menuWrapper.onclick = function (e) {
457      if (
458        e.target == menuWrapper
459                && container.className.indexOf('is-active') !== -1
460      ) {
461        closeMenu(); // Close menu.
462      }
463    };
464  }
465  if (window.innerWidth < responsivenav) {
466    mobileNav(); // fire right away for mobile devices
467  }
468
469  // Close menu function.
470  function closeMenu() {
471    button.removeEventListener('keydown', focusTrap, false);
472    html.className = html.className.replace(' disable-scroll', '');
473    body.className = body.className.replace(' js-nav-active', '');
474    container.className = container.className.replace(' is-active', '');
475    button.className = button.className.replace(' is-active', '');
476    button.setAttribute('aria-expanded', 'false');
477    menu.setAttribute('aria-expanded', 'false');
478    $('#nav-toggle-label').text(dr_screenReaderText.expand_toggle);
479
480    // Return focus to nav-toggle
481    button.focus();
482  }
483
484  // Get all the link elements within the menu.
485  links = menu.getElementsByTagName('a');
486  subMenus = menu.getElementsByTagName('ul');
487
488  // Each time a menu link is focused or blurred, toggle focus.
489  for (i = 0, len = links.length; i < len; i++) {
490    links[i].addEventListener('focus', toggleFocus, true);
491    links[i].addEventListener('blur', toggleFocus, true);
492  }
493
494  /**
495     * Sets or removes .focus class on an element.
496     */
497  function toggleFocus() {
498    var self = this;
499
500    // Move up through the ancestors of the current link until we hit .nav-menu.
501    while (self.className.indexOf('nav-menu') === -1) {
502      // On li elements toggle the class .focus.
503      if (self.tagName.toLowerCase() === 'li') {
504        if (self.className.indexOf('focus') !== -1) {
505          self.className = self.className.replace(' focus', '');
506        } else {
507          self.className += ' focus';
508        }
509      }
510
511      self = self.parentElement;
512    }
513  }
514
515  function focusTrap(e) {
516    // Set focusable elements inside main navigation.
517    focusableElements = [...container.querySelectorAll(
518      'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])',
519    )].filter((el) => !el.hasAttribute('disabled')).filter((el) => !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length));
520
521    firstFocusableElement = focusableElements[0];
522    lastFocusableElement = focusableElements[focusableElements.length - 1];
523
524    // Redirect last Tab to first focusable element.
525    if (lastFocusableElement === e.target && e.code === 'Tab' && !e.shiftKey) {
526      button.focus(); // Set focus on first element - that's actually close menu button.
527    }
528
529    // Redirect first Shift+Tab to toggle button element.
530    if (firstFocusableElement === e.target && e.code === 'Tab' && e.shiftKey) {
531      button.focus(); // Set focus on last element.
532    }
533
534    // Redirect Shift+Tab from the toggle button to last focusable element.
535    if (button === e.target && e.code === 'Tab' && e.shiftKey) {
536      lastFocusableElement.focus(); // Set focus on last element.
537    }
538  }
539
540  $(window).on('resize', () => {
541    if (window.innerWidth > responsivenav && body.className.indexOf('js-nav-active') !== -1) {
542      closeMenu(); // Close menu.
543    } else if (window.innerWidth < responsivenav && typeof window.mobileNavInstance == 'undefined') {
544      mobileNav();
545    }
546  });
547}(jQuery));

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.