@coreui/coreui
Version:
The most popular front-end framework for developing responsive, mobile-first projects on the web rewritten and maintained by the CoreUI Team
261 lines (241 loc) • 9.25 kB
JavaScript
/*!
* CoreUI navigation.js v5.4.0 (https://coreui.io)
* Copyright 2025 The CoreUI Team (https://github.com/orgs/coreui/people)
* Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/data.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/data', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Navigation = factory(global.BaseComponent, global.Data, global.EventHandler, global.SelectorEngine, global.Index));
})(this, (function (BaseComponent, Data, EventHandler, SelectorEngine, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* CoreUI navigation.js
* Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME = 'navigation';
const DATA_KEY = 'coreui.navigation';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const Default = {
activeLinksExact: true,
groupsAutoCollapse: true
};
const DefaultType = {
activeLinksExact: 'boolean',
groupsAutoCollapse: '(string|boolean)'
};
const CLASS_NAME_ACTIVE = 'active';
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_NAV_GROUP = 'nav-group';
const CLASS_NAME_NAV_GROUP_TOGGLE = 'nav-group-toggle';
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;
const SELECTOR_NAV_GROUP = '.nav-group';
const SELECTOR_NAV_GROUP_ITEMS = '.nav-group-items';
const SELECTOR_NAV_GROUP_TOGGLE = '.nav-group-toggle';
const SELECTOR_NAV_LINK = '.nav-link';
const SELECTOR_DATA_NAVIGATION = '[data-coreui="navigation"]';
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Navigation extends BaseComponent {
constructor(element, config) {
super(element);
this._config = this._getConfig(config);
this._setActiveLink();
this._addEventListeners();
Data.set(element, DATA_KEY, this);
}
// Getters
static get Default() {
return Default;
}
static get DATA_KEY() {
return DATA_KEY;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Private
_setActiveLink() {
for (const element of Array.from(this._element.querySelectorAll(SELECTOR_NAV_LINK))) {
if (element.classList.contains(CLASS_NAME_NAV_GROUP_TOGGLE)) {
continue;
}
let currentUrl = String(window.location);
const urlHasParams = /\?.*=/;
const urlHasQueryString = /\?./;
const urlHasHash = /#./;
if (urlHasParams.test(currentUrl) || urlHasQueryString.test(currentUrl)) {
currentUrl = currentUrl.split('?')[0];
}
if (urlHasHash.test(currentUrl)) {
currentUrl = currentUrl.split('#')[0];
}
if (this._config.activeLinksExact && element.href === currentUrl) {
element.classList.add(CLASS_NAME_ACTIVE);
// eslint-disable-next-line unicorn/no-array-for-each
Array.from(this._getParents(element, SELECTOR_NAV_GROUP)).forEach(element => {
element.classList.add(CLASS_NAME_SHOW);
element.setAttribute('aria-expanded', true);
});
}
if (!this._config.activeLinksExact && currentUrl.startsWith(element.href)) {
element.classList.add(CLASS_NAME_ACTIVE);
// eslint-disable-next-line unicorn/no-array-for-each
Array.from(this._getParents(element, SELECTOR_NAV_GROUP)).forEach(element => {
element.classList.add(CLASS_NAME_SHOW);
element.setAttribute('aria-expanded', true);
});
}
}
}
_getParents(element, selector) {
// Setup parents array
const parents = [];
// Get matching parent elements
for (; element && element !== document; element = element.parentNode) {
// Add matching parents to array
if (selector) {
if (element.matches(selector)) {
parents.push(element);
}
} else {
parents.push(element);
}
}
return parents;
}
_getAllSiblings(element, filter) {
const siblings = [];
element = element.parentNode.firstChild;
do {
if (element.nodeType === 3) {
continue; // text node
}
if (element.nodeType === 8) {
continue; // comment node
}
if (!filter || filter(element)) {
siblings.push(element);
}
// eslint-disable-next-line no-cond-assign
} while (element = element.nextSibling);
return siblings;
}
_getChildren(n, skipMe) {
const children = [];
for (; n; n = n.nextSibling) {
if (n.nodeType === 1 && n !== skipMe) {
children.push(n);
}
}
return children;
}
_getSiblings(element, filter) {
const siblings = this._getChildren(element.parentNode.firstChild, element).filter(filter);
return siblings;
}
_slideDown(element) {
element.style.height = 'auto';
const height = element.clientHeight;
element.style.height = '0px';
setTimeout(() => {
element.style.height = `${height}px`;
}, 0);
this._queueCallback(() => {
element.style.height = 'auto';
}, element, true);
}
_slideUp(element, callback) {
const height = element.clientHeight;
element.style.height = `${height}px`;
setTimeout(() => {
element.style.height = '0px';
}, 0);
this._queueCallback(() => {
if (typeof callback === 'function') {
callback();
}
}, element, true);
}
_toggleGroupItems(event) {
let toggler = event.target;
if (!toggler.classList.contains(CLASS_NAME_NAV_GROUP_TOGGLE)) {
toggler = toggler.closest(SELECTOR_NAV_GROUP_TOGGLE);
}
const filter = element => Boolean(element.classList.contains(CLASS_NAME_NAV_GROUP) && element.classList.contains(CLASS_NAME_SHOW));
// Close other groups
if (this._config.groupsAutoCollapse === true) {
for (const element of this._getSiblings(toggler.parentNode, filter)) {
this._slideUp(SelectorEngine.findOne(SELECTOR_NAV_GROUP_ITEMS, element), () => {
element.classList.remove(CLASS_NAME_SHOW);
element.setAttribute('aria-expanded', false);
});
}
}
if (toggler.parentNode.classList.contains(CLASS_NAME_SHOW)) {
this._slideUp(SelectorEngine.findOne(SELECTOR_NAV_GROUP_ITEMS, toggler.parentNode), () => {
toggler.parentNode.classList.remove(CLASS_NAME_SHOW);
toggler.parentNode.setAttribute('aria-expanded', false);
});
return;
}
toggler.parentNode.classList.add(CLASS_NAME_SHOW);
toggler.parentNode.setAttribute('aria-expanded', true);
this._slideDown(SelectorEngine.findOne(SELECTOR_NAV_GROUP_ITEMS, toggler.parentNode));
}
_addEventListeners() {
EventHandler.on(this._element, EVENT_CLICK_DATA_API, SELECTOR_NAV_GROUP_TOGGLE, event => {
event.preventDefault();
this._toggleGroupItems(event, this);
});
}
// Static
static navigationInterface(element, config) {
const data = Navigation.getOrCreateInstance(element, config);
if (typeof config === 'string') {
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`);
}
data[config]();
}
}
static jQueryInterface(config) {
return this.each(function () {
Navigation.navigationInterface(this, config);
});
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
for (const element of Array.from(document.querySelectorAll(SELECTOR_DATA_NAVIGATION))) {
Navigation.navigationInterface(element);
}
});
/**
* ------------------------------------------------------------------------
* jQuery
* ------------------------------------------------------------------------
* add .Navigation to jQuery only if jQuery is present
*/
index_js.defineJQueryPlugin(Navigation);
return Navigation;
}));
//# sourceMappingURL=navigation.js.map