bitwarden_rs/src/static/scripts/bootstrap-native.js

3571 lines
108 KiB
JavaScript

/*!
* Native JavaScript for Bootstrap v4.0.6 (https://thednp.github.io/bootstrap.native/)
* Copyright 2015-2021 © dnp_theme
* Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/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.BSN = factory());
}(this, (function () { 'use strict';
const transitionEndEvent = 'webkitTransition' in document.head.style ? 'webkitTransitionEnd' : 'transitionend';
const supportTransition = 'webkitTransition' in document.head.style || 'transition' in document.head.style;
const transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration';
const transitionProperty = 'webkitTransition' in document.head.style ? 'webkitTransitionProperty' : 'transitionProperty';
function getElementTransitionDuration(element) {
const computedStyle = getComputedStyle(element);
const propertyValue = computedStyle[transitionProperty];
const durationValue = computedStyle[transitionDuration];
const durationScale = durationValue.includes('ms') ? 1 : 1000;
const duration = supportTransition && propertyValue && propertyValue !== 'none'
? parseFloat(durationValue) * durationScale : 0;
return !Number.isNaN(duration) ? duration : 0;
}
function emulateTransitionEnd(element, handler) {
let called = 0;
const endEvent = new Event(transitionEndEvent);
const duration = getElementTransitionDuration(element);
if (duration) {
element.addEventListener(transitionEndEvent, function transitionEndWrapper(e) {
if (e.target === element) {
handler.apply(element, [e]);
element.removeEventListener(transitionEndEvent, transitionEndWrapper);
called = 1;
}
});
setTimeout(() => {
if (!called) element.dispatchEvent(endEvent);
}, duration + 17);
} else {
handler.apply(element, [endEvent]);
}
}
function queryElement(selector, parent) {
const lookUp = parent && parent instanceof Element ? parent : document;
return selector instanceof Element ? selector : lookUp.querySelector(selector);
}
function hasClass(element, classNAME) {
return element.classList.contains(classNAME);
}
function removeClass(element, classNAME) {
element.classList.remove(classNAME);
}
const addEventListener = 'addEventListener';
const removeEventListener = 'removeEventListener';
const fadeClass = 'fade';
const showClass = 'show';
const dataBsDismiss = 'data-bs-dismiss';
function bootstrapCustomEvent(namespacedEventType, eventProperties) {
const OriginalCustomEvent = new CustomEvent(namespacedEventType, { cancelable: true });
if (eventProperties instanceof Object) {
Object.keys(eventProperties).forEach((key) => {
Object.defineProperty(OriginalCustomEvent, key, {
value: eventProperties[key],
});
});
}
return OriginalCustomEvent;
}
function normalizeValue(value) {
if (value === 'true') {
return true;
}
if (value === 'false') {
return false;
}
if (!Number.isNaN(+value)) {
return +value;
}
if (value === '' || value === 'null') {
return null;
}
// string / function / Element / Object
return value;
}
function normalizeOptions(element, defaultOps, inputOps, ns) {
const normalOps = {};
const dataOps = {};
const data = { ...element.dataset };
Object.keys(data)
.forEach((k) => {
const key = k.includes(ns)
? k.replace(ns, '').replace(/[A-Z]/, (match) => match.toLowerCase())
: k;
dataOps[key] = normalizeValue(data[k]);
});
Object.keys(inputOps)
.forEach((k) => {
inputOps[k] = normalizeValue(inputOps[k]);
});
Object.keys(defaultOps)
.forEach((k) => {
if (k in inputOps) {
normalOps[k] = inputOps[k];
} else if (k in dataOps) {
normalOps[k] = dataOps[k];
} else {
normalOps[k] = defaultOps[k];
}
});
return normalOps;
}
/* Native JavaScript for Bootstrap 5 | Base Component
----------------------------------------------------- */
class BaseComponent {
constructor(name, target, defaults, config) {
const self = this;
const element = queryElement(target);
if (element[name]) element[name].dispose();
self.element = element;
if (defaults && Object.keys(defaults).length) {
self.options = normalizeOptions(element, defaults, (config || {}), 'bs');
}
element[name] = self;
}
dispose(name) {
const self = this;
self.element[name] = null;
Object.keys(self).forEach((prop) => { self[prop] = null; });
}
}
/* Native JavaScript for Bootstrap 5 | Alert
-------------------------------------------- */
// ALERT PRIVATE GC
// ================
const alertString = 'alert';
const alertComponent = 'Alert';
const alertSelector = `.${alertString}`;
const alertDismissSelector = `[${dataBsDismiss}="${alertString}"]`;
// ALERT CUSTOM EVENTS
// ===================
const closeAlertEvent = bootstrapCustomEvent(`close.bs.${alertString}`);
const closedAlertEvent = bootstrapCustomEvent(`closed.bs.${alertString}`);
// ALERT EVENT HANDLERS
// ====================
function alertTransitionEnd(self) {
const { element, relatedTarget } = self;
toggleAlertHandler(self);
if (relatedTarget) closedAlertEvent.relatedTarget = relatedTarget;
element.dispatchEvent(closedAlertEvent);
self.dispose();
element.parentNode.removeChild(element);
}
// ALERT PRIVATE METHOD
// ====================
function toggleAlertHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
if (self.dismiss) self.dismiss[action]('click', self.close);
}
// ALERT DEFINITION
// ================
class Alert extends BaseComponent {
constructor(target) {
super(alertComponent, target);
// bind
const self = this;
// initialization element
const { element } = self;
// the dismiss button
self.dismiss = queryElement(alertDismissSelector, element);
self.relatedTarget = null;
// add event listener
toggleAlertHandler(self, 1);
}
// ALERT PUBLIC METHODS
// ====================
close(e) {
const target = e ? e.target : null;
const self = e
? e.target.closest(alertSelector)[alertComponent]
: this;
const { element } = self;
if (self && element && hasClass(element, showClass)) {
if (target) {
closeAlertEvent.relatedTarget = target;
self.relatedTarget = target;
}
element.dispatchEvent(closeAlertEvent);
if (closeAlertEvent.defaultPrevented) return;
removeClass(element, showClass);
if (hasClass(element, fadeClass)) {
emulateTransitionEnd(element, () => alertTransitionEnd(self));
} else alertTransitionEnd(self);
}
}
dispose() {
toggleAlertHandler(this);
super.dispose(alertComponent);
}
}
Alert.init = {
component: alertComponent,
selector: alertSelector,
constructor: Alert,
};
function addClass(element, classNAME) {
element.classList.add(classNAME);
}
const activeClass = 'active';
const dataBsToggle = 'data-bs-toggle';
/* Native JavaScript for Bootstrap 5 | Button
---------------------------------------------*/
// BUTTON PRIVATE GC
// =================
const buttonString = 'button';
const buttonComponent = 'Button';
const buttonSelector = `[${dataBsToggle}="${buttonString}"]`;
const ariaPressed = 'aria-pressed';
// BUTTON PRIVATE METHOD
// =====================
function toggleButtonHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
self.element[action]('click', self.toggle);
}
// BUTTON DEFINITION
// =================
class Button extends BaseComponent {
constructor(target) {
super(buttonComponent, target);
const self = this;
// initialization element
const { element } = self;
// set initial state
self.isActive = hasClass(element, activeClass);
element.setAttribute(ariaPressed, !!self.isActive);
// add event listener
toggleButtonHandler(self, 1);
}
// BUTTON PUBLIC METHODS
// =====================
toggle(e) {
if (e) e.preventDefault();
const self = e ? this[buttonComponent] : this;
const { element } = self;
if (hasClass(element, 'disabled')) return;
self.isActive = hasClass(element, activeClass);
const { isActive } = self;
const action = isActive ? removeClass : addClass;
const ariaValue = isActive ? 'false' : 'true';
action(element, activeClass);
element.setAttribute(ariaPressed, ariaValue);
}
dispose() {
toggleButtonHandler(this);
super.dispose(buttonComponent);
}
}
Button.init = {
component: buttonComponent,
selector: buttonSelector,
constructor: Button,
};
const supportPassive = (() => {
let result = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
result = true;
return result;
},
});
document[addEventListener]('DOMContentLoaded', function wrap() {
document[removeEventListener]('DOMContentLoaded', wrap, opts);
}, opts);
} catch (e) {
throw Error('Passive events are not supported');
}
return result;
})();
// general event options
var passiveHandler = supportPassive ? { passive: true } : false;
function reflow(element) {
return element.offsetHeight;
}
function isElementInScrollRange(element) {
const bcr = element.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
return bcr.top <= viewportHeight && bcr.bottom >= 0; // bottom && top
}
/* Native JavaScript for Bootstrap 5 | Carousel
----------------------------------------------- */
// CAROUSEL PRIVATE GC
// ===================
const carouselString = 'carousel';
const carouselComponent = 'Carousel';
const carouselSelector = `[data-bs-ride="${carouselString}"]`;
const carouselControl = `${carouselString}-control`;
const carouselItem = `${carouselString}-item`;
const dataBsSlideTo = 'data-bs-slide-to';
const pausedClass = 'paused';
const defaultCarouselOptions = {
pause: 'hover', // 'boolean|string'
keyboard: false, // 'boolean'
touch: true, // 'boolean'
interval: 5000, // 'boolean|number'
};
let startX = 0;
let currentX = 0;
let endX = 0;
// CAROUSEL CUSTOM EVENTS
// ======================
const carouselSlideEvent = bootstrapCustomEvent(`slide.bs.${carouselString}`);
const carouselSlidEvent = bootstrapCustomEvent(`slid.bs.${carouselString}`);
// CAROUSEL EVENT HANDLERS
// =======================
function carouselTransitionEndHandler(self) {
const {
index, direction, element, slides, options, isAnimating,
} = self;
// discontinue disposed instances
if (isAnimating && element[carouselComponent]) {
const activeItem = getActiveIndex(self);
const orientation = direction === 'left' ? 'next' : 'prev';
const directionClass = direction === 'left' ? 'start' : 'end';
self.isAnimating = false;
addClass(slides[index], activeClass);
removeClass(slides[activeItem], activeClass);
removeClass(slides[index], `${carouselItem}-${orientation}`);
removeClass(slides[index], `${carouselItem}-${directionClass}`);
removeClass(slides[activeItem], `${carouselItem}-${directionClass}`);
element.dispatchEvent(carouselSlidEvent);
// check for element, might have been disposed
if (!document.hidden && options.interval
&& !hasClass(element, pausedClass)) {
self.cycle();
}
}
}
function carouselPauseHandler(e) {
const eventTarget = e.target;
const self = eventTarget.closest(carouselSelector)[carouselComponent];
const { element, isAnimating } = self;
if (!hasClass(element, pausedClass)) {
addClass(element, pausedClass);
if (!isAnimating) {
clearInterval(self.timer);
self.timer = null;
}
}
}
function carouselResumeHandler(e) {
const eventTarget = e.target;
const self = eventTarget.closest(carouselSelector)[carouselComponent];
const { isPaused, isAnimating, element } = self;
if (!isPaused && hasClass(element, pausedClass)) {
removeClass(element, pausedClass);
if (!isAnimating) {
clearInterval(self.timer);
self.timer = null;
self.cycle();
}
}
}
function carouselIndicatorHandler(e) {
e.preventDefault();
const { target } = e;
const self = target.closest(carouselSelector)[carouselComponent];
if (self.isAnimating) return;
const newIndex = target.getAttribute(dataBsSlideTo);
if (target && !hasClass(target, activeClass) // event target is not active
&& newIndex) { // AND has the specific attribute
self.to(+newIndex); // do the slide
}
}
function carouselControlsHandler(e) {
e.preventDefault();
const that = this;
const self = that.closest(carouselSelector)[carouselComponent];
const { controls } = self;
if (controls[1] && that === controls[1]) {
self.next();
} else if (controls[1] && that === controls[0]) {
self.prev();
}
}
function carouselKeyHandler({ which }) {
const [element] = Array.from(document.querySelectorAll(carouselSelector))
.filter((x) => isElementInScrollRange(x));
if (!element) return;
const self = element[carouselComponent];
switch (which) {
case 39:
self.next();
break;
case 37:
self.prev();
break;
}
}
// CAROUSEL TOUCH HANDLERS
// =======================
function carouselTouchDownHandler(e) {
const element = this;
const self = element[carouselComponent];
if (!self || self.isTouch) { return; }
startX = e.changedTouches[0].pageX;
if (element.contains(e.target)) {
self.isTouch = true;
toggleCarouselTouchHandlers(self, 1);
}
}
function carouselTouchMoveHandler(e) {
const { changedTouches, type } = e;
const self = this[carouselComponent];
if (!self || !self.isTouch) { return; }
currentX = changedTouches[0].pageX;
// cancel touch if more than one changedTouches detected
if (type === 'touchmove' && changedTouches.length > 1) {
e.preventDefault();
}
}
function carouselTouchEndHandler(e) {
const element = this;
const self = element[carouselComponent];
if (!self || !self.isTouch) { return; }
endX = currentX || e.changedTouches[0].pageX;
if (self.isTouch) {
// the event target is outside the carousel OR carousel doens't include the related target
if ((!element.contains(e.target) || !element.contains(e.relatedTarget))
&& Math.abs(startX - endX) < 75) { // AND swipe distance is less than 75px
// when the above conditions are satisfied, no need to continue
return;
} // OR determine next index to slide to
if (currentX < startX) {
self.index += 1;
} else if (currentX > startX) {
self.index -= 1;
}
self.isTouch = false;
self.to(self.index); // do the slide
toggleCarouselTouchHandlers(self); // remove touch events handlers
}
}
// CAROUSEL PRIVATE METHODS
// ========================
function activateCarouselIndicator(self, pageIndex) { // indicators
const { indicators } = self;
Array.from(indicators).forEach((x) => removeClass(x, activeClass));
if (self.indicators[pageIndex]) addClass(indicators[pageIndex], activeClass);
}
function toggleCarouselTouchHandlers(self, add) {
const { element } = self;
const action = add ? addEventListener : removeEventListener;
element[action]('touchmove', carouselTouchMoveHandler, passiveHandler);
element[action]('touchend', carouselTouchEndHandler, passiveHandler);
}
function toggleCarouselHandlers(self, add) {
const {
element, options, slides, controls, indicator,
} = self;
const {
touch, pause, interval, keyboard,
} = options;
const action = add ? addEventListener : removeEventListener;
if (pause && interval) {
element[action]('mouseenter', carouselPauseHandler);
element[action]('mouseleave', carouselResumeHandler);
element[action]('touchstart', carouselPauseHandler, passiveHandler);
element[action]('touchend', carouselResumeHandler, passiveHandler);
}
if (touch && slides.length > 1) {
element[action]('touchstart', carouselTouchDownHandler, passiveHandler);
}
controls.forEach((arrow) => {
if (arrow) arrow[action]('click', carouselControlsHandler);
});
if (indicator) indicator[action]('click', carouselIndicatorHandler);
if (keyboard) window[action]('keydown', carouselKeyHandler);
}
function getActiveIndex(self) {
const { slides, element } = self;
return Array.from(slides)
.indexOf(element.getElementsByClassName(`${carouselItem} ${activeClass}`)[0]) || 0;
}
// CAROUSEL DEFINITION
// ===================
class Carousel extends BaseComponent {
constructor(target, config) {
super(carouselComponent, target, defaultCarouselOptions, config);
// bind
const self = this;
// additional properties
self.timer = null;
self.direction = 'left';
self.isPaused = false;
self.isAnimating = false;
self.index = 0;
self.timer = null;
self.isTouch = false;
// initialization element
const { element } = self;
// carousel elements
// a LIVE collection is prefferable
self.slides = element.getElementsByClassName(carouselItem);
const { slides } = self;
// invalidate when not enough items
// no need to go further
if (slides.length < 2) { return; }
self.controls = [
queryElement(`.${carouselControl}-prev`, element),
queryElement(`.${carouselControl}-next`, element),
];
// a LIVE collection is prefferable
self.indicator = queryElement('.carousel-indicators', element);
self.indicators = (self.indicator && self.indicator.querySelectorAll(`[${dataBsSlideTo}]`)) || [];
// set JavaScript and DATA API options
const { options } = self;
// don't use TRUE as interval, it's actually 0, use the default 5000ms better
self.options.interval = options.interval === true
? defaultCarouselOptions.interval
: options.interval;
// set first slide active if none
if (getActiveIndex(self) < 0) {
if (slides.length) addClass(slides[0], activeClass);
if (self.indicators.length) activateCarouselIndicator(self, 0);
}
// attach event handlers
toggleCarouselHandlers(self, 1);
// start to cycle if interval is set
if (options.interval) self.cycle();
}
// CAROUSEL PUBLIC METHODS
// =======================
cycle() {
const self = this;
const { isPaused, element, options } = self;
if (self.timer) {
clearInterval(self.timer);
self.timer = null;
}
if (isPaused) {
removeClass(element, pausedClass);
self.isPaused = !isPaused;
}
self.timer = setInterval(() => {
if (isElementInScrollRange(element)) {
self.index += 1;
self.to(self.index);
}
}, options.interval);
}
pause() {
const self = this;
const { element, options, isPaused } = self;
if (options.interval && !isPaused) {
clearInterval(self.timer);
self.timer = null;
addClass(element, pausedClass);
self.isPaused = !isPaused;
}
}
next() {
const self = this;
if (!self.isAnimating) { self.index += 1; self.to(self.index); }
}
prev() {
const self = this;
if (!self.isAnimating) { self.index -= 1; self.to(self.index); }
}
to(idx) {
const self = this;
const {
element, isAnimating, slides, options,
} = self;
const activeItem = getActiveIndex(self);
let next = idx;
// when controled via methods, make sure to check again
// first return if we're on the same item #227
if (isAnimating || activeItem === next) return;
// determine transition direction
if ((activeItem < next) || (activeItem === 0 && next === slides.length - 1)) {
self.direction = 'left'; // next
} else if ((activeItem > next) || (activeItem === slides.length - 1 && next === 0)) {
self.direction = 'right'; // prev
}
const { direction } = self;
// find the right next index
if (next < 0) { next = slides.length - 1; } else if (next >= slides.length) { next = 0; }
// orientation, class name, eventProperties
const orientation = direction === 'left' ? 'next' : 'prev';
const directionClass = direction === 'left' ? 'start' : 'end';
const eventProperties = {
relatedTarget: slides[next], direction, from: activeItem, to: next,
};
// update event properties
Object.keys(eventProperties).forEach((k) => {
carouselSlideEvent[k] = eventProperties[k];
carouselSlidEvent[k] = eventProperties[k];
});
// discontinue when prevented
element.dispatchEvent(carouselSlideEvent);
if (carouselSlideEvent.defaultPrevented) return;
// update index
self.index = next;
clearInterval(self.timer);
self.timer = null;
self.isAnimating = true;
activateCarouselIndicator(self, next);
if (getElementTransitionDuration(slides[next]) && hasClass(element, 'slide')) {
addClass(slides[next], `${carouselItem}-${orientation}`);
reflow(slides[next]);
addClass(slides[next], `${carouselItem}-${directionClass}`);
addClass(slides[activeItem], `${carouselItem}-${directionClass}`);
emulateTransitionEnd(slides[next], () => carouselTransitionEndHandler(self));
} else {
addClass(slides[next], activeClass);
removeClass(slides[activeItem], activeClass);
setTimeout(() => {
self.isAnimating = false;
// check for element, might have been disposed
if (element && options.interval && !hasClass(element, pausedClass)) {
self.cycle();
}
element.dispatchEvent(carouselSlidEvent);
}, 100);
}
}
dispose() {
const self = this;
const { slides } = self;
const itemClasses = ['start', 'end', 'prev', 'next'];
Array.from(slides).forEach((slide, idx) => {
if (hasClass(slide, activeClass)) activateCarouselIndicator(self, idx);
itemClasses.forEach((c) => removeClass(slide, `${carouselItem}-${c}`));
});
toggleCarouselHandlers(self);
clearInterval(self.timer);
super.dispose(carouselComponent);
}
}
Carousel.init = {
component: carouselComponent,
selector: carouselSelector,
constructor: Carousel,
};
const ariaExpanded = 'aria-expanded';
// collapse / tab
const collapsingClass = 'collapsing';
const dataBsTarget = 'data-bs-target';
const dataBsParent = 'data-bs-parent';
const dataBsContainer = 'data-bs-container';
function getTargetElement(element) {
return queryElement(element.getAttribute(dataBsTarget) || element.getAttribute('href'))
|| element.closest(element.getAttribute(dataBsParent))
|| queryElement(element.getAttribute(dataBsContainer));
}
/* Native JavaScript for Bootstrap 5 | Collapse
----------------------------------------------- */
// COLLAPSE GC
// ===========
const collapseString = 'collapse';
const collapseComponent = 'Collapse';
const collapseSelector = `.${collapseString}`;
const collapseToggleSelector = `[${dataBsToggle}="${collapseString}"]`;
// COLLAPSE CUSTOM EVENTS
// ======================
const showCollapseEvent = bootstrapCustomEvent(`show.bs.${collapseString}`);
const shownCollapseEvent = bootstrapCustomEvent(`shown.bs.${collapseString}`);
const hideCollapseEvent = bootstrapCustomEvent(`hide.bs.${collapseString}`);
const hiddenCollapseEvent = bootstrapCustomEvent(`hidden.bs.${collapseString}`);
// COLLAPSE PRIVATE METHODS
// ========================
function expandCollapse(self) {
const {
element, parent, triggers,
} = self;
element.dispatchEvent(showCollapseEvent);
if (showCollapseEvent.defaultPrevented) return;
self.isAnimating = true;
if (parent) parent.isAnimating = true;
addClass(element, collapsingClass);
removeClass(element, collapseString);
element.style.height = `${element.scrollHeight}px`;
emulateTransitionEnd(element, () => {
self.isAnimating = false;
if (parent) parent.isAnimating = false;
triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'true'));
removeClass(element, collapsingClass);
addClass(element, collapseString);
addClass(element, showClass);
element.style.height = '';
element.dispatchEvent(shownCollapseEvent);
});
}
function collapseContent(self) {
const {
element, parent, triggers,
} = self;
element.dispatchEvent(hideCollapseEvent);
if (hideCollapseEvent.defaultPrevented) return;
self.isAnimating = true;
if (parent) parent.isAnimating = true;
element.style.height = `${element.scrollHeight}px`;
removeClass(element, collapseString);
removeClass(element, showClass);
addClass(element, collapsingClass);
reflow(element);
element.style.height = '0px';
emulateTransitionEnd(element, () => {
self.isAnimating = false;
if (parent) parent.isAnimating = false;
triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'false'));
removeClass(element, collapsingClass);
addClass(element, collapseString);
element.style.height = '';
element.dispatchEvent(hiddenCollapseEvent);
});
}
function toggleCollapseHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
const { triggers } = self;
if (triggers.length) {
triggers.forEach((btn) => btn[action]('click', collapseClickHandler));
}
}
// COLLAPSE EVENT HANDLER
// ======================
function collapseClickHandler(e) {
const { target } = e;
const trigger = target.closest(collapseToggleSelector);
const element = getTargetElement(trigger);
const self = element && element[collapseComponent];
if (self) self.toggle(target);
// event target is anchor link #398
if (trigger && trigger.tagName === 'A') e.preventDefault();
}
// COLLAPSE DEFINITION
// ===================
class Collapse extends BaseComponent {
constructor(target, config) {
super(collapseComponent, target, { parent: null }, config);
// bind
const self = this;
// initialization element
const { element, options } = self;
// set triggering elements
self.triggers = Array.from(document.querySelectorAll(collapseToggleSelector))
.filter((btn) => getTargetElement(btn) === element);
// set parent accordion
self.parent = queryElement(options.parent);
const { parent } = self;
// set initial state
self.isAnimating = false;
if (parent) parent.isAnimating = false;
// add event listeners
toggleCollapseHandler(self, 1);
}
// COLLAPSE PUBLIC METHODS
// =======================
toggle(related) {
const self = this;
if (!hasClass(self.element, showClass)) self.show(related);
else self.hide(related);
}
hide() {
const self = this;
const { triggers, isAnimating } = self;
if (isAnimating) return;
collapseContent(self);
if (triggers.length) {
triggers.forEach((btn) => addClass(btn, `${collapseString}d`));
}
}
show() {
const self = this;
const {
element, parent, triggers, isAnimating,
} = self;
let activeCollapse;
let activeCollapseInstance;
if (parent) {
activeCollapse = Array.from(parent.querySelectorAll(`.${collapseString}.${showClass}`))
.find((i) => i[collapseComponent]);
activeCollapseInstance = activeCollapse && activeCollapse[collapseComponent];
}
if ((!parent || (parent && !parent.isAnimating)) && !isAnimating) {
if (activeCollapseInstance && activeCollapse !== element) {
collapseContent(activeCollapseInstance);
activeCollapseInstance.triggers.forEach((btn) => {
addClass(btn, `${collapseString}d`);
});
}
expandCollapse(self);
if (triggers.length) {
triggers.forEach((btn) => removeClass(btn, `${collapseString}d`));
}
}
}
dispose() {
const self = this;
const { parent } = self;
toggleCollapseHandler(self);
if (parent) delete parent.isAnimating;
super.dispose(collapseComponent);
}
}
Collapse.init = {
component: collapseComponent,
selector: collapseSelector,
constructor: Collapse,
};
const dropdownMenuClasses = ['dropdown', 'dropup', 'dropstart', 'dropend'];
const dropdownMenuClass = 'dropdown-menu';
function isEmptyAnchor(elem) {
const parentAnchor = elem.closest('A');
// anchor href starts with #
return elem && ((elem.href && elem.href.slice(-1) === '#')
// OR a child of an anchor with href starts with #
|| (parentAnchor && parentAnchor.href && parentAnchor.href.slice(-1) === '#'));
}
function setFocus(element) {
element.focus();
}
/* Native JavaScript for Bootstrap 5 | Dropdown
----------------------------------------------- */
// DROPDOWN PRIVATE GC
// ===================
const [dropdownString] = dropdownMenuClasses;
const dropdownComponent = 'Dropdown';
const dropdownSelector = `[${dataBsToggle}="${dropdownString}"]`;
// DROPDOWN PRIVATE GC
// ===================
const dropupString = dropdownMenuClasses[1];
const dropstartString = dropdownMenuClasses[2];
const dropendString = dropdownMenuClasses[3];
const dropdownMenuEndClass = `${dropdownMenuClass}-end`;
const hideMenuClass = ['d-block', 'invisible'];
const verticalClass = [dropdownString, dropupString];
const horizontalClass = [dropstartString, dropendString];
const defaultDropdownOptions = {
offset: 5, // [number] 5(px)
display: 'dynamic', // [dynamic|static]
};
// DROPDOWN CUSTOM EVENTS
// ========================
const showDropdownEvent = bootstrapCustomEvent(`show.bs.${dropdownString}`);
const shownDropdownEvent = bootstrapCustomEvent(`shown.bs.${dropdownString}`);
const hideDropdownEvent = bootstrapCustomEvent(`hide.bs.${dropdownString}`);
const hiddenDropdownEvent = bootstrapCustomEvent(`hidden.bs.${dropdownString}`);
// DROPDOWN PRIVATE METHODS
// ========================
function styleDropdown(self, show) {
const {
element, menu, originalClass, menuEnd, options,
} = self;
const { offset } = options;
const parent = element.parentElement;
// reset menu offset and position
const resetProps = ['margin', 'top', 'bottom', 'left', 'right'];
resetProps.forEach((p) => { menu.style[p] = ''; });
removeClass(parent, 'position-static');
if (!show) {
const menuEndNow = hasClass(menu, dropdownMenuEndClass);
parent.className = originalClass.join(' ');
if (menuEndNow && !menuEnd) removeClass(menu, dropdownMenuEndClass);
else if (!menuEndNow && menuEnd) addClass(menu, dropdownMenuEndClass);
return;
}
// set initial position class
// take into account .btn-group parent as .dropdown
let positionClass = dropdownMenuClasses.find((c) => originalClass.includes(c)) || dropdownString;
let dropdownMargin = {
dropdown: [offset, 0, 0],
dropup: [0, 0, offset],
dropstart: [-1, offset, 0],
dropend: [-1, 0, 0, offset],
};
const dropdownPosition = {
dropdown: { top: '100%' },
dropup: { top: 'auto', bottom: '100%' },
dropstart: { left: 'auto', right: '100%' },
dropend: { left: '100%', right: 'auto' },
menuEnd: { right: 0, left: 'auto' },
};
// force showing the menu to calculate its size
hideMenuClass.forEach((c) => addClass(menu, c));
const dropdownRegex = new RegExp(`\\b(${dropdownString}|${dropupString}|${dropstartString}|${dropendString})+`);
const elementDimensions = { w: element.offsetWidth, h: element.offsetHeight };
const menuDimensions = { w: menu.offsetWidth, h: menu.offsetHeight };
const HTML = document.documentElement;
const BD = document.body;
const windowWidth = (HTML.clientWidth || BD.clientWidth);
const windowHeight = (HTML.clientHeight || BD.clientHeight);
const targetBCR = element.getBoundingClientRect();
// dropdownMenuEnd && [ dropdown | dropup ]
const leftExceed = targetBCR.left + elementDimensions.w - menuDimensions.w < 0;
// dropstart
const leftFullExceed = targetBCR.left - menuDimensions.w < 0;
// !dropdownMenuEnd && [ dropdown | dropup ]
const rightExceed = targetBCR.left + menuDimensions.w >= windowWidth;
// dropend
const rightFullExceed = targetBCR.left + menuDimensions.w + elementDimensions.w >= windowWidth;
// dropstart | dropend
const bottomExceed = targetBCR.top + menuDimensions.h >= windowHeight;
// dropdown
const bottomFullExceed = targetBCR.top + menuDimensions.h + elementDimensions.h >= windowHeight;
// dropup
const topExceed = targetBCR.top - menuDimensions.h < 0;
// recompute position
if (horizontalClass.includes(positionClass) && leftFullExceed && rightFullExceed) {
positionClass = dropdownString;
}
if (horizontalClass.includes(positionClass) && bottomExceed) {
positionClass = dropupString;
}
if (positionClass === dropstartString && leftFullExceed && !bottomExceed) {
positionClass = dropendString;
}
if (positionClass === dropendString && rightFullExceed && !bottomExceed) {
positionClass = dropstartString;
}
if (positionClass === dropupString && topExceed && !bottomFullExceed) {
positionClass = dropdownString;
}
if (positionClass === dropdownString && bottomFullExceed && !topExceed) {
positionClass = dropupString;
}
// set spacing
dropdownMargin = dropdownMargin[positionClass];
menu.style.margin = `${dropdownMargin.map((x) => (x ? `${x}px` : x)).join(' ')}`;
Object.keys(dropdownPosition[positionClass]).forEach((position) => {
menu.style[position] = dropdownPosition[positionClass][position];
});
// update dropdown position class
if (!hasClass(parent, positionClass)) {
parent.className = parent.className.replace(dropdownRegex, positionClass);
}
// update dropdown / dropup to handle parent btn-group element
// as well as the dropdown-menu-end utility class
if (verticalClass.includes(positionClass)) {
if (!menuEnd && rightExceed) addClass(menu, dropdownMenuEndClass);
else if (menuEnd && leftExceed) removeClass(menu, dropdownMenuEndClass);
if (hasClass(menu, dropdownMenuEndClass)) {
Object.keys(dropdownPosition.menuEnd).forEach((p) => {
menu.style[p] = dropdownPosition.menuEnd[p];
});
}
}
// remove util classes from the menu, we have its size
hideMenuClass.forEach((c) => removeClass(menu, c));
}
function toggleDropdownDismiss(self) {
const action = self.open ? addEventListener : removeEventListener;
document[action]('click', dropdownDismissHandler);
document[action]('focus', dropdownDismissHandler);
document[action]('keydown', dropdownPreventScroll);
document[action]('keyup', dropdownKeyHandler);
if (self.options.display === 'dynamic') {
window[action]('scroll', dropdownLayoutHandler, passiveHandler);
window[action]('resize', dropdownLayoutHandler, passiveHandler);
}
}
function toggleDropdownHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
self.element[action]('click', dropdownClickHandler);
}
function getCurrentOpenDropdown() {
const currentParent = dropdownMenuClasses.concat('btn-group')
.map((c) => document.getElementsByClassName(`${c} ${showClass}`))
.find((x) => x.length);
if (currentParent && currentParent.length) {
return Array.from(currentParent[0].children).find((x) => x.hasAttribute(dataBsToggle));
}
return null;
}
// DROPDOWN EVENT HANDLERS
// =======================
function dropdownDismissHandler(e) {
const { target, type } = e;
if (!target.closest) return; // some weird FF bug #409
const element = getCurrentOpenDropdown();
const parent = element && element.parentNode;
const self = element && element[dropdownComponent];
const menu = self && self.menu;
const hasData = target.closest(dropdownSelector) !== null;
const isForm = parent && parent.contains(target)
&& (target.tagName === 'form' || target.closest('form') !== null);
if (type === 'click' && isEmptyAnchor(target)) {
e.preventDefault();
}
if (type === 'focus'
&& (target === element || target === menu || menu.contains(target))) {
return;
}
if (isForm || hasData) ; else if (self) {
self.hide(element);
}
}
function dropdownClickHandler(e) {
const element = this;
const self = element[dropdownComponent];
self.toggle(element);
if (isEmptyAnchor(e.target)) e.preventDefault();
}
function dropdownPreventScroll(e) {
if (e.which === 38 || e.which === 40) e.preventDefault();
}
function dropdownKeyHandler({ which }) {
const element = getCurrentOpenDropdown();
const self = element[dropdownComponent];
const { menu, menuItems, open } = self;
const activeItem = document.activeElement;
const isSameElement = activeItem === element;
const isInsideMenu = menu.contains(activeItem);
const isMenuItem = activeItem.parentNode === menu || activeItem.parentNode.parentNode === menu;
let idx = menuItems.indexOf(activeItem);
if (isMenuItem) { // navigate up | down
if (isSameElement) {
idx = 0;
} else if (which === 38) {
idx = idx > 1 ? idx - 1 : 0;
} else if (which === 40) {
idx = idx < menuItems.length - 1 ? idx + 1 : idx;
}
if (menuItems[idx]) setFocus(menuItems[idx]);
}
if (((menuItems.length && isMenuItem) // menu has items
|| (!menuItems.length && (isInsideMenu || isSameElement)) // menu might be a form
|| !isInsideMenu) // or the focused element is not in the menu at all
&& open && which === 27 // menu must be open
) {
self.toggle();
}
}
function dropdownLayoutHandler() {
const element = getCurrentOpenDropdown();
const self = element && element[dropdownComponent];
if (self && self.open) styleDropdown(self, 1);
}
// DROPDOWN DEFINITION
// ===================
class Dropdown extends BaseComponent {
constructor(target, config) {
super(dropdownComponent, target, defaultDropdownOptions, config);
// bind
const self = this;
// initialization element
const { element } = self;
// set targets
const parent = element.parentElement;
self.menu = queryElement(`.${dropdownMenuClass}`, parent);
const { menu } = self;
self.originalClass = Array.from(parent.classList);
// set original position
self.menuEnd = hasClass(menu, dropdownMenuEndClass);
self.menuItems = [];
Array.from(menu.children).forEach((child) => {
if (child.children.length && (child.children[0].tagName === 'A')) self.menuItems.push(child.children[0]);
if (child.tagName === 'A') self.menuItems.push(child);
});
// set initial state to closed
self.open = false;
// add event listener
toggleDropdownHandler(self, 1);
}
// DROPDOWN PUBLIC METHODS
// =======================
toggle(related) {
const self = this;
const { open } = self;
if (open) self.hide(related);
else self.show(related);
}
show(related) {
const self = this;
const currentParent = queryElement(dropdownMenuClasses.concat('btn-group').map((c) => `.${c}.${showClass}`).join(','));
const currentElement = currentParent && queryElement(dropdownSelector, currentParent);
if (currentElement) currentElement[dropdownComponent].hide();
const { element, menu, open } = self;
const parent = element.parentNode;
// update relatedTarget and dispatch
showDropdownEvent.relatedTarget = related || null;
parent.dispatchEvent(showDropdownEvent);
if (showDropdownEvent.defaultPrevented) return;
// change menu position
styleDropdown(self, 1);
addClass(menu, showClass);
addClass(parent, showClass);
element.setAttribute(ariaExpanded, true);
self.open = !open;
setTimeout(() => {
setFocus(menu.getElementsByTagName('INPUT')[0] || element); // focus the first input item | element
toggleDropdownDismiss(self);
shownDropdownEvent.relatedTarget = related || null;
parent.dispatchEvent(shownDropdownEvent);
}, 1);
}
hide(related) {
const self = this;
const { element, menu, open } = self;
const parent = element.parentNode;
hideDropdownEvent.relatedTarget = related || null;
parent.dispatchEvent(hideDropdownEvent);
if (hideDropdownEvent.defaultPrevented) return;
removeClass(menu, showClass);
removeClass(parent, showClass);
// revert to original position
styleDropdown(self);
element.setAttribute(ariaExpanded, false);
self.open = !open;
setFocus(element);
// only re-attach handler if the instance is not disposed
setTimeout(() => toggleDropdownDismiss(self), 1);
// update relatedTarget and dispatch
hiddenDropdownEvent.relatedTarget = related || null;
parent.dispatchEvent(hiddenDropdownEvent);
}
dispose() {
const self = this;
const { element } = self;
if (hasClass(element.parentNode, showClass) && self.open) self.hide();
toggleDropdownHandler(self);
super.dispose(dropdownComponent);
}
}
Dropdown.init = {
component: dropdownComponent,
selector: dropdownSelector,
constructor: Dropdown,
};
const ariaHidden = 'aria-hidden';
const ariaModal = 'aria-modal';
const fixedTopClass = 'fixed-top';
const fixedBottomClass = 'fixed-bottom';
const stickyTopClass = 'sticky-top';
const fixedItems = Array.from(document.getElementsByClassName(fixedTopClass))
.concat(Array.from(document.getElementsByClassName(fixedBottomClass)))
.concat(Array.from(document.getElementsByClassName(stickyTopClass)))
.concat(Array.from(document.getElementsByClassName('is-fixed')));
function resetScrollbar() {
const bd = document.body;
bd.style.paddingRight = '';
bd.style.overflow = '';
if (fixedItems.length) {
fixedItems.forEach((fixed) => {
fixed.style.paddingRight = '';
fixed.style.marginRight = '';
});
}
}
function measureScrollbar() {
const windowWidth = document.documentElement.clientWidth;
return Math.abs(window.innerWidth - windowWidth);
}
function setScrollbar(scrollbarWidth, overflow) {
const bd = document.body;
const bdStyle = getComputedStyle(bd);
const bodyPad = parseInt(bdStyle.paddingRight, 10);
const isOpen = bdStyle.overflow === 'hidden';
const sbWidth = isOpen && bodyPad ? 0 : scrollbarWidth;
if (overflow) {
bd.style.overflow = 'hidden';
bd.style.paddingRight = `${bodyPad + sbWidth}px`;
if (fixedItems.length) {
fixedItems.forEach((fixed) => {
const isSticky = hasClass(fixed, stickyTopClass);
const itemPadValue = getComputedStyle(fixed).paddingRight;
fixed.style.paddingRight = `${parseInt(itemPadValue, 10) + sbWidth}px`;
if (isSticky) {
const itemMValue = getComputedStyle(fixed).marginRight;
fixed.style.marginRight = `${parseInt(itemMValue, 10) - sbWidth}px`;
}
});
}
}
}
const modalBackdropClass = 'modal-backdrop';
const offcanvasBackdropClass = 'offcanvas-backdrop';
const modalActiveSelector = `.modal.${showClass}`;
const offcanvasActiveSelector = `.offcanvas.${showClass}`;
const overlay = document.createElement('div');
function getCurrentOpen() {
return queryElement(`${modalActiveSelector},${offcanvasActiveSelector}`);
}
function toggleOverlayType(isModal) {
const targetClass = isModal ? modalBackdropClass : offcanvasBackdropClass;
[modalBackdropClass, offcanvasBackdropClass].forEach((c) => {
removeClass(overlay, c);
});
addClass(overlay, targetClass);
}
function appendOverlay(hasFade, isModal) {
toggleOverlayType(isModal);
document.body.appendChild(overlay);
if (hasFade) addClass(overlay, fadeClass);
}
function showOverlay() {
addClass(overlay, showClass);
reflow(overlay);
}
function hideOverlay() {
removeClass(overlay, showClass);
}
function removeOverlay() {
const bd = document.body;
const currentOpen = getCurrentOpen();
if (!currentOpen) {
removeClass(overlay, fadeClass);
bd.removeChild(overlay);
resetScrollbar();
}
}
function isVisible(element) {
return getComputedStyle(element).visibility !== 'hidden'
&& element.offsetParent !== null;
}
/* Native JavaScript for Bootstrap 5 | Modal
-------------------------------------------- */
// MODAL PRIVATE GC
// ================
const modalString = 'modal';
const modalComponent = 'Modal';
const modalSelector = `.${modalString}`;
const modalToggleSelector = `[${dataBsToggle}="${modalString}"]`;
const modalDismissSelector = `[${dataBsDismiss}="${modalString}"]`;
const modalStaticClass = `${modalString}-static`;
const modalDefaultOptions = {
backdrop: true, // boolean|string
keyboard: true, // boolean
};
// MODAL CUSTOM EVENTS
// ===================
const showModalEvent = bootstrapCustomEvent(`show.bs.${modalString}`);
const shownModalEvent = bootstrapCustomEvent(`shown.bs.${modalString}`);
const hideModalEvent = bootstrapCustomEvent(`hide.bs.${modalString}`);
const hiddenModalEvent = bootstrapCustomEvent(`hidden.bs.${modalString}`);
// MODAL PRIVATE METHODS
// =====================
function setModalScrollbar(self) {
const { element, scrollbarWidth } = self;
const bd = document.body;
const html = document.documentElement;
const bodyOverflow = html.clientHeight !== html.scrollHeight
|| bd.clientHeight !== bd.scrollHeight;
const modalOverflow = element.clientHeight !== element.scrollHeight;
if (!modalOverflow && scrollbarWidth) {
element.style.paddingRight = `${scrollbarWidth}px`;
}
setScrollbar(scrollbarWidth, (modalOverflow || bodyOverflow));
}
function toggleModalDismiss(self, add) {
const action = add ? addEventListener : removeEventListener;
window[action]('resize', self.update, passiveHandler);
self.element[action]('click', modalDismissHandler);
document[action]('keydown', modalKeyHandler);
}
function toggleModalHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
const { triggers } = self;
if (triggers.length) {
triggers.forEach((btn) => btn[action]('click', modalClickHandler));
}
}
function afterModalHide(self) {
const { triggers, options } = self;
if (!getCurrentOpen()) {
if (options.backdrop) removeOverlay();
resetScrollbar();
}
self.element.style.paddingRight = '';
self.isAnimating = false;
if (triggers.length) {
const visibleTrigger = triggers.find((x) => isVisible(x));
if (visibleTrigger) setFocus(visibleTrigger);
}
}
function afterModalShow(self) {
const { element, relatedTarget } = self;
setFocus(element);
self.isAnimating = false;
toggleModalDismiss(self, 1);
shownModalEvent.relatedTarget = relatedTarget;
element.dispatchEvent(shownModalEvent);
}
function beforeModalShow(self) {
const { element, hasFade } = self;
element.style.display = 'block';
setModalScrollbar(self);
if (!getCurrentOpen()) {
document.body.style.overflow = 'hidden';
}
addClass(element, showClass);
element.removeAttribute(ariaHidden);
element.setAttribute(ariaModal, true);
if (hasFade) emulateTransitionEnd(element, () => afterModalShow(self));
else afterModalShow(self);
}
function beforeModalHide(self, force) {
const {
element, options, relatedTarget, hasFade,
} = self;
element.style.display = '';
// force can also be the transitionEvent object, we wanna make sure it's not
// call is not forced and overlay is visible
if (options.backdrop && !force && hasFade && hasClass(overlay, showClass)
&& !getCurrentOpen()) { // AND no modal is visible
hideOverlay();
emulateTransitionEnd(overlay, () => afterModalHide(self));
} else {
afterModalHide(self);
}
toggleModalDismiss(self);
hiddenModalEvent.relatedTarget = relatedTarget;
element.dispatchEvent(hiddenModalEvent);
}
// MODAL EVENT HANDLERS
// ====================
function modalClickHandler(e) {
const { target } = e;
const trigger = target.closest(modalToggleSelector);
const element = getTargetElement(trigger);
const self = element && element[modalComponent];
if (trigger.tagName === 'A') e.preventDefault();
if (self.isAnimating) return;
self.relatedTarget = trigger;
self.toggle();
}
function modalKeyHandler({ which }) {
const element = queryElement(modalActiveSelector);
const self = element[modalComponent];
const { options, isAnimating } = self;
if (!isAnimating // modal has no animations running
&& options.keyboard && which === 27 // the keyboard option is enabled and the key is 27
&& hasClass(element, showClass)) { // the modal is not visible
self.relatedTarget = null;
self.hide();
}
}
function modalDismissHandler(e) {
const element = this;
const self = element[modalComponent];
if (self.isAnimating) return;
const { options, isStatic, modalDialog } = self;
const { backdrop } = options;
const { target } = e;
const selectedText = document.getSelection().toString().length;
const targetInsideDialog = modalDialog.contains(target);
const dismiss = target.closest(modalDismissSelector);
if (isStatic && !targetInsideDialog) {
addClass(element, modalStaticClass);
self.isAnimating = true;
emulateTransitionEnd(modalDialog, () => staticTransitionEnd(self));
} else if (dismiss || (!selectedText && !isStatic && !targetInsideDialog && backdrop)) {
self.relatedTarget = dismiss || null;
self.hide();
e.preventDefault();
}
}
function staticTransitionEnd(self) {
const duration = getElementTransitionDuration(self.modalDialog) + 17;
removeClass(self.element, modalStaticClass);
// user must wait for zoom out transition
setTimeout(() => { self.isAnimating = false; }, duration);
}
// MODAL DEFINITION
// ================
class Modal extends BaseComponent {
constructor(target, config) {
super(modalComponent, target, modalDefaultOptions, config);
// bind
const self = this;
// the modal
const { element } = self;
// the modal-dialog
self.modalDialog = queryElement(`.${modalString}-dialog`, element);
// modal can have multiple triggering elements
self.triggers = Array.from(document.querySelectorAll(modalToggleSelector))
.filter((btn) => getTargetElement(btn) === element);
// additional internals
self.isStatic = self.options.backdrop === 'static';
self.hasFade = hasClass(element, fadeClass);
self.isAnimating = false;
self.scrollbarWidth = measureScrollbar();
self.relatedTarget = null;
// attach event listeners
toggleModalHandler(self, 1);
// bind
self.update = self.update.bind(self);
}
// MODAL PUBLIC METHODS
// ====================
toggle() {
const self = this;
if (hasClass(self.element, showClass)) self.hide();
else self.show();
}
show() {
const self = this;
const {
element, options, isAnimating, hasFade, relatedTarget,
} = self;
const { backdrop } = options;
let overlayDelay = 0;
if (hasClass(element, showClass) && !isAnimating) return;
showModalEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(showModalEvent);
if (showModalEvent.defaultPrevented) return;
// we elegantly hide any opened modal/offcanvas
const currentOpen = getCurrentOpen();
if (currentOpen && currentOpen !== element) {
const that = currentOpen[modalComponent]
? currentOpen[modalComponent]
: currentOpen.Offcanvas;
that.hide();
}
self.isAnimating = true;
if (backdrop) {
if (!currentOpen && !hasClass(overlay, showClass)) {
appendOverlay(hasFade, 1);
} else {
toggleOverlayType(1);
}
overlayDelay = getElementTransitionDuration(overlay);
if (!hasClass(overlay, showClass)) showOverlay();
setTimeout(() => beforeModalShow(self), overlayDelay);
} else {
beforeModalShow(self);
if (currentOpen && hasClass(overlay, showClass)) {
hideOverlay();
}
}
}
hide(force) {
const self = this;
const {
element, isAnimating, hasFade, relatedTarget,
} = self;
if (!hasClass(element, showClass) && !isAnimating) return;
hideModalEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(hideModalEvent);
if (hideModalEvent.defaultPrevented) return;
self.isAnimating = true;
removeClass(element, showClass);
element.setAttribute(ariaHidden, true);
element.removeAttribute(ariaModal);
if (hasFade && force !== 1) {
emulateTransitionEnd(element, () => beforeModalHide(self));
} else {
beforeModalHide(self, force);
}
}
update() {
const self = this;
if (hasClass(self.element, showClass)) setModalScrollbar(self);
}
dispose() {
const self = this;
self.hide(1); // forced call
toggleModalHandler(self);
super.dispose(modalComponent);
}
}
Modal.init = {
component: modalComponent,
selector: modalSelector,
constructor: Modal,
};
/* Native JavaScript for Bootstrap 5 | OffCanvas
------------------------------------------------ */
// OFFCANVAS PRIVATE GC
// ====================
const offcanvasString = 'offcanvas';
const offcanvasComponent = 'Offcanvas';
const OffcanvasSelector = `.${offcanvasString}`;
const offcanvasToggleSelector = `[${dataBsToggle}="${offcanvasString}"]`;
const offcanvasDismissSelector = `[${dataBsDismiss}="${offcanvasString}"]`;
const offcanvasTogglingClass = `${offcanvasString}-toggling`;
const offcanvasDefaultOptions = {
backdrop: true, // boolean
keyboard: true, // boolean
scroll: false, // boolean
};
// OFFCANVAS CUSTOM EVENTS
// =======================
const showOffcanvasEvent = bootstrapCustomEvent(`show.bs.${offcanvasString}`);
const shownOffcanvasEvent = bootstrapCustomEvent(`shown.bs.${offcanvasString}`);
const hideOffcanvasEvent = bootstrapCustomEvent(`hide.bs.${offcanvasString}`);
const hiddenOffcanvasEvent = bootstrapCustomEvent(`hidden.bs.${offcanvasString}`);
// OFFCANVAS PRIVATE METHODS
// =========================
function setOffCanvasScrollbar(self) {
const bd = document.body;
const html = document.documentElement;
const bodyOverflow = html.clientHeight !== html.scrollHeight
|| bd.clientHeight !== bd.scrollHeight;
setScrollbar(self.scrollbarWidth, bodyOverflow);
}
function toggleOffcanvasEvents(self, add) {
const action = add ? addEventListener : removeEventListener;
self.triggers.forEach((btn) => btn[action]('click', offcanvasTriggerHandler));
}
function toggleOffCanvasDismiss(add) {
const action = add ? addEventListener : removeEventListener;
document[action]('keydown', offcanvasKeyDismissHandler);
document[action]('click', offcanvasDismissHandler);
}
function beforeOffcanvasShow(self) {
const { element, options } = self;
if (!options.scroll) {
document.body.style.overflow = 'hidden';
setOffCanvasScrollbar(self);
}
addClass(element, offcanvasTogglingClass);
addClass(element, showClass);
element.style.visibility = 'visible';
emulateTransitionEnd(element, () => showOffcanvasComplete(self));
}
function beforeOffcanvasHide(self) {
const { element, options } = self;
const currentOpen = getCurrentOpen();
element.blur();
if (!currentOpen && options.backdrop && hasClass(overlay, showClass)) {
hideOverlay();
emulateTransitionEnd(overlay, () => hideOffcanvasComplete(self));
} else hideOffcanvasComplete(self);
}
// OFFCANVAS EVENT HANDLERS
// ========================
function offcanvasTriggerHandler(e) {
const trigger = this.closest(offcanvasToggleSelector);
const element = getTargetElement(trigger);
const self = element && element[offcanvasComponent];
if (trigger.tagName === 'A') e.preventDefault();
if (self) {
self.relatedTarget = trigger;
self.toggle();
}
}
function offcanvasDismissHandler(e) {
const element = queryElement(offcanvasActiveSelector);
if (!element) return;
const offCanvasDismiss = queryElement(offcanvasDismissSelector, element);
const self = element[offcanvasComponent];
if (!self) return;
const { options, triggers } = self;
const { target } = e;
const trigger = target.closest(offcanvasToggleSelector);
if (trigger && trigger.tagName === 'A') e.preventDefault();
if ((!element.contains(target) && options.backdrop
&& (!trigger || (trigger && !triggers.includes(trigger))))
|| offCanvasDismiss.contains(target)) {
self.relatedTarget = target === offCanvasDismiss ? offCanvasDismiss : null;
self.hide();
}
}
function offcanvasKeyDismissHandler({ which }) {
const element = queryElement(offcanvasActiveSelector);
if (!element) return;
const self = element[offcanvasComponent];
if (self && self.options.keyboard && which === 27) {
self.relatedTarget = null;
self.hide();
}
}
function showOffcanvasComplete(self) {
const { element, triggers, relatedTarget } = self;
removeClass(element, offcanvasTogglingClass);
element.removeAttribute(ariaHidden);
element.setAttribute(ariaModal, true);
element.setAttribute('role', 'dialog');
self.isAnimating = false;
if (triggers.length) {
triggers.forEach((btn) => btn.setAttribute(ariaExpanded, true));
}
shownOffcanvasEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(shownOffcanvasEvent);
toggleOffCanvasDismiss(1);
setFocus(element);
}
function hideOffcanvasComplete(self) {
const {
element, options, relatedTarget, triggers,
} = self;
const currentOpen = getCurrentOpen();
element.setAttribute(ariaHidden, true);
element.removeAttribute(ariaModal);
element.removeAttribute('role');
element.style.visibility = '';
self.isAnimating = false;
if (triggers.length) {
triggers.forEach((btn) => btn.setAttribute(ariaExpanded, false));
const visibleTrigger = triggers.find((x) => isVisible(x));
if (visibleTrigger) setFocus(visibleTrigger);
}
// handle new offcanvas showing up
if (!currentOpen) {
if (options.backdrop) removeOverlay();
if (!options.scroll) {
resetScrollbar();
}
}
hiddenOffcanvasEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(hiddenOffcanvasEvent);
removeClass(element, offcanvasTogglingClass);
toggleOffCanvasDismiss();
}
// OFFCANVAS DEFINITION
// ====================
class Offcanvas extends BaseComponent {
constructor(target, config) {
super(offcanvasComponent, target, offcanvasDefaultOptions, config);
const self = this;
// instance element
const { element } = self;
// all the triggering buttons
self.triggers = Array.from(document.querySelectorAll(offcanvasToggleSelector))
.filter((btn) => getTargetElement(btn) === element);
// additional instance property
self.isAnimating = false;
self.scrollbarWidth = measureScrollbar();
// attach event listeners
toggleOffcanvasEvents(self, 1);
}
// OFFCANVAS PUBLIC METHODS
// ========================
toggle() {
const self = this;
if (hasClass(self.element, showClass)) self.hide();
else self.show();
}
show() {
const self = this[offcanvasComponent] ? this[offcanvasComponent] : this;
const {
element, options, isAnimating, relatedTarget,
} = self;
let overlayDelay = 0;
if (hasClass(element, showClass) || isAnimating) return;
showOffcanvasEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(showOffcanvasEvent);
if (showOffcanvasEvent.defaultPrevented) return;
// we elegantly hide any opened modal/offcanvas
const currentOpen = getCurrentOpen();
if (currentOpen && currentOpen !== element) {
const that = currentOpen[offcanvasComponent]
? currentOpen[offcanvasComponent]
: currentOpen.Modal;
that.hide();
}
self.isAnimating = true;
if (options.backdrop) {
if (!currentOpen) {
appendOverlay(1);
} else {
toggleOverlayType();
}
overlayDelay = getElementTransitionDuration(overlay);
if (!hasClass(overlay, showClass)) showOverlay();
setTimeout(() => beforeOffcanvasShow(self), overlayDelay);
} else {
beforeOffcanvasShow(self);
if (currentOpen && hasClass(overlay, showClass)) {
hideOverlay();
}
}
}
hide(force) {
const self = this;
const { element, isAnimating, relatedTarget } = self;
if (!hasClass(element, showClass) || isAnimating) return;
hideOffcanvasEvent.relatedTarget = relatedTarget || null;
element.dispatchEvent(hideOffcanvasEvent);
if (hideOffcanvasEvent.defaultPrevented) return;
self.isAnimating = true;
addClass(element, offcanvasTogglingClass);
removeClass(element, showClass);
if (!force) {
emulateTransitionEnd(element, () => beforeOffcanvasHide(self));
} else beforeOffcanvasHide(self);
}
dispose() {
const self = this;
self.hide(1);
toggleOffcanvasEvents(self);
super.dispose(offcanvasComponent);
}
}
Offcanvas.init = {
component: offcanvasComponent,
selector: OffcanvasSelector,
constructor: Offcanvas,
};
const ariaDescribedBy = 'aria-describedby';
var tipClassPositions = {
top: 'top', bottom: 'bottom', left: 'start', right: 'end',
};
function isVisibleTip(tip, container) {
return container.contains(tip);
}
function isMedia(element) {
return [SVGElement, HTMLImageElement, HTMLVideoElement]
.some((mediaType) => element instanceof mediaType);
}
function closestRelative(element) {
let retval = null;
let el = element;
while (el !== document.body) {
el = el.parentElement;
if (getComputedStyle(el).position === 'relative') {
retval = el;
break;
}
}
return retval;
}
// both popovers and tooltips (this, event)
function styleTip(self, e) {
const tipClasses = /\b(top|bottom|start|end)+/;
const tip = self.tooltip || self.popover;
// reset tip style
tip.style.top = '';
tip.style.left = '';
tip.style.right = '';
// continue with metrics
const isPopover = !!self.popover;
let tipDimensions = { w: tip.offsetWidth, h: tip.offsetHeight };
const windowWidth = (document.documentElement.clientWidth || document.body.clientWidth);
const windowHeight = (document.documentElement.clientHeight || document.body.clientHeight);
const { element, options, arrow } = self;
let { container, placement } = options;
let parentIsBody = container === document.body;
const targetPosition = getComputedStyle(element).position;
const parentPosition = getComputedStyle(container).position;
const staticParent = !parentIsBody && parentPosition === 'static';
let relativeParent = !parentIsBody && parentPosition === 'relative';
const relContainer = staticParent && closestRelative(container);
// static containers should refer to another relative container or the body
container = relContainer || container;
relativeParent = staticParent && relContainer ? 1 : relativeParent;
parentIsBody = container === document.body;
const parentRect = container.getBoundingClientRect();
const leftBoundry = relativeParent ? parentRect.left : 0;
const rightBoundry = relativeParent ? parentRect.right : windowWidth;
// this case should not be possible
// absoluteParent = !parentIsBody && parentPosition === 'absolute',
// this case requires a container with placement: relative
const absoluteTarget = targetPosition === 'absolute';
const targetRect = element.getBoundingClientRect();
const scroll = parentIsBody
? { x: window.pageXOffset, y: window.pageYOffset }
: { x: container.scrollLeft, y: container.scrollTop };
const elemDimensions = { w: element.offsetWidth, h: element.offsetHeight };
const top = relativeParent ? element.offsetTop : targetRect.top;
const left = relativeParent ? element.offsetLeft : targetRect.left;
// reset arrow style
arrow.style.top = '';
arrow.style.left = '';
arrow.style.right = '';
let topPosition;
let leftPosition;
let rightPosition;
let arrowTop;
let arrowLeft;
let arrowRight;
// check placement
let topExceed = targetRect.top - tipDimensions.h < 0;
let bottomExceed = targetRect.top + tipDimensions.h + elemDimensions.h >= windowHeight;
let leftExceed = targetRect.left - tipDimensions.w < leftBoundry;
let rightExceed = targetRect.left + tipDimensions.w + elemDimensions.w >= rightBoundry;
topExceed = ['left', 'right'].includes(placement)
? targetRect.top + elemDimensions.h / 2 - tipDimensions.h / 2 < 0
: topExceed;
bottomExceed = ['left', 'right'].includes(placement)
? targetRect.top + tipDimensions.h / 2 + elemDimensions.h / 2 >= windowHeight
: bottomExceed;
leftExceed = ['top', 'bottom'].includes(placement)
? targetRect.left + elemDimensions.w / 2 - tipDimensions.w / 2 < leftBoundry
: leftExceed;
rightExceed = ['top', 'bottom'].includes(placement)
? targetRect.left + tipDimensions.w / 2 + elemDimensions.w / 2 >= rightBoundry
: rightExceed;
// recompute placement
// first, when both left and right limits are exceeded, we fall back to top|bottom
placement = (['left', 'right'].includes(placement)) && leftExceed && rightExceed ? 'top' : placement;
placement = placement === 'top' && topExceed ? 'bottom' : placement;
placement = placement === 'bottom' && bottomExceed ? 'top' : placement;
placement = placement === 'left' && leftExceed ? 'right' : placement;
placement = placement === 'right' && rightExceed ? 'left' : placement;
// update tooltip/popover class
if (!tip.className.includes(placement)) {
tip.className = tip.className.replace(tipClasses, tipClassPositions[placement]);
}
// if position has changed, update tip dimensions
tipDimensions = { w: tip.offsetWidth, h: tip.offsetHeight };
// we check the computed width & height and update here
const arrowWidth = arrow.offsetWidth || 0;
const arrowHeight = arrow.offsetHeight || 0;
const arrowAdjust = arrowWidth / 2;
// compute tooltip / popover coordinates
if (['left', 'right'].includes(placement)) { // secondary|side positions
if (placement === 'left') { // LEFT
leftPosition = left + scroll.x - tipDimensions.w - (isPopover ? arrowWidth : 0);
} else { // RIGHT
leftPosition = left + scroll.x + elemDimensions.w + (isPopover ? arrowWidth : 0);
}
// adjust top and arrow
if (topExceed) {
topPosition = top + scroll.y;
arrowTop = elemDimensions.h / 2 - arrowWidth;
} else if (bottomExceed) {
topPosition = top + scroll.y - tipDimensions.h + elemDimensions.h;
arrowTop = tipDimensions.h - elemDimensions.h / 2 - arrowWidth;
} else {
topPosition = top + scroll.y - tipDimensions.h / 2 + elemDimensions.h / 2;
arrowTop = tipDimensions.h / 2 - arrowHeight / 2;
}
} else if (['top', 'bottom'].includes(placement)) {
if (e && isMedia(element)) {
const eX = !relativeParent ? e.pageX : e.layerX + (absoluteTarget ? element.offsetLeft : 0);
const eY = !relativeParent ? e.pageY : e.layerY + (absoluteTarget ? element.offsetTop : 0);
if (placement === 'top') {
topPosition = eY - tipDimensions.h - (isPopover ? arrowWidth : arrowHeight);
} else {
topPosition = eY + arrowHeight;
}
// adjust left | right and also the arrow
if (e.clientX - tipDimensions.w / 2 < leftBoundry) { // when exceeds left
leftPosition = 0;
arrowLeft = eX - arrowAdjust;
} else if (e.clientX + tipDimensions.w * 0.51 >= rightBoundry) { // when exceeds right
leftPosition = 'auto';
rightPosition = 0;
arrowLeft = tipDimensions.w - (rightBoundry - eX) - arrowAdjust;
} else { // normal top/bottom
leftPosition = eX - tipDimensions.w / 2;
arrowLeft = tipDimensions.w / 2 - arrowAdjust;
}
} else {
if (placement === 'top') {
topPosition = top + scroll.y - tipDimensions.h - (isPopover ? arrowHeight : 0);
} else { // BOTTOM
topPosition = top + scroll.y + elemDimensions.h + (isPopover ? arrowHeight : 0);
}
// adjust left | right and also the arrow
if (leftExceed) {
leftPosition = 0;
arrowLeft = left + elemDimensions.w / 2 - arrowAdjust;
} else if (rightExceed) {
leftPosition = 'auto';
rightPosition = 0;
arrowRight = elemDimensions.w / 2 + (parentRect.right - targetRect.right) - arrowAdjust;
} else {
leftPosition = left + scroll.x - tipDimensions.w / 2 + elemDimensions.w / 2;
arrowLeft = tipDimensions.w / 2 - arrowAdjust;
}
}
}
// apply style to tooltip/popover and its arrow
tip.style.top = `${topPosition}px`;
tip.style.left = leftPosition === 'auto' ? leftPosition : `${leftPosition}px`;
tip.style.right = rightPosition !== undefined ? `${rightPosition}px` : '';
// update arrow placement or clear side
if (arrowTop !== undefined) {
arrow.style.top = `${arrowTop}px`;
}
if (arrowLeft !== undefined) {
arrow.style.left = `${arrowLeft}px`;
} else if (arrowRight !== undefined) {
arrow.style.right = `${arrowRight}px`;
}
}
let bsnUID = 1;
// popover, tooltip, scrollspy need a unique id
function getUID(element, key) {
bsnUID += 1;
return element[key] || bsnUID;
}
function getTipContainer(element) {
// maybe the element is inside a modal
const modal = element.closest('.modal');
// OR maybe the element is inside a fixed navbar
const navbarFixed = element.closest(`.${fixedTopClass},.${fixedBottomClass}`);
// set default container option appropriate for the context
return modal || navbarFixed || document.body;
}
/* Native JavaScript for Bootstrap 5 | Popover
---------------------------------------------- */
// POPOVER PRIVATE GC
// ==================
const popoverString = 'popover';
const popoverComponent = 'Popover';
const popoverSelector = `[${dataBsToggle}="${popoverString}"],[data-tip="${popoverString}"]`;
const popoverDefaultOptions = {
template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>', // string
title: null, // string
content: null, // string
sanitizeFn: null, // function
customClass: null, // string
dismissible: false, // boolean
animation: true, // boolean
trigger: 'hover', // string
placement: 'top', // string
delay: 200, // number
};
// POPOVER PRIVATE GC
// ==================
const appleBrands = /(iPhone|iPod|iPad)/;
const isIphone = navigator.userAgentData
? navigator.userAgentData.brands.some((x) => appleBrands.test(x.brand))
: appleBrands.test(navigator.userAgent);
// popoverArrowClass = `${popoverString}-arrow`,
const popoverHeaderClass = `${popoverString}-header`;
const popoverBodyClass = `${popoverString}-body`;
// close btn for dissmissible popover
let popoverCloseButton = '<button type="button" class="btn-close"></button>';
// POPOVER CUSTOM EVENTS
// =====================
const showPopoverEvent = bootstrapCustomEvent(`show.bs.${popoverString}`);
const shownPopoverEvent = bootstrapCustomEvent(`shown.bs.${popoverString}`);
const hidePopoverEvent = bootstrapCustomEvent(`hide.bs.${popoverString}`);
const hiddenPopoverEvent = bootstrapCustomEvent(`hidden.bs.${popoverString}`);
// POPOVER EVENT HANDLERS
// ======================
function popoverForceFocus() {
setFocus(this);
}
function popoverTouchHandler({ target }) {
const self = this;
const { popover, element } = self;
if ((popover && popover.contains(target)) // popover includes touch target
|| target === element // OR touch target is element
|| element.contains(target)) ; else {
self.hide();
}
}
// POPOVER PRIVATE METHODS
// =======================
function createPopover(self) {
const { id, options } = self;
const {
animation, customClass, sanitizeFn, placement, dismissible,
} = options;
let { title, content, template } = options;
// set initial popover class
const placementClass = `bs-${popoverString}-${tipClassPositions[placement]}`;
// fixing #233
title = title ? title.trim() : null;
content = content ? content.trim() : null;
// sanitize title && content
if (sanitizeFn) {
title = title ? sanitizeFn(title) : null;
content = content ? sanitizeFn(content) : null;
template = template ? sanitizeFn(template) : null;
popoverCloseButton = sanitizeFn(popoverCloseButton);
}
self.popover = document.createElement('div');
const { popover } = self;
// set id and aria-describedby
popover.setAttribute('id', id);
popover.setAttribute('role', 'tooltip');
// load template
const popoverTemplate = document.createElement('div');
popoverTemplate.innerHTML = template.trim();
popover.className = popoverTemplate.firstChild.className;
popover.innerHTML = popoverTemplate.firstChild.innerHTML;
const popoverHeader = queryElement(`.${popoverHeaderClass}`, popover);
const popoverBody = queryElement(`.${popoverBodyClass}`, popover);
// set arrow
self.arrow = queryElement(`.${popoverString}-arrow`, popover);
// set dismissible button
if (dismissible) {
title = title ? title + popoverCloseButton : title;
content = title === null ? +popoverCloseButton : content;
}
// fill the template with content from data attributes
if (title && popoverHeader) popoverHeader.innerHTML = title.trim();
if (content && popoverBody) popoverBody.innerHTML = content.trim();
// set popover animation and placement
if (!hasClass(popover, popoverString)) addClass(popover, popoverString);
if (animation && !hasClass(popover, fadeClass)) addClass(popover, fadeClass);
if (customClass && !hasClass(popover, customClass)) {
addClass(popover, customClass);
}
if (!hasClass(popover, placementClass)) addClass(popover, placementClass);
}
function removePopover(self) {
const { element, popover, options } = self;
element.removeAttribute(ariaDescribedBy);
options.container.removeChild(popover);
self.timer = null;
}
function togglePopoverHandlers(self, add) {
const action = add ? addEventListener : removeEventListener;
const { element, options } = self;
const { trigger, dismissible } = options;
self.enabled = !!add;
if (trigger === 'hover') {
element[action]('mousedown', self.show);
element[action]('mouseenter', self.show);
if (isMedia(element)) element[action]('mousemove', self.update, passiveHandler);
if (!dismissible) element[action]('mouseleave', self.hide);
} else if (trigger === 'click') {
element[action](trigger, self.toggle);
} else if (trigger === 'focus') {
if (isIphone) element[action]('click', popoverForceFocus);
element[action]('focusin', self.show);
}
}
function dismissHandlerToggle(self, add) {
const action = add ? addEventListener : removeEventListener;
const { options, element, popover } = self;
const { trigger, dismissible } = options;
if (dismissible) {
const [btnClose] = popover.getElementsByClassName('btn-close');
if (btnClose) btnClose[action]('click', self.hide);
} else {
if (trigger === 'focus') element[action]('focusout', self.hide);
if (trigger === 'hover') document[action]('touchstart', popoverTouchHandler, passiveHandler);
}
if (!isMedia(element)) {
window[action]('scroll', self.update, passiveHandler);
window[action]('resize', self.update, passiveHandler);
}
}
function popoverShowTrigger(self) {
dismissHandlerToggle(self, 1);
self.element.dispatchEvent(shownPopoverEvent);
}
function popoverHideTrigger(self) {
dismissHandlerToggle(self);
removePopover(self);
self.element.dispatchEvent(hiddenPopoverEvent);
}
// POPOVER DEFINITION
// ==================
class Popover extends BaseComponent {
constructor(target, config) {
popoverDefaultOptions.container = getTipContainer(queryElement(target));
super(popoverComponent, target, popoverDefaultOptions, config);
// bind
const self = this;
// initialization element
const { element } = self;
// additional instance properties
self.timer = null;
self.popover = null;
self.arrow = null;
self.enabled = false;
// set unique ID for aria-describedby
self.id = `${popoverString}-${getUID(element)}`;
// set instance options
const { options } = self;
// media elements only work with body as a container
self.options.container = isMedia(element)
? popoverDefaultOptions.container
: queryElement(options.container);
// reset default container
popoverDefaultOptions.container = null;
// invalidate when no content is set
if (!options.content) return;
// crate popover
createPopover(self);
// bind
self.update = self.update.bind(self);
// attach event listeners
togglePopoverHandlers(self, 1);
}
update(e) {
styleTip(this, e);
}
// POPOVER PUBLIC METHODS
// ======================
toggle(e) {
const self = e ? this[popoverComponent] : this;
const { popover, options } = self;
if (!isVisibleTip(popover, options.container)) self.show();
else self.hide();
}
show(e) {
const self = e ? this[popoverComponent] : this;
const {
element, popover, options, id,
} = self;
const { container } = options;
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if (!isVisibleTip(popover, container)) {
element.dispatchEvent(showPopoverEvent);
if (showPopoverEvent.defaultPrevented) return;
// append to the container
container.appendChild(popover);
element.setAttribute(ariaDescribedBy, id);
self.update(e);
if (!hasClass(popover, showClass)) addClass(popover, showClass);
if (options.animation) emulateTransitionEnd(popover, () => popoverShowTrigger(self));
else popoverShowTrigger(self);
}
}, 17);
}
hide(e) {
let self;
if (e && this[popoverComponent]) {
self = this[popoverComponent];
} else if (e) { // dismissible popover
const dPopover = this.closest(`.${popoverString}`);
const dEl = dPopover && queryElement(`[${ariaDescribedBy}="${dPopover.id}"]`);
self = dEl[popoverComponent];
} else {
self = this;
}
const { element, popover, options } = self;
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if (isVisibleTip(popover, options.container)) {
element.dispatchEvent(hidePopoverEvent);
if (hidePopoverEvent.defaultPrevented) return;
removeClass(popover, showClass);
if (options.animation) emulateTransitionEnd(popover, () => popoverHideTrigger(self));
else popoverHideTrigger(self);
}
}, options.delay + 17);
}
enable() {
const self = this;
const { enabled } = self;
if (!enabled) {
togglePopoverHandlers(self, 1);
self.enabled = !enabled;
}
}
disable() {
const self = this;
const { enabled, popover, options } = self;
if (enabled) {
if (isVisibleTip(popover, options.container) && options.animation) {
self.hide();
setTimeout(
() => togglePopoverHandlers(self),
getElementTransitionDuration(popover) + options.delay + 17,
);
} else {
togglePopoverHandlers(self);
}
self.enabled = !enabled;
}
}
toggleEnabled() {
const self = this;
if (!self.enabled) self.enable();
else self.disable();
}
dispose() {
const self = this;
const { popover, options } = self;
const { container, animation } = options;
if (animation && isVisibleTip(popover, container)) {
options.delay = 0; // reset delay
self.hide();
emulateTransitionEnd(popover, () => togglePopoverHandlers(self));
} else {
togglePopoverHandlers(self);
}
super.dispose(popoverComponent);
}
}
Popover.init = {
component: popoverComponent,
selector: popoverSelector,
constructor: Popover,
};
/* Native JavaScript for Bootstrap 5 | ScrollSpy
------------------------------------------------ */
// SCROLLSPY PRIVATE GC
// ====================
const scrollspyString = 'scrollspy';
const scrollspyComponent = 'ScrollSpy';
const scrollspySelector = '[data-bs-spy="scroll"]';
const scrollSpyDefaultOptions = {
offset: 10,
target: null,
};
// SCROLLSPY CUSTOM EVENT
// ======================
const activateScrollSpy = bootstrapCustomEvent(`activate.bs.${scrollspyString}`);
// SCROLLSPY PRIVATE METHODS
// =========================
function updateSpyTargets(self) {
const {
target, scrollTarget, isWindow, options, itemsLength, scrollHeight,
} = self;
const { offset } = options;
const links = target.getElementsByTagName('A');
self.scrollTop = isWindow
? scrollTarget.pageYOffset
: scrollTarget.scrollTop;
// only update items/offsets once or with each mutation
if (itemsLength !== links.length || getScrollHeight(scrollTarget) !== scrollHeight) {
let href;
let targetItem;
let rect;
// reset arrays & update
self.items = [];
self.offsets = [];
self.scrollHeight = getScrollHeight(scrollTarget);
self.maxScroll = self.scrollHeight - getOffsetHeight(self);
Array.from(links).forEach((link) => {
href = link.getAttribute('href');
targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href);
if (targetItem) {
self.items.push(link);
rect = targetItem.getBoundingClientRect();
self.offsets.push((isWindow ? rect.top + self.scrollTop : targetItem.offsetTop) - offset);
}
});
self.itemsLength = self.items.length;
}
}
function getScrollHeight(scrollTarget) {
return scrollTarget.scrollHeight || Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
);
}
function getOffsetHeight({ element, isWindow }) {
if (!isWindow) return element.getBoundingClientRect().height;
return window.innerHeight;
}
function clear(target) {
Array.from(target.getElementsByTagName('A')).forEach((item) => {
if (hasClass(item, activeClass)) removeClass(item, activeClass);
});
}
function activate(self, item) {
const { target, element } = self;
clear(target);
self.activeItem = item;
addClass(item, activeClass);
// activate all parents
const parents = [];
let parentItem = item;
while (parentItem !== document.body) {
parentItem = parentItem.parentNode;
if (hasClass(parentItem, 'nav') || hasClass(parentItem, 'dropdown-menu')) parents.push(parentItem);
}
parents.forEach((menuItem) => {
const parentLink = menuItem.previousElementSibling;
if (parentLink && !hasClass(parentLink, activeClass)) {
addClass(parentLink, activeClass);
}
});
// update relatedTarget and dispatch
activateScrollSpy.relatedTarget = item;
element.dispatchEvent(activateScrollSpy);
}
function toggleSpyHandlers(self, add) {
const action = add ? addEventListener : removeEventListener;
self.scrollTarget[action]('scroll', self.refresh, passiveHandler);
}
// SCROLLSPY DEFINITION
// ====================
class ScrollSpy extends BaseComponent {
constructor(target, config) {
super(scrollspyComponent, target, scrollSpyDefaultOptions, config);
// bind
const self = this;
// initialization element & options
const { element, options } = self;
// additional properties
self.target = queryElement(options.target);
// invalidate
if (!self.target) return;
// set initial state
self.scrollTarget = element.clientHeight < element.scrollHeight ? element : window;
self.isWindow = self.scrollTarget === window;
self.scrollTop = 0;
self.maxScroll = 0;
self.scrollHeight = 0;
self.activeItem = null;
self.items = [];
self.offsets = [];
// bind events
self.refresh = self.refresh.bind(self);
// add event handlers
toggleSpyHandlers(self, 1);
self.refresh();
}
// SCROLLSPY PUBLIC METHODS
// ========================
refresh() {
const self = this;
const { target } = self;
// check if target is visible and invalidate
if (target.offsetHeight === 0) return;
updateSpyTargets(self);
const {
scrollTop, maxScroll, itemsLength, items, activeItem,
} = self;
if (scrollTop >= maxScroll) {
const newActiveItem = items[itemsLength - 1];
if (activeItem !== newActiveItem) {
activate(self, newActiveItem);
}
return;
}
const { offsets } = self;
if (activeItem && scrollTop < offsets[0] && offsets[0] > 0) {
self.activeItem = null;
clear(target);
return;
}
items.forEach((item, i) => {
if (activeItem !== item && scrollTop >= offsets[i]
&& (typeof offsets[i + 1] === 'undefined' || scrollTop < offsets[i + 1])) {
activate(self, item);
}
});
}
dispose() {
toggleSpyHandlers(this);
super.dispose(scrollspyComponent);
}
}
ScrollSpy.init = {
component: scrollspyComponent,
selector: scrollspySelector,
constructor: ScrollSpy,
};
const ariaSelected = 'aria-selected';
/* Native JavaScript for Bootstrap 5 | Tab
------------------------------------------ */
// TAB PRIVATE GC
// ================
const tabString = 'tab';
const tabComponent = 'Tab';
const tabSelector = `[${dataBsToggle}="${tabString}"]`;
// TAB CUSTOM EVENTS
// =================
const showTabEvent = bootstrapCustomEvent(`show.bs.${tabString}`);
const shownTabEvent = bootstrapCustomEvent(`shown.bs.${tabString}`);
const hideTabEvent = bootstrapCustomEvent(`hide.bs.${tabString}`);
const hiddenTabEvent = bootstrapCustomEvent(`hidden.bs.${tabString}`);
let nextTab;
let nextTabContent;
let nextTabHeight;
let activeTab;
let activeTabContent;
let tabContainerHeight;
let tabEqualContents;
// TAB PRIVATE METHODS
// ===================
function triggerTabEnd(self) {
const { tabContent, nav } = self;
tabContent.style.height = '';
removeClass(tabContent, collapsingClass);
nav.isAnimating = false;
}
function triggerTabShow(self) {
const { tabContent, nav } = self;
if (tabContent) { // height animation
if (tabEqualContents) {
triggerTabEnd(self);
} else {
setTimeout(() => { // enables height animation
tabContent.style.height = `${nextTabHeight}px`; // height animation
reflow(tabContent);
emulateTransitionEnd(tabContent, () => triggerTabEnd(self));
}, 50);
}
} else {
nav.isAnimating = false;
}
shownTabEvent.relatedTarget = activeTab;
nextTab.dispatchEvent(shownTabEvent);
}
function triggerTabHide(self) {
const { tabContent } = self;
if (tabContent) {
activeTabContent.style.float = 'left';
nextTabContent.style.float = 'left';
tabContainerHeight = activeTabContent.scrollHeight;
}
// update relatedTarget and dispatch event
showTabEvent.relatedTarget = activeTab;
hiddenTabEvent.relatedTarget = nextTab;
nextTab.dispatchEvent(showTabEvent);
if (showTabEvent.defaultPrevented) return;
addClass(nextTabContent, activeClass);
removeClass(activeTabContent, activeClass);
if (tabContent) {
nextTabHeight = nextTabContent.scrollHeight;
tabEqualContents = nextTabHeight === tabContainerHeight;
addClass(tabContent, collapsingClass);
tabContent.style.height = `${tabContainerHeight}px`; // height animation
reflow(tabContent);
activeTabContent.style.float = '';
nextTabContent.style.float = '';
}
if (hasClass(nextTabContent, fadeClass)) {
setTimeout(() => {
addClass(nextTabContent, showClass);
emulateTransitionEnd(nextTabContent, () => {
triggerTabShow(self);
});
}, 20);
} else { triggerTabShow(self); }
activeTab.dispatchEvent(hiddenTabEvent);
}
function getActiveTab({ nav }) {
const activeTabs = nav.getElementsByClassName(activeClass);
if (activeTabs.length === 1
&& !dropdownMenuClasses.some((c) => hasClass(activeTabs[0].parentNode, c))) {
[activeTab] = activeTabs;
} else if (activeTabs.length > 1) {
activeTab = activeTabs[activeTabs.length - 1];
}
return activeTab;
}
function getActiveTabContent(self) {
return queryElement(getActiveTab(self).getAttribute('href'));
}
function toggleTabHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
self.element[action]('click', tabClickHandler);
}
// TAB EVENT HANDLER
// =================
function tabClickHandler(e) {
const self = this[tabComponent];
e.preventDefault();
if (!self.nav.isAnimating) self.show();
}
// TAB DEFINITION
// ==============
class Tab extends BaseComponent {
constructor(target) {
super(tabComponent, target);
// bind
const self = this;
// initialization element
const { element } = self;
// event targets
self.nav = element.closest('.nav');
const { nav } = self;
self.dropdown = nav && queryElement(`.${dropdownMenuClasses[0]}-toggle`, nav);
activeTabContent = getActiveTabContent(self);
self.tabContent = supportTransition && activeTabContent.closest('.tab-content');
tabContainerHeight = activeTabContent.scrollHeight;
// set default animation state
nav.isAnimating = false;
// add event listener
toggleTabHandler(self, 1);
}
// TAB PUBLIC METHODS
// ==================
show() { // the tab we clicked is now the nextTab tab
const self = this;
const { element, nav, dropdown } = self;
nextTab = element;
if (!hasClass(nextTab, activeClass)) {
// this is the actual object, the nextTab tab content to activate
nextTabContent = queryElement(nextTab.getAttribute('href'));
activeTab = getActiveTab({ nav });
activeTabContent = getActiveTabContent({ nav });
// update relatedTarget and dispatch
hideTabEvent.relatedTarget = nextTab;
activeTab.dispatchEvent(hideTabEvent);
if (hideTabEvent.defaultPrevented) return;
nav.isAnimating = true;
removeClass(activeTab, activeClass);
activeTab.setAttribute(ariaSelected, 'false');
addClass(nextTab, activeClass);
nextTab.setAttribute(ariaSelected, 'true');
if (dropdown) {
if (!hasClass(element.parentNode, dropdownMenuClass)) {
if (hasClass(dropdown, activeClass)) removeClass(dropdown, activeClass);
} else if (!hasClass(dropdown, activeClass)) addClass(dropdown, activeClass);
}
if (hasClass(activeTabContent, fadeClass)) {
removeClass(activeTabContent, showClass);
emulateTransitionEnd(activeTabContent, () => triggerTabHide(self));
} else {
triggerTabHide(self);
}
}
}
dispose() {
toggleTabHandler(this);
super.dispose(tabComponent);
}
}
Tab.init = {
component: tabComponent,
selector: tabSelector,
constructor: Tab,
};
/* Native JavaScript for Bootstrap 5 | Toast
-------------------------------------------- */
// TOAST PRIVATE GC
// ================
const toastString = 'toast';
const toastComponent = 'Toast';
const toastSelector = `.${toastString}`;
const toastDismissSelector = `[${dataBsDismiss}="${toastString}"]`;
const showingClass = 'showing';
const hideClass = 'hide';
const toastDefaultOptions = {
animation: true,
autohide: true,
delay: 500,
};
// TOAST CUSTOM EVENTS
// ===================
const showToastEvent = bootstrapCustomEvent(`show.bs.${toastString}`);
const hideToastEvent = bootstrapCustomEvent(`hide.bs.${toastString}`);
const shownToastEvent = bootstrapCustomEvent(`shown.bs.${toastString}`);
const hiddenToastEvent = bootstrapCustomEvent(`hidden.bs.${toastString}`);
// TOAST PRIVATE METHODS
// =====================
function showToastComplete(self) {
const { element, options } = self;
if (!options.animation) {
removeClass(element, showingClass);
addClass(element, showClass);
}
element.dispatchEvent(shownToastEvent);
if (options.autohide) self.hide();
}
function hideToastComplete(self) {
const { element } = self;
addClass(element, hideClass);
element.dispatchEvent(hiddenToastEvent);
}
function closeToast(self) {
const { element, options } = self;
removeClass(element, showClass);
if (options.animation) {
reflow(element);
emulateTransitionEnd(element, () => hideToastComplete(self));
} else {
hideToastComplete(self);
}
}
function openToast(self) {
const { element, options } = self;
removeClass(element, hideClass);
if (options.animation) {
reflow(element);
addClass(element, showingClass);
addClass(element, showClass);
emulateTransitionEnd(element, () => showToastComplete(self));
} else {
showToastComplete(self);
}
}
function toggleToastHandler(self, add) {
const action = add ? addEventListener : removeEventListener;
if (self.dismiss) {
self.dismiss[action]('click', self.hide);
}
}
// TOAST EVENT HANDLERS
// ====================
function completeDisposeToast(self) {
clearTimeout(self.timer);
toggleToastHandler(self);
}
// TOAST DEFINITION
// ================
class Toast extends BaseComponent {
constructor(target, config) {
super(toastComponent, target, toastDefaultOptions, config);
// bind
const self = this;
// dismiss button
self.dismiss = queryElement(toastDismissSelector, self.element);
// bind
self.show = self.show.bind(self);
self.hide = self.hide.bind(self);
// add event listener
toggleToastHandler(self, 1);
}
// TOAST PUBLIC METHODS
// ====================
show() {
const self = this;
const { element } = self;
if (element && hasClass(element, hideClass)) {
element.dispatchEvent(showToastEvent);
if (showToastEvent.defaultPrevented) return;
addClass(element, fadeClass);
clearTimeout(self.timer);
self.timer = setTimeout(() => openToast(self), 10);
}
}
hide(noTimer) {
const self = this;
const { element, options } = self;
if (element && hasClass(element, showClass)) {
element.dispatchEvent(hideToastEvent);
if (hideToastEvent.defaultPrevented) return;
clearTimeout(self.timer);
self.timer = setTimeout(() => closeToast(self),
noTimer ? 10 : options.delay);
}
}
dispose() {
const self = this;
const { element, options } = self;
self.hide();
if (options.animation) emulateTransitionEnd(element, () => completeDisposeToast(self));
else completeDisposeToast(self);
super.dispose(toastComponent);
}
}
Toast.init = {
component: toastComponent,
selector: toastSelector,
constructor: Toast,
};
const dataOriginalTitle = 'data-original-title';
/* Native JavaScript for Bootstrap 5 | Tooltip
---------------------------------------------- */
// TOOLTIP PRIVATE GC
// ==================
const tooltipString = 'tooltip';
const tooltipComponent = 'Tooltip';
const tooltipSelector = `[${dataBsToggle}="${tooltipString}"],[data-tip="${tooltipString}"]`;
const titleAttr = 'title';
const tooltipInnerClass = `${tooltipString}-inner`;
const tooltipDefaultOptions = {
title: null,
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
placement: 'top',
animation: true,
customClass: null,
delay: 200,
sanitizeFn: null,
};
// TOOLTIP CUSTOM EVENTS
// =====================
const showTooltipEvent = bootstrapCustomEvent(`show.bs.${tooltipString}`);
const shownTooltipEvent = bootstrapCustomEvent(`shown.bs.${tooltipString}`);
const hideTooltipEvent = bootstrapCustomEvent(`hide.bs.${tooltipString}`);
const hiddenTooltipEvent = bootstrapCustomEvent(`hidden.bs.${tooltipString}`);
// TOOLTIP PRIVATE METHODS
// =======================
function createTooltip(self) {
const { options, id } = self;
const placementClass = `bs-${tooltipString}-${tipClassPositions[options.placement]}`;
let titleString = options.title.trim();
// sanitize stuff
if (options.sanitizeFn) {
titleString = options.sanitizeFn(titleString);
options.template = options.sanitizeFn(options.template);
}
if (!titleString) return;
// create tooltip
self.tooltip = document.createElement('div');
const { tooltip } = self;
// set aria
tooltip.setAttribute('id', id);
// set markup
const tooltipMarkup = document.createElement('div');
tooltipMarkup.innerHTML = options.template.trim();
tooltip.className = tooltipMarkup.firstChild.className;
tooltip.innerHTML = tooltipMarkup.firstChild.innerHTML;
queryElement(`.${tooltipInnerClass}`, tooltip).innerHTML = titleString;
// set arrow
self.arrow = queryElement(`.${tooltipString}-arrow`, tooltip);
// set class and role attribute
tooltip.setAttribute('role', tooltipString);
// set classes
if (!hasClass(tooltip, tooltipString)) addClass(tooltip, tooltipString);
if (options.animation && !hasClass(tooltip, fadeClass)) addClass(tooltip, fadeClass);
if (options.customClass && !hasClass(tooltip, options.customClass)) {
addClass(tooltip, options.customClass);
}
if (!hasClass(tooltip, placementClass)) addClass(tooltip, placementClass);
}
function removeTooltip(self) {
const { element, options, tooltip } = self;
element.removeAttribute(ariaDescribedBy);
options.container.removeChild(tooltip);
self.timer = null;
}
function disposeTooltipComplete(self) {
const { element } = self;
toggleTooltipHandlers(self);
if (element.hasAttribute(dataOriginalTitle)) toggleTooltipTitle(self);
}
function toggleTooltipAction(self, add) {
const action = add ? addEventListener : removeEventListener;
document[action]('touchstart', tooltipTouchHandler, passiveHandler);
if (!isMedia(self.element)) {
window[action]('scroll', self.update, passiveHandler);
window[action]('resize', self.update, passiveHandler);
}
}
function tooltipShownAction(self) {
toggleTooltipAction(self, 1);
self.element.dispatchEvent(shownTooltipEvent);
}
function tooltipHiddenAction(self) {
toggleTooltipAction(self);
removeTooltip(self);
self.element.dispatchEvent(hiddenTooltipEvent);
}
function toggleTooltipHandlers(self, add) {
const action = add ? addEventListener : removeEventListener;
const { element } = self;
if (isMedia(element)) element[action]('mousemove', self.update, passiveHandler);
element[action]('mousedown', self.show);
element[action]('mouseenter', self.show);
element[action]('mouseleave', self.hide);
}
function toggleTooltipTitle(self, content) {
// [0 - add, 1 - remove] | [0 - remove, 1 - add]
const titleAtt = [dataOriginalTitle, titleAttr];
const { element } = self;
element.setAttribute(titleAtt[content ? 0 : 1],
(content || element.getAttribute(titleAtt[0])));
element.removeAttribute(titleAtt[content ? 1 : 0]);
}
// TOOLTIP EVENT HANDLERS
// ======================
function tooltipTouchHandler({ target }) {
const { tooltip, element } = this;
if (tooltip.contains(target) || target === element || element.contains(target)) ; else {
this.hide();
}
}
// TOOLTIP DEFINITION
// ==================
class Tooltip extends BaseComponent {
constructor(target, config) {
// initialization element
const element = queryElement(target);
tooltipDefaultOptions.title = element.getAttribute(titleAttr);
tooltipDefaultOptions.container = getTipContainer(element);
super(tooltipComponent, element, tooltipDefaultOptions, config);
// bind
const self = this;
// additional properties
self.tooltip = null;
self.arrow = null;
self.timer = null;
self.enabled = false;
// instance options
const { options } = self;
// media elements only work with body as a container
self.options.container = isMedia(element)
? tooltipDefaultOptions.container
: queryElement(options.container);
// reset default options
tooltipDefaultOptions.container = null;
tooltipDefaultOptions[titleAttr] = null;
// invalidate
if (!options.title) return;
// all functions bind
tooltipTouchHandler.bind(self);
self.update = self.update.bind(self);
// set title attributes and add event listeners
if (element.hasAttribute(titleAttr)) toggleTooltipTitle(self, options.title);
// create tooltip here
self.id = `${tooltipString}-${getUID(element)}`;
createTooltip(self);
// attach events
toggleTooltipHandlers(self, 1);
}
// TOOLTIP PUBLIC METHODS
// ======================
show(e) {
const self = e ? this[tooltipComponent] : this;
const {
options, tooltip, element, id,
} = self;
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if (!isVisibleTip(tooltip, options.container)) {
element.dispatchEvent(showTooltipEvent);
if (showTooltipEvent.defaultPrevented) return;
// append to container
options.container.appendChild(tooltip);
element.setAttribute(ariaDescribedBy, id);
self.update(e);
if (!hasClass(tooltip, showClass)) addClass(tooltip, showClass);
if (options.animation) emulateTransitionEnd(tooltip, () => tooltipShownAction(self));
else tooltipShownAction(self);
}
}, 20);
}
hide(e) {
const self = e ? this[tooltipComponent] : this;
const { options, tooltip, element } = self;
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if (isVisibleTip(tooltip, options.container)) {
element.dispatchEvent(hideTooltipEvent);
if (hideTooltipEvent.defaultPrevented) return;
removeClass(tooltip, showClass);
if (options.animation) emulateTransitionEnd(tooltip, () => tooltipHiddenAction(self));
else tooltipHiddenAction(self);
}
}, options.delay);
}
update(e) {
styleTip(this, e);
}
toggle() {
const self = this;
const { tooltip, options } = self;
if (!isVisibleTip(tooltip, options.container)) self.show();
else self.hide();
}
enable() {
const self = this;
const { enabled } = self;
if (!enabled) {
toggleTooltipHandlers(self, 1);
self.enabled = !enabled;
}
}
disable() {
const self = this;
const { tooltip, options, enabled } = self;
if (enabled) {
if (!isVisibleTip(tooltip, options.container) && options.animation) {
self.hide();
setTimeout(
() => toggleTooltipHandlers(self),
getElementTransitionDuration(tooltip) + options.delay + 17,
);
} else {
toggleTooltipHandlers(self);
}
self.enabled = !enabled;
}
}
toggleEnabled() {
const self = this;
if (!self.enabled) self.enable();
else self.disable();
}
dispose() {
const self = this;
const { tooltip, options } = self;
if (options.animation && isVisibleTip(tooltip, options.container)) {
options.delay = 0; // reset delay
self.hide();
emulateTransitionEnd(tooltip, () => disposeTooltipComplete(self));
} else {
disposeTooltipComplete(self);
}
super.dispose(tooltipComponent);
}
}
Tooltip.init = {
component: tooltipComponent,
selector: tooltipSelector,
constructor: Tooltip,
};
var version = "4.0.6";
// import { alertInit } from '../components/alert-native.js';
// import { buttonInit } from '../components/button-native.js';
// import { carouselInit } from '../components/carousel-native.js';
// import { collapseInit } from '../components/collapse-native.js';
// import { dropdownInit } from '../components/dropdown-native.js';
// import { modalInit } from '../components/modal-native.js';
// import { offcanvasInit } from '../components/offcanvas-native.js';
// import { popoverInit } from '../components/popover-native.js';
// import { scrollSpyInit } from '../components/scrollspy-native.js';
// import { tabInit } from '../components/tab-native.js';
// import { toastInit } from '../components/toast-native.js';
// import { tooltipInit } from '../components/tooltip-native.js';
const componentsInit = {
Alert: Alert.init,
Button: Button.init,
Carousel: Carousel.init,
Collapse: Collapse.init,
Dropdown: Dropdown.init,
Modal: Modal.init,
Offcanvas: Offcanvas.init,
Popover: Popover.init,
ScrollSpy: ScrollSpy.init,
Tab: Tab.init,
Toast: Toast.init,
Tooltip: Tooltip.init,
};
function initializeDataAPI(Konstructor, collection) {
Array.from(collection).forEach((x) => new Konstructor(x));
}
function initCallback(context) {
const lookUp = context instanceof Element ? context : document;
Object.keys(componentsInit).forEach((comp) => {
const { constructor, selector } = componentsInit[comp];
initializeDataAPI(constructor, lookUp.querySelectorAll(selector));
});
}
// bulk initialize all components
if (document.body) initCallback();
else {
document.addEventListener('DOMContentLoaded', () => initCallback(), { once: true });
}
var index = {
Alert,
Button,
Carousel,
Collapse,
Dropdown,
Modal,
Offcanvas,
Popover,
ScrollSpy,
Tab,
Toast,
Tooltip,
initCallback,
Version: version,
};
return index;
})));