/*! * 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 `