import { _ as y, p as d } from "./vendor-preact-DThS2ZQ_.js";
//#region src/shared/modal/modal.hooks.ts
var MODAL_SCROLL_LOCK_CLASS = "IncodeModalScrollLock";
/** `id` of the `Modal.Title` heading; also the dialog's `aria-labelledby`. */
var MODAL_TITLE_ID = "incode-modal-title";
/**
* Selector for focusable elements that participate in Tab cycling inside the dialog.
*/
var TAB_FOCUSABLE_SELECTOR = [
	"button:not([disabled])",
	"[href]",
	"input:not([disabled])",
	"select:not([disabled])",
	"textarea:not([disabled])",
	"[tabindex]:not([tabindex=\"-1\"])"
].join(", ");
function getTabFocusableElements(container) {
	return Array.from(container.querySelectorAll(TAB_FOCUSABLE_SELECTOR));
}
function isTabFocusableElement(element) {
	try {
		return element.matches(TAB_FOCUSABLE_SELECTOR);
	} catch {
		return false;
	}
}
/**
* Picks the dialog's initial focus. By default focus lands on the `Modal.Title`
* heading so screen readers announce it on open, rather than the dialog
* role / item count or an action control. A dialog can opt a specific control
* into initial focus by marking it with the `autofocus` attribute. Falls back
* to the dialog container when no title is present.
*/
function resolveModalInitialFocusTarget(dialog) {
	const autofocusEl = dialog.querySelector("[autofocus]");
	if (autofocusEl && isTabFocusableElement(autofocusEl)) return autofocusEl;
	return dialog.querySelector(`#incode-modal-title`) ?? dialog;
}
/**
* Renders the modal into `.IncodeComponent` by default, falling back to `document.body`.
* Pass `portalContainer` to mount into a host you control.
* The ephemeral portal host is only created while `open` is true.
*/
function useModalPortalTarget(open, portalContainer) {
	const [node, setNode] = d(null);
	y(() => {
		if (!open) {
			setNode(null);
			return;
		}
		if (portalContainer) {
			setNode(portalContainer);
			return () => {
				setNode(null);
			};
		}
		const el = document.createElement("div");
		el.setAttribute("data-incode-modal-portal", "");
		(document.querySelector(".IncodeComponent") ?? document.body).appendChild(el);
		setNode(el);
		return () => {
			el.remove();
			setNode(null);
		};
	}, [open, portalContainer]);
	return node;
}
/**
* Locks scroll while the modal is open by adding a CSS class to `.IncodeComponent`
* when present, otherwise `document.body`.
*/
function useModalBodyScrollLock(open) {
	y(() => {
		if (!open) return;
		const incodeComponent = document.querySelector(".IncodeComponent");
		(incodeComponent ?? document.body).classList.add(MODAL_SCROLL_LOCK_CLASS);
		return () => {
			(incodeComponent ?? document.body).classList.remove(MODAL_SCROLL_LOCK_CLASS);
		};
	}, [open]);
}
/**
* Invokes `onRequestClose` when Escape is pressed while `open && dismissible`.
*/
function useModalEscapeDismiss(open, dismissible, onRequestClose) {
	y(() => {
		if (!open || !dismissible) return;
		const onKeyDown = (event) => {
			if (event.key === "Escape") {
				event.preventDefault();
				onRequestClose();
			}
		};
		document.addEventListener("keydown", onKeyDown);
		return () => document.removeEventListener("keydown", onKeyDown);
	}, [
		open,
		dismissible,
		onRequestClose
	]);
}
/**
* When `active` becomes true, moves focus to the first tabbable inside the dialog,
* or the dialog surface. Otherwise restores focus on cleanup — to the element in
* `returnFocusRef` if provided, otherwise to the previously focused element.
*
* An explicit `returnFocusRef` is strongly recommended for dialogs opened via
* touch on iOS/VoiceOver, where `document.activeElement` is frequently `<body>`
* at open time, which would otherwise cause focus to be restored to nothing and
* VoiceOver to land on a nearby empty container.
*
* `active` should include portal mount readiness (e.g. `open && portalTarget != null`)
* so focus runs after the dialog node exists.
*/
function useModalFocusCapture(active, dialogRef, manageInitialFocus = true, returnFocusRef) {
	y(() => {
		if (!active) return;
		const previous = document.activeElement;
		const explicitReturnTarget = returnFocusRef?.current ?? null;
		if (manageInitialFocus) {
			const dialog = dialogRef.current;
			if (dialog) resolveModalInitialFocusTarget(dialog).focus();
		}
		return () => {
			if (explicitReturnTarget && typeof explicitReturnTarget.focus === "function") {
				explicitReturnTarget.focus();
				return;
			}
			if (previous && typeof previous.focus === "function") previous.focus();
		};
	}, [
		active,
		dialogRef,
		manageInitialFocus,
		returnFocusRef
	]);
}
/**
* Tracks the visual viewport (shrinks when a mobile virtual keyboard opens) and
* publishes its size + offset as CSS custom properties on the target element:
*/
function useModalVisualViewportBounds(active, targetRef) {
	y(() => {
		if (!active) return;
		if (typeof window === "undefined") return;
		const viewport = window.visualViewport;
		if (!viewport) return;
		const update = () => {
			const el = targetRef.current;
			if (!el) return;
			el.style.setProperty("--incode-visual-viewport-height", `${viewport.height}px`);
			el.style.setProperty("--incode-visual-viewport-offset-top", `${viewport.offsetTop}px`);
		};
		update();
		viewport.addEventListener("resize", update);
		viewport.addEventListener("scroll", update);
		return () => {
			viewport.removeEventListener("resize", update);
			viewport.removeEventListener("scroll", update);
		};
	}, [active, targetRef]);
}
/**
* Keeps Tab / Shift+Tab cycling within the dialog while `active` is true.
*/
function useModalTabCycle(active, dialogRef) {
	y(() => {
		if (!active) return;
		const onKeyDown = (event) => {
			if (event.key !== "Tab") return;
			const dialog = dialogRef.current;
			if (!dialog) return;
			const focusables = getTabFocusableElements(dialog);
			if (focusables.length === 0) {
				event.preventDefault();
				return;
			}
			const first = focusables[0];
			const last = focusables[focusables.length - 1];
			if (event.shiftKey) {
				if (document.activeElement === first) {
					event.preventDefault();
					last.focus();
				}
			} else if (document.activeElement === last) {
				event.preventDefault();
				first.focus();
			}
		};
		document.addEventListener("keydown", onKeyDown);
		return () => document.removeEventListener("keydown", onKeyDown);
	}, [active, dialogRef]);
}
//#endregion
export { useModalPortalTarget as a, useModalFocusCapture as i, useModalBodyScrollLock as n, useModalTabCycle as o, useModalEscapeDismiss as r, useModalVisualViewportBounds as s, MODAL_TITLE_ID as t };
