import { _ as __rest } from './tslib.es6-es.js';
import React__default, { isValidElement } from 'react';
import { useWindowDimensions } from '@jobber/hooks';
import classnames from 'classnames';
import { n as normalizeBaseUiButtonProps, i as isAtlantisButtonRenderTarget, s as stripNativeTypeFromButtonRenderTarget, r as renderAtlantisButtonTarget } from './buttonRenderAdapter-es.js';
import { B as Button } from './Button-es.js';
import { S as ScrollAreaRoot, e as ScrollAreaViewport, g as DialogRoot, h as DialogTrigger$1, i as DialogPortal } from './ScrollAreaViewport-es.js';
import { m as mergeProps } from './useRenderElement-es.js';
import { H as Heading } from './Heading-es.js';
import { a as BottomSheet, B as BottomSheetScrollAreaContext } from './BottomSheet-es.js';
import { D as DrawerTitle, a as DrawerClose, b as DialogTitle$1, c as DialogClose$1 } from './DrawerTitle-es.js';
import { D as DrawerDescription, a as DialogBackdrop, b as DialogViewport, c as DialogPopup, d as DialogDescription, e as createDialogHandle } from './DrawerDescription-es.js';

// Copied from Menu's constant so the two surfaces agree on what "mobile" means.
// (The proposal calls for copying shared Menu values into the component first
// and deciding later whether to promote them to a shared location.)
const SMALL_SCREEN_BREAKPOINT = 490;

var styles = {"small":"uYYyCR3l6E8-","base":"X1jGz-sLWf4-","large":"_4zFf2PkFhaA-","fullScreen":"nJoCr8oWJ7w-","header":"XFKiCQ8QbQU-","close":"FDOhnO0TT08-","scrollRegion":"_8DjYA23vHXA-","scrollViewport":"Bwr9IUYpLfM-","body":"zp9kx9uue2I-","fullBleed":"EhtwBTy1ai4-","footer":"DRIJhI1knKw-","sheetSurface":"vmcZRwOcNMo-","popup":"mFX7qDCd17I-","backdrop":"NE-BIsVE-hE-","viewport":"hp70o-3Cf-w-","spinning":"Zf-Z8DJcC-Y-"};

var actionsStyles = {"actions":"hKVNddbxkmo-","leftActions":"nslrozOH-v4-","buttonGroup":"BrXAnQBttWE-","align-left":"_9Vgd-3YJxlI-","align-right":"_8HC9NqvmJdo-","align-fullWidth":"ZZ3XuxGv6ow-","spinning":"PWf7wHPhd2A-"};

/**
 * Resolves the trigger element for `Dialog.Trigger`. Copied from Menu's
 * `resolveMenuTriggerRender` so an Atlantis `<Button>` works as a trigger via
 * `children` or `render` identically in both shells. Raw text children are
 * wrapped in a default `Button`.
 */
function resolveComposableTriggerRender({ children, render, }) {
    if (render) {
        if (typeof render === "function") {
            return (props) => {
                const renderedElement = render(normalizeBaseUiButtonProps(props));
                return isAtlantisButtonRenderTarget(renderedElement)
                    ? stripNativeTypeFromButtonRenderTarget(renderedElement)
                    : renderedElement;
            };
        }
        return isAtlantisButtonRenderTarget(render)
            ? renderAtlantisButtonTarget(render)
            : render;
    }
    if (isAtlantisButtonRenderTarget(children)) {
        return renderAtlantisButtonTarget(children);
    }
    if (React__default.isValidElement(children)) {
        return children;
    }
    return renderAtlantisButtonTarget(React__default.createElement(Button, { type: "primary" },
        React__default.createElement(Button.Label, null, children)));
}
/**
 * Resolves the dismiss element for `Dialog.Close`, mirroring
 * `resolveComposableTriggerRender` so a custom render works in both shells.
 * Falls back to the subtle "remove" `ButtonDismiss` when neither `render` nor
 * `children` are supplied. An Atlantis `<Button>`/`<ButtonDismiss>` passed via
 * `render` or `children` is adapted; any other valid element is used as-is.
 */
function resolveComposableCloseRender({ children, render, }) {
    if (render) {
        if (typeof render === "function") {
            return (props) => {
                const renderedElement = render(normalizeBaseUiButtonProps(props));
                return isAtlantisButtonRenderTarget(renderedElement)
                    ? stripNativeTypeFromButtonRenderTarget(renderedElement)
                    : renderedElement;
            };
        }
        return isAtlantisButtonRenderTarget(render)
            ? renderAtlantisButtonTarget(render)
            : render;
    }
    if (isAtlantisButtonRenderTarget(children)) {
        return renderAtlantisButtonTarget(children);
    }
    if (React__default.isValidElement(children)) {
        return children;
    }
    return renderAtlantisButtonTarget(React__default.createElement(Button, { type: "tertiary", variation: "subtle", ariaLabel: "Close dialog" },
        React__default.createElement(Button.Icon, { name: "remove" })));
}
/**
 * Adapts a `Dialog.Content` `render` for the bottom sheet's drawer popup. A
 * render element passes through; a render function is wrapped to receive the
 * drawer's state re-shaped as the dialog popup's state (e.g. `nestedDrawerOpen`
 * becomes `nestedDialogOpen`), so `render` sees the same shape as the desktop
 * dialog.
 */
function resolveBottomSheetContentRender(render) {
    if (typeof render !== "function") {
        return render;
    }
    return (props, drawerState) => render(props, {
        open: drawerState.open,
        transitionStatus: drawerState.transitionStatus,
        nested: drawerState.nested,
        nestedDialogOpen: drawerState.nestedDrawerOpen,
    });
}
const DialogContext = React__default.createContext({
    type: "dialog",
    size: "base",
    isMobile: false,
    finalFocus: undefined,
});
/** Supplies the static config (type/size/isMobile/finalFocus) to the parts. */
function DialogProvider({ children, type, size, isMobile, finalFocus, }) {
    return (React__default.createElement(DialogContext.Provider, { value: { type, size, isMobile, finalFocus } }, children));
}
/** Reads the Dialog config; throws if a part is used outside `<Dialog>`. */
function useDialogContext() {
    const context = React__default.useContext(DialogContext);
    return context;
}
/**
 * True when the current parts render inside a `Dialog.ScrollArea` (or the scroll
 * region a `Dialog.Body` wraps itself in). Header/Footer read it to know whether
 * they are sticky (not nested inside a `Dialog.ScrollArea`) or scroll (nested inside a `Dialog.ScrollArea`); `Dialog.Body`
 * reads it to avoid creating a nested scroller.
 */
const DialogScrollAreaContext = React__default.createContext(false);
/** Whether the current part is nested inside a `Dialog.ScrollArea`. */
function useInsideDialogScrollArea() {
    return React__default.useContext(DialogScrollAreaContext);
}
/**
 * The modal's scroll region — a BaseUI `ScrollArea` that scrolls its content
 * with the browser's native scrollbar (re-enabled in Dialog.module.css). Content
 * placed inside it scrolls and owns the scrollbar; `Dialog.Body` used on its own
 * wraps itself in one. (The bottom sheet uses `DialogBottomSheetScrollArea`,
 * backed by the BottomSheet primitive's scroll area, for its fade.)
 */
function DialogScrollArea$1({ children, className, style, }) {
    return (React__default.createElement(ScrollAreaRoot, { className: classnames(styles.scrollRegion, className), style: style },
        React__default.createElement(ScrollAreaViewport, { className: styles.scrollViewport, 
            // BaseUI defaults the viewport to `overflow: scroll`; `auto` so the
            // native scrollbar only shows when the content overflows.
            style: { overflow: "auto" } },
            React__default.createElement(DialogScrollAreaContext.Provider, { value: true }, children))));
}
/** Layout-only header row; sticky unless nested inside a `Dialog.ScrollArea`. */
function SharedDialogHeader(_a) {
    var { children, className, style } = _a, rest = __rest(_a, ["children", "className", "style"]);
    const sticky = !useInsideDialogScrollArea();
    return (React__default.createElement("div", Object.assign({}, rest, { className: classnames(styles.header, className), style: style, "data-sticky": sticky || undefined }), children));
}
/** Generic footer region; sticky unless nested inside a `Dialog.ScrollArea`. */
function SharedDialogFooter(_a) {
    var { children, className, style } = _a, rest = __rest(_a, ["children", "className", "style"]);
    const sticky = !useInsideDialogScrollArea();
    return (React__default.createElement("div", Object.assign({}, rest, { className: classnames(styles.footer, className), style: style, "data-sticky": sticky || undefined, "data-testid": "ATL-Dialog-Footer" }), children));
}
function renderActionButton(action, defaults) {
    if (!action)
        return null;
    return isValidElement(action) ? (action) : (React__default.createElement(Button, Object.assign({}, mergeProps(defaults, action))));
}
/**
 * The action-button layout: tertiary on the left, primary/secondary grouped on
 * the right. Drop it inside a `Dialog.Footer`.
 */
function DialogActions(_a) {
    var { primary, secondary, tertiary, align = "right", className, style } = _a, rest = __rest(_a, ["primary", "secondary", "tertiary", "align", "className", "style"]);
    const primaryAction = renderActionButton(primary, { type: "primary" });
    const secondaryAction = renderActionButton(secondary, {
        type: "primary",
        variation: "subtle",
    });
    const tertiaryAction = renderActionButton(tertiary, {
        type: "secondary",
        variation: "destructive",
    });
    return (React__default.createElement("div", Object.assign({}, rest, { className: classnames(actionsStyles.actions, [actionsStyles[`align-${align}`]], className), style: style, "data-testid": "ATL-Dialog-Actions" }),
        tertiary && (React__default.createElement("div", { className: actionsStyles.leftActions }, tertiaryAction)),
        React__default.createElement("div", { className: classnames(actionsStyles.buttonGroup, actionsStyles[`align-${align}`]) },
            secondary && secondaryAction,
            primary && primaryAction)));
}

function DialogBottomSheet(props) {
    return (React__default.createElement(BottomSheet.Root, Object.assign({}, props)));
}
function DialogBottomSheetTrigger(props) {
    return (React__default.createElement(BottomSheet.Trigger, Object.assign({}, props)));
}
function DialogBottomSheetContent(_a) {
    var { children, className, style, render } = _a, rest = __rest(_a, ["children", "className", "style", "render"]);
    const { type, size, finalFocus } = useDialogContext();
    return (
    // Opt-out of the BottomSheet's built-in scroll area.
    // Dialog will handle the scroll area itself.
    React__default.createElement(BottomSheetScrollAreaContext.Provider, { value: false },
        React__default.createElement(BottomSheet.Popup, Object.assign({}, rest, { 
            // The bottom sheet is backed by a drawer popup, so re-shape its state
            // into the dialog popup state the consumer's `render` is typed against.
            render: resolveBottomSheetContentRender(render), className: classnames(styles.sheetSurface, className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "sheet", "data-size": size }), children)));
}
function DialogBottomSheetTitle(_a) {
    var { children } = _a, rest = __rest(_a, ["children"]);
    return (React__default.createElement(DrawerTitle, Object.assign({}, rest, { render: titleProps => (React__default.createElement(Heading, Object.assign({ level: 2 }, titleProps), children)) })));
}
function DialogBottomSheetClose(_a) {
    var { children, render } = _a, rest = __rest(_a, ["children", "render"]);
    const closeProps = mergeProps({ className: styles.close }, Object.assign({}, rest));
    return (React__default.createElement(DrawerClose, Object.assign({}, closeProps, { render: resolveComposableCloseRender({ children, render }) })));
}
/**
 * The sheet's scroll region — backed by the BottomSheet primitive's styled
 * scroll area (native scrollbar + bottom fade). Marks its content as inside a
 * scroll area so a nested `Dialog.Body` renders plain and sticky regions know
 * they scroll.
 */
function DialogBottomSheetScrollArea({ children, className, style, }) {
    return (React__default.createElement(BottomSheet.ScrollArea, { className: className, style: style },
        React__default.createElement(DialogScrollAreaContext.Provider, { value: true }, children)));
}
function DialogBottomSheetBody(_a) {
    var { children, fullBleed = false, className, style } = _a, rest = __rest(_a, ["children", "fullBleed", "className", "style"]);
    const insideScrollArea = useInsideDialogScrollArea();
    // Owns padding and the accessible-description wiring. By default it is the
    // scroll region (wraps itself in the sheet's scroll area); inside an explicit
    // `Dialog.ScrollArea` it renders as plain content (no nested scroller).
    const content = (React__default.createElement(DrawerDescription, { render: descriptionProps => (React__default.createElement("div", Object.assign({}, rest, descriptionProps, { className: classnames(styles.body, { [styles.fullBleed]: fullBleed }, className), style: style }), children)) }));
    return insideScrollArea ? (content) : (React__default.createElement(DialogBottomSheetScrollArea, null, content));
}
/** Mobile shell: a bottom sheet built on the BottomSheet/Drawer primitives. */
DialogBottomSheet.Trigger = DialogBottomSheetTrigger;
DialogBottomSheet.Content = DialogBottomSheetContent;
DialogBottomSheet.Header = SharedDialogHeader;
DialogBottomSheet.Title = DialogBottomSheetTitle;
DialogBottomSheet.Close = DialogBottomSheetClose;
DialogBottomSheet.Body = DialogBottomSheetBody;
DialogBottomSheet.Footer = SharedDialogFooter;
DialogBottomSheet.ScrollArea = DialogBottomSheetScrollArea;

function DialogModal(props) {
    return React__default.createElement(DialogRoot, Object.assign({}, props));
}
function DialogModalTrigger(_a) {
    var { children, render, handle, payload, className, style } = _a, rest = __rest(_a, ["children", "render", "handle", "payload", "className", "style"]);
    return (React__default.createElement(DialogTrigger$1, Object.assign({}, rest, { handle: handle, payload: payload, className: className, style: style, render: resolveComposableTriggerRender({ children, render }) })));
}
function DialogModalContent(_a) {
    var { children, className, style } = _a, rest = __rest(_a, ["children", "className", "style"]);
    const { type, size, finalFocus } = useDialogContext();
    return (React__default.createElement(DialogPortal, null,
        React__default.createElement(DialogBackdrop, { forceRender: true, className: styles.backdrop, "data-testid": "ATL-Dialog-Backdrop" }),
        React__default.createElement(DialogViewport, { className: styles.viewport },
            React__default.createElement(DialogPopup, Object.assign({}, rest, { className: classnames(styles.popup, styles[size], className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "modal" }), children))));
}
function DialogModalTitle(_a) {
    var { children } = _a, rest = __rest(_a, ["children"]);
    return (React__default.createElement(DialogTitle$1, Object.assign({}, rest, { render: titleProps => (React__default.createElement(Heading, Object.assign({ level: 2 }, titleProps), children)) })));
}
function DialogModalClose(_a) {
    var { children, render } = _a, rest = __rest(_a, ["children", "render"]);
    const closeProps = mergeProps({ className: styles.close }, Object.assign({}, rest));
    return (React__default.createElement(DialogClose$1, Object.assign({}, closeProps, { render: resolveComposableCloseRender({ children, render }) })));
}
function DialogModalBody(_a) {
    var { children, fullBleed = false, className, style } = _a, rest = __rest(_a, ["children", "fullBleed", "className", "style"]);
    const insideScrollArea = useInsideDialogScrollArea();
    // Owns padding and the accessible-description wiring. On its own the body is
    // the scroll region, so it wraps itself in a `DialogScrollArea`; inside an
    // explicit `Dialog.ScrollArea` it renders as plain content (no nested scroller).
    const content = (React__default.createElement(DialogDescription, { render: descriptionProps => (React__default.createElement("div", Object.assign({}, rest, descriptionProps, { className: classnames(styles.body, { [styles.fullBleed]: fullBleed }, className), style: style }), children)) }));
    return insideScrollArea ? (content) : (React__default.createElement(DialogScrollArea$1, null, content));
}
DialogModal.Trigger = DialogModalTrigger;
DialogModal.Content = DialogModalContent;
DialogModal.Header = SharedDialogHeader;
DialogModal.Title = DialogModalTitle;
DialogModal.Close = DialogModalClose;
DialogModal.Body = DialogModalBody;
DialogModal.Footer = SharedDialogFooter;
DialogModal.ScrollArea = DialogScrollArea$1;

/**
 * Creates a handle that opens/closes a `Dialog` from detached triggers (e.g. an
 * action inside a `Combobox`/`Menu`) without prop-drilling open state. Mirrors
 * BaseUI's `Dialog.createHandle()`.
 */
function createHandle() {
    return createDialogHandle();
}
/**
 * Returns the active shell — the bottom sheet on mobile, otherwise the centered
 * modal. Each shell is a root component with
 * its parts attached, so callers do `const Shell = useDialogShell()` then render
 * `<Shell.Whatever />`.
 */
function useDialogShell() {
    const { isMobile } = useDialogContext();
    return isMobile ? DialogBottomSheet : DialogModal;
}
/**
 * The Dialog root is a centered modal on
 * desktop, a bottom sheet on mobile. Compose it with `Dialog.Trigger`,
 * `Dialog.Content`, and the layout parts.
 */
function Dialog(_a) {
    var { onRequestClose, size = "base", type = "dialog", finalFocus, children, onOpenChange } = _a, rest = __rest(_a, ["onRequestClose", "size", "type", "finalFocus", "children", "onOpenChange"]);
    function handleOpenChange(nextOpen, eventDetails) {
        if (!nextOpen) {
            onRequestClose === null || onRequestClose === void 0 ? void 0 : onRequestClose(eventDetails);
        }
        onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen, eventDetails);
    }
    const { width } = useWindowDimensions();
    const isMobile = width !== undefined && width <= SMALL_SCREEN_BREAKPOINT;
    // The shell is the root component; its parts are reached via `useDialogShell`
    // in each part below (the Menu pattern).
    const RootComponent = isMobile ? DialogBottomSheet : DialogModal;
    const rootProps = Object.assign(Object.assign({}, rest), { onOpenChange: handleOpenChange, modal: true, disablePointerDismissal: type === "alertdialog", children });
    return (React__default.createElement(DialogProvider, { type: type, size: size, isMobile: isMobile, finalFocus: finalFocus },
        React__default.createElement(RootComponent, Object.assign({}, rootProps))));
}
/**
 * Opens the dialog. Accepts an Atlantis `<Button>` (via `children` or `render`)
 * or links a detached `Dialog` through a matching `handle`.
 */
function DialogTrigger(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Trigger, Object.assign({}, props));
}
/**
 * The dialog popup surface. Holds the `Header`, `Body`, and `Footer`; renders as a
 * centered modal on desktop and a bottom sheet on mobile.
 */
function DialogContent(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Content, Object.assign({}, props));
}
/** Layout-only header row — typically a `Dialog.Title` and a `Dialog.Close`. */
function DialogHeader(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Header, Object.assign({}, props));
}
/** The dialog's accessible title; wired to `aria-labelledby`. */
function DialogTitle(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Title, Object.assign({}, props));
}
/** A dismiss button that closes the dialog. */
function DialogClose(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Close, Object.assign({}, props));
}
/** The dialog body; wired to `aria-describedby`. */
function DialogBody(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Body, Object.assign({}, props));
}
/** Footer region for actions or supporting content. */
function DialogFooter(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.Footer, Object.assign({}, props));
}
/**
 * Advanced scroll region. Delegates to the shell: the modal uses a native
 * scrollbar; the sheet uses the BottomSheet primitive's scroll area (with fade).
 */
function DialogScrollArea(props) {
    const Shell = useDialogShell();
    return React__default.createElement(Shell.ScrollArea, Object.assign({}, props));
}
Dialog.Trigger = DialogTrigger;
Dialog.Content = DialogContent;
Dialog.Header = DialogHeader;
Dialog.Title = DialogTitle;
Dialog.Close = DialogClose;
Dialog.Body = DialogBody;
Dialog.Footer = DialogFooter;
Dialog.Actions = DialogActions;
Dialog.ScrollArea = DialogScrollArea;
Dialog.createHandle = createHandle;

/**
 * Selector for elements that can receive focus. Mirrors the common
 * "focus-trap"/"tabbable" baseline without pulling in a dependency: anything
 * natively focusable plus explicit positive/zero tabindex, minus disabled
 * controls and `tabindex="-1"`.
 */
const FOCUSABLE_SELECTOR = [
    "a[href]",
    "area[href]",
    "button:not([disabled])",
    "input:not([disabled]):not([type='hidden'])",
    "select:not([disabled])",
    "textarea:not([disabled])",
    "iframe",
    "audio[controls]",
    "video[controls]",
    "[contenteditable]:not([contenteditable='false'])",
    '[tabindex]:not([tabindex="-1"])',
].join(",");
function isVisible(element) {
    if (typeof element.checkVisibility === "function") {
        return element.checkVisibility();
    }
    return Boolean(element.offsetWidth ||
        element.offsetHeight ||
        element.getClientRects().length);
}
/**
 * The first focusable element within `container` (the container itself counts if
 * it is focusable). Skips hidden elements. Returns `null` when nothing inside is
 * focusable.
 */
function getFirstFocusableWithin(container) {
    var _a;
    if (!container)
        return null;
    if (container.matches(FOCUSABLE_SELECTOR) && isVisible(container)) {
        return container;
    }
    const candidates = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
    return (_a = candidates.find(isVisible)) !== null && _a !== void 0 ? _a : null;
}
/**
 * Builds a `finalFocus` callback for `Dialog` that keeps keyboard focus near the
 * trigger even when the trigger unmounts while the dialog is open (e.g. an
 * autocomplete "Create" action whose row is replaced by a card on save).
 *
 * Resolution order on close:
 * 1. the `trigger`, if it is still connected;
 * 2. otherwise the first focusable element inside `boundary` (the "closest"
 *    landing spot);
 * 3. otherwise `null`, which lets BaseUI fall back to its default behavior.
 *
 * @example
 * const triggerRef = useRef<HTMLInputElement>(null);
 * const boundaryRef = useRef<HTMLDivElement>(null);
 * <div ref={boundaryRef}>…</div>
 * <Dialog
 *   handle={handle}
 *   finalFocus={getDialogReturnFocus({ trigger: triggerRef, boundary: boundaryRef })}
 * >
 */
function getDialogReturnFocus({ trigger, boundary, }) {
    return () => {
        const target = trigger === null || trigger === void 0 ? void 0 : trigger.current;
        if (target === null || target === void 0 ? void 0 : target.isConnected)
            return target;
        return getFirstFocusableWithin(boundary.current);
    };
}

export { Dialog as D, getFirstFocusableWithin as a, getDialogReturnFocus as g };
