// packages/components/src/modal/index.tsx
import clsx from "clsx";
import { createPortal, useCallback, useEffect, useRef, useState, forwardRef, useLayoutEffect, createContext, useContext } from "@wordpress/element";
import { useInstanceId, useFocusReturn, useFocusOnMount, useConstrainedTabbing, useMergeRefs } from "@wordpress/compose";
import { __ } from "@wordpress/i18n";
import { close } from "@wordpress/icons";
import { getScrollContainer } from "@wordpress/dom";
import * as ariaHelper from "./aria-helper.js";
import Button from "../button/index.js";
import StyleProvider from "../style-provider/index.js";
import { withIgnoreIMEEvents } from "../utils/with-ignore-ime-events.js";
import { Spacer } from "../spacer/index.js";
import { useModalExitAnimation } from "./use-modal-exit-animation.js";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
var ModalContext = createContext(/* @__PURE__ */ new Set());
ModalContext.displayName = "ModalContext";
var bodyOpenClasses = /* @__PURE__ */ new Map();
function UnforwardedModal(props, forwardedRef) {
  const {
    bodyOpenClassName = "modal-open",
    role = "dialog",
    title = null,
    focusOnMount = true,
    shouldCloseOnEsc = true,
    shouldCloseOnClickOutside = true,
    isDismissible = true,
    /* Accessibility. */
    aria = {
      labelledby: void 0,
      describedby: void 0
    },
    onRequestClose,
    icon,
    closeButtonLabel,
    children,
    style,
    overlayClassName: overlayClassnameProp,
    className,
    contentLabel,
    onKeyDown,
    isFullScreen = false,
    size,
    headerActions = null,
    __experimentalHideHeader = false
  } = props;
  const ref = useRef();
  const instanceId = useInstanceId(Modal);
  const headingId = title ? `components-modal-header-${instanceId}` : aria.labelledby;
  const focusOnMountRef = useFocusOnMount(focusOnMount === "firstContentElement" ? "firstElement" : focusOnMount);
  const constrainedTabbingRef = useConstrainedTabbing();
  const focusReturnRef = useFocusReturn();
  const contentRef = useRef(null);
  const childrenContainerRef = useRef(null);
  const [hasScrolledContent, setHasScrolledContent] = useState(false);
  const [hasScrollableContent, setHasScrollableContent] = useState(false);
  let sizeClass;
  if (isFullScreen || size === "fill") {
    sizeClass = "is-full-screen";
  } else if (size) {
    sizeClass = `has-size-${size}`;
  }
  const isContentScrollable = useCallback(() => {
    if (!contentRef.current) {
      return;
    }
    const closestScrollContainer = getScrollContainer(contentRef.current);
    if (contentRef.current === closestScrollContainer) {
      setHasScrollableContent(true);
    } else {
      setHasScrollableContent(false);
    }
  }, [contentRef]);
  useEffect(() => {
    ariaHelper.modalize(ref.current);
    return () => ariaHelper.unmodalize();
  }, []);
  const onRequestCloseRef = useRef();
  useEffect(() => {
    onRequestCloseRef.current = onRequestClose;
  }, [onRequestClose]);
  const dismissers = useContext(ModalContext);
  const [nestedDismissers] = useState(() => /* @__PURE__ */ new Set());
  useEffect(() => {
    dismissers.add(onRequestCloseRef);
    for (const dismisser of dismissers) {
      if (dismisser !== onRequestCloseRef) {
        dismisser.current?.();
      }
    }
    return () => {
      for (const dismisser of nestedDismissers) {
        dismisser.current?.();
      }
      dismissers.delete(onRequestCloseRef);
    };
  }, [dismissers, nestedDismissers]);
  useEffect(() => {
    var _bodyOpenClasses$get;
    const theClass = bodyOpenClassName;
    const oneMore = 1 + ((_bodyOpenClasses$get = bodyOpenClasses.get(theClass)) !== null && _bodyOpenClasses$get !== void 0 ? _bodyOpenClasses$get : 0);
    bodyOpenClasses.set(theClass, oneMore);
    document.body.classList.add(bodyOpenClassName);
    return () => {
      const oneLess = bodyOpenClasses.get(theClass) - 1;
      if (oneLess === 0) {
        document.body.classList.remove(theClass);
        bodyOpenClasses.delete(theClass);
      } else {
        bodyOpenClasses.set(theClass, oneLess);
      }
    };
  }, [bodyOpenClassName]);
  const {
    closeModal,
    frameRef,
    frameStyle,
    overlayClassname
  } = useModalExitAnimation();
  useLayoutEffect(() => {
    if (!window.ResizeObserver || !childrenContainerRef.current) {
      return;
    }
    const resizeObserver = new ResizeObserver(isContentScrollable);
    resizeObserver.observe(childrenContainerRef.current);
    isContentScrollable();
    return () => {
      resizeObserver.disconnect();
    };
  }, [isContentScrollable, childrenContainerRef]);
  function handleEscapeKeyDown(event) {
    if (shouldCloseOnEsc && (event.code === "Escape" || event.key === "Escape") && !event.defaultPrevented) {
      event.preventDefault();
      closeModal().then(() => onRequestClose(event));
    }
  }
  const onContentContainerScroll = useCallback((e) => {
    var _e$currentTarget$scro;
    const scrollY = (_e$currentTarget$scro = e?.currentTarget?.scrollTop) !== null && _e$currentTarget$scro !== void 0 ? _e$currentTarget$scro : -1;
    if (!hasScrolledContent && scrollY > 0) {
      setHasScrolledContent(true);
    } else if (hasScrolledContent && scrollY <= 0) {
      setHasScrolledContent(false);
    }
  }, [hasScrolledContent]);
  let pressTarget = null;
  const overlayPressHandlers = {
    onPointerDown: (event) => {
      if (event.target === event.currentTarget) {
        pressTarget = event.target;
        event.preventDefault();
      }
    },
    // Closes the modal with two exceptions. 1. Opening the context menu on
    // the overlay. 2. Pressing on the overlay then dragging the pointer
    // over the modal and releasing. Due to the modal being a child of the
    // overlay, such a gesture is a `click` on the overlay and cannot be
    // excepted by a `click` handler. Thus the tactic of handling
    // `pointerup` and comparing its target to that of the `pointerdown`.
    onPointerUp: ({
      target,
      button
    }) => {
      const isSameTarget = target === pressTarget;
      pressTarget = null;
      if (button === 0 && isSameTarget) {
        closeModal().then(() => onRequestClose());
      }
    }
  };
  const modal = (
    // eslint-disable-next-line jsx-a11y/no-static-element-interactions
    /* @__PURE__ */ _jsx("div", {
      ref: useMergeRefs([ref, forwardedRef]),
      className: clsx("components-modal__screen-overlay", overlayClassname, overlayClassnameProp),
      onKeyDown: withIgnoreIMEEvents(handleEscapeKeyDown),
      ...shouldCloseOnClickOutside ? overlayPressHandlers : {},
      children: /* @__PURE__ */ _jsx(StyleProvider, {
        document,
        children: /* @__PURE__ */ _jsx("div", {
          className: clsx("components-modal__frame", sizeClass, className),
          style: {
            ...frameStyle,
            ...style
          },
          ref: useMergeRefs([frameRef, constrainedTabbingRef, focusReturnRef, focusOnMount !== "firstContentElement" ? focusOnMountRef : null]),
          role,
          "aria-label": contentLabel,
          "aria-labelledby": contentLabel ? void 0 : headingId,
          "aria-describedby": aria.describedby,
          tabIndex: -1,
          onKeyDown,
          children: /* @__PURE__ */ _jsxs("div", {
            className: clsx("components-modal__content", {
              "hide-header": __experimentalHideHeader,
              "is-scrollable": hasScrollableContent,
              "has-scrolled-content": hasScrolledContent
            }),
            role: "document",
            onScroll: onContentContainerScroll,
            ref: contentRef,
            "aria-label": hasScrollableContent ? __("Scrollable section") : void 0,
            tabIndex: hasScrollableContent ? 0 : void 0,
            children: [!__experimentalHideHeader && /* @__PURE__ */ _jsxs("div", {
              className: "components-modal__header",
              children: [/* @__PURE__ */ _jsxs("div", {
                className: "components-modal__header-heading-container",
                children: [icon && /* @__PURE__ */ _jsx("span", {
                  className: "components-modal__icon-container",
                  "aria-hidden": true,
                  children: icon
                }), title && /* @__PURE__ */ _jsx("h1", {
                  id: headingId,
                  className: "components-modal__header-heading",
                  children: title
                })]
              }), headerActions, isDismissible && /* @__PURE__ */ _jsxs(_Fragment, {
                children: [/* @__PURE__ */ _jsx(Spacer, {
                  marginBottom: 0,
                  marginLeft: 2
                }), /* @__PURE__ */ _jsx(Button, {
                  size: "compact",
                  onClick: (event) => closeModal().then(() => onRequestClose(event)),
                  icon: close,
                  label: closeButtonLabel || __("Close")
                })]
              })]
            }), /* @__PURE__ */ _jsx("div", {
              ref: useMergeRefs([childrenContainerRef, focusOnMount === "firstContentElement" ? focusOnMountRef : null]),
              className: "components-modal__children-container",
              children
            })]
          })
        })
      })
    })
  );
  return createPortal(/* @__PURE__ */ _jsx(ModalContext.Provider, {
    value: nestedDismissers,
    children: modal
  }), document.body);
}
var Modal = forwardRef(UnforwardedModal);
var modal_default = Modal;
export {
  Modal,
  modal_default as default
};
//# sourceMappingURL=index.js.map
