@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
1,190 lines (1,016 loc) • 31.3 kB
JavaScript
/**
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact Volker Schukai.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import {
arrow,
autoPlacement,
autoUpdate,
computePosition,
offset,
flip,
hide,
size,
shift,
} from "@floating-ui/dom";
import { isArray, isFunction, isObject, isString } from "../../../types/is.mjs";
import { Processing } from "../../../util/processing.mjs";
export {
applyAdaptiveFloatingElementSize,
closePositionedPopper,
resolveClippingBoundaryElement,
resolveParentPopperContentBoundary,
isPositionedPopperOpen,
openPositionedPopper,
positionPopper,
};
const autoUpdateCleanupMap = new WeakMap();
const settlingFrameMap = new WeakMap();
const floatingResizeObserverMap = new WeakMap();
const floatingSyncCycleMap = new WeakMap();
const floatingAppearanceFrameMap = new WeakMap();
const floatingAppearanceTimeoutMap = new WeakMap();
const adaptiveScrollHeightDatasetKey = "monsterAdaptiveScrollHeight";
/**
* @private
* @param controlElement
* @param popperElement
* @param options
* @return {Promise|*}
*/
function positionPopper(controlElement, popperElement, options) {
const config = normalizePopperConfig(options, controlElement, popperElement);
return new Processing(() => {
enableFloatingPositioning(controlElement, popperElement, config);
}).run();
}
function openPositionedPopper(controlElement, popperElement, options) {
const config = normalizePopperConfig(options, controlElement, popperElement);
stopAutoUpdate(popperElement);
cancelFloatingAppearanceFrame(popperElement);
popperElement.dataset.monsterAppearance = "opening";
popperElement.style.display = "block";
popperElement.style.position = config.strategy;
popperElement.style.removeProperty("transform");
// Keep the call signature stable even though only Floating UI is used now.
void controlElement;
}
function enableFloatingPositioning(controlElement, popperElement, config) {
if (!isPositionedPopperOpen(popperElement)) {
return;
}
popperElement.style.removeProperty("visibility");
scheduleFloatingAppearanceOpen(popperElement);
startFloatingResizeObserver(controlElement, popperElement, config);
startAutoUpdate(controlElement, popperElement, () => {
if (!isPositionedPopperOpen(popperElement)) {
return;
}
runFloatingUpdateHook(popperElement);
syncFloatingPopover(controlElement, popperElement, config);
});
runFloatingUpdateHook(popperElement);
syncFloatingPopover(controlElement, popperElement, config);
}
function syncFloatingPopover(
controlElement,
popperElement,
config,
allowSettlingPass = true,
) {
if (!isPositionedPopperOpen(popperElement)) {
return;
}
const arrowElement = popperElement.querySelector("[data-monster-role=arrow]");
const syncCycleId = claimFloatingSyncCycle(popperElement);
const floatingMiddleware = buildFloatingMiddleware(
config.middleware,
config.placement,
config.detectOverflowOptions,
popperElement,
syncCycleId,
);
resetAdaptiveFloatingElementSize(popperElement);
if (
arrowElement instanceof HTMLElement &&
config.middlewareTokens.includes("arrow")
) {
floatingMiddleware.push(arrow({ element: arrowElement }));
}
if (!config.middlewareTokens.includes("size")) {
floatingMiddleware.push(
createAdaptiveSizeMiddleware(
config.detectOverflowOptions,
popperElement,
syncCycleId,
),
);
}
computePosition(
controlElement,
popperElement,
Object.assign({}, config, {
middleware: floatingMiddleware,
}),
).then(({ x, y, placement, middlewareData }) => {
if (!isActiveFloatingSyncCycle(popperElement, syncCycleId)) {
return;
}
if (!isPositionedPopperOpen(popperElement)) {
return;
}
Object.assign(popperElement.style, {
top: "0",
left: "0",
position: config.strategy,
transform: `translate(${roundByDPR(x)}px,${roundByDPR(y)}px)`,
});
if (middlewareData.arrow) {
applyFloatingArrowStyles(arrowElement, placement, middlewareData.arrow);
}
if (allowSettlingPass) {
scheduleSettlingPass(controlElement, popperElement, config);
}
});
}
function closePositionedPopper(popperElement) {
stopAutoUpdate(popperElement);
cancelFloatingAppearanceFrame(popperElement);
delete popperElement.dataset.monsterAppearance;
popperElement.style.display = "none";
popperElement.style.removeProperty("visibility");
popperElement.style.removeProperty("position");
popperElement.style.removeProperty("transform");
}
function isPositionedPopperOpen(popperElement) {
return popperElement.style.display === "block";
}
function normalizePopperConfig(options, controlElement, popperElement) {
const config = Object.assign(
{},
{
placement: "top",
engine: "floating",
strategy: "absolute",
},
options,
);
if (!(config.boundaryElement instanceof HTMLElement)) {
config.boundaryElement = resolveClippingBoundaryElement(
controlElement,
popperElement,
);
}
config.detectOverflowOptions = buildDetectOverflowOptions(
config.boundaryElement,
);
config.middleware = normalizeMiddleware(config);
config.middlewareTokens = config.middleware.filter((line) => isString(line));
return config;
}
function normalizeMiddleware(config) {
const middleware = config?.middleware;
if (isArray(middleware)) {
return [...middleware];
}
if (isString(middleware)) {
return middleware.split(",").filter((line) => line.trim().length > 0);
}
return [];
}
function buildFloatingMiddleware(
middleware,
placement,
detectOverflowOptions,
popperElement,
syncCycleId = null,
) {
const result = [...middleware];
for (const key in result) {
const line = result[key];
if (isFunction(line) || isObject(line)) {
continue;
}
if (!isString(line)) {
throw new Error(
`Middleware must be a string, a function or an object. Got ${typeof line}`,
);
}
const kv = line.split(":");
const fn = kv.shift();
switch (fn) {
case "flip":
result[key] = flip(detectOverflowOptions);
break;
case "shift":
result[key] = shift(normalizeShiftOptions(kv, detectOverflowOptions));
break;
case "autoPlacement":
let defaultAllowedPlacements = ["top", "bottom", "left", "right"];
const defPlacement = kv?.shift();
if (isString(defPlacement) && defPlacement.trim().length > 0) {
defaultAllowedPlacements = defPlacement
.split(",")
.filter((entry) => entry.trim().length > 0);
}
if (defaultAllowedPlacements.includes(placement)) {
defaultAllowedPlacements.splice(
defaultAllowedPlacements.indexOf(placement),
1,
);
}
defaultAllowedPlacements.unshift(placement);
result[key] = autoPlacement(
Object.assign({}, detectOverflowOptions, {
crossAxis: true,
autoAlignment: true,
allowedPlacements: defaultAllowedPlacements,
}),
);
break;
case "arrow":
result[key] = null;
break;
case "size":
result[key] = createAdaptiveSizeMiddleware(
detectOverflowOptions,
popperElement,
syncCycleId,
);
break;
case "offset":
result[key] = offset(parseInt(kv?.shift()) || 10);
break;
case "hide":
result[key] = hide(detectOverflowOptions);
break;
default:
throw new Error(`Unknown function: ${fn}`);
}
}
return result.filter(Boolean);
}
function normalizeShiftOptions(tokens, detectOverflowOptions) {
const options = Object.assign({}, detectOverflowOptions);
const normalizedTokens = tokens
.join(":")
.split(",")
.map((token) => token.trim())
.filter((token) => token.length > 0);
for (const token of normalizedTokens) {
switch (token) {
case "crossAxis":
options.crossAxis = true;
break;
case "mainAxis=false":
options.mainAxis = false;
break;
default:
throw new Error(`Unknown shift option: ${token}`);
}
}
return options;
}
function createAdaptiveSizeMiddleware(
detectOverflowOptions,
popperElement,
syncCycleId = null,
) {
return size(
Object.assign({}, detectOverflowOptions, {
apply({ availableWidth, availableHeight, elements }) {
const floatingElement = elements?.floating || popperElement;
if (!(floatingElement instanceof HTMLElement)) {
return;
}
if (
syncCycleId !== null &&
!isActiveFloatingSyncCycle(floatingElement, syncCycleId)
) {
return;
}
applyAdaptiveFloatingElementSize(floatingElement, {
availableWidth,
availableHeight,
});
},
}),
);
}
function applyAdaptiveFloatingElementSize(
floatingElement,
{ availableWidth, availableHeight },
) {
const contentElement = getFloatingContentElement(floatingElement);
const usesVisibleOverflow =
contentElement instanceof HTMLElement &&
contentElement.dataset.monsterOverflowMode === "visible";
const maxWidth = clampAvailableDimension(
availableWidth,
readMaxDimension(floatingElement, "maxWidth"),
);
const maxHeight = clampAvailableDimension(
availableHeight,
readMaxDimension(floatingElement, "maxHeight"),
);
const nextStyle = {
boxSizing: "border-box",
};
if (Number.isFinite(maxWidth) && maxWidth > 0) {
nextStyle.maxWidth = `${maxWidth}px`;
} else {
nextStyle.maxWidth = "";
}
if (!usesVisibleOverflow && Number.isFinite(maxHeight) && maxHeight > 0) {
nextStyle.maxHeight = `${maxHeight}px`;
} else {
nextStyle.maxHeight = "";
}
Object.assign(floatingElement.style, nextStyle);
syncPreferredFloatingWidth(floatingElement, maxWidth);
syncPreferredFloatingMaxWidth(floatingElement, maxWidth);
applyAdaptiveFloatingContentSize(floatingElement, maxHeight);
}
function applyAdaptiveFloatingContentSize(floatingElement, maxHeight) {
const contentElement = getFloatingContentElement(floatingElement);
if (!(contentElement instanceof HTMLElement)) {
return;
}
const overflowMode = contentElement.dataset.monsterOverflowMode;
if (overflowMode === "visible") {
return;
}
const reservedHeight = getReservedFloatingHeight(
floatingElement,
contentElement,
);
const contentMaxHeight = clampAvailableDimension(
Number.isFinite(maxHeight) ? maxHeight - reservedHeight : null,
readMaxDimension(contentElement, "maxHeight"),
);
const minimumReadableHeight = getMinimumReadableContentHeight(contentElement);
const nextContentMaxHeight =
Number.isFinite(contentMaxHeight) && contentMaxHeight > 0
? Math.max(contentMaxHeight, minimumReadableHeight)
: contentMaxHeight;
const preferredContentHeight = getRequiredContentHeight(contentElement);
if (overflowMode === "horizontal" || overflowMode === "both") {
// Overlay-aware modes keep the parent wrapper sized to the real inline content.
// Nested poppers may still escape, but they must not inflate the outer popper height.
const contentHeightConstrained = applyOverlayAwareContentHeight(
contentElement,
preferredContentHeight,
nextContentMaxHeight,
minimumReadableHeight,
);
syncNestedScrollContainerHeight(
contentElement,
contentHeightConstrained ? nextContentMaxHeight : null,
);
return;
}
if (Number.isFinite(nextContentMaxHeight) && nextContentMaxHeight > 0) {
contentElement.style.maxHeight = `${nextContentMaxHeight}px`;
} else {
contentElement.style.maxHeight = "";
}
syncNestedScrollContainerHeight(contentElement, nextContentMaxHeight);
}
function applyOverlayAwareContentHeight(
contentElement,
preferredContentHeight,
contentMaxHeight,
minimumReadableHeight,
) {
const preferredHeight =
Number.isFinite(preferredContentHeight) && preferredContentHeight > 0
? Math.max(preferredContentHeight, minimumReadableHeight)
: minimumReadableHeight;
const constrainedHeight =
Number.isFinite(contentMaxHeight) && contentMaxHeight > 0
? Math.min(preferredHeight, contentMaxHeight)
: preferredHeight;
const isConstrained =
Number.isFinite(contentMaxHeight) &&
contentMaxHeight > 0 &&
preferredHeight > contentMaxHeight;
contentElement.style.removeProperty("overflow");
contentElement.style.overflowX =
contentElement.dataset.monsterOverflowMode === "horizontal"
? "auto"
: "visible";
contentElement.style.overflowY = isConstrained ? "auto" : "visible";
if (
isConstrained &&
Number.isFinite(constrainedHeight) &&
constrainedHeight > 0
) {
contentElement.style.height = `${constrainedHeight}px`;
contentElement.style.maxHeight = `${constrainedHeight}px`;
return true;
}
// overflow-x:auto computes overflow-y to auto in browsers, so an unconstrained
// horizontal wrapper must keep its natural height to avoid a spurious scrollbar.
contentElement.style.removeProperty("height");
if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
contentElement.style.maxHeight = `${contentMaxHeight}px`;
} else {
contentElement.style.removeProperty("maxHeight");
}
return false;
}
function getFloatingContentElement(floatingElement) {
for (const child of floatingElement.children) {
if (!(child instanceof HTMLElement)) {
continue;
}
const part = child.getAttribute("part");
if (
isString(part) &&
part
.split(/\s+/)
.filter((token) => token.length > 0)
.includes("content")
) {
return child;
}
}
return null;
}
function getReservedFloatingHeight(floatingElement, contentElement) {
const floatingStyle = getComputedStyle(floatingElement);
let reservedHeight = 0;
reservedHeight += readBoxDimension(floatingStyle.paddingTop);
reservedHeight += readBoxDimension(floatingStyle.paddingBottom);
reservedHeight += readBoxDimension(floatingStyle.borderTopWidth);
reservedHeight += readBoxDimension(floatingStyle.borderBottomWidth);
for (const child of floatingElement.children) {
if (!(child instanceof HTMLElement) || child === contentElement) {
continue;
}
const childStyle = getComputedStyle(child);
reservedHeight += child.getBoundingClientRect().height;
reservedHeight += readBoxDimension(childStyle.marginTop);
reservedHeight += readBoxDimension(childStyle.marginBottom);
}
return Math.max(0, reservedHeight);
}
function readBoxDimension(rawValue) {
const value = Number.parseFloat(rawValue);
return Number.isFinite(value) ? value : 0;
}
function getMinimumReadableContentHeight(contentElement) {
if (!(contentElement instanceof HTMLElement)) {
return 0;
}
const measurementTarget =
getPrimaryReadableContentElement(contentElement) || contentElement;
const style = getComputedStyle(measurementTarget);
const lineHeight = readLineHeight(style);
const paddingHeight =
readBoxDimension(getComputedStyle(contentElement).paddingTop) +
readBoxDimension(getComputedStyle(contentElement).paddingBottom);
return Math.max(0, lineHeight + paddingHeight);
}
function readLineHeight(style) {
if (!style) {
return 0;
}
const lineHeight = Number.parseFloat(style.lineHeight);
if (Number.isFinite(lineHeight) && lineHeight > 0) {
return lineHeight;
}
const fontSize = Number.parseFloat(style.fontSize);
if (Number.isFinite(fontSize) && fontSize > 0) {
return fontSize * 1.4;
}
return 0;
}
function getPrimaryReadableContentElement(contentElement) {
if (!(contentElement instanceof HTMLElement)) {
return null;
}
const slotElement = contentElement.querySelector("slot");
if (slotElement?.assignedElements instanceof Function) {
for (const element of slotElement.assignedElements({ flatten: true })) {
if (element instanceof HTMLElement) {
return element;
}
}
}
for (const child of contentElement.children) {
if (child instanceof HTMLElement) {
return child;
}
}
return null;
}
function syncNestedScrollContainerHeight(contentElement, contentMaxHeight) {
const nestedScrollableElement = getNestedScrollableElement(contentElement);
if (!(nestedScrollableElement instanceof HTMLElement)) {
return;
}
if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
const nextNestedHeight = resolveNestedScrollContainerHeight(
nestedScrollableElement,
contentMaxHeight,
);
nestedScrollableElement.style.height = `${nextNestedHeight}px`;
nestedScrollableElement.style.maxHeight = `${nextNestedHeight}px`;
nestedScrollableElement.dataset[adaptiveScrollHeightDatasetKey] = "true";
return;
}
if (
nestedScrollableElement.dataset[adaptiveScrollHeightDatasetKey] !== "true"
) {
return;
}
nestedScrollableElement.style.height = "";
nestedScrollableElement.style.maxHeight = "";
delete nestedScrollableElement.dataset[adaptiveScrollHeightDatasetKey];
}
function resolveNestedScrollContainerHeight(
nestedScrollableElement,
contentMaxHeight,
) {
const declaredHeight = readDeclaredDimension(
nestedScrollableElement,
"height",
);
const declaredMaxHeight = readDeclaredDimension(
nestedScrollableElement,
"maxHeight",
);
const scrollHeight = nestedScrollableElement.scrollHeight;
const preferredHeightCandidates = [
declaredHeight,
declaredMaxHeight,
scrollHeight,
];
const preferredHeight = preferredHeightCandidates.find((value) => {
return Number.isFinite(value) && value > 0;
});
if (Number.isFinite(preferredHeight) && preferredHeight > 0) {
return Math.min(contentMaxHeight, preferredHeight);
}
return contentMaxHeight;
}
function readDeclaredDimension(element, property) {
if (!(element instanceof HTMLElement)) {
return NaN;
}
const value = Number.parseFloat(element.style?.[property] || "");
return Number.isFinite(value) ? value : NaN;
}
function syncPreferredFloatingWidth(floatingElement, maxWidth) {
const preferredWidth = Number.parseFloat(
floatingElement.dataset.monsterPreferredWidth || "",
);
if (!Number.isFinite(preferredWidth) || preferredWidth <= 0) {
return;
}
const widthBehavior = floatingElement.dataset.monsterWidthBehavior;
if (widthBehavior === "preferred") {
const nextFloatingWidth = clampAvailableDimension(
preferredWidth,
Number.isFinite(maxWidth) ? maxWidth : Infinity,
);
if (Number.isFinite(nextFloatingWidth) && nextFloatingWidth > 0) {
floatingElement.style.width = `${nextFloatingWidth}px`;
floatingElement.style.minWidth = `${nextFloatingWidth}px`;
}
return;
}
const contentElement = getFloatingContentElement(floatingElement);
if (!(contentElement instanceof HTMLElement)) {
floatingElement.style.width = `${preferredWidth}px`;
return;
}
const floatingRect = floatingElement.getBoundingClientRect();
const contentRect = contentElement.getBoundingClientRect();
const reservedWidth = Math.max(0, floatingRect.width - contentRect.width);
const contentRequiredWidth = getRequiredContentWidth(contentElement);
const preferredFloatingWidth = preferredWidth;
const requiredFloatingWidth = Math.max(
preferredFloatingWidth,
contentRequiredWidth + reservedWidth,
);
const nextFloatingWidth = clampAvailableDimension(
requiredFloatingWidth,
Number.isFinite(maxWidth) ? maxWidth : Infinity,
);
if (Number.isFinite(nextFloatingWidth) && nextFloatingWidth > 0) {
floatingElement.style.width = `${nextFloatingWidth}px`;
}
}
function syncPreferredFloatingMaxWidth(floatingElement, maxWidth) {
const preferredMaxWidth = Number.parseFloat(
floatingElement.dataset.monsterPreferredMaxWidth || "",
);
if (!Number.isFinite(preferredMaxWidth) || preferredMaxWidth <= 0) {
return;
}
const nextFloatingMaxWidth = clampAvailableDimension(
preferredMaxWidth,
Number.isFinite(maxWidth) ? maxWidth : Infinity,
);
if (Number.isFinite(nextFloatingMaxWidth) && nextFloatingMaxWidth > 0) {
floatingElement.style.maxWidth = `${nextFloatingMaxWidth}px`;
}
}
function resetAdaptiveFloatingElementSize(floatingElement) {
if (!(floatingElement instanceof HTMLElement)) {
return;
}
floatingElement.style.removeProperty("maxHeight");
const contentElement = getFloatingContentElement(floatingElement);
if (!(contentElement instanceof HTMLElement)) {
return;
}
contentElement.style.removeProperty("height");
contentElement.style.removeProperty("maxHeight");
contentElement.style.removeProperty("overflow");
contentElement.style.removeProperty("overflowX");
contentElement.style.removeProperty("overflowY");
const nestedScrollableElement = getNestedScrollableElement(contentElement);
if (!(nestedScrollableElement instanceof HTMLElement)) {
return;
}
nestedScrollableElement.style.removeProperty("height");
nestedScrollableElement.style.removeProperty("maxHeight");
delete nestedScrollableElement.dataset[adaptiveScrollHeightDatasetKey];
}
function runFloatingUpdateHook(popperElement) {
const hook = popperElement?.monsterBeforeFloatingUpdate;
if (typeof hook === "function") {
hook();
}
}
function getRequiredContentWidth(contentElement) {
const nestedScrollableElement = getNestedScrollableElement(contentElement);
if (nestedScrollableElement instanceof HTMLElement) {
return Math.max(
contentElement.scrollWidth,
nestedScrollableElement.scrollWidth,
contentElement.clientWidth,
);
}
return Math.max(contentElement.scrollWidth, contentElement.clientWidth);
}
function getRequiredContentHeight(contentElement) {
const nestedScrollableElement = getNestedScrollableElement(contentElement);
if (nestedScrollableElement instanceof HTMLElement) {
const contentRect = contentElement.getBoundingClientRect();
const nestedRect = nestedScrollableElement.getBoundingClientRect();
const reservedHeight = Math.max(0, contentRect.height - nestedRect.height);
return Math.max(
contentElement.scrollHeight,
nestedScrollableElement.scrollHeight + reservedHeight,
contentElement.clientHeight,
);
}
return Math.max(contentElement.scrollHeight, contentElement.clientHeight);
}
function getNestedScrollableElement(contentElement) {
for (const child of contentElement.children) {
if (!(child instanceof HTMLElement)) {
continue;
}
const style = getComputedStyle(child);
if (["auto", "scroll"].includes(style.overflowY)) {
return child;
}
}
return null;
}
function buildDetectOverflowOptions(boundaryElement) {
const result = {
rootBoundary: "viewport",
padding: 0,
};
if (boundaryElement instanceof HTMLElement) {
result.boundary = boundaryElement;
}
return result;
}
function resolveClippingBoundaryElement(...elements) {
for (const element of elements) {
const clippingBoundary = findNearestClippingContainer(element);
if (clippingBoundary instanceof HTMLElement) {
return clippingBoundary;
}
}
return null;
}
function resolveParentPopperContentBoundary(...elements) {
for (const element of elements) {
const clippingBoundary = findNearestIgnoredClippingContainer(element);
if (clippingBoundary instanceof HTMLElement) {
return clippingBoundary;
}
}
return null;
}
function findNearestClippingContainer(element) {
let current = getComposedParent(element);
while (current) {
if (
current instanceof HTMLElement &&
isClippingContainer(getComputedStyle(current)) &&
!shouldIgnoreClippingContainer(current)
) {
return current;
}
current = getComposedParent(current);
}
return null;
}
function findNearestIgnoredClippingContainer(element) {
let current = getComposedParent(element);
while (current) {
if (
current instanceof HTMLElement &&
shouldIgnoreClippingContainer(current) &&
shouldEscapeParentPopperContentWrapper(current)
) {
return current;
}
current = getComposedParent(current);
}
return null;
}
function shouldEscapeParentPopperContentWrapper(element) {
if (!(element instanceof HTMLElement) || !isPopperContentWrapper(element)) {
return false;
}
const overflowMode = element.getAttribute("data-monster-overflow-mode");
if (overflowMode === "both") {
return true;
}
return isClippingContainer(getComputedStyle(element));
}
function getComposedParent(node) {
if (!node) {
return null;
}
if (node instanceof Element && node.assignedSlot) {
return node.assignedSlot;
}
if (node instanceof ShadowRoot) {
return node.host || null;
}
if (node.parentElement instanceof HTMLElement) {
return node.parentElement;
}
const rootNode = node.getRootNode?.();
if (rootNode instanceof ShadowRoot) {
return rootNode.host || null;
}
return node.parentNode || null;
}
function shouldIgnoreClippingContainer(element) {
if (!(element instanceof HTMLElement)) {
return false;
}
if (!isPopperContentWrapper(element)) {
return false;
}
const parent = getComposedParent(element);
return (
parent instanceof HTMLElement &&
parent.getAttribute("data-monster-role") === "popper"
);
}
function isPopperContentWrapper(element) {
if (!(element instanceof HTMLElement)) {
return false;
}
if (!element.hasAttribute("data-monster-overflow-mode")) {
return false;
}
const part = element.getAttribute("part");
return (
isString(part) &&
part
.split(/\s+/)
.filter((token) => token.length > 0)
.includes("content")
);
}
function isClippingContainer(style) {
if (!style) {
return false;
}
return [style.overflow, style.overflowX, style.overflowY].some((value) => {
return ["hidden", "auto", "scroll", "clip"].includes(value);
});
}
function readMaxDimension(element, property) {
const rawValue = getComputedStyle(element)?.[property];
if (!rawValue || rawValue === "none") {
return Infinity;
}
const value = Number.parseFloat(rawValue);
return Number.isFinite(value) ? value : Infinity;
}
function clampAvailableDimension(available, configuredMax) {
if (!Number.isFinite(available) || available <= 0) {
return null;
}
if (!Number.isFinite(configuredMax)) {
return available;
}
return Math.min(available, configuredMax);
}
function startAutoUpdate(controlElement, popperElement, callback) {
stopAutoUpdate(popperElement);
autoUpdateCleanupMap.set(
popperElement,
autoUpdate(controlElement, popperElement, callback, {
elementResize: typeof ResizeObserver === "function",
layoutShift: false,
}),
);
}
function stopAutoUpdate(popperElement) {
cancelSettlingPass(popperElement);
stopFloatingResizeObserver(popperElement);
const cleanup = autoUpdateCleanupMap.get(popperElement);
if (typeof cleanup === "function") {
cleanup();
}
autoUpdateCleanupMap.delete(popperElement);
}
function startFloatingResizeObserver(controlElement, popperElement, config) {
stopFloatingResizeObserver(popperElement);
if (typeof ResizeObserver !== "function") {
return;
}
const observer = new ResizeObserver(() => {
if (!isPositionedPopperOpen(popperElement)) {
return;
}
scheduleSettlingPass(controlElement, popperElement, config);
});
observer.observe(popperElement);
floatingResizeObserverMap.set(popperElement, observer);
}
function stopFloatingResizeObserver(popperElement) {
const observer = floatingResizeObserverMap.get(popperElement);
if (
typeof ResizeObserver === "function" &&
observer instanceof ResizeObserver
) {
observer.disconnect();
}
floatingResizeObserverMap.delete(popperElement);
}
function scheduleSettlingPass(controlElement, popperElement, config) {
if (settlingFrameMap.has(popperElement)) {
return;
}
const frameId = requestAnimationFrame(() => {
settlingFrameMap.delete(popperElement);
if (!isPositionedPopperOpen(popperElement)) {
return;
}
runFloatingUpdateHook(popperElement);
syncFloatingPopover(controlElement, popperElement, config, false);
});
settlingFrameMap.set(popperElement, frameId);
}
function claimFloatingSyncCycle(popperElement) {
const nextCycleId = (floatingSyncCycleMap.get(popperElement) || 0) + 1;
floatingSyncCycleMap.set(popperElement, nextCycleId);
return nextCycleId;
}
function isActiveFloatingSyncCycle(popperElement, syncCycleId) {
return floatingSyncCycleMap.get(popperElement) === syncCycleId;
}
function cancelSettlingPass(popperElement) {
const frameId = settlingFrameMap.get(popperElement);
if (typeof frameId === "number") {
cancelAnimationFrame(frameId);
}
settlingFrameMap.delete(popperElement);
}
function scheduleFloatingAppearanceOpen(popperElement) {
if (!(popperElement instanceof HTMLElement)) {
return;
}
if (popperElement.dataset.monsterAppearance !== "opening") {
return;
}
if (floatingAppearanceFrameMap.has(popperElement)) {
return;
}
const finishAppearanceOpen = () => {
cancelFloatingAppearanceFrame(popperElement);
if (!isPositionedPopperOpen(popperElement)) {
return;
}
popperElement.dataset.monsterAppearance = "open";
};
const schedule =
typeof requestAnimationFrame === "function"
? requestAnimationFrame
: (callback) => {
return setTimeout(callback, 16);
};
const frameId = schedule(() => {
finishAppearanceOpen();
});
const timeoutId = setTimeout(() => {
finishAppearanceOpen();
}, 24);
floatingAppearanceFrameMap.set(popperElement, frameId);
floatingAppearanceTimeoutMap.set(popperElement, timeoutId);
}
function cancelFloatingAppearanceFrame(popperElement) {
const frameId = floatingAppearanceFrameMap.get(popperElement);
if (
frameId !== undefined &&
frameId !== null &&
Number.isNaN(frameId) === false
) {
if (typeof cancelAnimationFrame === "function") {
cancelAnimationFrame(frameId);
} else {
clearTimeout(frameId);
}
}
floatingAppearanceFrameMap.delete(popperElement);
const timeoutId = floatingAppearanceTimeoutMap.get(popperElement);
if (
timeoutId !== undefined &&
timeoutId !== null &&
Number.isNaN(timeoutId) === false
) {
clearTimeout(timeoutId);
}
floatingAppearanceTimeoutMap.delete(popperElement);
}
function applyFloatingArrowStyles(arrowElement, placement, arrowData) {
if (!(arrowElement instanceof HTMLElement)) {
return;
}
const side = placement.split("-")[0];
const staticSide = {
top: "bottom",
right: "left",
bottom: "top",
left: "right",
}[side];
const arrowLen = arrowElement.offsetWidth + 4;
const borderStyle = {
borderLeft: "transparent",
borderRight: "transparent",
borderBottom: "transparent",
borderTop: "transparent",
};
const defaultBorder =
"var(--monster-border-width) var(--monster-border-style) var(--monster-bg-color-primary-4)";
switch (side) {
case "top":
borderStyle.borderRight = defaultBorder;
borderStyle.borderBottom = defaultBorder;
break;
case "bottom":
borderStyle.borderTop = defaultBorder;
borderStyle.borderLeft = defaultBorder;
break;
case "left":
borderStyle.borderRight = defaultBorder;
borderStyle.borderTop = defaultBorder;
break;
case "right":
borderStyle.borderBottom = defaultBorder;
borderStyle.borderLeft = defaultBorder;
break;
}
Object.assign(
arrowElement.style,
{
left: arrowData.x != null ? `${arrowData.x}px` : "",
top: arrowData.y != null ? `${arrowData.y}px` : "",
right: "",
bottom: "",
[staticSide]: `${-arrowLen / 2}px`,
transform: "rotate(45deg)",
},
borderStyle,
);
}
function roundByDPR(value) {
const dpr = window.devicePixelRatio || 1;
return Math.round(value * dpr) / dpr;
}