/*!
* Bootstrap Native Offcanvas 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, ObjectKeys, addClass, ariaHidden, ariaModal, closest, createCustomEvent, createElement, dispatchEvent, emulateTransitionEnd, focus, getAttribute, getDocument, getDocumentBody, getDocumentElement, getElementStyle, getElementTransitionDuration, getElementsByClassName, getInstance, getWindow, hasClass, isElement, isHTMLElement, isNode, isString, keyEscape, keydownEvent, mouseclickEvent, normalizeOptions, querySelector, querySelectorAll, reflow, removeAttribute, removeClass, setAttribute, setElementStyle, toggleFocusTrap } from "@thednp/shorty";
import { addListener, removeListener } from "@thednp/event-listener";
//#region src/strings/dataBsDismiss.ts
/**
* Global namespace for most components `dismiss` option.
*/
const dataBsDismiss = "data-bs-dismiss";
//#endregion
//#region src/strings/dataBsToggle.ts
/**
* Global namespace for most components `toggle` option.
*/
const dataBsToggle = "data-bs-toggle";
//#endregion
//#region src/strings/showClass.ts
/**
* Global namespace for most components `show` class.
*/
const showClass = "show";
//#endregion
//#region src/strings/offcanvasString.ts
/** @type {string} */
const offcanvasString = "offcanvas";
//#endregion
//#region src/strings/offcanvasComponent.ts
/** @type {string} */
const offcanvasComponent = "Offcanvas";
//#endregion
//#region src/strings/dataBsTarget.ts
/**
* Global namespace for most components `target` option.
*/
const dataBsTarget = "data-bs-target";
//#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/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/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 `
`
* 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 `` 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/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/strings/fadeClass.ts
/**
* Global namespace for most components `fade` class.
*/
const fadeClass = "fade";
//#endregion
//#region src/strings/modalString.ts
/** @type {string} */
const modalString = "modal";
//#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/isDisabled.ts
/**
* Check if interactive element is disabled.
* @param target either a `