741 lines
26 KiB
JavaScript
741 lines
26 KiB
JavaScript
/*!
|
|
* Bootstrap Native Popover 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, closest, createCustomEvent, createElement, dispatchEvent, emulateTransitionEnd, focus, focusEvent, focusinEvent, focusoutEvent, getAttribute, getBoundingClientRect, getDocument, getDocumentBody, getDocumentElement, getElementStyle, getInstance, getNodeScroll, getParentNode, getRectRelativeToOffsetParent, getUID, getWindow, hasAttribute, hasClass, isApple, isArray, isElement, isFunction, isHTMLElement, isNode, isNodeList, isRTL, isShadowRoot, isString, isTableElement, mouseclickEvent, mousedownEvent, mouseenterEvent, mousehoverEvent, mouseleaveEvent, normalizeOptions, passiveHandler, querySelector, removeAttribute, removeClass, setAttribute, setElementStyle, toLowerCase, touchstartEvent } from "@thednp/shorty";
|
|
import { addListener, removeListener } from "@thednp/event-listener";
|
|
import PositionObserver from "@thednp/position-observer";
|
|
//#region src/strings/dataBsToggle.ts
|
|
/**
|
|
* Global namespace for most components `toggle` option.
|
|
*/
|
|
const dataBsToggle = "data-bs-toggle";
|
|
//#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/showClass.ts
|
|
/**
|
|
* Global namespace for most components `show` class.
|
|
*/
|
|
const showClass = "show";
|
|
//#endregion
|
|
//#region src/strings/tooltipComponent.ts
|
|
/** @type {string} */
|
|
const tooltipComponent = "Tooltip";
|
|
//#endregion
|
|
//#region src/strings/modalString.ts
|
|
/** @type {string} */
|
|
const modalString = "modal";
|
|
//#endregion
|
|
//#region src/strings/offcanvasString.ts
|
|
/** @type {string} */
|
|
const offcanvasString = "offcanvas";
|
|
//#endregion
|
|
//#region src/strings/fadeClass.ts
|
|
/**
|
|
* Global namespace for most components `fade` class.
|
|
*/
|
|
const fadeClass = "fade";
|
|
//#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/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/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/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/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
|
|
export { Popover as default };
|
|
|
|
//# sourceMappingURL=popover.mjs.map
|