Files

3305 lines
107 KiB
JavaScript

/*!
* Bootstrap Native ESM v5.1.10 (https://thednp.github.io/bootstrap.native/)
* Copyright 2026 © thednp
* Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE)
*/
"use strict";
import { Data, ObjectAssign, ObjectKeys, Timer, addClass, ariaDescribedBy, ariaExpanded, ariaHidden, ariaModal, ariaPressed, ariaSelected, closest, createCustomEvent, createElement, dispatchEvent, dragstartEvent, emulateTransitionEnd, focus, focusEvent, focusinEvent, focusoutEvent, getAttribute, getBoundingClientRect, getDocument, getDocumentBody, getDocumentElement, getElementById, getElementStyle, getElementTransitionDuration, getElementsByClassName, getElementsByTagName, getInstance, getNodeScroll, getParentNode, getRectRelativeToOffsetParent, getUID, getWindow, hasAttribute, hasClass, isApple, isArray, isElement, isElementInScrollRange, isFunction, isHTMLElement, isNode, isNodeList, isRTL, isShadowRoot, isString, isTableElement, keyArrowDown, keyArrowLeft, keyArrowRight, keyArrowUp, keyEscape, keydownEvent, keyupEvent, matches, mouseclickEvent, mousedownEvent, mouseenterEvent, mousehoverEvent, mouseleaveEvent, noop, normalizeOptions, passiveHandler, pointerdownEvent, pointermoveEvent, pointerupEvent, querySelector, querySelectorAll, reflow, removeAttribute, removeClass, setAttribute, setElementStyle, toLowerCase, toggleFocusTrap, touchstartEvent } from "@thednp/shorty";
import { addListener, removeListener } from "@thednp/event-listener";
import PositionObserver from "@thednp/position-observer";
//#region src/strings/fadeClass.ts
/**
* Global namespace for most components `fade` class.
*/
const fadeClass = "fade";
//#endregion
//#region src/strings/showClass.ts
/**
* Global namespace for most components `show` class.
*/
const showClass = "show";
//#endregion
//#region src/strings/dataBsDismiss.ts
/**
* Global namespace for most components `dismiss` option.
*/
const dataBsDismiss = "data-bs-dismiss";
//#endregion
//#region src/strings/alertString.ts
/** @type {string} */
const alertString = "alert";
//#endregion
//#region src/strings/alertComponent.ts
/** @type {string} */
const alertComponent = "Alert";
//#endregion
//#region src/util/isDisabled.ts
/**
* Check if interactive element is disabled.
* @param target either a `<button>` or an `<a>`
* @returns whether the target is disabled
*/
const isDisabled = (target) => {
return hasClass(target, "disabled") || getAttribute(target, "disabled") === "true";
};
//#endregion
//#region src/version.ts
const Version = "5.1.10";
//#endregion
//#region src/components/base-component.ts
/** Returns a new `BaseComponent` instance. */
var BaseComponent = class {
/**
* @param target `Element` or selector string
* @param config component instance options
*/
constructor(target, config) {
let element;
try {
if (isElement(target)) element = target;
else if (isString(target)) {
element = querySelector(target);
if (!element) throw Error(`"${target}" is not a valid selector.`);
} else throw Error(`your target is not an instance of HTMLElement.`);
} catch (e) {
throw Error(`${this.name} Error: ${e.message}`);
}
const prevInstance = Data.get(element, this.name);
if (prevInstance) prevInstance._toggleEventListeners();
this.element = element;
this.options = this.defaults && ObjectKeys(this.defaults).length ? normalizeOptions(element, this.defaults, config || {}, "bs") : {};
Data.set(element, this.name, this);
}
get version() {
return Version;
}
get name() {
return "BaseComponent";
}
get defaults() {
return {};
}
/** just to have something to extend from */
_toggleEventListeners = () => {};
/** Removes component from target element. */
dispose() {
Data.remove(this.element, this.name);
ObjectKeys(this).forEach((prop) => {
delete this[prop];
});
}
};
//#endregion
//#region src/components/alert.ts
const alertSelector = `.${alertString}`;
const alertDismissSelector = `[${dataBsDismiss}="${alertString}"]`;
/**
* Static method which returns an existing `Alert` instance associated
* to a target `Element`.
*/
const getAlertInstance = (element) => getInstance(element, alertComponent);
/**
* An `Alert` initialization callback.
*/
const alertInitCallback = (element) => new Alert(element);
const closeAlertEvent = createCustomEvent(`close.bs.${alertString}`);
const closedAlertEvent = createCustomEvent(`closed.bs.${alertString}`);
/**
* Alert `transitionend` callback.
*
* @param that target Alert instance
*/
const alertTransitionEnd = (self) => {
const { element } = self;
dispatchEvent(element, closedAlertEvent);
self._toggleEventListeners();
self.dispose();
element.remove();
};
/** Creates a new Alert instance. */
var Alert = class extends BaseComponent {
static selector = alertSelector;
static init = alertInitCallback;
static getInstance = getAlertInstance;
dismiss;
constructor(target) {
super(target);
this.dismiss = querySelector(alertDismissSelector, this.element);
this._toggleEventListeners(true);
}
/** Returns component name string. */
get name() {
return alertComponent;
}
/**
* Public method that hides the `.alert` element from the user,
* disposes the instance once animation is complete, then
* removes the element from the DOM.
*/
close = (e) => {
const { element, dismiss } = this;
if (!element || !hasClass(element, "show")) return;
if (e && dismiss && isDisabled(dismiss)) return;
dispatchEvent(element, closeAlertEvent);
if (closeAlertEvent.defaultPrevented) return;
removeClass(element, showClass);
if (hasClass(element, "fade")) emulateTransitionEnd(element, () => alertTransitionEnd(this));
else alertTransitionEnd(this);
};
/**
* Toggle on / off the `click` event listener.
*
* @param add when `true`, event listener is added
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
const { dismiss, close } = this;
if (dismiss) action(dismiss, mouseclickEvent, close);
};
/** Remove the component from target element. */
dispose() {
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/strings/activeClass.ts
/**
* Global namespace for most components active class.
*/
const activeClass = "active";
//#endregion
//#region src/strings/dataBsToggle.ts
/**
* Global namespace for most components `toggle` option.
*/
const dataBsToggle = "data-bs-toggle";
//#endregion
//#region src/strings/buttonString.ts
/** @type {string} */
const buttonString = "button";
//#endregion
//#region src/strings/buttonComponent.ts
/** @type {string} */
const buttonComponent = "Button";
//#endregion
//#region src/components/button.ts
const buttonSelector = `[${dataBsToggle}="${buttonString}"]`;
/**
* Static method which returns an existing `Button` instance associated
* to a target `Element`.
*/
const getButtonInstance = (element) => getInstance(element, buttonComponent);
/** A `Button` initialization callback. */
const buttonInitCallback = (element) => new Button(element);
/** Creates a new `Button` instance. */
var Button = class extends BaseComponent {
static selector = buttonSelector;
static init = buttonInitCallback;
static getInstance = getButtonInstance;
/**
* @param target usually a `.btn` element
*/
constructor(target) {
super(target);
const { element } = this;
this.isActive = hasClass(element, activeClass);
setAttribute(element, ariaPressed, String(!!this.isActive));
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return buttonComponent;
}
/**
* Toggles the state of the target button.
*
* @param e usually `click` Event object
*/
toggle = (e) => {
if (e) e.preventDefault();
const { element, isActive } = this;
if (isDisabled(element)) return;
(isActive ? removeClass : addClass)(element, activeClass);
setAttribute(element, ariaPressed, isActive ? "false" : "true");
this.isActive = hasClass(element, activeClass);
};
/**
* Toggles on/off the `click` event listener.
*
* @param add when `true`, event listener is added
*/
_toggleEventListeners = (add) => {
(add ? addListener : removeListener)(this.element, mouseclickEvent, this.toggle);
};
/** Removes the `Button` component from the target element. */
dispose() {
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/strings/dataBsTarget.ts
/**
* Global namespace for most components `target` option.
*/
const dataBsTarget = "data-bs-target";
//#endregion
//#region src/strings/carouselString.ts
/** @type {string} */
const carouselString = "carousel";
//#endregion
//#region src/strings/carouselComponent.ts
/** @type {string} */
const carouselComponent = "Carousel";
//#endregion
//#region src/strings/dataBsParent.ts
/**
* Global namespace for most components `parent` option.
*/
const dataBsParent = "data-bs-parent";
//#endregion
//#region src/strings/dataBsContainer.ts
/**
* Global namespace for most components `container` option.
*/
const dataBsContainer = "data-bs-container";
//#endregion
//#region src/util/getTargetElement.ts
/**
* Returns the `Element` that THIS one targets
* via `data-bs-target`, `href`, `data-bs-parent` or `data-bs-container`.
*
* @param element the target element
* @returns the query result
*/
const getTargetElement = (element) => {
const targetAttr = [
dataBsTarget,
dataBsParent,
dataBsContainer,
"href"
];
const doc = getDocument(element);
return targetAttr.map((att) => {
const attValue = getAttribute(element, att);
if (attValue) return att === "data-bs-parent" ? closest(element, attValue) : querySelector(attValue, doc);
return null;
}).filter((x) => x)[0];
};
//#endregion
//#region src/components/carousel.ts
const carouselSelector = `[data-bs-ride="${carouselString}"]`;
const carouselItem = `${carouselString}-item`;
const dataBsSlideTo = "data-bs-slide-to";
const dataBsSlide = "data-bs-slide";
const pausedClass = "paused";
const carouselDefaults = {
pause: "hover",
keyboard: false,
touch: true,
interval: 5e3
};
/**
* Static method which returns an existing `Carousel` instance associated
* to a target `Element`.
*/
const getCarouselInstance = (element) => getInstance(element, carouselComponent);
/**
* A `Carousel` initialization callback.
*/
const carouselInitCallback = (element) => new Carousel(element);
let startX = 0;
let currentX = 0;
let endX = 0;
const carouselSlideEvent = createCustomEvent(`slide.bs.${carouselString}`);
const carouselSlidEvent = createCustomEvent(`slid.bs.${carouselString}`);
/**
* The `transitionend` event listener of the `Carousel`.
*
* @param self the `Carousel` instance
*/
const carouselTransitionEndHandler = (self) => {
const { index, direction, element, slides, options } = self;
if (self.isAnimating) {
const activeItem = getActiveIndex(self);
const orientation = direction === "left" ? "next" : "prev";
const directionClass = direction === "left" ? "start" : "end";
addClass(slides[index], activeClass);
removeClass(slides[index], `${carouselItem}-${orientation}`);
removeClass(slides[index], `${carouselItem}-${directionClass}`);
removeClass(slides[activeItem], activeClass);
removeClass(slides[activeItem], `${carouselItem}-${directionClass}`);
dispatchEvent(element, carouselSlidEvent);
Timer.clear(element, dataBsSlide);
if (self.cycle && !getDocument(element).hidden && options.interval && !self.isPaused) self.cycle();
}
};
/**
* Handles the `mouseenter` events when *options.pause*
* is set to `hover`.
*/
function carouselPauseHandler() {
const self = getCarouselInstance(this);
if (self && !self.isPaused && !Timer.get(this, pausedClass)) addClass(this, pausedClass);
}
/**
* Handles the `mouseleave` events when *options.pause*
* is set to `hover`.
*/
function carouselResumeHandler() {
const self = getCarouselInstance(this);
if (self && self.isPaused && !Timer.get(this, pausedClass)) self.cycle();
}
/**
* Handles the `click` event for the `Carousel` indicators.
*
* @param e the `Event` object
*/
function carouselIndicatorHandler(e) {
e.preventDefault();
const element = closest(this, carouselSelector) || getTargetElement(this);
const self = element && getCarouselInstance(element);
if (isDisabled(this)) return;
if (!self || self.isAnimating) return;
const newIndex = Number(getAttribute(this, dataBsSlideTo) || 0);
if (this && !hasClass(this, "active") && !Number.isNaN(newIndex)) self.to(newIndex);
}
/**
* Handles the `click` event for the `Carousel` arrows.
*
* @param e the `Event` object
*/
function carouselControlsHandler(e) {
e.preventDefault();
const element = closest(this, carouselSelector) || getTargetElement(this);
const self = element && getCarouselInstance(element);
if (isDisabled(this)) return;
if (!self || self.isAnimating) return;
const orientation = getAttribute(this, dataBsSlide);
if (orientation === "next") self.next();
else if (orientation === "prev") self.prev();
}
/**
* Handles the keyboard `keydown` event for the visible `Carousel` elements.
*
* @param e the `Event` object
*/
const carouselKeyHandler = ({ code, target }) => {
const [element] = [...querySelectorAll(carouselSelector, getDocument(target))].filter((x) => isElementInScrollRange(x));
const self = getCarouselInstance(element);
if (!self || self.isAnimating || /textarea|input|select/i.test(target.nodeName)) return;
const RTL = isRTL(element);
const arrowKeyNext = !RTL ? keyArrowRight : keyArrowLeft;
if (code === (!RTL ? keyArrowLeft : keyArrowRight)) self.prev();
else if (code === arrowKeyNext) self.next();
};
/**
* Prevents the `touchstart` and `dragstart` events for the `Carousel` element.
*
* @param e the `Event` object
*/
function carouselDragHandler(e) {
const { target } = e;
const self = getCarouselInstance(this);
if (self && self.isTouch && !self.controls.includes(target) && !self.controls.includes(target?.parentElement) && (!self.indicator || !self.indicator.contains(target))) e.preventDefault();
}
/**
* Handles the `pointerdown` event for the `Carousel` element.
*
* @param e the `Event` object
*/
function carouselPointerDownHandler(e) {
const { target } = e;
const self = getCarouselInstance(this);
if (!self || self.isAnimating || self.isTouch) return;
const { controls, indicator } = self;
if (![...controls, indicator].every((el) => el && (el === target || el.contains(target))) && this.contains(target)) {
startX = e.pageX;
self.isTouch = true;
toggleCarouselTouchHandlers(self, true);
}
}
/**
* Handles the `pointermove` event for the `Carousel` element.
*
* @param e
*/
const carouselPointerMoveHandler = (e) => {
currentX = e.pageX;
};
/**
* Handles the `pointerup` event for the `Carousel` element.
*
* @param e
*/
const carouselPointerUpHandler = (e) => {
const { target } = e;
const doc = getDocument(target);
const self = [...querySelectorAll(carouselSelector, doc)].map((c) => getCarouselInstance(c)).find((i) => i.isTouch);
if (!self) return;
const { element, index } = self;
const RTL = isRTL(element);
endX = e.pageX;
self.isTouch = false;
toggleCarouselTouchHandlers(self);
if (!doc.getSelection()?.toString().length && element.contains(target) && Math.abs(startX - endX) > 120) {
if (currentX < startX) self.to(index + (RTL ? -1 : 1));
else if (currentX > startX) self.to(index + (RTL ? 1 : -1));
}
startX = 0;
currentX = 0;
endX = 0;
};
/**
* Sets active indicator for the `Carousel` instance.
*
* @param self the `Carousel` instance
* @param index the index of the new active indicator
*/
const activateCarouselIndicator = (self, index) => {
const { indicators } = self;
[...indicators].forEach((x) => removeClass(x, activeClass));
if (self.indicators[index]) addClass(indicators[index], activeClass);
};
/**
* Toggles the pointer event listeners for a given `Carousel` instance.
*
* @param self the `Carousel` instance
* @param add when `TRUE` event listeners are added
*/
const toggleCarouselTouchHandlers = (self, add) => {
const { element } = self;
const action = add ? addListener : removeListener;
action(getDocument(element), pointermoveEvent, carouselPointerMoveHandler, passiveHandler);
action(getDocument(element), pointerupEvent, carouselPointerUpHandler, passiveHandler);
};
/**
* Returns the index of the current active item.
*
* @param self the `Carousel` instance
* @returns the query result
*/
const getActiveIndex = (self) => {
const { slides, element } = self;
const activeItem = querySelector(`.${carouselItem}.${activeClass}`, element);
return activeItem ? [...slides].indexOf(activeItem) : -1;
};
/** Creates a new `Carousel` instance. */
var Carousel = class extends BaseComponent {
static selector = carouselSelector;
static init = carouselInitCallback;
static getInstance = getCarouselInstance;
/**
* @param target mostly a `.carousel` element
* @param config instance options
*/
constructor(target, config) {
super(target, config);
const { element } = this;
this.direction = isRTL(element) ? "right" : "left";
this.isTouch = false;
this.slides = getElementsByClassName(carouselItem, element);
const { slides } = this;
if (slides.length < 2) return;
const activeIndex = getActiveIndex(this);
const transitionItem = [...slides].find((s) => matches(s, `.${carouselItem}-next`));
this.index = activeIndex;
const doc = getDocument(element);
this.controls = [...querySelectorAll(`[${dataBsSlide}]`, element), ...querySelectorAll(`[${dataBsSlide}][${dataBsTarget}="#${element.id}"]`, doc)].filter((c, i, ar) => i === ar.indexOf(c));
this.indicator = querySelector(`.${carouselString}-indicators`, element);
this.indicators = [...this.indicator ? querySelectorAll(`[${dataBsSlideTo}]`, this.indicator) : [], ...querySelectorAll(`[${dataBsSlideTo}][${dataBsTarget}="#${element.id}"]`, doc)].filter((c, i, ar) => i === ar.indexOf(c));
const { options } = this;
this.options.interval = options.interval === true ? carouselDefaults.interval : options.interval;
if (transitionItem) this.index = [...slides].indexOf(transitionItem);
else if (activeIndex < 0) {
this.index = 0;
addClass(slides[0], activeClass);
if (this.indicators.length) activateCarouselIndicator(this, 0);
}
if (this.indicators.length) activateCarouselIndicator(this, this.index);
this._toggleEventListeners(true);
if (options.interval) this.cycle();
}
/**
* Returns component name string.
*/
get name() {
return carouselComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return carouselDefaults;
}
/**
* Check if instance is paused.
*/
get isPaused() {
return hasClass(this.element, pausedClass);
}
/**
* Check if instance is animating.
*/
get isAnimating() {
return querySelector(`.${carouselItem}-next,.${carouselItem}-prev`, this.element) !== null;
}
/** Slide automatically through items. */
cycle() {
const { element, options, isPaused, index } = this;
Timer.clear(element, carouselString);
if (isPaused) {
Timer.clear(element, pausedClass);
removeClass(element, pausedClass);
}
Timer.set(element, () => {
if (this.element && !this.isPaused && !this.isTouch && isElementInScrollRange(element)) this.to(index + 1);
}, options.interval, carouselString);
}
/** Pause the automatic cycle. */
pause() {
const { element, options } = this;
if (this.isPaused || !options.interval) return;
addClass(element, pausedClass);
Timer.set(element, () => {}, 1, pausedClass);
}
/** Slide to the next item. */
next() {
if (!this.isAnimating) this.to(this.index + 1);
}
/** Slide to the previous item. */
prev() {
if (!this.isAnimating) this.to(this.index - 1);
}
/**
* Jump to the item with the `idx` index.
*
* @param idx the index of the item to jump to
*/
to(idx) {
const { element, slides, options } = this;
const activeItem = getActiveIndex(this);
const RTL = isRTL(element);
let next = idx;
if (this.isAnimating || activeItem === next || Timer.get(element, dataBsSlide)) return;
if (activeItem < next || activeItem === 0 && next === slides.length - 1) this.direction = RTL ? "right" : "left";
else if (activeItem > next || activeItem === slides.length - 1 && next === 0) this.direction = RTL ? "left" : "right";
const { direction } = this;
if (next < 0) next = slides.length - 1;
else if (next >= slides.length) next = 0;
const orientation = direction === "left" ? "next" : "prev";
const directionClass = direction === "left" ? "start" : "end";
const eventProperties = {
relatedTarget: slides[next],
from: activeItem,
to: next,
direction
};
ObjectAssign(carouselSlideEvent, eventProperties);
ObjectAssign(carouselSlidEvent, eventProperties);
dispatchEvent(element, carouselSlideEvent);
if (carouselSlideEvent.defaultPrevented) return;
this.index = next;
activateCarouselIndicator(this, next);
if (getElementTransitionDuration(slides[next]) && hasClass(element, "slide")) Timer.set(element, () => {
addClass(slides[next], `${carouselItem}-${orientation}`);
reflow(slides[next]);
addClass(slides[next], `${carouselItem}-${directionClass}`);
addClass(slides[activeItem], `${carouselItem}-${directionClass}`);
emulateTransitionEnd(slides[next], () => this.slides && this.slides.length && carouselTransitionEndHandler(this));
}, 0, dataBsSlide);
else {
addClass(slides[next], activeClass);
removeClass(slides[activeItem], activeClass);
Timer.set(element, () => {
Timer.clear(element, dataBsSlide);
if (element && options.interval && !this.isPaused) this.cycle();
dispatchEvent(element, carouselSlidEvent);
}, 0, dataBsSlide);
}
}
/**
* Toggles all event listeners for the `Carousel` instance.
*
* @param add when `TRUE` event listeners are added
*/
_toggleEventListeners = (add) => {
const { element, options, slides, controls, indicators } = this;
const { touch, pause, interval, keyboard } = options;
const action = add ? addListener : removeListener;
if (pause && interval) {
action(element, mouseenterEvent, carouselPauseHandler);
action(element, mouseleaveEvent, carouselResumeHandler);
}
if (touch && slides.length > 2) {
action(element, pointerdownEvent, carouselPointerDownHandler, passiveHandler);
action(element, touchstartEvent, carouselDragHandler, { passive: false });
action(element, dragstartEvent, carouselDragHandler, { passive: false });
}
if (controls.length) controls.forEach((arrow) => {
action(arrow, mouseclickEvent, carouselControlsHandler);
});
if (indicators.length) indicators.forEach((indicator) => {
action(indicator, mouseclickEvent, carouselIndicatorHandler);
});
if (keyboard) action(getDocument(element), keydownEvent, carouselKeyHandler);
};
/** Remove `Carousel` component from target. */
dispose() {
const { isAnimating } = this;
const clone = {
...this,
isAnimating
};
this._toggleEventListeners();
super.dispose();
if (clone.isAnimating) emulateTransitionEnd(clone.slides[clone.index], () => {
carouselTransitionEndHandler(clone);
});
}
};
//#endregion
//#region src/strings/collapsingClass.ts
/**
* Global namespace for most components `collapsing` class.
* As used by `Collapse` / `Tab`.
*/
const collapsingClass = "collapsing";
//#endregion
//#region src/strings/collapseString.ts
/** @type {string} */
const collapseString = "collapse";
//#endregion
//#region src/strings/collapseComponent.ts
/** @type {string} */
const collapseComponent = "Collapse";
//#endregion
//#region src/components/collapse.ts
const collapseSelector = `.${collapseString}`;
const collapseToggleSelector = `[${dataBsToggle}="${collapseString}"]`;
const collapseDefaults = { parent: null };
/**
* Static method which returns an existing `Collapse` instance associated
* to a target `Element`.
*/
const getCollapseInstance = (element) => getInstance(element, collapseComponent);
/**
* A `Collapse` initialization callback.
*/
const collapseInitCallback = (element) => new Collapse(element);
const showCollapseEvent = createCustomEvent(`show.bs.${collapseString}`);
const shownCollapseEvent = createCustomEvent(`shown.bs.${collapseString}`);
const hideCollapseEvent = createCustomEvent(`hide.bs.${collapseString}`);
const hiddenCollapseEvent = createCustomEvent(`hidden.bs.${collapseString}`);
/**
* Expand the designated `Element`.
*
* @param self the `Collapse` instance
*/
const expandCollapse = (self) => {
const { element, parent, triggers } = self;
dispatchEvent(element, showCollapseEvent);
if (!showCollapseEvent.defaultPrevented) {
Timer.set(element, noop, 17);
if (parent) Timer.set(parent, noop, 17);
addClass(element, collapsingClass);
removeClass(element, collapseString);
setElementStyle(element, { height: `${element.scrollHeight}px` });
emulateTransitionEnd(element, () => {
Timer.clear(element);
if (parent) Timer.clear(parent);
triggers.forEach((btn) => setAttribute(btn, ariaExpanded, "true"));
removeClass(element, collapsingClass);
addClass(element, collapseString);
addClass(element, showClass);
setElementStyle(element, { height: "" });
dispatchEvent(element, shownCollapseEvent);
});
}
};
/**
* Collapse the designated `Element`.
*
* @param self the `Collapse` instance
*/
const collapseContent = (self) => {
const { element, parent, triggers } = self;
dispatchEvent(element, hideCollapseEvent);
if (!hideCollapseEvent.defaultPrevented) {
Timer.set(element, noop, 17);
if (parent) Timer.set(parent, noop, 17);
setElementStyle(element, { height: `${element.scrollHeight}px` });
removeClass(element, collapseString);
removeClass(element, showClass);
addClass(element, collapsingClass);
reflow(element);
setElementStyle(element, { height: "0px" });
emulateTransitionEnd(element, () => {
Timer.clear(element);
if (parent) Timer.clear(parent);
triggers.forEach((btn) => setAttribute(btn, ariaExpanded, "false"));
removeClass(element, collapsingClass);
addClass(element, collapseString);
setElementStyle(element, { height: "" });
dispatchEvent(element, hiddenCollapseEvent);
});
}
};
/**
* Handles the `click` event for the `Collapse` instance.
*
* @param e the `Event` object
*/
const collapseClickHandler = (e) => {
const { target } = e;
const trigger = target && closest(target, collapseToggleSelector);
const element = trigger && getTargetElement(trigger);
const self = element && getCollapseInstance(element);
if (trigger && isDisabled(trigger)) return;
if (!self) return;
self.toggle();
if (trigger?.tagName === "A") e.preventDefault();
};
/** Returns a new `Colapse` instance. */
var Collapse = class extends BaseComponent {
static selector = collapseSelector;
static init = collapseInitCallback;
static getInstance = getCollapseInstance;
/**
* @param target and `Element` that matches the selector
* @param config instance options
*/
constructor(target, config) {
super(target, config);
const { element, options } = this;
const doc = getDocument(element);
this.triggers = [...querySelectorAll(collapseToggleSelector, doc)].filter((btn) => getTargetElement(btn) === element);
this.parent = isHTMLElement(options.parent) ? options.parent : isString(options.parent) ? getTargetElement(element) || querySelector(options.parent, doc) : null;
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return collapseComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return collapseDefaults;
}
/** Hides the collapse. */
hide() {
const { triggers, element } = this;
if (!Timer.get(element)) {
collapseContent(this);
if (triggers.length) triggers.forEach((btn) => addClass(btn, `${collapseString}d`));
}
}
/** Shows the collapse. */
show() {
const { element, parent, triggers } = this;
let activeCollapse;
let activeCollapseInstance;
if (parent) {
activeCollapse = [...querySelectorAll(`.${collapseString}.${showClass}`, parent)].find((i) => getCollapseInstance(i));
activeCollapseInstance = activeCollapse && getCollapseInstance(activeCollapse);
}
if ((!parent || !Timer.get(parent)) && !Timer.get(element)) {
if (activeCollapseInstance && activeCollapse !== element) {
collapseContent(activeCollapseInstance);
activeCollapseInstance.triggers.forEach((btn) => {
addClass(btn, `${collapseString}d`);
});
}
expandCollapse(this);
if (triggers.length) triggers.forEach((btn) => removeClass(btn, `${collapseString}d`));
}
}
/** Toggles the visibility of the collapse. */
toggle() {
if (!hasClass(this.element, "show")) this.show();
else this.hide();
}
/**
* Toggles on/off the event listener(s) of the `Collapse` instance.
*
* @param add when `true`, the event listener is added
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
const { triggers } = this;
if (triggers.length) triggers.forEach((btn) => {
action(btn, mouseclickEvent, collapseClickHandler);
});
};
/** Remove the `Collapse` component from the target `Element`. */
dispose() {
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/strings/dropdownClasses.ts
/**
* Global namespace for `Dropdown` types / classes.
*/
const dropdownMenuClasses = [
"dropdown",
"dropup",
"dropstart",
"dropend"
];
//#endregion
//#region src/strings/dropdownComponent.ts
/** @type {string} */
const dropdownComponent = "Dropdown";
//#endregion
//#region src/strings/dropdownMenuClass.ts
/**
* Global namespace for `.dropdown-menu`.
*/
const dropdownMenuClass = "dropdown-menu";
//#endregion
//#region src/util/isEmptyAnchor.ts
/**
* Checks if an *event.target* or its parent has an `href="#"` value.
* We need to prevent jumping around onclick, don't we?
*
* @param element the target element
* @returns the query result
*/
const isEmptyAnchor = (element) => {
const parentAnchor = closest(element, "A");
return element.tagName === "A" && hasAttribute(element, "href") && getAttribute(element, "href")?.slice(-1) === "#" || parentAnchor && hasAttribute(parentAnchor, "href") && getAttribute(parentAnchor, "href")?.slice(-1) === "#";
};
//#endregion
//#region src/components/dropdown.ts
const [dropdownString, dropupString, dropstartString, dropendString] = dropdownMenuClasses;
const dropdownSelector = `[${dataBsToggle}="${dropdownString}"]`;
/**
* Static method which returns an existing `Dropdown` instance associated
* to a target `Element`.
*/
const getDropdownInstance = (element) => getInstance(element, dropdownComponent);
/**
* A `Dropdown` initialization callback.
*/
const dropdownInitCallback = (element) => new Dropdown(element);
const dropdownMenuEndClass = `${dropdownMenuClass}-end`;
const verticalClass = [dropdownString, dropupString];
const horizontalClass = [dropstartString, dropendString];
const menuFocusTags = ["A", "BUTTON"];
const dropdownDefaults = {
offset: 5,
display: "dynamic"
};
const showDropdownEvent = createCustomEvent(`show.bs.${dropdownString}`);
const shownDropdownEvent = createCustomEvent(`shown.bs.${dropdownString}`);
const hideDropdownEvent = createCustomEvent(`hide.bs.${dropdownString}`);
const hiddenDropdownEvent = createCustomEvent(`hidden.bs.${dropdownString}`);
const updatedDropdownEvent = createCustomEvent(`updated.bs.${dropdownString}`);
/**
* Apply specific style or class names to a `.dropdown-menu` to automatically
* accomodate the layout and the page scroll.
*
* @param self the `Dropdown` instance
*/
const styleDropdown = (self) => {
const { element, menu, parentElement, options } = self;
const { offset } = options;
if (getElementStyle(menu, "position") === "static") return;
const RTL = isRTL(element);
const menuEnd = hasClass(menu, dropdownMenuEndClass);
[
"margin",
"top",
"bottom",
"left",
"right"
].forEach((p) => {
const style = {};
style[p] = "";
setElementStyle(menu, style);
});
let positionClass = dropdownMenuClasses.find((c) => hasClass(parentElement, c)) || dropdownString;
const dropdownMargin = {
dropdown: [
offset,
0,
0
],
dropup: [
0,
0,
offset
],
dropstart: RTL ? [
-1,
0,
0,
offset
] : [
-1,
offset,
0
],
dropend: RTL ? [
-1,
offset,
0
] : [
-1,
0,
0,
offset
]
};
const dropdownPosition = {
dropdown: { top: "100%" },
dropup: {
top: "auto",
bottom: "100%"
},
dropstart: RTL ? {
left: "100%",
right: "auto"
} : {
left: "auto",
right: "100%"
},
dropend: RTL ? {
left: "auto",
right: "100%"
} : {
left: "100%",
right: "auto"
},
menuStart: RTL ? {
right: "0",
left: "auto"
} : {
right: "auto",
left: "0"
},
menuEnd: RTL ? {
right: "auto",
left: "0"
} : {
right: "0",
left: "auto"
}
};
const { offsetWidth: menuWidth, offsetHeight: menuHeight } = menu;
const { clientWidth, clientHeight } = getDocumentElement(element);
const { left: targetLeft, top: targetTop, width: targetWidth, height: targetHeight } = getBoundingClientRect(element);
const leftFullExceed = targetLeft - menuWidth - offset < 0;
const rightFullExceed = targetLeft + menuWidth + targetWidth + offset >= clientWidth;
const bottomExceed = targetTop + menuHeight + offset >= clientHeight;
const bottomFullExceed = targetTop + menuHeight + targetHeight + offset >= clientHeight;
const topExceed = targetTop - menuHeight - offset < 0;
const leftExceed = (!RTL && menuEnd || RTL && !menuEnd) && targetLeft + targetWidth - menuWidth < 0;
const rightExceed = (RTL && menuEnd || !RTL && !menuEnd) && targetLeft + menuWidth >= clientWidth;
if (horizontalClass.includes(positionClass) && leftFullExceed && rightFullExceed) positionClass = dropdownString;
if (positionClass === dropstartString && (!RTL ? leftFullExceed : rightFullExceed)) positionClass = dropendString;
if (positionClass === dropendString && (RTL ? leftFullExceed : rightFullExceed)) positionClass = dropstartString;
if (positionClass === dropupString && topExceed && !bottomFullExceed) positionClass = dropdownString;
if (positionClass === dropdownString && bottomFullExceed && !topExceed) positionClass = dropupString;
if (horizontalClass.includes(positionClass) && bottomExceed) ObjectAssign(dropdownPosition[positionClass], {
top: "auto",
bottom: 0
});
if (verticalClass.includes(positionClass) && (leftExceed || rightExceed)) {
let posAjust = {
left: "auto",
right: "auto"
};
if (!leftExceed && rightExceed && !RTL) posAjust = {
left: "auto",
right: 0
};
if (leftExceed && !rightExceed && RTL) posAjust = {
left: 0,
right: "auto"
};
if (posAjust) ObjectAssign(dropdownPosition[positionClass], posAjust);
}
const margins = dropdownMargin[positionClass];
setElementStyle(menu, {
...dropdownPosition[positionClass],
margin: `${margins.map((x) => x ? `${x}px` : x).join(" ")}`
});
if (verticalClass.includes(positionClass) && menuEnd) {
if (menuEnd) setElementStyle(menu, dropdownPosition[!RTL && leftExceed || RTL && rightExceed ? "menuStart" : "menuEnd"]);
}
dispatchEvent(parentElement, updatedDropdownEvent);
};
/**
* Returns an `Array` of focusable items in the given dropdown-menu.
*
* @param menu the target menu
* @returns all children of the dropdown menu
*/
const getMenuItems = (menu) => {
return Array.from(menu.children).map((c) => {
if (c && menuFocusTags.includes(c.tagName)) return c;
const { firstElementChild } = c;
if (firstElementChild && menuFocusTags.includes(firstElementChild.tagName)) return firstElementChild;
return null;
}).filter((c) => c);
};
/**
* Toggles on/off the listeners for the events that close the dropdown
* as well as event that request a new position for the dropdown.
*
* @param {Dropdown} self the `Dropdown` instance
*/
const toggleDropdownDismiss = (self) => {
const { element, options, menu } = self;
const action = self.open ? addListener : removeListener;
const doc = getDocument(element);
action(doc, mouseclickEvent, dropdownDismissHandler);
action(doc, focusEvent, dropdownDismissHandler);
action(doc, keydownEvent, dropdownPreventScroll);
action(doc, keyupEvent, dropdownKeyHandler);
if (options.display === "dynamic") if (self.open) self._observer.observe(menu);
else self._observer.disconnect();
};
/**
* Returns the currently open `.dropdown` element.
*
* @param element target
* @returns the query result
*/
const getCurrentOpenDropdown = (element) => {
const currentParent = [
...dropdownMenuClasses,
"btn-group",
"input-group"
].map((c) => getElementsByClassName(`${c} ${showClass}`, getDocument(element))).find((x) => x.length);
if (currentParent && currentParent.length) return [...currentParent[0].children].find((x) => dropdownMenuClasses.some((c) => c === getAttribute(x, dataBsToggle)));
};
/**
* Handles the `click` event for the `Dropdown` instance.
*
* @param e event object
*/
const dropdownDismissHandler = (e) => {
const { target, type } = e;
if (!isHTMLElement(target)) return;
const element = getCurrentOpenDropdown(target);
const self = element && getDropdownInstance(element);
if (!self) return;
const { parentElement, menu } = self;
const isForm = parentElement && parentElement.contains(target) && (target.tagName === "form" || closest(target, "form") !== null);
if ([mouseclickEvent, mousedownEvent].includes(type) && isEmptyAnchor(target)) e.preventDefault();
if (!isForm && type !== focusEvent && target !== element && target !== menu) self.hide();
};
/**
* Handles `click` event listener for `Dropdown`.
*
* @param e event object
*/
function dropdownClickHandler(e) {
const self = getDropdownInstance(this);
if (isDisabled(this)) return;
if (!self) return;
e.stopPropagation();
self.toggle();
if (isEmptyAnchor(this)) e.preventDefault();
}
/**
* Prevents scroll when dropdown-menu is visible.
*
* @param e event object
*/
const dropdownPreventScroll = (e) => {
if ([keyArrowDown, keyArrowUp].includes(e.code)) e.preventDefault();
};
/**
* Handles keyboard `keydown` events for `Dropdown`.
*
* @param e keyboard key
*/
function dropdownKeyHandler(e) {
const { code } = e;
const element = getCurrentOpenDropdown(this);
if (!element) return;
const self = getDropdownInstance(element);
const { activeElement } = getDocument(element);
if (!self || !activeElement) return;
const { menu, open } = self;
const menuItems = getMenuItems(menu);
if (menuItems && menuItems.length && [keyArrowDown, keyArrowUp].includes(code)) {
let idx = menuItems.indexOf(activeElement);
if (activeElement === element) idx = 0;
else if (code === keyArrowUp) idx = idx > 1 ? idx - 1 : 0;
else if (code === keyArrowDown) idx = idx < menuItems.length - 1 ? idx + 1 : idx;
if (menuItems[idx]) focus(menuItems[idx]);
}
if (keyEscape === code && open) {
self.toggle();
focus(element);
}
}
/** Returns a new Dropdown instance. */
var Dropdown = class extends BaseComponent {
static selector = dropdownSelector;
static init = dropdownInitCallback;
static getInstance = getDropdownInstance;
/**
* @param target Element or string selector
* @param config the instance options
*/
constructor(target, config) {
super(target, config);
const { parentElement } = this.element;
const [menu] = getElementsByClassName(dropdownMenuClass, parentElement);
if (!menu) return;
this.parentElement = parentElement;
this.menu = menu;
this._observer = new PositionObserver(() => styleDropdown(this));
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return dropdownComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return dropdownDefaults;
}
/** Shows/hides the dropdown menu to the user. */
toggle() {
if (this.open) this.hide();
else this.show();
}
/** Shows the dropdown menu to the user. */
show() {
const { element, open, menu, parentElement } = this;
if (open) return;
const currentElement = getCurrentOpenDropdown(element);
const currentInstance = currentElement && getDropdownInstance(currentElement);
if (currentInstance) currentInstance.hide();
[
showDropdownEvent,
shownDropdownEvent,
updatedDropdownEvent
].forEach((e) => {
e.relatedTarget = element;
});
dispatchEvent(parentElement, showDropdownEvent);
if (showDropdownEvent.defaultPrevented) return;
addClass(menu, showClass);
addClass(parentElement, showClass);
setAttribute(element, ariaExpanded, "true");
styleDropdown(this);
this.open = !open;
focus(element);
toggleDropdownDismiss(this);
dispatchEvent(parentElement, shownDropdownEvent);
}
/** Hides the dropdown menu from the user. */
hide() {
const { element, open, menu, parentElement } = this;
if (!open) return;
[hideDropdownEvent, hiddenDropdownEvent].forEach((e) => {
e.relatedTarget = element;
});
dispatchEvent(parentElement, hideDropdownEvent);
if (hideDropdownEvent.defaultPrevented) return;
removeClass(menu, showClass);
removeClass(parentElement, showClass);
setAttribute(element, ariaExpanded, "false");
this.open = !open;
toggleDropdownDismiss(this);
dispatchEvent(parentElement, hiddenDropdownEvent);
}
/**
* Toggles on/off the `click` event listener of the `Dropdown`.
*
* @param add when `true`, it will add the event listener
*/
_toggleEventListeners = (add) => {
(add ? addListener : removeListener)(this.element, mouseclickEvent, dropdownClickHandler);
};
/** Removes the `Dropdown` component from the target element. */
dispose() {
if (this.open) this.hide();
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/strings/modalString.ts
/** @type {string} */
const modalString = "modal";
//#endregion
//#region src/strings/modalComponent.ts
/** @type {string} */
const modalComponent = "Modal";
//#endregion
//#region src/strings/offcanvasComponent.ts
/** @type {string} */
const offcanvasComponent = "Offcanvas";
//#endregion
//#region src/strings/fixedTopClass.ts
/**
* Global namespace for components `fixed-top` class.
*/
const fixedTopClass = "fixed-top";
//#endregion
//#region src/strings/fixedBottomClass.ts
/**
* Global namespace for components `fixed-bottom` class.
*/
const fixedBottomClass = "fixed-bottom";
//#endregion
//#region src/strings/stickyTopClass.ts
/**
* Global namespace for components `sticky-top` class.
*/
const stickyTopClass = "sticky-top";
//#endregion
//#region src/strings/positionStickyClass.ts
/**
* Global namespace for components `position-sticky` class.
*/
const positionStickyClass = "position-sticky";
//#endregion
//#region src/util/scrollbar.ts
const getFixedItems = (parent) => [
...getElementsByClassName(fixedTopClass, parent),
...getElementsByClassName(fixedBottomClass, parent),
...getElementsByClassName(stickyTopClass, parent),
...getElementsByClassName(positionStickyClass, parent),
...getElementsByClassName("is-fixed", parent)
];
/**
* Removes *padding* and *overflow* from the `<body>`
* and all spacing from fixed items.
*
* @param element the target modal/offcanvas
*/
const resetScrollbar = (element) => {
const bd = getDocumentBody(element);
setElementStyle(bd, {
paddingRight: "",
overflow: ""
});
const fixedItems = getFixedItems(bd);
if (fixedItems.length) fixedItems.forEach((fixed) => {
setElementStyle(fixed, {
paddingRight: "",
marginRight: ""
});
});
};
/**
* Returns the scrollbar width if the body does overflow
* the window.
*
* @param element target element
* @returns the scrollbar width value
*/
const measureScrollbar = (element) => {
const { clientWidth } = getDocumentElement(element);
const { innerWidth } = getWindow(element);
return Math.abs(innerWidth - clientWidth);
};
/**
* Sets the `<body>` and fixed items style when modal / offcanvas
* is shown to the user.
*
* @param element the target modal/offcanvas
* @param overflow body does overflow or not
*/
const setScrollbar = (element, overflow) => {
const bd = getDocumentBody(element);
const bodyPad = parseInt(getElementStyle(bd, "paddingRight"), 10);
const sbWidth = getElementStyle(bd, "overflow") === "hidden" && bodyPad ? 0 : measureScrollbar(element);
const fixedItems = getFixedItems(bd);
if (!overflow) return;
setElementStyle(bd, {
overflow: "hidden",
paddingRight: `${bodyPad + sbWidth}px`
});
if (!fixedItems.length) return;
fixedItems.forEach((fixed) => {
const itemPadValue = getElementStyle(fixed, "paddingRight");
fixed.style.paddingRight = `${parseInt(itemPadValue, 10) + sbWidth}px`;
if (["sticky-top", "position-sticky"].some((c) => hasClass(fixed, c))) {
const itemMValue = getElementStyle(fixed, "marginRight");
fixed.style.marginRight = `${parseInt(itemMValue, 10) - sbWidth}px`;
}
});
};
//#endregion
//#region src/strings/offcanvasString.ts
/** @type {string} */
const offcanvasString = "offcanvas";
//#endregion
//#region src/util/popupContainer.ts
const popupContainer = createElement({
tagName: "div",
className: "popup-container"
});
const appendPopup = (target, customContainer) => {
const containerIsBody = isNode(customContainer) && customContainer.nodeName === "BODY";
const lookup = isNode(customContainer) && !containerIsBody ? customContainer : popupContainer;
const BODY = containerIsBody ? customContainer : getDocumentBody(target);
if (isNode(target)) {
if (lookup === popupContainer) BODY.append(popupContainer);
lookup.append(target);
}
};
const removePopup = (target, customContainer) => {
const containerIsBody = isNode(customContainer) && customContainer.nodeName === "BODY";
const lookup = isNode(customContainer) && !containerIsBody ? customContainer : popupContainer;
if (isNode(target)) {
target.remove();
if (lookup === popupContainer && !popupContainer.children.length) popupContainer.remove();
}
};
const hasPopup = (target, customContainer) => {
const lookup = isNode(customContainer) && customContainer.nodeName !== "BODY" ? customContainer : popupContainer;
return isNode(target) && lookup.contains(target);
};
//#endregion
//#region src/util/backdrop.ts
const backdropString = "backdrop";
const modalBackdropClass = `${modalString}-${backdropString}`;
const offcanvasBackdropClass = `${offcanvasString}-${backdropString}`;
const modalActiveSelector = `.${modalString}.${showClass}`;
const offcanvasActiveSelector = `.${offcanvasString}.${showClass}`;
const overlay = createElement("div");
/**
* Returns the current active modal / offcancas element.
*
* @param element the context element
* @returns the requested element
*/
const getCurrentOpen = (element) => {
return querySelector(`${modalActiveSelector},${offcanvasActiveSelector}`, getDocument(element));
};
/**
* Toogles from a Modal overlay to an Offcanvas, or vice-versa.
*
* @param isModal
*/
const toggleOverlayType = (isModal) => {
const targetClass = isModal ? modalBackdropClass : offcanvasBackdropClass;
[modalBackdropClass, offcanvasBackdropClass].forEach((c) => {
removeClass(overlay, c);
});
addClass(overlay, targetClass);
};
/**
* Append the overlay to DOM.
*
* @param element
* @param hasFade
* @param isModal
*/
const appendOverlay = (element, hasFade, isModal) => {
toggleOverlayType(isModal);
appendPopup(overlay, getDocumentBody(element));
if (hasFade) addClass(overlay, fadeClass);
};
/**
* Shows the overlay to the user.
*/
const showOverlay = () => {
if (!hasClass(overlay, "show")) {
addClass(overlay, showClass);
reflow(overlay);
}
};
/**
* Hides the overlay from the user.
*/
const hideOverlay = () => {
removeClass(overlay, showClass);
};
/**
* Removes the overlay from DOM.
*
* @param element
*/
const removeOverlay = (element) => {
if (!getCurrentOpen(element)) {
removeClass(overlay, fadeClass);
removePopup(overlay, getDocumentBody(element));
resetScrollbar(element);
}
};
//#endregion
//#region src/util/isVisible.ts
/**
* @param element target
* @returns the check result
*/
const isVisible = (element) => {
return isHTMLElement(element) && getElementStyle(element, "visibility") !== "hidden" && element.offsetParent !== null;
};
//#endregion
//#region src/components/modal.ts
const modalSelector = `.${modalString}`;
const modalToggleSelector = `[${dataBsToggle}="${modalString}"]`;
const modalDismissSelector = `[${dataBsDismiss}="${modalString}"]`;
const modalStaticClass = `${modalString}-static`;
const modalDefaults = {
backdrop: true,
keyboard: true
};
/**
* Static method which returns an existing `Modal` instance associated
* to a target `Element`.
*/
const getModalInstance = (element) => getInstance(element, modalComponent);
/**
* A `Modal` initialization callback.
*/
const modalInitCallback = (element) => new Modal(element);
const showModalEvent = createCustomEvent(`show.bs.${modalString}`);
const shownModalEvent = createCustomEvent(`shown.bs.${modalString}`);
const hideModalEvent = createCustomEvent(`hide.bs.${modalString}`);
const hiddenModalEvent = createCustomEvent(`hidden.bs.${modalString}`);
/**
* Applies special style for the `<body>` and fixed elements
* when a modal instance is shown to the user.
*
* @param self the `Modal` instance
*/
const setModalScrollbar = (self) => {
const { element } = self;
const scrollbarWidth = measureScrollbar(element);
const { clientHeight, scrollHeight } = getDocumentElement(element);
const { clientHeight: modalHeight, scrollHeight: modalScrollHeight } = element;
const modalOverflow = modalHeight !== modalScrollHeight;
if (!modalOverflow && scrollbarWidth) setElementStyle(element, { [!isRTL(element) ? "paddingRight" : "paddingLeft"]: `${scrollbarWidth}px` });
setScrollbar(element, modalOverflow || clientHeight !== scrollHeight);
};
/**
* Toggles on/off the listeners of events that close the modal.
*
* @param self the `Modal` instance
* @param add when `true`, event listeners are added
*/
const toggleModalDismiss = (self, add) => {
const action = add ? addListener : removeListener;
const { element } = self;
action(element, mouseclickEvent, modalDismissHandler);
action(getDocument(element), keydownEvent, modalKeyHandler);
if (add) self._observer.observe(element);
else self._observer.disconnect();
};
/**
* Executes after a modal is hidden to the user.
*
* @param self the `Modal` instance
*/
const afterModalHide = (self) => {
const { triggers, element, relatedTarget } = self;
removeOverlay(element);
setElementStyle(element, {
paddingRight: "",
display: ""
});
toggleModalDismiss(self);
const focusElement = showModalEvent.relatedTarget || triggers.find(isVisible);
if (focusElement) focus(focusElement);
hiddenModalEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, hiddenModalEvent);
toggleFocusTrap(element);
};
/**
* Executes after a modal is shown to the user.
*
* @param self the `Modal` instance
*/
const afterModalShow = (self) => {
const { element, relatedTarget } = self;
focus(element);
toggleModalDismiss(self, true);
shownModalEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, shownModalEvent);
toggleFocusTrap(element);
};
/**
* Executes before a modal is shown to the user.
*
* @param self the `Modal` instance
*/
const beforeModalShow = (self) => {
const { element, hasFade } = self;
setElementStyle(element, { display: "block" });
setModalScrollbar(self);
if (!getCurrentOpen(element)) setElementStyle(getDocumentBody(element), { overflow: "hidden" });
addClass(element, showClass);
removeAttribute(element, ariaHidden);
setAttribute(element, ariaModal, "true");
if (hasFade) emulateTransitionEnd(element, () => afterModalShow(self));
else afterModalShow(self);
};
/**
* Executes before a modal is hidden to the user.
*
* @param self the `Modal` instance
*/
const beforeModalHide = (self) => {
const { element, options, hasFade } = self;
if (options.backdrop && hasFade && hasClass(overlay, "show") && !getCurrentOpen(element)) {
hideOverlay();
emulateTransitionEnd(overlay, () => afterModalHide(self));
} else afterModalHide(self);
};
/**
* Handles the `click` event listener for modal.
*
* @param e the `Event` object
*/
function modalClickHandler(e) {
const element = getTargetElement(this);
const self = element && getModalInstance(element);
if (isDisabled(this)) return;
if (!self) return;
if (this.tagName === "A") e.preventDefault();
self.relatedTarget = this;
self.toggle();
}
/**
* Handles the `keydown` event listener for modal
* to hide the modal when user type the `ESC` key.
*
* @param e the `Event` object
*/
const modalKeyHandler = ({ code, target }) => {
const element = querySelector(modalActiveSelector, getDocument(target));
const self = element && getModalInstance(element);
if (!self) return;
const { options } = self;
if (options.keyboard && code === keyEscape && hasClass(element, "show")) {
self.relatedTarget = null;
self.hide();
}
};
/**
* Handles the `click` event listeners that hide the modal.
*
* @param e the `Event` object
*/
const modalDismissHandler = (e) => {
const { currentTarget } = e;
const self = currentTarget && getModalInstance(currentTarget);
if (!self || !currentTarget || Timer.get(currentTarget)) return;
const { options, isStatic, modalDialog } = self;
const { backdrop } = options;
const { target } = e;
const selectedText = getDocument(currentTarget)?.getSelection()?.toString().length;
const targetInsideDialog = modalDialog.contains(target);
const dismiss = target && closest(target, modalDismissSelector);
if (isStatic && !targetInsideDialog) Timer.set(currentTarget, () => {
addClass(currentTarget, modalStaticClass);
emulateTransitionEnd(modalDialog, () => staticTransitionEnd(self));
}, 17);
else if (dismiss || !selectedText && !isStatic && !targetInsideDialog && backdrop) {
self.relatedTarget = dismiss || null;
self.hide();
e.preventDefault();
}
};
/**
* Handles the `transitionend` event listeners for `Modal`.
*
* @param self the `Modal` instance
*/
const staticTransitionEnd = (self) => {
const { element, modalDialog } = self;
const duration = (getElementTransitionDuration(modalDialog) || 0) + 17;
removeClass(element, modalStaticClass);
Timer.set(element, () => Timer.clear(element), duration);
};
/** Returns a new `Modal` instance. */
var Modal = class extends BaseComponent {
static selector = modalSelector;
static init = modalInitCallback;
static getInstance = getModalInstance;
/**
* @param target usually the `.modal` element
* @param config instance options
*/
constructor(target, config) {
super(target, config);
const { element } = this;
const modalDialog = querySelector(`.${modalString}-dialog`, element);
if (!modalDialog) return;
this.modalDialog = modalDialog;
this.triggers = [...querySelectorAll(modalToggleSelector, getDocument(element))].filter((btn) => getTargetElement(btn) === element);
this.isStatic = this.options.backdrop === "static";
this.hasFade = hasClass(element, fadeClass);
this.relatedTarget = null;
this._observer = new ResizeObserver(() => this.update());
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return modalComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return modalDefaults;
}
/** Toggles the visibility of the modal. */
toggle() {
if (hasClass(this.element, "show")) this.hide();
else this.show();
}
/** Shows the modal to the user. */
show() {
const { element, options, hasFade, relatedTarget } = this;
const { backdrop } = options;
let overlayDelay = 0;
if (hasClass(element, "show")) return;
showModalEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, showModalEvent);
if (showModalEvent.defaultPrevented) return;
const currentOpen = getCurrentOpen(element);
if (currentOpen && currentOpen !== element) {
const that = getModalInstance(currentOpen) || getInstance(currentOpen, "Offcanvas");
if (that) that.hide();
}
if (backdrop) {
if (!hasPopup(overlay)) appendOverlay(element, hasFade, true);
else toggleOverlayType(true);
overlayDelay = getElementTransitionDuration(overlay);
showOverlay();
setTimeout(() => beforeModalShow(this), overlayDelay);
} else {
beforeModalShow(this);
if (currentOpen && hasClass(overlay, "show")) hideOverlay();
}
}
/** Hide the modal from the user. */
hide() {
const { element, hasFade, relatedTarget } = this;
if (!hasClass(element, "show")) return;
hideModalEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, hideModalEvent);
if (hideModalEvent.defaultPrevented) return;
removeClass(element, showClass);
setAttribute(element, ariaHidden, "true");
removeAttribute(element, ariaModal);
if (hasFade) emulateTransitionEnd(element, () => beforeModalHide(this));
else beforeModalHide(this);
}
/**
* Updates the modal layout.
*/
update = () => {
if (hasClass(this.element, "show")) setModalScrollbar(this);
};
/**
* Toggles on/off the `click` event listener of the `Modal` instance.
*
* @param add when `true`, event listener(s) is/are added
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
const { triggers } = this;
if (!triggers.length) return;
triggers.forEach((btn) => {
action(btn, mouseclickEvent, modalClickHandler);
});
};
/** Removes the `Modal` component from target element. */
dispose() {
const { modalDialog, hasFade } = { ...this };
const callback = () => setTimeout(() => super.dispose(), 17);
this.hide();
this._toggleEventListeners();
if (hasFade) emulateTransitionEnd(modalDialog, callback);
else callback();
}
};
//#endregion
//#region src/components/offcanvas.ts
const offcanvasSelector = `.${offcanvasString}`;
const offcanvasToggleSelector = `[${dataBsToggle}="${offcanvasString}"]`;
const offcanvasDismissSelector = `[${dataBsDismiss}="${offcanvasString}"]`;
const offcanvasTogglingClass = `${offcanvasString}-toggling`;
const offcanvasDefaults = {
backdrop: true,
keyboard: true,
scroll: false
};
/**
* Static method which returns an existing `Offcanvas` instance associated
* to a target `Element`.
*/
const getOffcanvasInstance = (element) => getInstance(element, offcanvasComponent);
/**
* An `Offcanvas` initialization callback.
*/
const offcanvasInitCallback = (element) => new Offcanvas(element);
const showOffcanvasEvent = createCustomEvent(`show.bs.${offcanvasString}`);
const shownOffcanvasEvent = createCustomEvent(`shown.bs.${offcanvasString}`);
const hideOffcanvasEvent = createCustomEvent(`hide.bs.${offcanvasString}`);
const hiddenOffcanvasEvent = createCustomEvent(`hidden.bs.${offcanvasString}`);
/**
* Sets additional style for the `<body>` and other elements
* when showing an offcanvas to the user.
*
* @param self the `Offcanvas` instance
*/
const setOffCanvasScrollbar = (self) => {
const { element } = self;
const { clientHeight, scrollHeight } = getDocumentElement(element);
setScrollbar(element, clientHeight !== scrollHeight);
};
/**
* Toggles on/off the listeners of the events that close the offcanvas.
*
* @param self the `Offcanvas` instance
* @param add when *true* listeners are added
*/
const toggleOffCanvasDismiss = (self, add) => {
const action = add ? addListener : removeListener;
const doc = getDocument(self.element);
action(doc, keydownEvent, offcanvasKeyDismissHandler);
action(doc, mouseclickEvent, offcanvasDismissHandler);
};
/**
* Executes before showing the offcanvas.
*
* @param self the `Offcanvas` instance
*/
const beforeOffcanvasShow = (self) => {
const { element, options } = self;
if (!options.scroll) {
setOffCanvasScrollbar(self);
setElementStyle(getDocumentBody(element), { overflow: "hidden" });
}
addClass(element, offcanvasTogglingClass);
addClass(element, showClass);
setElementStyle(element, { visibility: "visible" });
emulateTransitionEnd(element, () => showOffcanvasComplete(self));
};
/**
* Executes before hiding the offcanvas.
*
* @param self the `Offcanvas` instance
*/
const beforeOffcanvasHide = (self) => {
const { element, options } = self;
const currentOpen = getCurrentOpen(element);
element.blur();
if (!currentOpen && options.backdrop && hasClass(overlay, "show")) hideOverlay();
emulateTransitionEnd(element, () => hideOffcanvasComplete(self));
};
/**
* Handles the `click` event listeners.
*
* @param e the `Event` object
*/
function offcanvasTriggerHandler(e) {
const element = getTargetElement(this);
const self = element && getOffcanvasInstance(element);
if (isDisabled(this)) return;
if (!self) return;
self.relatedTarget = this;
self.toggle();
if (this.tagName === "A") e.preventDefault();
}
/**
* Handles the event listeners that close the offcanvas.
*
* @param e the `Event` object
*/
const offcanvasDismissHandler = (e) => {
const { target } = e;
const element = querySelector(offcanvasActiveSelector, getDocument(target));
if (!element) return;
const offCanvasDismiss = querySelector(offcanvasDismissSelector, element);
const self = getOffcanvasInstance(element);
if (!self) return;
const { options, triggers } = self;
const { backdrop } = options;
const trigger = closest(target, offcanvasToggleSelector);
const selection = getDocument(element).getSelection();
if (overlay.contains(target) && backdrop === "static") return;
const isOwnTrigger = triggers.includes(target);
const isOwnTarget = offCanvasDismiss?.contains(target) || false;
if (!(selection && selection.toString().length) && (!element.contains(target) && backdrop && (!trigger || isOwnTrigger) || isOwnTarget)) {
self.relatedTarget = offCanvasDismiss && isOwnTarget ? offCanvasDismiss : void 0;
self.hide();
}
if (trigger && trigger.tagName === "A") e.preventDefault();
};
/**
* Handles the `keydown` event listener for offcanvas
* to hide it when user type the `ESC` key.
*
* @param e the `Event` object
*/
const offcanvasKeyDismissHandler = ({ code, target }) => {
const element = querySelector(offcanvasActiveSelector, getDocument(target));
const self = element && getOffcanvasInstance(element);
if (!self) return;
if (self.options.keyboard && code === keyEscape) {
self.relatedTarget = void 0;
self.hide();
}
};
/**
* Handles the `transitionend` when showing the offcanvas.
*
* @param self the `Offcanvas` instance
*/
const showOffcanvasComplete = (self) => {
const { element } = self;
removeClass(element, offcanvasTogglingClass);
removeAttribute(element, ariaHidden);
setAttribute(element, ariaModal, "true");
setAttribute(element, "role", "dialog");
dispatchEvent(element, shownOffcanvasEvent);
toggleOffCanvasDismiss(self, true);
focus(element);
toggleFocusTrap(element);
};
/**
* Handles the `transitionend` when hiding the offcanvas.
*
* @param self the `Offcanvas` instance
*/
const hideOffcanvasComplete = (self) => {
const { element, triggers } = self;
setAttribute(element, ariaHidden, "true");
removeAttribute(element, ariaModal);
removeAttribute(element, "role");
setElementStyle(element, { visibility: "" });
const visibleTrigger = showOffcanvasEvent.relatedTarget || triggers.find(isVisible);
if (visibleTrigger) focus(visibleTrigger);
removeOverlay(element);
dispatchEvent(element, hiddenOffcanvasEvent);
removeClass(element, offcanvasTogglingClass);
toggleFocusTrap(element);
if (!getCurrentOpen(element)) toggleOffCanvasDismiss(self);
};
/** Returns a new `Offcanvas` instance. */
var Offcanvas = class extends BaseComponent {
static selector = offcanvasSelector;
static init = offcanvasInitCallback;
static getInstance = getOffcanvasInstance;
/**
* @param target usually an `.offcanvas` element
* @param config instance options
*/
constructor(target, config) {
super(target, config);
const { element } = this;
this.triggers = [...querySelectorAll(offcanvasToggleSelector, getDocument(element))].filter((btn) => getTargetElement(btn) === element);
this.relatedTarget = void 0;
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return offcanvasComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return offcanvasDefaults;
}
/** Shows or hides the offcanvas from the user. */
toggle() {
if (hasClass(this.element, "show")) this.hide();
else this.show();
}
/** Shows the offcanvas to the user. */
show() {
const { element, options, relatedTarget } = this;
let overlayDelay = 0;
if (hasClass(element, "show")) return;
showOffcanvasEvent.relatedTarget = relatedTarget || void 0;
shownOffcanvasEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, showOffcanvasEvent);
if (showOffcanvasEvent.defaultPrevented) return;
const currentOpen = getCurrentOpen(element);
if (currentOpen && currentOpen !== element) {
const that = getOffcanvasInstance(currentOpen) || getInstance(currentOpen, "Modal");
if (that) that.hide();
}
if (options.backdrop) {
if (!hasPopup(overlay)) appendOverlay(element, true);
else toggleOverlayType();
overlayDelay = getElementTransitionDuration(overlay);
showOverlay();
setTimeout(() => beforeOffcanvasShow(this), overlayDelay);
} else {
beforeOffcanvasShow(this);
if (currentOpen && hasClass(overlay, "show")) hideOverlay();
}
}
/** Hides the offcanvas from the user. */
hide() {
const { element, relatedTarget } = this;
if (!hasClass(element, "show")) return;
hideOffcanvasEvent.relatedTarget = relatedTarget || void 0;
hiddenOffcanvasEvent.relatedTarget = relatedTarget || void 0;
dispatchEvent(element, hideOffcanvasEvent);
if (hideOffcanvasEvent.defaultPrevented) return;
addClass(element, offcanvasTogglingClass);
removeClass(element, showClass);
beforeOffcanvasHide(this);
}
/**
* Toggles on/off the `click` event listeners.
*
* @param self the `Offcanvas` instance
* @param add when *true*, listeners are added
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
this.triggers.forEach((btn) => {
action(btn, mouseclickEvent, offcanvasTriggerHandler);
});
};
/** Removes the `Offcanvas` from the target element. */
dispose() {
const { element } = this;
const isOpen = hasClass(element, showClass);
const callback = () => setTimeout(() => super.dispose(), 1);
this.hide();
this._toggleEventListeners();
if (isOpen) emulateTransitionEnd(element, callback);
else callback();
}
};
//#endregion
//#region src/strings/popoverString.ts
/** @type {string} */
const popoverString = "popover";
//#endregion
//#region src/strings/popoverComponent.ts
/** @type {string} */
const popoverComponent = "Popover";
//#endregion
//#region src/strings/tooltipString.ts
/** @type {string} */
const tooltipString = "tooltip";
//#endregion
//#region src/util/getTipTemplate.ts
/**
* Returns a template for Popover / Tooltip.
*
* @param tipType the expected markup type
* @returns the template markup
*/
const getTipTemplate = (tipType) => {
const isTooltip = tipType === tooltipString;
const bodyClass = isTooltip ? `${tipType}-inner` : `${tipType}-body`;
const header = !isTooltip ? `<h3 class="${tipType}-header"></h3>` : "";
const arrow = `<div class="${tipType}-arrow"></div>`;
const body = `<div class="${bodyClass}"></div>`;
return `<div class="${tipType}" role="${tooltipString}">${header + arrow + body}</div>`;
};
//#endregion
//#region src/util/tipClassPositions.ts
const tipClassPositions = {
top: "top",
bottom: "bottom",
left: "start",
right: "end"
};
//#endregion
//#region src/util/styleTip.ts
/**
* Style popovers and tooltips.
*
* @param self the `Popover` / `Tooltip` instance
*/
const styleTip = (self) => {
requestAnimationFrame(() => {
const tipClasses = /\b(top|bottom|start|end)+/;
const { element, tooltip, container, offsetParent, options, arrow } = self;
if (!tooltip) return;
const RTL = isRTL(element);
const { x: scrollLeft, y: scrollTop } = getNodeScroll(offsetParent);
setElementStyle(tooltip, {
top: "",
left: "",
right: "",
bottom: ""
});
const { offsetWidth: tipWidth, offsetHeight: tipHeight } = tooltip;
const { clientWidth: htmlcw, clientHeight: htmlch, offsetWidth: htmlow } = getDocumentElement(element);
let { placement } = options;
const { clientWidth: parentCWidth, offsetWidth: parentOWidth } = container;
const fixedParent = getElementStyle(container, "position") === "fixed";
const scrollbarWidth = fixedParent ? Math.abs(parentCWidth - parentOWidth) : Math.abs(htmlcw - htmlow);
const leftBoundry = RTL && fixedParent ? scrollbarWidth : 0;
const rightBoundry = htmlcw - (!RTL ? scrollbarWidth : 0) - 1;
const { width: elemWidth, height: elemHeight, left: elemRectLeft, right: elemRectRight, top: elemRectTop } = self._observer.getEntry(element)?.boundingClientRect || getBoundingClientRect(element, true);
const { x: elemOffsetLeft, y: elemOffsetTop } = getRectRelativeToOffsetParent(element, offsetParent, {
x: scrollLeft,
y: scrollTop
});
setElementStyle(arrow, {
top: "",
left: "",
right: "",
bottom: ""
});
let topPosition = 0;
let bottomPosition = "";
let leftPosition = 0;
let rightPosition = "";
let arrowTop = "";
let arrowLeft = "";
let arrowRight = "";
const arrowWidth = arrow.offsetWidth || 0;
const arrowHeight = arrow.offsetHeight || 0;
const arrowAdjust = arrowWidth / 2;
let topExceed = elemRectTop - tipHeight - arrowHeight < 0;
let bottomExceed = elemRectTop + tipHeight + elemHeight + arrowHeight >= htmlch;
let leftExceed = elemRectLeft - tipWidth - arrowWidth < leftBoundry;
let rightExceed = elemRectLeft + tipWidth + elemWidth + arrowWidth >= rightBoundry;
const horizontals = ["left", "right"];
const verticals = ["top", "bottom"];
topExceed = horizontals.includes(placement) ? elemRectTop + elemHeight / 2 - tipHeight / 2 - arrowHeight < 0 : topExceed;
bottomExceed = horizontals.includes(placement) ? elemRectTop + tipHeight / 2 + elemHeight / 2 + arrowHeight >= htmlch : bottomExceed;
leftExceed = verticals.includes(placement) ? elemRectLeft + elemWidth / 2 - tipWidth / 2 < leftBoundry : leftExceed;
rightExceed = verticals.includes(placement) ? elemRectLeft + tipWidth / 2 + elemWidth / 2 >= rightBoundry : rightExceed;
placement = horizontals.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;
if (!tooltip.className.includes(placement)) tooltip.className = tooltip.className.replace(tipClasses, tipClassPositions[placement]);
if (horizontals.includes(placement)) {
if (placement === "left") leftPosition = elemOffsetLeft - tipWidth - arrowWidth;
else leftPosition = elemOffsetLeft + elemWidth + arrowWidth;
if (topExceed && bottomExceed) {
topPosition = 0;
bottomPosition = 0;
arrowTop = elemOffsetTop + elemHeight / 2 - arrowHeight / 2;
} else if (topExceed) {
topPosition = elemOffsetTop;
bottomPosition = "";
arrowTop = elemHeight / 2 - arrowWidth;
} else if (bottomExceed) {
topPosition = elemOffsetTop - tipHeight + elemHeight;
bottomPosition = "";
arrowTop = tipHeight - elemHeight / 2 - arrowWidth;
} else {
topPosition = elemOffsetTop - tipHeight / 2 + elemHeight / 2;
arrowTop = tipHeight / 2 - arrowHeight / 2;
}
} else if (verticals.includes(placement)) {
if (placement === "top") topPosition = elemOffsetTop - tipHeight - arrowHeight;
else topPosition = elemOffsetTop + elemHeight + arrowHeight;
if (leftExceed) {
leftPosition = 0;
arrowLeft = elemOffsetLeft + elemWidth / 2 - arrowAdjust;
} else if (rightExceed) {
leftPosition = "auto";
rightPosition = 0;
arrowRight = elemWidth / 2 + rightBoundry - elemRectRight - arrowAdjust;
} else {
leftPosition = elemOffsetLeft - tipWidth / 2 + elemWidth / 2;
arrowLeft = tipWidth / 2 - arrowAdjust;
}
}
setElementStyle(tooltip, {
top: `${topPosition}px`,
bottom: bottomPosition === "" ? "" : `${bottomPosition}px`,
left: leftPosition === "auto" ? leftPosition : `${leftPosition}px`,
right: rightPosition !== "" ? `${rightPosition}px` : ""
});
if (isHTMLElement(arrow)) {
if (arrowTop !== "") arrow.style.top = `${arrowTop}px`;
if (arrowLeft !== "") arrow.style.left = `${arrowLeft}px`;
else if (arrowRight !== "") arrow.style.right = `${arrowRight}px`;
}
dispatchEvent(element, createCustomEvent(`updated.bs.${toLowerCase(self.name)}`));
});
};
//#endregion
//#region src/util/tooltipDefaults.ts
const tooltipDefaults = {
template: getTipTemplate(tooltipString),
title: "",
customClass: "",
trigger: "hover focus",
placement: "top",
sanitizeFn: void 0,
animation: true,
delay: 200,
container: document.body,
content: "",
dismissible: false,
btnClose: ""
};
//#endregion
//#region src/strings/dataOriginalTitle.ts
/**
* Global namespace for `data-bs-title` attribute.
*/
const dataOriginalTitle = "data-original-title";
//#endregion
//#region src/strings/tooltipComponent.ts
/** @type {string} */
const tooltipComponent = "Tooltip";
//#endregion
//#region src/util/setHtml.ts
/**
* Append an existing `Element` to Popover / Tooltip component or HTML
* markup string to be parsed & sanitized to be used as popover / tooltip content.
*
* @param element target
* @param content the `Element` to append / string
* @param sanitizeFn a function to sanitize string content
*/
const setHtml = (element, content, sanitizeFn) => {
if (isString(content) && content.length) {
let dirty = content.trim();
if (isFunction(sanitizeFn)) dirty = sanitizeFn(dirty);
const tempDocument = new DOMParser().parseFromString(dirty, "text/html");
element.append(...[...tempDocument.body.childNodes]);
} else if (isHTMLElement(content)) element.append(content);
else if (isNodeList(content) || isArray(content) && content.every(isNode)) element.append(...[...content]);
};
//#endregion
//#region src/util/createTip.ts
/**
* Creates a new tooltip / popover.
*
* @param self the `Tooltip` / `Popover` instance
*/
const createTip = (self) => {
const isTooltip = self.name === tooltipComponent;
const { id, element, options } = self;
const { title, placement, template, animation, customClass, sanitizeFn, dismissible, content, btnClose } = options;
const tipString = isTooltip ? tooltipString : popoverString;
const tipPositions = { ...tipClassPositions };
let titleParts = [];
let contentParts = [];
if (isRTL(element)) {
tipPositions.left = "end";
tipPositions.right = "start";
}
const placementClass = `bs-${tipString}-${tipPositions[placement]}`;
let tooltipTemplate;
if (isHTMLElement(template)) tooltipTemplate = template;
else {
const htmlMarkup = createElement("div");
setHtml(htmlMarkup, template, sanitizeFn);
tooltipTemplate = htmlMarkup.firstChild;
}
if (!isHTMLElement(tooltipTemplate)) return;
self.tooltip = tooltipTemplate.cloneNode(true);
const { tooltip } = self;
setAttribute(tooltip, "id", id);
setAttribute(tooltip, "role", tooltipString);
const bodyClass = isTooltip ? `${tooltipString}-inner` : `${popoverString}-body`;
const tooltipHeader = isTooltip ? null : querySelector(`.${popoverString}-header`, tooltip);
const tooltipBody = querySelector(`.${bodyClass}`, tooltip);
self.arrow = querySelector(`.${tipString}-arrow`, tooltip);
const { arrow } = self;
if (isHTMLElement(title)) titleParts = [title.cloneNode(true)];
else {
const tempTitle = createElement("div");
setHtml(tempTitle, title, sanitizeFn);
titleParts = [...[...tempTitle.childNodes]];
}
if (isHTMLElement(content)) contentParts = [content.cloneNode(true)];
else {
const tempContent = createElement("div");
setHtml(tempContent, content, sanitizeFn);
contentParts = [...[...tempContent.childNodes]];
}
if (dismissible) if (title) if (isHTMLElement(btnClose)) titleParts = [...titleParts, btnClose.cloneNode(true)];
else {
const tempBtn = createElement("div");
setHtml(tempBtn, btnClose, sanitizeFn);
titleParts = [...titleParts, tempBtn.firstChild];
}
else {
if (tooltipHeader) tooltipHeader.remove();
if (isHTMLElement(btnClose)) contentParts = [...contentParts, btnClose.cloneNode(true)];
else {
const tempBtn = createElement("div");
setHtml(tempBtn, btnClose, sanitizeFn);
contentParts = [...contentParts, tempBtn.firstChild];
}
}
if (!isTooltip) {
if (title && tooltipHeader) setHtml(tooltipHeader, titleParts, sanitizeFn);
if (content && tooltipBody) setHtml(tooltipBody, contentParts, sanitizeFn);
self.btn = querySelector(".btn-close", tooltip) || void 0;
} else if (title && tooltipBody) setHtml(tooltipBody, title, sanitizeFn);
addClass(tooltip, "position-absolute");
addClass(arrow, "position-absolute");
if (!hasClass(tooltip, tipString)) addClass(tooltip, tipString);
if (animation && !hasClass(tooltip, "fade")) addClass(tooltip, fadeClass);
if (customClass && !hasClass(tooltip, customClass)) addClass(tooltip, customClass);
if (!hasClass(tooltip, placementClass)) addClass(tooltip, placementClass);
};
//#endregion
//#region src/util/getElementContainer.ts
/**
* Returns an `HTMLElement` to be used as default value for *options.container*
* for `Tooltip` / `Popover` components.
*
* @see https://github.com/floating-ui/floating-ui
*
* @param element the target
* @returns the query result
*/
const getElementContainer = (element) => {
const majorBlockTags = ["HTML", "BODY"];
const containers = [];
let { parentNode } = element;
while (parentNode && !majorBlockTags.includes(parentNode.nodeName)) {
parentNode = getParentNode(parentNode);
if (!(isShadowRoot(parentNode) || isTableElement(parentNode))) containers.push(parentNode);
}
return containers.find((c, i) => {
if ((getElementStyle(c, "position") !== "relative" || getElementStyle(c, "position") === "relative" && c.offsetHeight !== c.scrollHeight) && containers.slice(i + 1).every((r) => getElementStyle(r, "position") === "static")) return c;
return null;
}) || getDocument(element).body;
};
//#endregion
//#region src/components/tooltip.ts
const tooltipSelector = `[${dataBsToggle}="${tooltipString}"],[data-tip="${tooltipString}"]`;
const titleAttr = "title";
/**
* Static method which returns an existing `Tooltip` instance associated
* to a target `Element`.
*/
let getTooltipInstance = (element) => getInstance(element, tooltipComponent);
/**
* A `Tooltip` initialization callback.
*/
const tooltipInitCallback = (element) => new Tooltip(element);
/**
* Removes the tooltip from the DOM.
*
* @param self the `Tooltip` instance
*/
const removeTooltip = (self) => {
const { element, tooltip, container } = self;
removeAttribute(element, ariaDescribedBy);
removePopup(tooltip, container);
};
/**
* Check if container contains the tooltip.
*
* @param self Tooltip
*/
const hasTip = (self) => {
const { tooltip, container } = self;
return tooltip && hasPopup(tooltip, container);
};
/**
* Executes after the instance has been disposed.
*
* @param self the `Tooltip` instance
* @param callback the parent dispose callback
*/
const disposeTooltipComplete = (self, callback) => {
const { element } = self;
self._toggleEventListeners();
if (hasAttribute(element, "data-original-title") && self.name === "Tooltip") toggleTooltipTitle(self);
if (callback) callback();
};
/**
* Toggles on/off the special `Tooltip` event listeners.
*
* @param self the `Tooltip` instance
* @param add when `true`, event listeners are added
*/
const toggleTooltipAction = (self, add) => {
const action = add ? addListener : removeListener;
const { element } = self;
action(getDocument(element), touchstartEvent, self.handleTouch, passiveHandler);
};
/**
* Executes after the tooltip was shown to the user.
*
* @param self the `Tooltip` instance
*/
const tooltipShownAction = (self) => {
const { element } = self;
const shownTooltipEvent = createCustomEvent(`shown.bs.${toLowerCase(self.name)}`);
toggleTooltipAction(self, true);
dispatchEvent(element, shownTooltipEvent);
Timer.clear(element, "in");
};
/**
* Executes after the tooltip was hidden to the user.
*
* @param self the `Tooltip` instance
*/
const tooltipHiddenAction = (self) => {
const { element } = self;
const hiddenTooltipEvent = createCustomEvent(`hidden.bs.${toLowerCase(self.name)}`);
toggleTooltipAction(self);
removeTooltip(self);
dispatchEvent(element, hiddenTooltipEvent);
Timer.clear(element, "out");
};
/**
* Toggles on/off the `Tooltip` event listeners that hide/update the tooltip.
*
* @param self the `Tooltip` instance
* @param add when `true`, event listeners are added
*/
const toggleTooltipOpenHandlers = (self, add) => {
const action = add ? addListener : removeListener;
const { element, tooltip } = self;
const parentModal = closest(element, `.${modalString}`);
const parentOffcanvas = closest(element, `.${offcanvasString}`);
if (add) [element, tooltip].forEach((target) => self._observer.observe(target));
else self._observer.disconnect();
if (parentModal) action(parentModal, `hide.bs.${modalString}`, self.handleHide);
if (parentOffcanvas) action(parentOffcanvas, `hide.bs.${offcanvasString}`, self.handleHide);
};
/**
* Toggles the `title` and `data-original-title` attributes.
*
* @param self the `Tooltip` instance
* @param content when `true`, event listeners are added
*/
const toggleTooltipTitle = (self, content) => {
const titleAtt = [dataOriginalTitle, titleAttr];
const { element } = self;
setAttribute(element, titleAtt[content ? 0 : 1], content || getAttribute(element, titleAtt[0]) || "");
removeAttribute(element, titleAtt[content ? 1 : 0]);
};
/** Creates a new `Tooltip` instance. */
var Tooltip = class extends BaseComponent {
static selector = tooltipSelector;
static init = tooltipInitCallback;
static getInstance = getTooltipInstance;
static styleTip = styleTip;
/**
* @param target the target element
* @param config the instance options
*/
constructor(target, config) {
super(target, config);
const { element } = this;
const isTooltip = this.name === tooltipComponent;
const tipString = isTooltip ? tooltipString : popoverString;
const tipComponent = isTooltip ? tooltipComponent : popoverComponent;
getTooltipInstance = (elem) => getInstance(elem, tipComponent);
this.enabled = true;
/** Set unique ID for `aria-describedby`. */
this.id = `${tipString}-${getUID(element, tipString)}`;
const { options } = this;
if (!options.title && isTooltip || !isTooltip && !options.content) return;
ObjectAssign(tooltipDefaults, { titleAttr: "" });
if (hasAttribute(element, titleAttr) && isTooltip && typeof options.title === "string") toggleTooltipTitle(this, options.title);
const container = getElementContainer(element);
const offsetParent = [
"sticky",
"fixed",
"relative"
].some((position) => getElementStyle(container, "position") === position) ? container : getWindow(element);
this.container = container;
this.offsetParent = offsetParent;
createTip(this);
if (!this.tooltip) return;
this._observer = new PositionObserver(() => this.update());
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return tooltipComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return tooltipDefaults;
}
/** Handles the focus event on iOS. */
handleFocus = () => focus(this.element);
/** Shows the tooltip. */
handleShow = () => this.show();
show() {
const { options, tooltip, element, container, id } = this;
const { animation } = options;
const outTimer = Timer.get(element, "out");
Timer.clear(element, "out");
if (tooltip && !outTimer && !hasTip(this)) Timer.set(element, () => {
const showTooltipEvent = createCustomEvent(`show.bs.${toLowerCase(this.name)}`);
dispatchEvent(element, showTooltipEvent);
if (!showTooltipEvent.defaultPrevented) {
appendPopup(tooltip, container);
setAttribute(element, ariaDescribedBy, `#${id}`);
this.update();
toggleTooltipOpenHandlers(this, true);
if (!hasClass(tooltip, "show")) addClass(tooltip, showClass);
if (animation) emulateTransitionEnd(tooltip, () => tooltipShownAction(this));
else tooltipShownAction(this);
}
}, 17, "in");
}
/** Hides the tooltip. */
handleHide = () => this.hide();
hide() {
const { options, tooltip, element } = this;
const { animation, delay } = options;
Timer.clear(element, "in");
if (tooltip && hasTip(this)) Timer.set(element, () => {
const hideTooltipEvent = createCustomEvent(`hide.bs.${toLowerCase(this.name)}`);
dispatchEvent(element, hideTooltipEvent);
if (!hideTooltipEvent.defaultPrevented) {
this.update();
removeClass(tooltip, showClass);
toggleTooltipOpenHandlers(this);
if (animation) emulateTransitionEnd(tooltip, () => tooltipHiddenAction(this));
else tooltipHiddenAction(this);
}
}, delay + 17, "out");
}
/** Updates the tooltip position. */
update = () => {
styleTip(this);
};
/** Toggles the tooltip visibility. */
toggle = () => {
const { tooltip } = this;
if (tooltip && !hasTip(this)) this.show();
else this.hide();
};
/** Enables the tooltip. */
enable() {
const { enabled } = this;
if (!enabled) {
this._toggleEventListeners(true);
this.enabled = !enabled;
}
}
/** Disables the tooltip. */
disable() {
const { tooltip, enabled } = this;
if (enabled) {
if (tooltip && hasTip(this)) this.hide();
this._toggleEventListeners();
this.enabled = !enabled;
}
}
/** Toggles the `disabled` property. */
toggleEnabled() {
if (!this.enabled) this.enable();
else this.disable();
}
/**
* Handles the `touchstart` event listener for `Tooltip`
*
* @this {Tooltip}
* @param {TouchEvent} e the `Event` object
*/
handleTouch = ({ target }) => {
const { tooltip, element } = this;
if (tooltip && tooltip.contains(target) || target === element || target && element.contains(target)) {} else this.hide();
};
/**
* Toggles on/off the `Tooltip` event listeners.
*
* @param add when `true`, event listeners are added
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
const { element, options, btn } = this;
const { trigger } = options;
const dismissible = this.name !== "Tooltip" && options.dismissible ? true : false;
if (!trigger.includes("manual")) {
this.enabled = !!add;
trigger.split(" ").forEach((tr) => {
if (tr === mousehoverEvent) {
action(element, mousedownEvent, this.handleShow);
action(element, mouseenterEvent, this.handleShow);
if (!dismissible) {
action(element, mouseleaveEvent, this.handleHide);
action(getDocument(element), touchstartEvent, this.handleTouch, passiveHandler);
}
} else if (tr === mouseclickEvent) action(element, tr, !dismissible ? this.toggle : this.handleShow);
else if (tr === focusEvent) {
action(element, focusinEvent, this.handleShow);
if (!dismissible) action(element, focusoutEvent, this.handleHide);
if (isApple()) action(element, mouseclickEvent, this.handleFocus);
}
if (dismissible && btn) action(btn, mouseclickEvent, this.handleHide);
});
}
};
/** Removes the `Tooltip` from the target element. */
dispose() {
const { tooltip, options } = this;
const clone = {
...this,
name: this.name
};
const callback = () => setTimeout(() => disposeTooltipComplete(clone, () => super.dispose()), 17);
if (options.animation && hasTip(clone)) {
this.options.delay = 0;
this.hide();
emulateTransitionEnd(tooltip, callback);
} else callback();
}
};
//#endregion
//#region src/components/popover.ts
const popoverSelector = `[${dataBsToggle}="${popoverString}"],[data-tip="${popoverString}"]`;
const popoverDefaults = ObjectAssign({}, tooltipDefaults, {
template: getTipTemplate(popoverString),
content: "",
dismissible: false,
btnClose: "<button class=\"btn-close position-absolute top-0 end-0 m-1\" aria-label=\"Close\"></button>"
});
/**
* Static method which returns an existing `Popover` instance associated
* to a target `Element`.
*/
const getPopoverInstance = (element) => getInstance(element, popoverComponent);
/**
* A `Popover` initialization callback.
*/
const popoverInitCallback = (element) => new Popover(element);
/** Returns a new `Popover` instance. */
var Popover = class extends Tooltip {
static selector = popoverSelector;
static init = popoverInitCallback;
static getInstance = getPopoverInstance;
static styleTip = styleTip;
/**
* @param target the target element
* @param config the instance options
*/
constructor(target, config) {
super(target, config);
}
/**
* Returns component name string.
*/
get name() {
return popoverComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return popoverDefaults;
}
show = () => {
super.show();
const { options, btn } = this;
if (options.dismissible && btn) setTimeout(() => focus(btn), 17);
};
};
//#endregion
//#region src/strings/scrollspyString.ts
/** @type {string} */
const scrollspyString = "scrollspy";
//#endregion
//#region src/strings/scrollspyComponent.ts
/** @type {string} */
const scrollspyComponent = "ScrollSpy";
//#endregion
//#region src/components/scrollspy.ts
const scrollspySelector = "[data-bs-spy=\"scroll\"]";
const scrollSpyAnchorSelector = "[href]";
const scrollspyDefaults = {
offset: 10,
target: void 0
};
/**
* Static method which returns an existing `ScrollSpy` instance associated
* to a target `Element`.
*/
const getScrollSpyInstance = (element) => getInstance(element, scrollspyComponent);
/**
* A `ScrollSpy` initialization callback.
*/
const scrollspyInitCallback = (element) => new ScrollSpy(element);
const activateScrollSpy = createCustomEvent(`activate.bs.${scrollspyString}`);
/**
* Update the state of all items.
*
* @param self the `ScrollSpy` instance
*/
const updateSpyTargets = (self) => {
const { target, _itemsLength, _observables } = self;
const links = getElementsByTagName("A", target);
const doc = getDocument(target);
if (!links.length || _itemsLength === _observables.size) return;
_observables.clear();
Array.from(links).forEach((link) => {
const hash = getAttribute(link, "href")?.slice(1);
const targetItem = hash?.length ? doc.getElementById(hash) : null;
if (targetItem && !isDisabled(link)) self._observables.set(targetItem, link);
});
self._itemsLength = self._observables.size;
};
/**
* Clear all items of the target.
*
* @param target a single item
*/
const clear = (target) => {
Array.from(getElementsByTagName("A", target)).forEach((item) => {
if (hasClass(item, "active")) removeClass(item, activeClass);
});
};
/**
* Activates a new item.
*
* @param self the `ScrollSpy` instance
* @param item a single item
*/
const activate = (self, item) => {
const { target, element } = self;
clear(target);
self._activeItem = item;
addClass(item, activeClass);
let parentItem = item;
while (parentItem !== target) {
parentItem = parentItem.parentElement;
if ([
"nav",
"dropdown-menu",
"list-group"
].some((c) => hasClass(parentItem, c))) {
const parentLink = parentItem.previousElementSibling;
if (parentLink && !hasClass(parentLink, "active")) addClass(parentLink, activeClass);
}
}
activateScrollSpy.relatedTarget = item;
dispatchEvent(element, activateScrollSpy);
};
const getOffset = (self, target) => {
const { scrollTarget, element, options } = self;
return (scrollTarget !== element ? getBoundingClientRect(target).top + scrollTarget.scrollTop : target.offsetTop) - (options.offset || 10);
};
/** Returns a new `ScrollSpy` instance. */
var ScrollSpy = class extends BaseComponent {
static selector = scrollspySelector;
static init = scrollspyInitCallback;
static getInstance = getScrollSpyInstance;
/**
* @param target the target element
* @param config the instance options
*/
constructor(target, config) {
super(target, config);
const { element, options } = this;
const spyTarget = querySelector(options.target, getDocument(element));
if (!spyTarget) return;
this.target = spyTarget;
this.scrollTarget = element.clientHeight < element.scrollHeight ? element : getDocumentElement(element);
this._observables = /* @__PURE__ */ new Map();
this.refresh();
this._observer = new PositionObserver(() => {
requestAnimationFrame(() => this.refresh?.());
}, { root: this.scrollTarget });
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return scrollspyComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return scrollspyDefaults;
}
/** Updates all items. */
refresh = () => {
const { target, scrollTarget } = this;
if (!target || target.offsetHeight === 0) return;
updateSpyTargets(this);
const { _itemsLength, _observables, _activeItem } = this;
if (!_itemsLength) return;
const entries = _observables.entries().toArray();
const { scrollTop, scrollHeight, clientHeight } = scrollTarget;
if (scrollTop >= scrollHeight - clientHeight) {
const newActiveItem = entries[_itemsLength - 1]?.[1];
if (_activeItem !== newActiveItem) activate(this, newActiveItem);
return;
}
const firstOffset = entries[0]?.[0] ? getOffset(this, entries[0][0]) : null;
if (firstOffset !== null && scrollTop < firstOffset && firstOffset > 0) {
this._activeItem = null;
clear(target);
return;
}
for (let i = 0; i < _itemsLength; i += 1) {
const [targetItem, item] = entries[i];
const offsetTop = getOffset(this, targetItem);
const nextTarget = entries[i + 1]?.[0];
const nextOffsetTop = nextTarget ? getOffset(this, nextTarget) : null;
if (_activeItem !== item && scrollTop >= offsetTop && (nextOffsetTop === null || scrollTop < nextOffsetTop)) {
activate(this, item);
break;
}
}
};
/**
* This method provides an event handle
* for scrollspy
* @param e the event listener object
*/
_scrollTo = (e) => {
const item = closest(e.target, scrollSpyAnchorSelector);
const hash = item && getAttribute(item, "href")?.slice(1);
const targetItem = hash && getElementById(hash, this.target);
if (targetItem) {
this.scrollTarget.scrollTo({
top: targetItem.offsetTop,
behavior: "smooth"
});
e.preventDefault();
}
};
/**
* Toggles on/off the component observer.
*
* @param self the ScrollSpy instance
* @param add when `true`, listener is added
*/
_toggleEventListeners = (add) => {
const { target, _observables, _observer, _scrollTo } = this;
(add ? addListener : removeListener)(target, mouseclickEvent, _scrollTo);
if (add) _observables?.forEach((_, targetItem) => _observer.observe(targetItem));
else _observer.disconnect();
};
/** Removes `ScrollSpy` from the target element. */
dispose() {
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/components/tab.ts
const tabSelector = `[${dataBsToggle}="tab"]`;
/**
* Static method which returns an existing `Tab` instance associated
* to a target `Element`.
*/
const getTabInstance = (element) => getInstance(element, "Tab");
/** A `Tab` initialization callback. */
const tabInitCallback = (element) => new Tab(element);
const showTabEvent = createCustomEvent(`show.bs.tab`);
const shownTabEvent = createCustomEvent(`shown.bs.tab`);
const hideTabEvent = createCustomEvent(`hide.bs.tab`);
const hiddenTabEvent = createCustomEvent(`hidden.bs.tab`);
/**
* Stores the current active tab and its content
* for a given `.nav` element.
*/
const tabPrivate = /* @__PURE__ */ new Map();
/**
* Executes after tab transition has finished.
*
* @param self the `Tab` instance
*/
const triggerTabEnd = (self) => {
const { tabContent, nav } = self;
if (tabContent && hasClass(tabContent, "collapsing")) {
tabContent.style.height = "";
removeClass(tabContent, collapsingClass);
}
if (nav) Timer.clear(nav);
};
/**
* Executes before showing the tab content.
*
* @param self the `Tab` instance
*/
const triggerTabShow = (self) => {
const { element, tabContent, content: nextContent, nav } = self;
const { tab } = isHTMLElement(nav) && tabPrivate.get(nav) || { tab: null };
if (tabContent && nextContent && hasClass(nextContent, "fade")) {
const { currentHeight, nextHeight } = tabPrivate.get(element) || {
currentHeight: 0,
nextHeight: 0
};
if (currentHeight !== nextHeight) setTimeout(() => {
tabContent.style.height = `${nextHeight}px`;
reflow(tabContent);
emulateTransitionEnd(tabContent, () => triggerTabEnd(self));
}, 50);
else triggerTabEnd(self);
} else if (nav) Timer.clear(nav);
shownTabEvent.relatedTarget = tab;
dispatchEvent(element, shownTabEvent);
};
/**
* Executes before hiding the tab.
*
* @param self the `Tab` instance
*/
const triggerTabHide = (self) => {
const { element, content: nextContent, tabContent, nav } = self;
const { tab, content } = nav && tabPrivate.get(nav) || {
tab: null,
content: null
};
let currentHeight = 0;
if (tabContent && nextContent && hasClass(nextContent, "fade")) {
[content, nextContent].forEach((c) => {
if (c) addClass(c, "overflow-hidden");
});
currentHeight = content ? content.scrollHeight : 0;
}
showTabEvent.relatedTarget = tab;
hiddenTabEvent.relatedTarget = element;
dispatchEvent(element, showTabEvent);
if (showTabEvent.defaultPrevented) return;
if (nextContent) addClass(nextContent, activeClass);
if (content) removeClass(content, activeClass);
if (tabContent && nextContent && hasClass(nextContent, "fade")) {
const nextHeight = nextContent.scrollHeight;
tabPrivate.set(element, {
currentHeight,
nextHeight,
tab: null,
content: null
});
addClass(tabContent, collapsingClass);
tabContent.style.height = `${currentHeight}px`;
reflow(tabContent);
[content, nextContent].forEach((c) => {
if (c) removeClass(c, "overflow-hidden");
});
}
if (nextContent && nextContent && hasClass(nextContent, "fade")) setTimeout(() => {
addClass(nextContent, showClass);
emulateTransitionEnd(nextContent, () => {
triggerTabShow(self);
});
}, 1);
else {
if (nextContent) addClass(nextContent, showClass);
triggerTabShow(self);
}
if (tab) dispatchEvent(tab, hiddenTabEvent);
};
/**
* Returns the current active tab and its target content.
*
* @param self the `Tab` instance
* @returns the query result
*/
const getActiveTab = (self) => {
const { nav } = self;
if (!isHTMLElement(nav)) return {
tab: null,
content: null
};
const activeTabs = getElementsByClassName(activeClass, nav);
let tab = null;
if (activeTabs.length === 1 && !dropdownMenuClasses.some((c) => hasClass(activeTabs[0].parentElement, c))) [tab] = activeTabs;
else if (activeTabs.length > 1) tab = activeTabs[activeTabs.length - 1];
const content = isHTMLElement(tab) ? getTargetElement(tab) : null;
return {
tab,
content
};
};
/**
* Returns a parent dropdown.
*
* @param element the `Tab` element
* @returns the parent dropdown
*/
const getParentDropdown = (element) => {
if (!isHTMLElement(element)) return null;
const dropdown = closest(element, `.${dropdownMenuClasses.join(",.")}`);
return dropdown ? querySelector(`.${dropdownMenuClasses[0]}-toggle`, dropdown) : null;
};
/**
* Handles the `click` event listener.
*
* @param e the `Event` object
*/
const tabClickHandler = (e) => {
const element = closest(e.target, tabSelector);
const self = element && getTabInstance(element);
if (!self) return;
e.preventDefault();
self.show();
};
/** Creates a new `Tab` instance. */
var Tab = class extends BaseComponent {
static selector = tabSelector;
static init = tabInitCallback;
static getInstance = getTabInstance;
/** @param target the target element */
constructor(target) {
super(target);
const { element } = this;
const content = getTargetElement(element);
if (!content) return;
const nav = closest(element, ".nav");
const container = closest(content, ".tab-content");
this.nav = nav;
this.content = content;
this.tabContent = container;
this.dropdown = getParentDropdown(element);
const { tab } = getActiveTab(this);
if (nav && !tab) {
const firstTab = querySelector(tabSelector, nav);
const firstTabContent = firstTab && getTargetElement(firstTab);
if (firstTabContent) {
addClass(firstTab, activeClass);
addClass(firstTabContent, showClass);
addClass(firstTabContent, activeClass);
setAttribute(element, ariaSelected, "true");
}
}
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return "Tab";
}
/** Shows the tab to the user. */
show() {
const { element, content: nextContent, nav, dropdown } = this;
if (nav && Timer.get(nav) || hasClass(element, "active")) return;
const { tab, content } = getActiveTab(this);
if (nav && tab) tabPrivate.set(nav, {
tab,
content,
currentHeight: 0,
nextHeight: 0
});
hideTabEvent.relatedTarget = element;
if (!isHTMLElement(tab)) return;
dispatchEvent(tab, hideTabEvent);
if (hideTabEvent.defaultPrevented) return;
addClass(element, activeClass);
setAttribute(element, ariaSelected, "true");
const activeDropdown = isHTMLElement(tab) && getParentDropdown(tab);
if (activeDropdown && hasClass(activeDropdown, "active")) removeClass(activeDropdown, activeClass);
if (nav) {
const toggleTab = () => {
if (tab) {
removeClass(tab, activeClass);
setAttribute(tab, ariaSelected, "false");
}
if (dropdown && !hasClass(dropdown, "active")) addClass(dropdown, activeClass);
};
if (content && (hasClass(content, "fade") || nextContent && hasClass(nextContent, "fade"))) Timer.set(nav, toggleTab, 1);
else toggleTab();
}
if (content) {
removeClass(content, showClass);
if (hasClass(content, "fade")) emulateTransitionEnd(content, () => triggerTabHide(this));
else triggerTabHide(this);
}
}
/**
* Toggles on/off the `click` event listener.
*
* @param add when `true`, event listener is added
*/
_toggleEventListeners = (add) => {
(add ? addListener : removeListener)(this.element, mouseclickEvent, tabClickHandler);
};
/** Removes the `Tab` component from the target element. */
dispose() {
this._toggleEventListeners();
super.dispose();
}
};
//#endregion
//#region src/strings/toastString.ts
/** @type {string} */
const toastString = "toast";
//#endregion
//#region src/strings/toastComponent.ts
/** @type {string} */
const toastComponent = "Toast";
//#endregion
//#region src/components/toast.ts
const toastSelector = `.${toastString}`;
const toastDismissSelector = `[${dataBsDismiss}="${toastString}"]`;
const toastToggleSelector = `[${dataBsToggle}="${toastString}"]`;
const showingClass = "showing";
/** @deprecated */
const hideClass = "hide";
const toastDefaults = {
animation: true,
autohide: true,
delay: 5e3
};
/**
* Static method which returns an existing `Toast` instance associated
* to a target `Element`.
*/
const getToastInstance = (element) => getInstance(element, toastComponent);
/**
* A `Toast` initialization callback.
*/
const toastInitCallback = (element) => new Toast(element);
const showToastEvent = createCustomEvent(`show.bs.${toastString}`);
const shownToastEvent = createCustomEvent(`shown.bs.${toastString}`);
const hideToastEvent = createCustomEvent(`hide.bs.${toastString}`);
const hiddenToastEvent = createCustomEvent(`hidden.bs.${toastString}`);
/**
* Executes after the toast is shown to the user.
*
* @param self the `Toast` instance
*/
const showToastComplete = (self) => {
const { element, options } = self;
removeClass(element, showingClass);
Timer.clear(element, showingClass);
dispatchEvent(element, shownToastEvent);
if (options.autohide) Timer.set(element, () => self.hide(), options.delay, toastString);
};
/**
* Executes after the toast is hidden to the user.
*
* @param self the `Toast` instance
*/
const hideToastComplete = (self) => {
const { element } = self;
removeClass(element, showingClass);
removeClass(element, showClass);
addClass(element, hideClass);
Timer.clear(element, toastString);
dispatchEvent(element, hiddenToastEvent);
};
/**
* Executes before hiding the toast.
*
* @param self the `Toast` instance
*/
const hideToast = (self) => {
const { element, options } = self;
addClass(element, showingClass);
if (options.animation) {
reflow(element);
emulateTransitionEnd(element, () => hideToastComplete(self));
} else hideToastComplete(self);
};
/**
* Executes before showing the toast.
*
* @param self the `Toast` instance
*/
const showToast = (self) => {
const { element, options } = self;
Timer.set(element, () => {
removeClass(element, hideClass);
reflow(element);
addClass(element, showClass);
addClass(element, showingClass);
if (options.animation) emulateTransitionEnd(element, () => showToastComplete(self));
else showToastComplete(self);
}, 17, showingClass);
};
/**
* Handles the `click` event listener for toast.
*
* @param e the `Event` object
*/
function toastClickHandler(e) {
const element = getTargetElement(this);
const self = element && getToastInstance(element);
if (isDisabled(this)) return;
if (!self) return;
if (this.tagName === "A") e.preventDefault();
self.relatedTarget = this;
self.show();
}
/**
* Executes when user interacts with the toast without closing it,
* usually by hovering or focusing it.
*
* @param e the `Toast` instance
*/
const interactiveToastHandler = (e) => {
const element = e.target;
const self = getToastInstance(element);
const { type, relatedTarget } = e;
if (!self || element === relatedTarget || element.contains(relatedTarget)) return;
if ([mouseenterEvent, focusinEvent].includes(type)) Timer.clear(element, toastString);
else Timer.set(element, () => self.hide(), self.options.delay, toastString);
};
/** Creates a new `Toast` instance. */
var Toast = class extends BaseComponent {
static selector = toastSelector;
static init = toastInitCallback;
static getInstance = getToastInstance;
/**
* @param target the target `.toast` element
* @param config the instance options
*/
constructor(target, config) {
super(target, config);
const { element, options } = this;
if (options.animation && !hasClass(element, "fade")) addClass(element, fadeClass);
else if (!options.animation && hasClass(element, "fade")) removeClass(element, fadeClass);
this.dismiss = querySelector(toastDismissSelector, element);
this.triggers = [...querySelectorAll(toastToggleSelector, getDocument(element))].filter((btn) => getTargetElement(btn) === element);
this._toggleEventListeners(true);
}
/**
* Returns component name string.
*/
get name() {
return toastComponent;
}
/**
* Returns component default options.
*/
get defaults() {
return toastDefaults;
}
/**
* Returns *true* when toast is visible.
*/
get isShown() {
return hasClass(this.element, showClass);
}
/** Shows the toast. */
show = () => {
const { element, isShown } = this;
if (!element || isShown) return;
dispatchEvent(element, showToastEvent);
if (!showToastEvent.defaultPrevented) showToast(this);
};
/** Hides the toast. */
hide = () => {
const { element, isShown } = this;
if (!element || !isShown) return;
dispatchEvent(element, hideToastEvent);
if (!hideToastEvent.defaultPrevented) hideToast(this);
};
/**
* Toggles on/off the `click` event listener.
*
* @param add when `true`, it will add the listener
*/
_toggleEventListeners = (add) => {
const action = add ? addListener : removeListener;
const { element, triggers, dismiss, options, hide } = this;
if (dismiss) action(dismiss, mouseclickEvent, hide);
if (options.autohide) [
focusinEvent,
focusoutEvent,
mouseenterEvent,
mouseleaveEvent
].forEach((e) => action(element, e, interactiveToastHandler));
if (triggers.length) triggers.forEach((btn) => {
action(btn, mouseclickEvent, toastClickHandler);
});
};
/** Removes the `Toast` component from the target element. */
dispose() {
const { element, isShown } = this;
this._toggleEventListeners();
Timer.clear(element, toastString);
if (isShown) removeClass(element, showClass);
super.dispose();
}
};
//#endregion
//#region src/util/init.ts
const componentsList = /* @__PURE__ */ new Map();
[
Alert,
Button,
Carousel,
Collapse,
Dropdown,
Modal,
Offcanvas,
Popover,
ScrollSpy,
Tab,
Toast,
Tooltip
].forEach((c) => componentsList.set(c.prototype.name, c));
/**
* Initialize all matched `Element`s for one component.
*
* @param callback
* @param collection
*/
const initComponentDataAPI = (callback, collection) => {
[...collection].forEach((x) => callback(x));
};
/**
* Remove one component from a target container element or all in the page.
*
* @param component the component name
* @param context parent `Node`
*/
const removeComponentDataAPI = (component, context) => {
const compData = Data.getAllFor(component);
if (compData) [...compData].forEach(([element, instance]) => {
if (context.contains(element)) instance.dispose();
});
};
/**
* Initialize all BSN components for a target container.
*
* @param context parent `Node`
*/
const initCallback = (context) => {
const elemCollection = [...getElementsByTagName("*", context && context.nodeName ? context : document)];
componentsList.forEach((cs) => {
const { init, selector } = cs;
initComponentDataAPI(init, elemCollection.filter((item) => matches(item, selector)));
});
};
/**
* Remove all BSN components for a target container.
*
* @param context parent `Node`
*/
const removeDataAPI = (context) => {
const lookUp = context && context.nodeName ? context : document;
componentsList.forEach((comp) => {
removeComponentDataAPI(comp.prototype.name, lookUp);
});
};
if (document.body) initCallback();
else addListener(document, "DOMContentLoaded", () => initCallback(), { once: true });
//#endregion
export { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip, initCallback, removeDataAPI };
//# sourceMappingURL=index.js.map