/*! * 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 `" }); /** * 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