/**
 * Copyright IBM Corp. 2026
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import "../../globals/settings.js";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.126.0/helpers/decorate.js";
import FocusMixin from "../../globals/mixins/focus.js";
import HostListener from "../../globals/decorators/host-listener.js";
import HostListenerMixin from "../../globals/mixins/host-listener.js";
import { FLOATING_MENU_DIRECTION, FLOATING_MENU_POSITION_DIRECTION } from "./defs.js";
import { LitElement } from "lit";
//#region src/components/floating-menu/floating-menu.ts
/**
* Copyright IBM Corp. 2019, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Calculates the offset for the floating menu.
*
* @param menuBody - The menu body element.
* @param menuDirection - The floating menu direction.
* @param trigger - The trigger element.
* @param flipped - Whether the menu is flipped.
* @returns The adjustment of the floating menu position.
*/
const getMenuOffset = (menuBody, menuDirection, trigger, flipped) => {
	const { offsetWidth: menuWidth } = menuBody;
	switch (menuDirection) {
		case "top":
		case "bottom": {
			const triggerWidth = !trigger ? 0 : trigger.offsetWidth;
			return {
				left: (!flipped ? 1 : -1) * (menuWidth / 2 - triggerWidth / 2),
				top: 0
			};
		}
		default: return {
			left: 0,
			top: 0
		};
	}
};
/**
* Observes resize of the given element with the given resize observer.
*
* @param observer The resize observer.
* @param elem The element to observe the resize.
*/
const observeResize = (observer, elem) => {
	observer.observe(elem);
	return { release() {
		observer.unobserve(elem);
		return null;
	} };
};
/**
* @param elem The starting element.
* @param selector The CSS selector.
* @returns {Element}
*   The closest ancestor node of the given element that matches the given selector, crossing Shadow DOM boundary.
*/
const closestComposed = (elem, selector) => {
	const found = elem.closest(selector);
	if (found) return found;
	const { host } = elem.getRootNode();
	if (host) return closestComposed(host, selector);
	return null;
};
/**
* Floating menu.
*/
var CDSFloatingMenu = class extends HostListenerMixin(FocusMixin(LitElement)) {
	constructor(..._args) {
		super(..._args);
		this._hObserveResizeParent = null;
		this._hObserveResizeContainer = null;
		this._resizeObserver = new ResizeObserver(() => {
			const { container, open, parent, position } = this;
			if (container && open && parent) {
				const { top } = position;
				this.style.top = `${top}px`;
			}
		});
		this._handleBlur = ({ relatedTarget }) => {
			if (!this.contains(relatedTarget)) {
				const { parent } = this;
				if (parent && parent !== relatedTarget) {
					parent.open = false;
					HTMLElement.prototype.focus.call(this.parent);
				}
			}
		};
		this._click = () => {
			const { parent } = this;
			if (parent) parent.open = false;
		};
		this._handleKeyDown = (event) => {
			if (event.key === "Tab") {
				if (event.shiftKey) {
					const { parent } = this;
					if (parent) parent.open = false;
				}
			}
		};
		this.parent = null;
	}
	/**
	* Gets the menu offset configuration (object or function).
	* Subclasses override this to specify custom offset configs.
	*
	* @returns The menu offset configuration, or undefined for no offset.
	*/
	getOffsetConfig() {}
	/**
	* Resolves the final menu offset by evaluating the offset configuration.
	* Handles both static offset objects and dynamic offset functions.
	*
	* @returns The resolved offset with left and top values.
	*/
	resolveOffset() {
		const config = this.getOffsetConfig();
		if (!config) return {
			left: 0,
			top: 0
		};
		if (typeof config === "function") {
			const trigger = this.parent;
			return config(this, this.direction, trigger, this.flipped);
		}
		return config;
	}
	/**
	* The DOM element to put this menu into.
	*/
	get container() {
		return closestComposed(this, this.constructor.selectorContainer) || this.ownerDocument.body;
	}
	/**
	* The position of this floating menu.
	*/
	get position() {
		const { triggerPosition } = this.parent;
		if (!triggerPosition) throw new TypeError("Missing information of trigger button position.");
		const { container } = this;
		const { left: refLeft = 0, top: refTop = 0, right: refRight = 0, bottom: refBottom = 0 } = triggerPosition;
		const { width, height } = this.getBoundingClientRect();
		const containerRect = container.getBoundingClientRect();
		const containerComputedStyle = container.ownerDocument.defaultView.getComputedStyle(container);
		const containerPosition = containerComputedStyle.getPropertyValue("position");
		const positionDirection = containerComputedStyle.getPropertyValue("direction");
		const scrollX = globalThis.scrollX ?? 0;
		const scrollY = globalThis.scrollY ?? 0;
		const effectiveScrollX = containerPosition !== "static" ? 0 : scrollX;
		const effectiveScrollY = containerPosition !== "static" ? 0 : scrollY;
		const relativeDiff = {
			top: containerPosition !== "static" ? containerRect.top : 0,
			left: containerPosition !== "static" ? containerRect.left : 0
		};
		const refCenterHorizontal = (refLeft + refRight) / 2;
		const refCenterVertical = (refTop + refBottom) / 2;
		const { top = 0, left = 0 } = this.resolveOffset();
		const { direction } = this;
		if (Object.values(FLOATING_MENU_DIRECTION).indexOf(direction) < 0) throw new Error(`Wrong menu position direction: ${direction}`);
		const { left: calculatedLeft, top: calculatedTop } = {
			["left"]: () => ({
				left: refLeft - width + effectiveScrollX - left - relativeDiff.left,
				top: refCenterVertical - height / 2 + effectiveScrollY + top - 9 - relativeDiff.top
			}),
			["top"]: () => ({
				left: refCenterHorizontal - width / 2 + effectiveScrollX + left - relativeDiff.left,
				top: refTop - height + effectiveScrollY - top - relativeDiff.top
			}),
			["right"]: () => ({
				left: refRight + effectiveScrollX + left - relativeDiff.left,
				top: refCenterVertical - height / 2 + effectiveScrollY + top + 3 - relativeDiff.top
			}),
			["bottom"]: () => ({
				left: refCenterHorizontal - width / 2 + effectiveScrollX + left - relativeDiff.left,
				top: refBottom + effectiveScrollY + top - relativeDiff.top
			})
		}[direction]();
		return {
			direction: positionDirection,
			start: calculatedLeft,
			top: calculatedTop
		};
	}
	disconnectedCallback() {
		if (this._hObserveResizeContainer) this._hObserveResizeContainer = this._hObserveResizeContainer.release();
		if (this._hObserveResizeParent) this._hObserveResizeParent = this._hObserveResizeParent.release();
	}
	updated(changedProperties) {
		const { container, open, parent } = this;
		if ((changedProperties.has("flipped") || changedProperties.has("direction") || changedProperties.has("open")) && open) {
			if (!parent) {
				this.parent = this.parentElement;
				container.appendChild(this);
			}
			const { start, top } = this.position;
			this.style.left = `${start}px`;
			this.style.right = "auto";
			this.style.top = `${top}px`;
		}
		if (changedProperties.has("open")) {
			if (this._hObserveResizeContainer) this._hObserveResizeContainer = this._hObserveResizeContainer.release();
			if (this._hObserveResizeParent) this._hObserveResizeParent = this._hObserveResizeParent.release();
			if (open) {
				const { parentElement } = this.parent ?? {};
				this._hObserveResizeContainer = observeResize(this._resizeObserver, container);
				if (parentElement) this._hObserveResizeParent = observeResize(this._resizeObserver, parentElement);
			}
		}
	}
	static {
		this.FLOATING_MENU = true;
	}
	/**
	* The CSS selector to find the element to put this floating menu in.
	*/
	static get selectorContainer() {
		return `[data-floating-menu-container],cds-custom-modal`;
	}
	static {
		this.shadowRootOptions = {
			...LitElement.shadowRootOptions,
			delegatesFocus: true
		};
	}
};
__decorate([HostListener("focusout")], CDSFloatingMenu.prototype, "_handleBlur", void 0);
__decorate([HostListener("click")], CDSFloatingMenu.prototype, "_click", void 0);
__decorate([HostListener("keydown")], CDSFloatingMenu.prototype, "_handleKeyDown", void 0);
//#endregion
export { FLOATING_MENU_DIRECTION, FLOATING_MENU_POSITION_DIRECTION, CDSFloatingMenu as default, getMenuOffset };

//# sourceMappingURL=floating-menu.js.map