PageSourceSearch

https://eosc.eu/wp-content/themes/eosc_association/js/hc-offcanvas-nav.js?ver=1.0.0

js eosc.eu collected 2026-09-24 09:04:27 UTC 59,833 bytes, 1,720 lines download raw bytes

1/*
2 * HC Off-canvas Nav
3 * ===================
4 * Version: 6.1.5
5 * Author: Some Web Media
6 * Author URL: https://github.com/somewebmedia/
7 * Plugin URL: https://github.com/somewebmedia/hc-offcanvas-nav
8 * Description: JavaScript library for creating off-canvas multi-level navigations
9 * License: MIT
10 */
11
12'use strict';
13
14(function(global, factory) {
15  if (typeof module === 'object' && typeof module.exports === 'object') {
16    if (global.document) {
17      module.exports = factory(global);
18    }
19    else {
20      throw new Error('HC Off-canvas Nav requires a browser to run.');
21    }
22  }
23  else if (typeof define === 'function' && define.amd) {
24    define('hcOffcanvasNav', [], factory(global));
25  }
26  else {
27    factory(global);
28  }
29})(typeof window !== 'undefined' ? window : this, (window) => {
30  const document = window.document;
31  const html = document.getElementsByTagName('html')[0];
32
33  let navCount = 0;
34  const navOpenClass = 'nav-open';
35
36  const hcOffcanvasNav = function HcOffcanvasNav(elem, options) {
37    options = options || {};
38
39    // use querySelector if string is passed
40    if (typeof elem === 'string') {
41      elem = elem.charAt(0) === '#' && elem.indexOf(' ') === -1 ? document.querySelector(elem) : document.querySelectorAll(elem);
42    }
43
44    // check if element exist
45    if (!elem) return false;
46
47    const Helpers = hcOffcanvasNav.Helpers;
48
49    const defaults = {
50      width:              280,
51      height:             'auto',
52      disableAt:          false,
53      pushContent:        null,
54      swipeGestures:      true,
55      expanded:           false,
56      position:           'left', // left, right, top, bottom
57      levelOpen:          'overlap', // overlap, expand, none/false
58      levelSpacing:       40,
59      levelTitles:        true,
60      closeOpenLevels:    true,
61      closeActiveLevel:   false,
62      navTitle:           null,
63      navClass:           '',
64      disableBody:        true,
65      closeOnClick:       true,
66      closeOnEsc:         true,
67      customToggle:       null,
68      activeToggleClass:  null,
69      bodyInsert:         'prepend', // prepend/append
70      keepClasses:        true,
71      removeOriginalNav:  false,
72      rtl:                false,
73      insertClose:        true,
74      insertBack:         true,
75      levelTitleAsBack:   true,
76      labelClose:         '',
77      labelBack:          'Back'
78    };
79
80    // show deprecated messages
81    if (typeof options.maxWidth !== 'undefined') {
82      Helpers.deprecated('maxWidth', 'disableAt', 'option');
83      options.disableAt = options.maxWidth;
84    }
85
86    let Settings = Object.assign({}, defaults, options);
87
88    Settings.ariaLabels = Object.assign({}, {
89      open:    'Open Menu',
90      close:   'Close Menu',
91      submenu: 'Submenu'
92    }, options.ariaLabels);
93
94    let UpdatedSettings = [];
95
96    const checkForUpdate = (options) => {
97      if (!UpdatedSettings.length) {
98        return false;
99      }
100
101      let hasUpdated = false;
102
103      if (typeof options === 'string') {
104        options = [options];
105      }
106
107      let l = options.length;
108      for (let i = 0; i < l; i++) {
109        if (UpdatedSettings.indexOf(options[i]) !== -1) {
110          hasUpdated = true;
111        }
112      }
113
114      return hasUpdated;
115    };
116
117    const Plugin = function($originalNav) {
118      if (!$originalNav.querySelector('ul') && $originalNav.tagName !== 'UL') {
119        console.error('%c! HC Offcanvas Nav:' + `%c Navigation must contain <ul> element.`, 'color: #fa253b', 'color: default');
120        return;
121      }
122
123      // count our nav
124      navCount++;
125
126      const navUniqId = `hc-nav-${navCount}`;
127      const Styles = Helpers.printStyle(`hc-offcanvas-${navCount}-style`);
128      const keydownEventName = 'keydown.hcOffcanvasNav';
129      const activeToggleClass = Settings.activeToggleClass || 'toggle-open';
130
131      // this is our new nav element
132      const $nav = Helpers.createElement('nav', {id: navUniqId});
133      const $nav_container = Helpers.createElement('div', {class: 'nav-container'});
134
135      $nav.addEventListener('click', Helpers.stopPropagation);
136      $nav.appendChild($nav_container);
137
138      let $toggle = null;
139      let $toggle_open = null;
140      let $push_content = null;
141
142      let Model = {};
143
144      let _open = false; // is nav currently open
145      let _initExpanded = false; // should nav be opened on init
146      let _nextActiveLevel = null; // level that should be open next
147      let _top = 0; // to remember scroll position
148      let _containerWidth = 0;
149      let _containerHeight = 0;
150      let _transitionProperty;
151      let _transitionDuration;
152      let _transitionFunction;
153      let _closeLevelsTimeout = null;
154      let _indexes = {}; // object with level indexes
155      const _openLevels = []; // array with current open levels ids
156      let _keyboard = false;
157      const _focusEls = []; // array to store keyboard accessed items
158      let _xStart = null; // the X touch point
159      let _yStart = null; // the Y touch point
160      let _touchMoved = false;
161      let _touchNavTriggered = false;
162
163      if (!Settings.customToggle) {
164        // our toggle
165        $toggle = [Helpers.createElement('a', {
166          href: '#'
167        }, Helpers.createElement('span'))];
168
169        $originalNav.insertAdjacentElement('afterend', $toggle[0]);
170      }
171      else {
172        // user toggle
173        $toggle = Helpers.getElements(Settings.customToggle);
174      }
175
176      if ($toggle && $toggle.length) {
177        $toggle.forEach(($t) => {
178          $t.addEventListener('click', toggleNav($t));
179          $t.classList.add('hc-nav-trigger', navUniqId);
180
181          // ARIA
182          $t.setAttribute('role', 'button');
183          $t.setAttribute('aria-label', (Settings.ariaLabels || {}).open);
184          $t.setAttribute('aria-controls', navUniqId);
185          $t.setAttribute('aria-expanded', false);
186
187          // make nav opening keyboard accessible
188          $t.addEventListener('keydown', (e) => {
189            if (e.key === 'Enter' || e.keyCode === 13) {
190              // trap focus inside nav
191              setTimeout(() => {
192                trapFocus(0, 0);
193              }, 0);
194            }
195          });
196        });
197      }
198
199      /* ARIA Keyboard Focus */
200
201      const trapFocus = (n, l, i) => {
202        if (typeof l !== 'number' || (typeof n !== 'number' && !_focusEls.length)) {
203          return;
204        }
205
206        const focusableSelector = '[tabindex="0"], a[role="menuitem"], a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select';
207
208        let $focusable = Array.prototype.filter.call($nav_container.querySelectorAll('.nav-wrapper'), (el) => {
209          return el.getAttribute('data-level') == l && (typeof i !== 'number' || (typeof i === 'number' && el.getAttribute('data-index') == i));
210        })[0];
211
212        $focusable = Helpers.children($focusable, '.nav-content')[0];
213
214        const $navControls = Helpers.children($focusable, '.nav-close, .nav-back');
215
216        $focusable = Helpers.children($focusable, 'ul');
217        $focusable = Helpers.children($focusable, 'li');
218        $focusable = Helpers.children($focusable, ':not(.nav-wrapper)');
219        $focusable = [...$navControls, ...$focusable];
220        $focusable = Array.prototype.map.call($focusable, (el) => {
221          return Array.prototype.slice.call(el.querySelectorAll(focusableSelector));
222        }).flat();
223        $focusable = Array.prototype.filter.call($focusable, (el) => el.getAttribute('tabindex') !== '-1');
224
225        if (!$focusable) {
226          return;
227        }
228
229        // so we can keep the outline
230        $nav.classList.add('user-is-tabbing');
231
232        const $first = $focusable[0];
233        const $last = $focusable[$focusable.length - 1];
234
235        if (typeof n === 'number') {
236          // put focus on item with desired index
237          $focusable[n].focus();
238        }
239        else {
240          // focus last focusable element
241          _focusEls[_focusEls.length - 1].focus();
242          // remove last element from focusable array
243          _focusEls.pop();
244        }
245
246        // remove previous keydown event
247        document.removeEventListener(keydownEventName);
248
249        document.addEventListener(keydownEventName, (e) => {
250          if (!(e.key === 'Tab' || e.keyCode === 9)) {
251            return;
252          }
253
254          /* shift + tab */
255          if (e.shiftKey) {
256            if (document.activeElement === $first) {
257              e.preventDefault();
258              $last.focus();
259            }
260          }
261          /* tab */
262          else {
263            if (document.activeElement === $last) {
264              e.preventDefault();
265              $first.focus();
266            }
267          }
268        });
269      };
270
271      const untrapFocus = () => {
272        document.removeEventListener(keydownEventName);
273
274        if ($toggle_open) {
275          setTimeout(() => {
276            $toggle_open.focus();
277          }, _transitionDuration);
278        }
279      };
280
281      /* Build methods */
282
283      const calcNav = () => {
284        // remove transition from the nav container so we can update the nav without flickering
285        $nav_container.style.transition = 'none';
286
287        // nav must be block so we can get rendered width
288        $nav.style.display = 'block';
289
290        const computedWidth = Helpers.formatSizeVal(_containerWidth = $nav_container.offsetWidth);
291        const computedHeight = Helpers.formatSizeVal(_containerHeight = $nav_container.offsetHeight);
292
293        // fix 100% transform glitching
294        Styles.add(
295          `.hc-offcanvas-nav.${navUniqId}.nav-position-left .nav-container`,
296          `transform: translate3d(-${computedWidth}, 0, 0)`
297        );
298        Styles.add(
299          `.hc-offcanvas-nav.${navUniqId}.nav-position-right .nav-container`,
300          `transform: translate3d(${computedWidth}, 0, 0)`
301        );
302        Styles.add(
303          `.hc-offcanvas-nav.${navUniqId}.nav-position-top .nav-container`,
304          `transform: translate3d(0, -${computedHeight}, 0)`
305        );
306        Styles.add(
307          `.hc-offcanvas-nav.${navUniqId}.nav-position-bottom .nav-container`,
308          `transform: translate3d(0, ${computedHeight}, 0)`
309        );
310
311        Styles.insert();
312
313        // clear our temp inline styles
314        $nav.style.display = '';
315        $nav_container.style.transition = '';
316
317        _transitionProperty = window.getComputedStyle($nav_container).transitionProperty;
318        _transitionDuration = Helpers.toMs(window.getComputedStyle($nav_container).transitionDuration);
319        _transitionFunction = window.getComputedStyle($nav_container).transitionTimingFunction;
320
321        if (Settings.pushContent && $push_content && _transitionProperty) {
322          Styles.add(Helpers.getElementCssTag($push_content), `transition: ${_transitionProperty} ${_transitionDuration}ms ${_transitionFunction}`);
323        }
324
325        Styles.insert();
326      };
327
328      // init function
329      const initNav = (reinit) => {
330        const toggleDisplay = $toggle ? window.getComputedStyle($toggle[0]).display : false;
331        const mediaquery = Settings.disableAt ? `max-width: ${Settings.disableAt - 1}px` : false;
332        const width = Helpers.formatSizeVal(Settings.width);
333        const height = Helpers.formatSizeVal(Settings.height);
334        const spacing = Helpers.formatSizeVal(Settings.levelSpacing);
335
336        if (Helpers.isNumeric(width) || width.indexOf('px') !== -1) {
337          _containerWidth = parseInt(width);
338        }
339
340        if (Helpers.isNumeric(height) || height.indexOf('px') !== -1) {
341          _containerHeight = parseInt(height);
342        }
343
344        if (checkForUpdate(['disableAt', 'position'])) {
345          // clear media queries from previous run
346          Styles.reset();
347        }
348
349        // create styles
350        Styles.add(`.hc-offcanvas-nav.${navUniqId}`, 'display: block', mediaquery);
351
352        // hide original
353        Styles.add(`.hc-nav-original.${navUniqId}`, 'display: none', mediaquery);
354
355        // trigger
356        if (toggleDisplay) {
357          Styles.add(`.hc-nav-trigger.${navUniqId}`, `display: ${toggleDisplay && toggleDisplay !== 'none' ? toggleDisplay : 'block'}`, mediaquery);
358        }
359
360        if (['left', 'right'].indexOf(Settings.position) !== -1) {
361          // container width
362          Styles.add(`.hc-offcanvas-nav.${navUniqId} .nav-container`, `width: ${width}`);
363        }
364        else {
365          // container height
366          Styles.add(`.hc-offcanvas-nav.${navUniqId} .nav-container`, `height: ${height}`);
367        }
368
369        // container transform
370        Styles.add(
371          `.hc-offcanvas-nav.${navUniqId}.nav-position-left .nav-container`,
372          `transform: translate3d(-${width}, 0, 0);`
373        );
374        Styles.add(
375          `.hc-offcanvas-nav.${navUniqId}.nav-position-right .nav-container`,
376          `transform: translate3d(${width}, 0, 0);`
377        );
378        Styles.add(
379          `.hc-offcanvas-nav.${navUniqId}.nav-position-top .nav-container`,
380          `transform: translate3d(0, -${height}, 0);`
381        );
382        Styles.add(
383          `.hc-offcanvas-nav.${navUniqId}.nav-position-bottom .nav-container`,
384          `transform: translate3d(0, ${height}, 0);`
385        );
386
387        // wrappers
388        Styles.add(
389          `.hc-offcanvas-nav.${navUniqId}.nav-levels-overlap.nav-position-left li.level-open > .nav-wrapper`,
390          `transform: translate3d(-${spacing}, 0, 0)`, mediaquery
391        );
392        Styles.add(
393          `.hc-offcanvas-nav.${navUniqId}.nav-levels-overlap.nav-position-right li.level-open > .nav-wrapper`,
394          `transform: translate3d(${spacing}, 0, 0)`, mediaquery
395        );
396        Styles.add(
397          `.hc-offcanvas-nav.${navUniqId}.nav-levels-overlap.nav-position-top li.level-open > .nav-wrapper`,
398          `transform: translate3d(0, -${spacing}, 0)`, mediaquery
399        );
400        Styles.add(
401          `.hc-offcanvas-nav.${navUniqId}.nav-levels-overlap.nav-position-bottom li.level-open > .nav-wrapper`,
402          `transform: translate3d(0, ${spacing}, 0)`, mediaquery
403        );
404
405        Styles.insert();
406
407        // get page content
408        if (!reinit || (reinit && checkForUpdate('pushContent'))) {
409          $push_content = Settings.pushContent ? Helpers.getElements(Settings.pushContent)[0] : null;
410        }
411
412        // remove transition from the nav container so we can update the nav without flickering
413        $nav_container.style.transition = 'none';
414
415        const wasOpen = $nav.classList.contains(navOpenClass);
416
417        const navClasses = [
418          'hc-offcanvas-nav',
419          Settings.navClass || '',
420          navUniqId,
421          'nav-levels-' + (Settings.levelOpen || 'none'),
422          `nav-position-${Settings.position}`,
423          Settings.disableBody ? 'disable-body' : '',
424          Helpers.isIos ? 'is-ios' : '',
425          Helpers.isTouchDevice ? 'touch-device' : '',
426          wasOpen ? navOpenClass : '',
427          Settings.rtl ? 'rtl' : '',
428          Settings.insertClose === true && !Settings.labelClose ? 'nav-close-button-empty' : ''
429        ].join(' ').trim().replace(/  +/g, ' ');
430
431        $nav.removeEventListener('click');
432        $nav.className = navClasses;
433        $nav.setAttribute('aria-hidden', true);
434
435        // set css variable so we can use it in themes if needed
436        document.documentElement.style.setProperty('--nav-level-spacing', Settings.levelSpacing + 'px');
437
438        // close menu on body click (nav::after)
439        if (Settings.disableBody) {
440          $nav.addEventListener('click', closeNav);
441        }
442
443        if (reinit) {
444          calcNav();
445        }
446        else {
447          // timed out so we can get computed data
448          setTimeout(calcNav, 0);
449        }
450      };
451
452      // create nav model function
453      const createModel = () => {
454
455        // get first level menus
456        const $first_level = () => {
457          return $originalNav.tagName === 'UL'
458            ? [$originalNav]
459            : Array.prototype.filter.call($originalNav.children, (child) => child.tagName === 'UL' || child instanceof HTMLHeadingElement);
460        };
461
462        // call
463        Model = getModel($first_level(), null);
464
465        function getModel($menu, id) {
466          const level = [];
467
468          Array.prototype.forEach.call($menu, ($ul) => {
469            if ($ul.tagName !== 'UL' && !($ul instanceof HTMLHeadingElement)) {
470              return;
471            }
472
473            const nav = {
474              tagName: $ul.tagName,
475              id: id,
476              htmlClass: $ul.getAttribute('class') || null,
477              items: []
478            };
479
480            if ($ul instanceof HTMLHeadingElement) {
481              // this is a heading
482              nav.content = Helpers.clone($ul, false, true);
483            }
484            else {
485              // this submenu should be open next
486              if ($ul.getAttribute('data-nav-active') !== null) {
487                _nextActiveLevel = id;
488                // remove data attribute
489                $ul.removeAttribute('data-nav-active');
490              }
491
492              Array.prototype.forEach.call($ul.children, ($li) => {
493                const customContent = $li.getAttribute('data-nav-custom-content') !== null;
494                let $content = customContent ? $li.childNodes : Array.prototype.filter.call($li.children, (child) => child.tagName !== 'UL' && !child.querySelector('ul')).concat($li.children.length ? [] : [$li.firstChild]);
495                const $nested_navs = customContent ? [] : Array.prototype.slice.call($li.querySelectorAll('ul'));
496                const $subnav = !$nested_navs.length ? [] : [].concat(Array.prototype.filter.call($nested_navs[0].parentNode.children, (child) => child.tagName === 'UL' || child instanceof HTMLHeadingElement));
497
498                let uniqid = null;
499
500                // if no content check for text
501                if (!$content.length) {
502                  let text = '';
503
504                  for (let i = 0; i < $li.childNodes.length; i++) {
505                    if ($li.childNodes[i].nodeType === Node.TEXT_NODE) {
506                      text += $li.childNodes[i].textContent.trim();
507                    }
508                  }
509
510                  $content = [document.createTextNode(text)];
511                }
512
513                // save unique identifier for remembering open sub menus
514                if ($subnav.length) {
515                  if (!Helpers.data($li, 'hc-uniqid')) {
516                    uniqid = Math.random().toString(36).substr(2);
517                    Helpers.data($li, 'hc-uniqid', uniqid);
518                  }
519                  else {
520                    uniqid = Helpers.data($li, 'hc-uniqid');
521                  }
522                }
523
524                // submenu of this list element should be open next
525                if ($li.getAttribute('data-nav-active') !== null) {
526                  _nextActiveLevel = uniqid;
527                  // remove data attribute
528                  $li.removeAttribute('data-nav-active');
529                }
530
531                // add elements to this level
532                nav.items.push({
533                  id: uniqid,
534                  htmlClass: $li.getAttribute('class') || '',
535                  content: $content,
536                  custom: customContent,
537                  subnav: $subnav.length ? getModel($subnav, uniqid) : [],
538                  highlight: $li.getAttribute('data-nav-highlight') !== null
539                });
540              });
541            }
542
543            level.push(nav);
544          });
545
546          return level;
547        }
548      };
549
550      // create nav DOM function
551      const createNavDom = (reinit) => {
552        if (reinit) {
553          // empty the container
554          while ($nav_container.firstChild) $nav_container.removeChild($nav_
554container.firstChild);
555          // reset indexes
556          _indexes = {};
557        }
558
559        // call
560        createDom(Model, $nav_container, 0, Settings.navTitle);
561
562        function createDom(menu, $container, level, title, backIndex, backTitle) {
563          const $wrapper = Helpers.createElement('div', {
564            class: `nav-wrapper nav-wrapper-${level}`,
565            'data-level': level,
566            'data-index': backIndex || 0
567          });
568          const $content = Helpers.createElement('div', {class: 'nav-content'});
569
570          $wrapper.addEventListener('click', Helpers.stopPropagation);
571          $wrapper.appendChild($content);
572          $container.appendChild($wrapper);
573
574          // titles
575          if (title && (level === 0 || (level > 0 && Settings.levelOpen === 'overlap'))) {
576            const _title = typeof title === 'string'
577              ? title
578              : Helpers.clone(window.jQuery && title instanceof window.jQuery && title.length ? title[0] : title, true, true);
579
580            $content.insertBefore(Helpers.createElement('h2', {
581              id: level === 0 ? `${navUniqId}-nav-title` : null,
582              class: level === 0 ? 'nav-title' : 'level-title'
583            }, _title), $content.firstChild);
584
585            // Nav ARIA title
586            if (level === 0 && typeof title === 'string') {
587              $nav.setAttribute('aria-labelledby', `${navUniqId}-nav-title`);
588            }
589          }
590
591          let menu_count = -1;
592
593          menu.forEach((nav, i_nav) => {
594            if (nav.tagName !== 'UL') {
595              $content.appendChild(nav.content);
596              return;
597            }
598            else {
599              menu_count++;
600            }
601
602            const $menu = Helpers.createElement('ul', {
603              id: nav.id
604                ? menu.length > 1
605                    ? `menu-${nav.id}-${menu_count}` // sibling menus
606                    : `menu-${nav.id}`
607                : null,
608              role: 'menu',
609              'aria-level': level + 1
610            });
611
612            $content.appendChild($menu);
613
614            // keep original menu classes
615            if (Settings.keepClasses && nav.htmlClass) {
616              $menu.classList.add.apply($menu.classList, nav.htmlClass.split(' '));
617            }
618
619            nav.items.forEach((item, i_item) => {
620              const $item_content = item.content;
621
622              // item has custom content
623              if (item.custom) {
624
625                const $custom_item = Helpers.createElement('li', {class: 'nav-item nav-item-custom'},
626                  Helpers.createElement('div', {class: 'nav-custom-content'}, Array.prototype.map.call($item_content, (el) => {
627                    return Helpers.clone(el, true, true);
628                  }))
629                );
630
631                // keep original menu item classes
632                if (Settings.keepClasses && item.htmlClass) {
633                  $custom_item.classList.add.apply($custom_item.classList, item.htmlClass.split(' '));
634                }
635
636                // insert item
637                $menu.appendChild($custom_item);
638
639                // stop here
640                return;
641              }
642
643              const $original_link = Array.prototype.filter.call($item_content, (child) => {
644                return child.tagName === 'A' || (child.nodeType !== Node.TEXT_NODE && child.querySelector('a'));
645              })[0];
646
647              let $item_link;
648
649              if ($original_link) {
650                $item_link = Helpers.clone($original_link, false, true);
651                $item_link.classList.add('nav-item-link');
652              }
653              else {
654                $item_link = Helpers.createElement(item.subnav.length ? 'a' : 'span', {
655                  class: 'nav-item-link'
656                }, Array.prototype.map.call($item_content, (el) => {
657                  return Helpers.clone(el, true, true);
658                }));
659              }
660
661              if ($item_link.tagName === 'A') {
662                $item_link.setAttribute('tabindex', '0');
663                $item_link.setAttribute('role', 'menuitem');
664
665                if (!$item_link.getAttribute('href')) {
666                  $item_link.setAttribute('href', '#');
667                }
668              }
669
670              if ($original_link) {
671                $item_link.addEventListener('click', (e) => {
672                  e.stopPropagation();
673
674                  // trigger original click event
675                  if (Helpers.hasListener($original_link, 'click')) {
676                    $original_link.click();
677                  }
678                });
679              }
680
681              if ($item_link.getAttribute('href') === '#') {
682                // prevent page jumping
683                $item_link.addEventListener('click', Helpers.preventDefault);
684              }
685
686              // close nav on item click
687              if (Settings.closeOnClick) {
688                if (!areLevelsOpenable()) {
689                  // every item should close the nav except disabled
690                  if (
691                    $item_link.tagName === 'A' &&
692                    $item_link.dataset.navClose !== 'false' &&
693                    ($item_link.getAttribute('disabled') === null || $item_link.getAttribute('disabled') === 'false')
694                  ) {
695                    $item_link.addEventListener('click', closeNav);
696                  }
697                }
698                else {
699                  // only items without submenus
700                  // or with submenus but with valid links
701                  if (
702                    $item_link.tagName === 'A' &&
703                    $item_link.dataset.navClose !== 'false' &&
704                    ($item_link.getAttribute('disabled') === null || $item_link.getAttribute('disabled') === 'false') &&
705                    (!item.subnav.length || ($item_link.getAttribute('href') && $item_link.getAttribute('href').charAt(0) !== '#'))
706                  ) {
707                    $item_link.addEventListener('click', closeNav);
708                  }
709                }
710              }
711
712              // our nav item
713              const $item = Helpers.createElement('li', {
714                class: 'nav-item'
715              });
716
717              $item.appendChild($item_link);
718              $menu.appendChild($item);
719
720              // keep original menu item classes
721              if (Settings.keepClasses && item.htmlClass) {
722                $item.classList.add.apply($item.classList, item.htmlClass.split(' '));
723              }
724
725              // is nav item highlighted?
726              if (item.highlight) {
727                $item.classList.add('nav-highlight');
728              }
729
730              // wrap item link
731              Helpers.wrap($item_link, Helpers.createElement('div', {class: 'nav-item-wrapper'}));
732
733              // do subnav
734              if (item.subnav.length) {
735                const nextLevel = level + 1;
736                const uniqid = item.id;
737                let nav_title = '';
738
739                // create new level
740                if (!_indexes[nextLevel]) {
741                  _indexes[nextLevel] = 0;
742                }
743
744                // li parent class
745                $item.classList.add('nav-parent');
746
747                if (!areLevelsOpenable()) {
748                  $item_link.setAttribute('aria-expanded', true);
749                }
750                // if we can open levels
751                else {
752                  const index = _indexes[nextLevel];
753
754                  const $checkbox = Helpers.createElement('input', {
755                    type: 'checkbox',
756                    id: `${navUniqId}-${nextLevel}-${index}`,
757                    class: 'hc-chk',
758                    tabindex: -1,
759                    'data-level': nextLevel,
760                    'data-index': index,
761                    value: uniqid
762                  });
763
764                  $checkbox.addEventListener('click', Helpers.stopPropagation);
765                  $checkbox.addEventListener('change', checkboxChange);
766                  $item.insertBefore($checkbox, $item.firstChild);
767
768                  const attachToLink = ($el) => {
769                    $el.addEventListener('click', (e) => {
770                      e.stopPropagation();
771                      // trigger checkbox to toggle level
772                      $checkbox.setAttribute('checked', $checkbox.getAttribute('checked') === 'true' ? false : true);
773
774                      if ('createEvent' in document) {
775                        const evt = document.createEvent('HTMLEvents');
776                        evt.initEvent('change', false, true);
777                        $checkbox.dispatchEvent(evt);
778                      }
779                    });
780
781                    $el.addEventListener('keydown', function(e) {
782                      if (e.key === 'Enter' || e.keyCode === 13) {
783                        // remember we are accessing via keyboard
784                        _keyboard = true;
785                        _focusEls.push(this);
786                      }
787                    });
788
789                    // ARIA
790                    $el.setAttribute('aria-controls', item.subnav.length > 1
791                      ? item.subnav.filter(s => s.tagName === 'UL').map((s, i) => `menu-${s.id}-${i}`).join(' ') // sibling menus
792                      : `menu-${uniqid}`
793                    );
794                    $el.setAttribute('aria-haspopup', Settings.levelOpen === 'overlap');
795                    $el.setAttribute('aria-expanded', false);
796                  }
797
798                  // nav is updated, we should keep this level open
799                  if (_openLevels.indexOf(uniqid) !== -1) {
800                    $wrapper.classList.add('sub-level-open');
801                    $wrapper.addEventListener('click', () => closeLevel(nextLevel, index)); // close on self click
802                    $item.classList.add('level-open');
803                    $checkbox.setAttribute('checked', true);
804                  }
805
806                  // subnav title
807                  nav_title = Settings.levelTitles === true ? $item_content[0].textContent.trim() : '';
808
809                  // item has no actual link
810                  if (!$item_link.getAttribute('href') || $item_link.getAttribute('href') === '#') {
811                    $item_link.appendChild(Helpers.createElement('span', {class: 'nav-next'}, Helpers.createElement('span')));
812                    attachToLink($item_link);
813                  }
814                  // item has valid link, create our next link
815                  else {
816                    const $a_next = Helpers.createElement('a', {
817                      href: '#',
818                      class: 'nav-next',
819                      'aria-label': `${(Settings.ariaLabels || {}).submenu}: ${nav_title}`,
820                      role: 'menuitem',
821                      tabindex: 0
822                    }, Helpers.createElement('span'));
823
824                    $a_next.addEventListener('click', Helpers.preventClick());
825                    attachToLink($a_next);
826
827                    if (Settings.rtl) {
828                      $item_link.parentNode.appendChild($a_next);
829                    }
830                    else {
831                      $item_link.parentNode.insertBefore($a_next, $item_link.nextSibling);
832                    }
833                  }
834                }
835
836                _indexes[nextLevel]++;
837
838                createDom(item.subnav, $item, nextLevel, nav_title, _indexes[nextLevel]-1, typeof title === 'string' ? title : '');
839              }
840            });
841          });
842
843          // insert back links
844          if (level && typeof backIndex !== 'undefined') {
845            if (Settings.insertBack !== false && Settings.levelOpen === 'overlap') {
846              const $children_menus = Helpers.children($content, 'ul');
847              const backLabel = (Settings.levelTitleAsBack ? (backTitle || Settings.labelBack) : Settings.labelBack) || '';
848              const $back_a = Helpers.createElement('a', {href: '#', class: 'nav-back-button', role: 'menuitem', tabindex: 0}, [
849                backLabel,
850                Helpers.createElement('span')
851              ]);
852
853              if (Settings.insertBack === true || Settings.insertBack === 0) {
854                const $back = Helpers.createElement('div', {class: 'nav-back'}, $back_a);
855                $content.insertBefore($back, Helpers.children($content, ':not(.level-title)')[0]);
856              }
857              else {
858                const $back = Helpers.createElement('li', {class: 'nav-item nav-back'}, $back_a);
859                Helpers.insertAt($back, Settings.insertBack === true ? 0 : Settings.insertBack, $children_menus);
860              }
861
862              const closeThisLevel = () => closeLevel(level, backIndex);
863
864              Helpers.wrap($back_a, Helpers.createElement('div', {class: 'nav-item-wrapper'}));
865              $back_a.addEventListener('click', Helpers.preventClick(closeThisLevel));
866              $back_a.addEventListener('keydown', (e) => {
867                if (e.key === 'Enter' || e.keyCode === 13) {
868                  // remember we are accessing via keyboard
869                  _keyboard = true;
870                }
871              });
872            }
873          }
874
875          // insert close button
876          if (level === 0 && Settings.insertClose !== false) {
877            const $close_a = Helpers.createElement('a', {
878              href: '#',
879              class: 'nav-close-button' + (Settings.labelClose ? ' has-label' : ''),
880              role: 'menuitem',
881              tabindex: 0,
882              'aria-label': !Settings.labelClose ? (Settings.ariaLabels || {}).close : ''
883            },
884              [Settings.labelClose || '', Helpers.createElement('span')]
885            );
886
887            $close_a.addEventListener('click', Helpers.preventClick(closeNav));
888            $close_a.addEventListener('keydown', (e) => {
889              if (e.key === 'Enter' || e.keyCode === 13) {
890                untrapFocus();
891              }
892            });
893
894            if (title && Settings.insertClose === true) {
895              // after nav title
896              $content.insertBefore(Helpers.createElement('div', {
897                class: 'nav-close'
898              }, $close_a), $content.children[1]);
899            }
900            else if (Settings.insertClose === true) {
901              // before nav content
902              $content.insertBefore(Helpers.createElement('div', {
903                class: 'nav-close'
904              }, $close_a), $content.firstChild);
905            }
906            else {
907              // as menu item
908              const $nav_ul = Helpers.children($content, 'ul');
909              const $close = Helpers.createElement('li', {class: 'nav-item nav-close'}, $close_a);
910
911              Helpers.wrap($close_a, Helpers.createElement('div', {class: 'nav-item-wrapper'}));
912              Helpers.insertAt($close, Settings.insertClose, $nav_ul);
913            }
914          }
915        }
916      };
917
918      /* Touch swipe gestures */
919
920      const touchStart = (target) => {
921        return (e) => {
922          if (Settings.position !== 'left' && Settings.position !== 'right') {
923            return;
924          }
925
926          _xStart = e.touches[0].clientX;
927          _yStart = e.touches[0].clientY;
928
929          // temporary attach touch listeners
930          if (target === 'doc') {
931            if (!_touchNavTriggered) {
932              document.addEventListener('touchmove', touchMove_open, Helpers.supportsPassive);
933              document.addEventListener('touchend', touchEnd_open, Helpers.supportsPassive);
934            }
935          }
936          else {
937            _touchNavTriggered = true;
938            $nav_container.addEventListener('touchmove', touchMove_close, Helpers.supportsPassive);
939            $nav_container.addEventListener('touchend', touchEnd_close, Helpers.supportsPassive);
940          }
941        };
942      };
943
944      const touchCaptureNav = (transNav, transContent) => {
945        window.addEventListener('touchmove', Helpers.preventDefault, Helpers.supportsPassive); // disable page scroll
946        $nav.style.visibility = 'visible';
947        $nav_container.style[Helpers.browserPrefix('transition')] = 'none';
948        Helpers.setTransform($nav_container, transNav, Settings.position);
949
950        if ($push_content) {
951          $push_content.style[Helpers.browserPrefix('transition')] = 'none';
952          Helpers.setTransform($push_content, transContent, Settings.position);
953        }
954      };
955
956      const touchReleaseNav = (action, timeoutVsb = true, transNav = false, transContent = false) => {
957        window.removeEventListener('touchmove', Helpers.preventDefault, Helpers.supportsPassive) // enable page scroll
958        $nav_container.style[Helpers.browserPrefix('transition')] = '';
959        Helpers.setTransform($nav_container, transNav, Settings.position);
960
961        if ($push_content) {
962          $push_content.style[Helpers.browserPrefix('transition')] = '';
963          Helpers.setTransform($push_content, transContent, Settings.position);
964        }
965
966        if (action === 'open') {
967          openNav();
968        }
969        else {
970          closeNav();
971
972          if (timeoutVsb) {
973            setTimeout(() => {
974              $nav.style.visibility = '';
975            }, _transitionDuration);
976          }
977          else {
978            $nav.style.visibility = '';
979          }
980        }
981      };
982
983      const touchMove_open = (e) => {
984        let xDiff = 0 - (_xStart - e.touches[0].clientX);
985        const levelSpacing = Settings.levelOpen === 'overlap' ? activeLevel() * Settings.levelSpacing : 0;
986        const swipeWidth = _containerWidth + levelSpacing;
987        const maxStart = 50; // from the edge of the screen
988
989        if (Settings.position === 'left') {
990          xDiff = Math.min(Math.max(xDiff, 0), swipeWidth);
991        }
992        else {
993          xDiff = Math.abs(Math.min(Math.max(xDiff, -swipeWidth), 0));
994        }
995
996        if (
997          (Settings.position === 'left' && _xStart < maxStart) || // swipe right ->
998          (Settings.position === 'right' && _xStart > document.body.clientWidth - maxStart) // swipe left <-
999        ) {
1000          _touchMoved = true;
1001          touchCaptureNav(0 - (_containerWidth - xDiff), Math.abs(xDiff));
1002        }
1003      };
1004
1005      const touchEnd_open = (e) => {
1006        // remove touch listeners from document
1007        document.removeEventListener('touchmove', touchMove_open);
1008        document.removeEventListener('touchend', touchEnd_open);
1009
1010        if (!_touchMoved) {
1011          return;
1012        }
1013
1014        const lastTouch = e.changedTouches[e.changedTouches.length-1];
1015        let xDiff = 0 - (_xStart - lastTouch.clientX);
1016        const levelSpacing = Settings.levelOpen === 'overlap' ? activeLevel() * Settings.levelSpacing : 0;
1017        const swipeWidth = _containerWidth + levelSpacing;
1018        const diffTrashold = 70; // swipe distance required
1019
1020        if (Settings.position === 'left') {
1021          xDiff = Math.min(Math.max(xDiff, 0), swipeWidth);
1022        }
1023        else {
1024          xDiff = Math.abs(Math.min(Math.max(xDiff, -swipeWidth), 0));
1025        }
1026
1027        if (!xDiff) {
1028          touchReleaseNav('close', false);
1029        }
1030        else if (xDiff > diffTrashold) {
1031          touchReleaseNav('open');
1032        }
1033        else {
1034          touchReleaseNav('close');
1035        }
1036
1037        // reset touch
1038        _xStart = null;
1039        _yStart = null;
1040        _touchMoved = false;
1041      };
1042
1043      const touchMove_close = (e) => {
1044        let xDiff = 0 - (_xStart - e.touches[0].clientX);
1045        let yDiff = 0 - (_yStart - e.touches[0].clientY);
1046
1047        if (Math.abs(xDiff) < Math.abs(yDiff)) {
1048          return;
1049        }
1050
1051        const levelSpacing = Settings.levelOpen === 'overlap' ? activeLevel() * Settings.levelSpacing : 0;
1052        const swipeWidth = _containerWidth + levelSpacing;
1053
1054        if (Settings.position === 'left') {
1055          xDiff = Math.min(Math.max(xDiff, -swipeWidth), 0);
1056        }
1057        else {
1058          xDiff = Math.min(Math.max(xDiff, 0), swipeWidth);
1059        }
1060
1061        if (
1062          (Settings.position === 'left' && xDiff < 0) || // swipe right ->
1063          (Settings.position === 'right' && xDiff > 0) // swipe left <-
1064        ) {
1065          _touchMoved = true;
1066          touchCaptureNav(-Math.abs(xDiff) + levelSpacing, swipeWidth - Math.abs(xDiff));
1067        }
1068      };
1069
1070      const touchEnd_close = (e) => {
1071        // remove touch listeners from nav
1072        $nav_container.removeEventListener('touchmove', touchMove_close);
1073        $nav_container.removeEventListener('touchend', touchEnd_close);
1074        _touchNavTriggered = false;
1075
1076        if (!_touchMoved) {
1077          return;
1078        }
1079
1080        const lastTouch = e.changedTouches[e.changedTouches.length-1];
1081        let xDiff = 0 - (_xStart - lastTouch.clientX);
1082        const levelSpacing = Settings.levelOpen === 'overlap' ? activeLevel() * Settings.levelSpacing : 0;
1083        const swipeWidth = _containerWidth + levelSpacing;
1084        const diffTrashold = 50;
1085
1086        if (Settings.position === 'left') {
1087          xDiff = Math.abs(Math.min(Math.max(xDiff, -swipeWidth), 0));
1088        }
1089        else {
1090          xDiff = Math.abs(Math.min(Math.max(xDiff, 0), swipeWidth));
1091        }
1092
1093        if (xDiff === swipeWidth) {
1094          touchReleaseNav('close', false);
1095        }
1096        else if (xDiff > diffTrashold) {
1097          touchReleaseNav('close');
1098        }
1099        else {
1100          touchReleaseNav('open', true, levelSpacing, swipeWidth);
1101        }
1102
1103        // reset touch
1104        _xStart = null;
1105        _yStart = null;
1106        _touchMoved = false;
1107      };
1108
1109      /* Setup our nav */
1110
1111      // init nav
1112      initNav();
1113
1114      // init our Model
1115      createModel();
1116
1117      // create view from model
1118      createNavDom();
1119
1120      // remove original nav
1121      if (Settings.removeOriginalNav === true) {
1122        $originalNav.parentNode.removeChild($originalNav);
1123      }
1124      else {
1125        // add classes to original menu so we know it's connected to our copy
1126        $originalNav.classList.add('hc-nav-original', navUniqId);
1127      }
1128
1129      // insert nav to DOM
1130      if (Settings.bodyInsert === 'prepend') {
1131        document.body.insertBefore($nav, document.body.firstChild);
1132      }
1133      else if (Settings.bodyInsert === 'append') {
1134        document.body.appendChild($nav);
1135      }
1136
1137      // opened nav right away
1138      if (Settings.expanded === true) {
1139        _initExpanded = true; // set flag
1140        openNav();
1141      }
1142
1143      if (Settings.swipeGestures) {
1144        // close touch event on nav swipe
1145        // trigger before document touch
1146        $nav_container.addEventListener('touchstart', touchStart('nav'), Helpers.supportsPassive);
1147        // open touch event on document swipe
1148        document.addEventListener('touchstart', touchStart('doc'), Helpers.supportsPassive);
1149      }
1150
1151      // close levels on escape
1152      if (Settings.closeOnEsc) {
1153        document.addEventListener('keydown', checkEsc);
1154      }
1155
1156      // re-calculate our nav if window is resized
1157      const debounceResize = Helpers.debounce(calcNav, 500);
1158      window.addEventListener('resize', debounceResize, Helpers.supportsPassive);
1159
1160      /* Private methods */
1161
1162      function checkEsc(e) {
1163        if (isOpen() && (e.key === 'Escape' || e.keyCode === 27)) {
1164          const level = activeLevel();
1165
1166          if (level === 0) {
1167            closeNav();
1168            untrapFocus();
1169          }
1170          else {
1171            closeLevel(level, activeIndex());
1172            trapFocus(null, level-1);
1173          }
1174        }
1175      };
1176
1177      function checkboxChange() {
1178        const l = Number(this.dataset.level);
1179        const i = Number(this.dataset.index);
1180
1181        if (this.getAttribute('checked') === 'true') {
1182          openLevel(l, i);
1183        }
1184        else {
1185          closeLevel(l, i);
1186        }
1187      }
1188
1189      function closeToggles($t) {
1190        $t.classList.remove(activeToggleClass);
1191        $t.setAttribute('aria-expanded', false);
1192      };
1193
1194      function areLevelsOpenable() {
1195        return Settings.levelOpen !== false && Settings.levelOpen !== 'none';
1196      }
1197
1198      function isOpen() {
1199        return _open;
1200      }
1201
1202      function activeLevel() {
1203        return _openLevels.length
1204          ? Number(
1205              Array.prototype.filter.call($nav_container.querySelectorAll('.hc-chk'), (el) => {
1206                return el.value == _openLevels[_openLevels.length - 1];
1207              })[0].dataset.level
1208            )
1209          : 0;
1210      }
1211
1212      function activeIndex() {
1213        return _openLevels.length
1214          ? Number(
1215              Array.prototype.filter.call($nav_container.querySelectorAll('.hc-chk'), (el) => {
1216                return el.value == _openLevels[_openLevels.length - 1];
1217              })[0].dataset.index
1218            )
1219          : 0;
1220      }
1221
1222      function openNav(l, i) {
1223        // check if already open
1224        if (isOpen() && typeof i === 'undefined') {
1225          return;
1226        }
1227
1228        // open main nav
1229        _openNav();
1230
1231        if (!areLevelsOpenable()) {
1232          return;
1233        }
1234
1235        let $checkbox;
1236
1237        if ((typeof l === 'number' || Helpers.isNumeric(l)) && (typeof i === 'number' || Helpers.isNumeric(i))) {
1238
1239          $checkbox = document.querySelector(`#${navUniqId}-${l}-${i}`);
1240
1241          if (!$checkbox) {
1242            console.warn(`HC Offcanvas Nav: level ${l} doesn't have index ${i}`);
1243            return;
1244          }
1245        }
1246        else if (_nextActiveLevel) {
1247          // get level to open from [data-nav-active]
1248          $checkbox = Array.prototype.filter.call($nav_container.querySelectorAll('.hc-chk'), (el) => {
1249            return el.value == _nextActiveLevel;
1250          })[0];
1251
1252          // reset flag
1253          if (Settings.closeActiveLevel || !Settings.closeOpenLevels) {
1254            _nextActiveLevel = null;
1255          }
1256        }
1257        else if (Settings.closeOpenLevels === false) {
1258          // get last checked level
1259          $checkbox = Array.prototype.filter.call($nav_container.querySelectorAll('.hc-chk'), (el) => {
1260            return el.getAttribute('checked') === 'true';
1261          });
1262          $checkbox = $checkbox[$checkbox.length - 1];
1263        }
1264
1265        // open sub levels as well
1266        if ($checkbox) {
1267          let levels = [];
1268          l = Number($checkbox.dataset.level);
1269          i = Number($checkbox.dataset.index);
1270
1271          if (l > 1) {
1272            const $parents = [];
1273
1274            for (; $checkbox && $checkbox !== document; $checkbox = $checkbox.parentNode) {
1275              if ($checkbox.matches('.nav-wrapper')) {
1276                $parents.push($checkbox);
1277              }
1278              continue;
1279            }
1280
1281            // get parent levels to open
1282            for (let i = 0; i < $parents.length; i++) {
1283              const $this = $parents[i];
1284              const level = Number($this.dataset.level);
1285
1286              if (level > 0) {
1287                levels.push({
1288                  level: level,
1289                  index: Number($this.dataset.index)
1290                });
1291              }
1292            };
1293
1294            levels = levels.reverse();
1295          }
1296
1297          levels.push({
1298            level: l,
1299            index: i
1300          });
1301
1302          for (let n = 0; n < levels.length; n++) {
1303            // open each level without transition
1304            openLevel(levels[n].level, levels[n].index, false);
1305          }
1306        }
1307      }
1308
1309      function _openNav() {
1310        // check if already open
1311        if (isOpen()) {
1312          return;
1313        }
1314
1315        _open = true;
1316
1317        $nav.style.visibility = 'visible';
1318        $nav.setAttribute('aria-hidden', false);
1319        $nav.classList.add(navOpenClass);
1320
1321        if ($toggle) {
1322          $toggle.forEach(closeToggles);
1323
1324          if ($toggle_open) {
1325            $toggle_open.classList.add(activeToggleClass);
1326            $toggle_open.setAttribute('aria-expanded', true);
1327          }
1328        }
1329
1330        if (Settings.levelOpen === 'expand' && _closeLevelsTimeout) {
1331          clearTimeout(_closeLevelsTimeout);
1332        }
1333
1334        if (Settings.disableBody) {
1335          // remember scroll position
1336          _top = window.pageYOffset || html.scrollTop || document.documentElement.scrollTop || document.body.scrollTop;
1337
1338          if (document.documentElement.scrollHeight > document.documentElement.clientHeight) {
1339            // has scroll bar
1340            html.classList.add('hc-nav-yscroll');
1341          }
1342
1343          document.body.classList.add('hc-nav-open');
1344
1345          if (_top) {
1346            // leave page in place
1347            document.body.style.top = `${-_top}
1347px`;
1348          }
1349        }
1350
1351        if ($push_content) {
1352          const transformVal = Helpers.getAxis(Settings.position) === 'x' ? _containerWidth : _containerHeight;
1353          Helpers.setTransform($push_content, transformVal, Settings.position);
1354        }
1355
1356        if (_initExpanded) {
1357          // reset flag
1358          _initExpanded = false;
1359          // don't trigger open event if nav is initially expanded
1360          return;
1361        }
1362
1363        // trigger "toggle" event
1364        if ($nav._eventListeners.toggle) {
1365          $nav._eventListeners.toggle.forEach((ev) => {
1366            ev.fn(Helpers.customEventObject('toggle', $nav, $nav, {
1367              action: 'open'
1368            }), Object.assign({}, Settings));
1369          });
1370        }
1371
1372        setTimeout(() => {
1373          // trigger "open" event
1374          if ($nav._eventListeners.open) {
1375            $nav._eventListeners.open.forEach((ev) => {
1376              ev.fn(Helpers.customEventObject('open', $nav, $nav), Object.assign({}, Settings));
1377            });
1378          }
1379        }, _transitionDuration);
1380      }
1381
1382      function closeNav() {
1383        // check if already closed
1384        if (!isOpen()) {
1385          return;
1386        }
1387
1388        _open = false;
1389
1390        if ($push_content) {
1391          Helpers.setTransform($push_content, false);
1392        }
1393
1394        $nav.classList.remove(navOpenClass);
1395        $nav.classList.remove('user-is-tabbing');
1396        $nav.setAttribute('aria-hidden', true);
1397        $nav_container.removeAttribute('style');
1398
1399        if ($toggle) {
1400          $toggle.forEach(closeToggles);
1401        }
1402
1403        if (Settings.levelOpen === 'expand' && ['top', 'bottom'].indexOf(Settings.position) !== -1) {
1404          // close all levels before closing the nav because the nav height changed
1405          closeLevel(0);
1406        }
1407        else if (areLevelsOpenable()) {
1408          // close all levels when nav closes
1409          _closeLevelsTimeout = setTimeout(() => {
1410            // keep timeout so we can prevent it if nav opens again before it's closed
1411            closeLevel(0);
1412          }, Settings.levelOpen === 'expand' ? _transitionDuration : 0);
1413        }
1414
1415        if (Settings.disableBody) {
1416          document.body.classList.remove('hc-nav-open');
1417          html.classList.remove('hc-nav-yscroll');
1418
1419          if (_top) {
1420            document.body.style.top = '';
1421            document.body.scrollTop = _top;
1422            html.scrollTop = _top;
1423
1424            // for some reason we need timeout if position is bottom
1425            if (Settings.position === 'bottom') {
1426              const t = _top;
1427              setTimeout(() => {
1428                // reset page position
1429                document.body.scrollTop = t;
1430                html.scrollTop = t;
1431              }, 0);
1432            }
1433
1434            // reset top
1435            _top = 0;
1436          }
1437        }
1438
1439        // trigger "toggle" event
1440        if ($nav._eventListeners.toggle) {
1441          $nav._eventListeners.toggle.forEach((ev) => {
1442            ev.fn(Helpers.customEventObject('toggle', $nav, $nav, {
1443              action: 'close'
1444            }), Object.assign({}, Settings));
1445          });
1446        }
1447
1448        setTimeout(() => {
1449          $nav.style.visibility = '';
1450
1451          // trigger "close" event
1452          if ($nav._eventListeners.close) {
1453            $nav._eventListeners.close.forEach((ev) => {
1454              ev.fn(Helpers.customEventObject('close', $nav, $nav), Object.assign({}, Settings));
1455            });
1456          }
1457
1458          // only trigger this "close" event once and then remove it
1459          if ($nav._eventListeners['close.once']) {
1460            $nav._eventListeners['close.once'].forEach((ev) => {
1461              ev.fn(Helpers.customEventObject('close.once', $nav, $nav), Object.assign({}, Settings));
1462            });
1463          }
1464          $nav.removeEventListener('close.once');
1465        }, _transitionDuration);
1466      }
1467
1468      function toggleNav($t) {
1469        return (e) => {
1470          if (e) {
1471            e.preventDefault();
1472            e.stopPropagation();
1473          }
1474
1475          if ($t) {
1476            // remember which toggle button was pressed
1477            $toggle_open = $t;
1478          }
1479
1480          if (_open) closeNav();
1481          else openNav();
1482        }
1483      }
1484
1485      function openLevel(l, i, transition = true) {
1486        const $checkbox = document.querySelector(`#${navUniqId}-${l}-${i}`);
1487        const uniqid = $checkbox.value;
1488        const $li = $checkbox.parentNode;
1489        const $wrap = $li.closest('.nav-wrapper');
1490        const $sub_wrap = Helpers.children($li, '.nav-wrapper')[0];
1491
1492        if (transition === false) {
1493          // disable level transition
1494          $sub_wrap.style.transition = 'none';
1495        }
1496
1497        $checkbox.setAttribute('checked', true); // ensure it is checked
1498        $wrap.classList.add('sub-level-open');
1499        $li.classList.add('level-open');
1500        $li.querySelectorAll('[aria-controls]')[0].setAttribute('aria-expanded', true);
1501
1502        if (transition === false) {
1503          setTimeout(() => {
1504            // re-enable level transition after nav open
1505            $sub_wrap.style.transition = '';
1506          }, _transitionDuration);
1507        }
1508
1509        // remember what is open
1510        if (_openLevels.indexOf(uniqid) === -1) {
1511          _openLevels.push(uniqid);
1512        }
1513
1514        if (Settings.levelOpen === 'overlap') {
1515          // close on self click
1516          $wrap.addEventListener('click', () => closeLevel(l, i));
1517          // expand the nav
1518          Helpers.setTransform($nav_container, l * Settings.levelSpacing, Settings.position);
1519
1520          // push content
1521          if ($push_content) {
1522            const transformVal = Helpers.getAxis(Settings.position) === 'x' ? _containerWidth : _containerHeight;
1523            Helpers.setTransform($push_content, transformVal + l * Settings.levelSpacing, Settings.position);
1524          }
1525        }
1526
1527        // trigger level open event
1528        if ($nav._eventListeners['open.level']) {
1529          $nav._eventListeners['open.level'].forEach((ev) => {
1530            ev.fn(Helpers.customEventObject('open.level', $nav, $sub_wrap, {
1531              currentLevel: l,
1532              currentIndex: i
1533            }), Object.assign({}, Settings));
1534          });
1535        }
1536
1537        if (_keyboard) {
1538          // trap focus inside level when keyboard accessing
1539          trapFocus(0, l, i);
1540          // reset keyboard flag
1541          _keyboard = false;
1542        }
1543      }
1544
1545      const _closeLevel = (l, i, transform) => {
1546        const $checkbox = document.querySelector(`#${navUniqId}-${l}-${i}`);
1547
1548        if (!$checkbox) return;
1549
1550        const uniqid = $checkbox.value;
1551        const $li = $checkbox.parentNode;
1552        const $wrap = $li.closest('.nav-wrapper');
1553
1554        $checkbox.setAttribute('checked', false); // ensure it is unchecked
1555        $wrap.classList.remove('sub-level-open');
1556        $li.classList.remove('level-open');
1557        $li.querySelectorAll('[aria-controls]')[0].setAttribute('aria-expanded', false);
1558
1559        // this is not open anymore
1560        if (_openLevels.indexOf(uniqid) !== -1) {
1561          _openLevels.splice(_openLevels.indexOf(uniqid), 1);
1562        }
1563
1564        if (transform && Settings.levelOpen === 'overlap') {
1565          //level closed, remove wrapper click
1566          $wrap.removeEventListener('click');
1567          $wrap.addEventListener('click', Helpers.stopPropagation);
1568          // collapse the nav
1569          Helpers.setTransform($nav_container, (l - 1) * Settings.levelSpacing, Settings.position);
1570
1571          // push back content
1572          if ($push_content) {
1573            const transformVal = Helpers.getAxis(Settings.position) === 'x' ? _containerWidth : _containerHeight;
1574            Helpers.setTransform($push_content, transformVal + (l - 1) * Settings.levelSpacing, Settings.position);
1575          }
1576        }
1577      };
1578
1579      function closeLevel(l, i) {
1580        for (let level = l; level <= Object.keys(_indexes).length; level++) {
1581          if (level === l && typeof i !== 'undefined') {
1582            // close specified level with index
1583            _closeLevel(l, i, true);
1584          }
1585          else {
1586            if (l === 0 && !Settings.closeOpenLevels) {
1587              // do nothing
1588            } else {
1589              // close all sub sub levels
1590              for (let index = 0; index < _indexes[level]; index++) {
1591                _closeLevel(level, index, level === l);
1592              }
1593            }
1594          }
1595        }
1596
1597        // trigger level open event
1598        if (l > 0 && $nav._eventListeners['close.level']) {
1599          const $wrap = document.querySelector(`#${navUniqId}-${l}-${i}`).closest('.nav-wrapper');
1600
1601          $nav._eventListeners['close.level'].forEach((ev) => {
1602            ev.fn(Helpers.customEventObject('close.level', $nav, $wrap, {
1603              currentLevel: l - 1,
1604              currentIndex: activeIndex()
1605            }), Object.assign({}, Settings));
1606          });
1607        }
1608
1609        if (_keyboard) {
1610          // trap focus back one level when keyboard accessing
1611          trapFocus(null, l-1);
1612          // reset keyboard flag
1613          _keyboard = false;
1614        }
1615      }
1616
1617      /* Public methods */
1618
1619      $nav.on = (type, cb) => {
1620        $nav.addEventListener(type, cb);
1621      };
1622
1623      $nav.off = (type, cb) => {
1624        $nav.removeEventListener(type, cb);
1625      };
1626
1627      $nav.getSettings = () => Object.assign({}, Settings);
1628
1629      $nav.isOpen = isOpen;
1630
1631      $nav.open = openNav;
1632
1633      $nav.close = closeNav;
1634
1635      $nav.toggle = toggleNav(null);
1636
1637      $nav.update = (options, updateDom) => {
1638        // clear updated array
1639        UpdatedSettings = [];
1640
1641        // update our settings
1642        if (typeof options === 'object') {
1643          // only get what's been actually updated
1644          for (let prop in options) {
1645            if (Settings[prop] !== options[prop]) {
1646              UpdatedSettings.push(prop);
1647            }
1648          }
1649
1650          Settings = Object.assign({}, Settings, options);
1651        }
1652
1653        if (options === true || updateDom === true) {
1654          // can't update Model if original nav is removed
1655          if (Settings.removeOriginalNav) {
1656            console.warn('%c! HC Offcanvas Nav:' + `%c Can't update because original navigation has been removed. Disable \`removeOriginalNav\` option.`, 'color: #fa253b', 'color: default');
1657            return;
1658          }
1659
1660          // hard update, reinit Model and DOM
1661          initNav(true);
1662          createModel();
1663          createNavDom(true);
1664        }
1665        else {
1666          // soft update just reinit DOM from existing Model
1667          initNav(true);
1668          createNavDom(true);
1669        }
1670      };
1671
1672      return $nav; // return our nav element
1673    };
1674
1675    if (Array.isArray(elem) || elem instanceof NodeList) {
1676      const navs = [];
1677      // call the plugin on each element
1678      for (let i = 0; i < elem.length; i++) {
1679        navs.push(Plugin(elem[i]));
1680      }
1681
1682      return navs.length > 1 ? navs : navs[0];
1683    }
1684    else {
1685      return Plugin(elem);
1686    }
1687  };
1688
1689  // jQuery Plugin
1690  if (typeof window.jQuery !== 'undefined') {
1691    const $ = window.jQuery;
1692    const namespace = 'hcOffcanvasNav';
1693
1694    $.fn.extend({
1695      hcOffcanvasNav: function(args) {
1696        // check if selected element exist
1697        if (!this.length) return this;
1698
1699        return this.each(function() {
1700          let instance = $.data(this, namespace);
1701
1702          if (instance) {
1703            // already created, just update
1704            instance.update(args);
1705          }
1706          else {
1707            // create new instance
1708            instance = new hcOffcanvasNav(this, args);
1709            $.data(this, namespace, instance);
1710          }
1711        });
1712      }
1713    });
1714  }
1715
1716  // browser global
1717  window.hcOffcanvasNav = window.hcOffcanvasNav || hcOffcanvasNav;
1718
1719  return hcOffcanvasNav;
1720});

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.