/*!
 * EdGEL: The University of Edinburgh Design System (mobile-toggles.js) v5.1.6-rc.2 (https://design-system.ed.ac.uk/)
 * Copyright 2014-2024 The University of Edinburgh
 *
 * Bootstrap  v5.3.3 (https://getbootstrap.com/)
 * Copyright 2011-2024 The Bootstrap Authors
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 */

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.MobileToggles = factory());
})(this, (function () { 'use strict';

  /**
   * --------------------------------------------------------------------------
   * Bootstrap util/index.js
   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
   * --------------------------------------------------------------------------
   */

  const MAX_UID = 1000000;

  /**
   * Properly escape IDs selectors to handle weird IDs
   * @param {string} selector
   * @returns {string}
   */
  const parseSelector = selector => {
    if (selector && window.CSS && window.CSS.escape) {
      // document.querySelector needs escaping to handle IDs (html5+) containing for instance /
      selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`);
    }
    return selector;
  };

  /**
   * Public Util API
   */

  const getUID = prefix => {
    do {
      prefix += Math.floor(Math.random() * MAX_UID);
    } while (document.getElementById(prefix));
    return prefix;
  };
  const isElement = object => {
    if (!object || typeof object !== 'object') {
      return false;
    }
    if (typeof object.jquery !== 'undefined') {
      object = object[0];
    }
    return typeof object.nodeType !== 'undefined';
  };
  const isVisible = element => {
    if (!isElement(element) || element.getClientRects().length === 0) {
      return false;
    }
    const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';
    // Handle `details` element as its content may falsie appear visible when it is closed
    const closedDetails = element.closest('details:not([open])');
    if (!closedDetails) {
      return elementIsVisible;
    }
    if (closedDetails !== element) {
      const summary = element.closest('summary');
      if (summary && summary.parentNode !== closedDetails) {
        return false;
      }
      if (summary === null) {
        return false;
      }
    }
    return elementIsVisible;
  };
  const isDisabled = element => {
    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
      return true;
    }
    if (element.classList.contains('disabled')) {
      return true;
    }
    if (typeof element.disabled !== 'undefined') {
      return element.disabled;
    }
    return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
  };
  const DOMContentLoadedCallbacks = [];
  const onDOMContentLoaded = callback => {
    if (document.readyState === 'loading') {
      // add listener on the first call when the document is in loading state
      if (!DOMContentLoadedCallbacks.length) {
        document.addEventListener('DOMContentLoaded', () => {
          for (const callback of DOMContentLoadedCallbacks) {
            callback();
          }
        });
      }
      DOMContentLoadedCallbacks.push(callback);
    } else {
      callback();
    }
  };

  /**
   * --------------------------------------------------------------------------
   * Bootstrap dom/selector-engine.js
   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
   * --------------------------------------------------------------------------
   */

  const getSelector = element => {
    let selector = element.getAttribute('data-bs-target');
    if (!selector || selector === '#') {
      let hrefAttribute = element.getAttribute('href');

      // The only valid content that could double as a selector are IDs or classes,
      // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
      // `document.querySelector` will rightfully complain it is invalid.
      // See https://github.com/twbs/bootstrap/issues/32273
      if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {
        return null;
      }

      // Just in case some CMS puts out a full URL with the anchor appended
      if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
        hrefAttribute = `#${hrefAttribute.split('#')[1]}`;
      }
      selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;
    }
    return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;
  };
  const SelectorEngine = {
    find(selector, element = document.documentElement) {
      return [].concat(...Element.prototype.querySelectorAll.call(element, selector));
    },
    findOne(selector, element = document.documentElement) {
      return Element.prototype.querySelector.call(element, selector);
    },
    children(element, selector) {
      return [].concat(...element.children).filter(child => child.matches(selector));
    },
    parents(element, selector) {
      const parents = [];
      let ancestor = element.parentNode.closest(selector);
      while (ancestor) {
        parents.push(ancestor);
        ancestor = ancestor.parentNode.closest(selector);
      }
      return parents;
    },
    prev(element, selector) {
      let previous = element.previousElementSibling;
      while (previous) {
        if (previous.matches(selector)) {
          return [previous];
        }
        previous = previous.previousElementSibling;
      }
      return [];
    },
    // TODO: this is now unused; remove later along with prev()
    next(element, selector) {
      let next = element.nextElementSibling;
      while (next) {
        if (next.matches(selector)) {
          return [next];
        }
        next = next.nextElementSibling;
      }
      return [];
    },
    focusableChildren(element) {
      const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(',');
      return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));
    },
    getSelectorFromElement(element) {
      const selector = getSelector(element);
      if (selector) {
        return SelectorEngine.findOne(selector) ? selector : null;
      }
      return null;
    },
    getElementFromSelector(element) {
      const selector = getSelector(element);
      return selector ? SelectorEngine.findOne(selector) : null;
    },
    getMultipleElementsFromSelector(element) {
      const selector = getSelector(element);
      return selector ? SelectorEngine.find(selector) : [];
    }
  };

  /**
   * --------------------------------------------------------------------------
   * helpers.js
   * Helper functions
   * --------------------------------------------------------------------------
   */

  const ID_PREFIX = 'uoe-';

  /**
   * Public methods
   */

  const uoeUID = function () {
    return getUID(ID_PREFIX);
  };

  /**
   * --------------------------------------------------------------------------
   * mobile-toggles.js
   * Toggle and mobile responsive functionality
   * --------------------------------------------------------------------------
   */


  /**
   * ------------------------------------------------------------------------
   * Constants
   * ------------------------------------------------------------------------
   */

  // const NAME = 'mobileToggles'
  const CLASS_NAME_JS = 'edgel-js';
  const CLASS_NAME_SEARCH_COLLAPSIBLE = 'masthead-logo-collapsible-search';
  const SELECTOR_JS_CLASS = 'html';
  const SELECTOR_UOE_NAV = '.uoe-nav ul';
  const SELECTOR_SEARCH = '.masthead-search';
  const SELECTOR_MASTHEAD = '.masthead';
  const SELECTOR_ROW = '.row';
  const TEXT_SEARCH_TOGGLE = '<span class="visually-hidden">Show/hide site search</span>';
  const TEXT_MENU_TOGGLE = '<span class="visually-hidden">Toggle Navigation</span> Menu';

  /**
   * Public methods
   */

  const MobileToggles = {
    addIcons(selector, icon, iconSelector) {
      const _addIcons = function () {
        const selectorElements = SelectorEngine.find(this.selector);
        for (const [i, element] of selectorElements.entries()) {
          if (!SelectorEngine.findOne(this.iconSelector, element)) {
            element.prepend(this.icon);
            if (i !== selectorElements.length - 1) {
              this.icon = this.icon.cloneNode(true);
            }
          }
        }
      }.bind({
        selector,
        icon,
        iconSelector
      });
      onDOMContentLoaded(_addIcons);
    },
    addJsClass() {
      const _addJsClass = function () {
        const selectorElement = this.selector === 'html' ? document.documentElement : SelectorEngine.findOne(this.selector);
        if (selectorElement) {
          selectorElement.classList.add(this.className);
        }
      }.bind({
        selector: SELECTOR_JS_CLASS,
        className: CLASS_NAME_JS
      });
      onDOMContentLoaded(_addJsClass);
    },
    addNavToggle() {
      const _addNavToggle = function () {
        const toggle = document.createElement('button');
        toggle.className = 'btn btn-uoe-nav-toggler btn-hamburger';
        toggle.setAttribute('type', 'button');
        toggle.setAttribute('aria-expanded', 'false');
        toggle.dataset.bsToggle = 'hamburger';
        toggle.innerHTML = this.TEXT_MENU_TOGGLE;
        const element = SelectorEngine.findOne(this.selector);
        if (element) {
          element.classList.add('collapse');
          element.id || (element.id = this.uoeUID());
          toggle.dataset.bsTarget = `#${element.id}`;
          toggle.setAttribute('aria-controls', element.id);
          element.before(toggle);
        }
      }.bind({
        selector: SELECTOR_UOE_NAV,
        uoeUID,
        TEXT_MENU_TOGGLE
      });
      onDOMContentLoaded(_addNavToggle);
    },
    addSearchToggle() {
      const _addSearchToggle = function () {
        const searchBlock = SelectorEngine.findOne(this.SELECTOR_SEARCH);
        if (!searchBlock) {
          return;
        }
        const toggle = document.createElement('button');
        toggle.setAttribute('type', 'button');
        toggle.dataset.bsToggle = 'collapse';
        toggle.className = 'btn btn-masthead-search-toggler';
        toggle.innerHTML = this.TEXT_SEARCH_TOGGLE;
        const row = searchBlock.closest(SELECTOR_ROW);
        if (!row || !row.closest(SELECTOR_MASTHEAD)) {
          throw new Error('Please use Bootstrap row and col classes in masthead');
        }

        // Make masthead-search collapsible
        const element = SelectorEngine.findOne(this.SELECTOR_SEARCH);
        if (element) {
          element.classList.add('collapse');
          element.id || (element.id = this.uoeUID());
          toggle.dataset.bsTarget = `#${element.id}`;
          toggle.setAttribute('aria-controls', element.id);

          // Add padding to move the logo away from the search toggle button
          const insertPoint = row.firstElementChild;
          insertPoint.classList.add(CLASS_NAME_SEARCH_COLLAPSIBLE);
          insertPoint.after(toggle);
        }
      }.bind({
        uoeUID,
        TEXT_SEARCH_TOGGLE,
        SELECTOR_SEARCH
      });
      onDOMContentLoaded(_addSearchToggle);
    }
  };

  /**
   * Add EdGEL toggle buttons
   */
  MobileToggles.addJsClass();
  MobileToggles.addNavToggle();
  MobileToggles.addSearchToggle();

  return MobileToggles;

}));
//# sourceMappingURL=mobile-toggles.js.map
