/**
 * Port of Base UI React's `useDismiss` hook.
 *
 * React source:
 *   `base-ui/packages/react/src/floating-ui-react/hooks/useDismiss.ts`
 *
 * Dismisses the floating element on:
 *  - Escape key (gated by `escapeKey`).
 *  - Outside press (gated by `outsidePress`) — driven by an
 *    `intentional`/`sloppy` press-type state machine:
 *      * `intentional` — mouse fires on `pointerup`; touch requires a
 *        minimal movement or `touchend` within a short window.
 *      * `sloppy` — mouse fires on `pointerdown`; touch fires on
 *        `touchend` (within 1 s) or a scroll-away gesture.
 *  - Reference press (gated by `referencePress`) — the bubble handler
 *    fires on `onPointerDown` when `sloppy` and `onClick` when
 *    `intentional`; a fallback `onClick` also fires in `sloppy` mode so
 *    keyboard activation keeps working.
 *
 * ### React → Solid deviations
 *
 * 1. `useEffect` with deps → `createEffect` in split compute/apply form.
 *    The apply runs whenever any tracked read changes. We purposely
 *    track a memoised options-snapshot getter to match React's dep
 *    array: the effect re-installs listeners when `open`, `enabled`,
 *    `escapeKey`, `outsidePress` (only boolean/fn swap), or `doc`
 *    identity changes.
 *
 * 2. `useRef` → `MutableRef<T>` (keeps the `.current` read/write shape
 *    callers elsewhere in the port already use).
 *
 * 3. `useStableCallback` → `createStableCallback` from
 *    `src/internals/stable-callback.ts`. Used to stabilise listener
 *    identity so `addEventListener` / `removeEventListener` pair up.
 *
 * 4. React's `isReactEvent` branch (unwrap `.nativeEvent`) is dropped —
 *    Solid event handlers receive native events directly, so the
 *    keydown handler takes a plain `KeyboardEvent`.
 *
 * 5. React's `useTimeout` (from `@base-ui/utils`) maps to the
 *    `useTimeout` helper at `src/internals/use-timeout.ts`. The `Timeout`
 *    class ships `start(delay, fn)` + `clear()` with the same
 *    semantics.
 *
 * 6. Shadow-DOM-safe `contains()` from `src/internals/dom/contains.ts`
 *    replaces raw `node.contains(target)`.
 *
 * 7. Tree-aware dismissal (parent popover not dismissing when click
 *    lands inside a child floating element, gated by `bubbles.*`)
 *    follows React 1:1 — the parent reads `__escapeKeyBubbles` /
 *    `__outsidePressBubbles` latches stamped on each child's
 *    `dataRef` during its own `useDismiss` effect.
 */
import { getComputedStyle, getParentNode, isElement, isHTMLElement, isLastTraversableNode, isShadowRoot, isWebKit } from "@floating-ui/utils/dom";
import { createEffect, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../change-event-details.jsx";
import { contains } from "../../dom/contains.jsx";
import { ownerDocument } from "../../dom/owner-document.jsx";
import { readOption } from "../../maybe-accessor.jsx";
import { createStableCallback } from "../../stable-callback.jsx";
import { Timeout, useTimeout } from "../../use-timeout.jsx";
import { isInsideNestedPortal } from "../components/floating-portal.jsx";
import { useFloatingTree } from "../components/floating-tree.jsx";
import { createAttribute } from "../utils/create-attribute.jsx";
import { getTarget, isEventTargetWithin, isRootElement } from "../utils/element.jsx";
import { getNodeChildren } from "../utils/nodes.jsx";
const bubbleHandlerKeys = {
  intentional: "onClick",
  sloppy: "onPointerDown"
};
function alwaysFalse() {
  return false;
}

/**
 * Normalise `bubbles` into an explicit `{ escapeKey, outsidePress }`
 * pair. Shorthand boolean applies to both; object form fills defaults.
 * Accessors on either the outer `bubbles` prop or the nested fields are
 * resolved at the call site. Defaults (matching React):
 * `escapeKey: false`, `outsidePress: true`.
 */
function normalizeProp(normalizable) {
  const resolved = typeof normalizable === "function" ? normalizable() : normalizable;
  if (typeof resolved === "boolean") {
    return {
      escapeKey: resolved,
      outsidePress: resolved
    };
  }
  const obj = resolved;
  return {
    escapeKey: obj?.escapeKey === undefined ? false : readOption(obj.escapeKey),
    outsidePress: obj?.outsidePress === undefined ? true : readOption(obj.outsidePress)
  };
}
/**
 * Closes the floating element when a dismissal is requested — by default,
 * when the user presses `Escape` or outside of the floating element.
 * @see https://floating-ui.com/docs/useDismiss
 */
export function useDismiss(context, props = {}) {
  /**
   * Resolve the active floating root store. Accepts either a bare
   * context or an accessor so navigation-menu-style compounds can swap
   * the root context without re-installing the hook.
   */
  const resolveStore = () => {
    const ctx = typeof context === "function" ? context() : context;
    return "rootStore" in ctx ? ctx.rootStore : ctx;
  };
  // The initial store snapshot — handlers that don't re-run on each
  // event (e.g. capture-phase listeners) use the live `resolveStore()`
  // at call time instead of this binding. `dataRef` is read eagerly
  // from the initial snapshot; when the context swaps later, outside
  // handlers resolve the live store but still look at the caller's
  // original dataRef for the short-lived latches.
  // One-shot snapshot (see comment above). `resolveStore` reads the reactive
  // `floatingRootContext` accessor (navigation-menu List passes an accessor),
  // so untrack the body-time read to avoid `[STRICT_READ_UNTRACKED]`.
  const initialStore = untrack(resolveStore);
  const {
    dataRef
  } = initialStore.context;

  // Untrack the body-time destructure: parents may pass signal-derived
  // values via JSX, making each `props.foo` access fire
  // `[STRICT_READ_UNTRACKED]` under the strict-read scope set up by
  // `devComponent(...)`. These fields are not `MaybeAccessor` shapes —
  // they're snapshot values — so a one-shot read matches the React
  // fork's semantics.
  const {
    outsidePress: outsidePressProp = true,
    outsidePressEvent = "sloppy",
    referencePress = alwaysFalse,
    referencePressEvent = "sloppy",
    externalTree
  } = untrack(() => ({
    outsidePress: props.outsidePress,
    outsidePressEvent: props.outsidePressEvent,
    referencePress: props.referencePress,
    referencePressEvent: props.referencePressEvent,
    externalTree: props.externalTree
  }));
  const getEnabled = () => props.enabled === undefined ? true : readOption(props.enabled);
  const getEscapeKey = () => props.escapeKey === undefined ? true : readOption(props.escapeKey);
  const tree = useFloatingTree(externalTree);

  // React: `useStableCallback(typeof fn === 'function' ? fn : () => false)`.
  // We mirror by wrapping in a stable callback that routes to the latest
  // predicate (or a constant `false` for non-fn values).
  const outsidePressFn = createStableCallback(() => typeof outsidePressProp === "function" ? outsidePressProp : alwaysFalse);
  const outsidePress = typeof outsidePressProp === "function" ? outsidePressFn : outsidePressProp;
  const outsidePressEnabled = outsidePress !== false;
  const getOutsidePressEventProp = createStableCallback(() => () => outsidePressEvent);

  // State machine refs (React `useRef(false)` equivalents).
  const pressStartedInsideRef = {
    current: false
  };
  const pressStartPreventedRef = {
    current: false
  };
  // Ignore only the very next outside click after dragging from inside to outside.
  const suppressNextOutsideClickRef = {
    current: false
  };

  // Lazy readers for bubbles — each call re-reads the reactive source
  // so flips (e.g. closeParentOnEsc in menu submenus) take
  // effect without recomposing the consumer.
  const getBubblesPair = () => normalizeProp(props.bubbles);
  const getEscapeKeyBubbles = () => getBubblesPair().escapeKey;
  const touchStateRef = {
    current: null
  };
  const cancelDismissOnEndTimeout = useTimeout();
  const clearInsideReactTreeTimeout = useTimeout();
  const clearInsideReactTree = createStableCallback(() => () => {
    clearInsideReactTreeTimeout.clear();
    dataRef.current.insideReactTree = false;
  });
  const isComposingRef = {
    current: false
  };
  const currentPointerTypeRef = {
    current: ""
  };
  const isReferencePressEnabled = createStableCallback(() => referencePress);
  const closeOnEscapeKeyDown = createStableCallback(() => event => {
    const liveStore = resolveStore();
    if (!liveStore.state.open || !getEnabled() || !getEscapeKey() || event.key !== "Escape") {
      return;
    }

    // Wait until IME is settled. Pressing `Escape` while composing should
    // close the compose menu, but not the floating element.
    if (isComposingRef.current) {
      return;
    }
    const liveDataRef = liveStore.context.dataRef;
    const nodeId = liveDataRef.current.floatingContext?.nodeId;
    const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
    const escapeKeyBubbles = getEscapeKeyBubbles();
    if (!escapeKeyBubbles) {
      if (children.length > 0) {
        let shouldDismiss = true;
        for (const child of children) {
          if (child.context?.open && !child.context.dataRef.current.__escapeKeyBubbles) {
            shouldDismiss = false;
          }
        }
        if (!shouldDismiss) {
          return;
        }
      }
    }
    const eventDetails = createChangeEventDetails(REASONS.escapeKey, event);
    liveStore.setOpen(false, eventDetails);
    if (!escapeKeyBubbles && !eventDetails.isPropagationAllowed) {
      event.stopPropagation();
    }
  });
  const markInsideReactTree = createStableCallback(() => () => {
    dataRef.current.insideReactTree = true;
    clearInsideReactTreeTimeout.start(0, clearInsideReactTree);
  });

  // Main effect: install document listeners when `open && enabled`.
  // React uses `useEffect` with a big dep array; Solid's `createEffect`
  // re-runs whenever any of the tracked reads below change. We read
  // `store.useState('open')` through the reactive accessor so the
  // effect re-runs on open toggles.
  createEffect(() => {
    // Track the resolved store so a context swap re-installs
    // listeners on the new one. `useState` tracks open /
    // floating so open flips also re-run.
    const liveStore = resolveStore();
    // Track bubbles so the dataRef latch is rewritten when the
    // underlying accessor flips (solidify.md P3).
    const bubbles = getBubblesPair();
    return {
      liveStore,
      open: liveStore.state.open,
      floatingElement: liveStore.state.floatingElement,
      enabled: getEnabled(),
      escapeKey: getEscapeKey(),
      escapeKeyBubbles: bubbles.escapeKey,
      outsidePressBubbles: bubbles.outsidePress
    };
  }, snapshot => {
    const store = snapshot.liveStore;
    const dataRef = store.context.dataRef;
    const {
      open,
      enabled,
      escapeKey,
      escapeKeyBubbles,
      outsidePressBubbles
    } = snapshot;
    if (!open || !enabled) {
      return;
    }
    dataRef.current.__escapeKeyBubbles = escapeKeyBubbles;
    dataRef.current.__outsidePressBubbles = outsidePressBubbles;
    const compositionTimeout = new Timeout();
    const preventedPressSuppressionTimeout = new Timeout();
    function handleCompositionStart() {
      compositionTimeout.clear();
      isComposingRef.current = true;
    }
    function handleCompositionEnd() {
      // Safari fires `compositionend` before `keydown`, so we need to
      // wait until the next tick to set `isComposing` to `false`.
      // https://bugs.webkit.org/show_bug.cgi?id=165004
      compositionTimeout.start(
      // 0 ms / 1 ms don't work in Safari. 5 ms is consistent.
      isWebKit() ? 5 : 0, () => {
        isComposingRef.current = false;
      });
    }
    function suppressImmediateOutsideClickAfterPreventedStart() {
      suppressNextOutsideClickRef.current = true;
      // Firefox can emit the synthetic outside click in a later task
      // after pointer-lock exit, so microtask clearing is too early.
      preventedPressSuppressionTimeout.start(0, () => {
        suppressNextOutsideClickRef.current = false;
      });
    }
    function resetPressStartState() {
      pressStartedInsideRef.current = false;
      pressStartPreventedRef.current = false;
    }
    function getOutsidePressEvent() {
      const type = currentPointerTypeRef.current;
      const computedType = type === "pen" || !type ? "mouse" : type;
      const outsidePressEventValue = getOutsidePressEventProp();
      const resolved = typeof outsidePressEventValue === "function" ? outsidePressEventValue() : outsidePressEventValue;
      if (typeof resolved === "string") {
        return resolved;
      }
      return resolved[computedType];
    }
    function shouldIgnoreEvent(event) {
      const computedOutsidePressEvent = getOutsidePressEvent();
      return computedOutsidePressEvent === "intentional" && event.type !== "click" || computedOutsidePressEvent === "sloppy" && event.type === "click";
    }
    function isEventWithinFloatingTree(event) {
      const nodeId = dataRef.current.floatingContext?.nodeId;
      const targetIsInsideChildren = !!tree && getNodeChildren(tree.nodesRef.current, nodeId).some(node => isEventTargetWithin(event, node.context?.elements.floating));
      return isEventTargetWithin(event, store.state.floatingElement) || isEventTargetWithin(event, store.state.domReferenceElement) || targetIsInsideChildren ||
      // Presses inside popups portaled out of our DOM subtree but
      // logically nested under us (a Select popup inside a Dialog,
      // say) are not outside presses. React covers this via portal
      // synthetic-event propagation (`insideReactTree`); Solid
      // events follow the DOM tree, so we walk the logical portal
      // ancestry instead. See `isInsideNestedPortal`.
      isInsideNestedPortal(getTarget(event), store.state.floatingElement);
    }

    // A press landed on a third-party element injected (e.g. a
    // `[data-inert]` overlay) after the floating element rendered, so
    // none of the recorded markers sit on the target's ancestor chain.
    // Walks the target up to its last-traversable ancestor and checks
    // that against the marker set.
    function isInjectedThirdPartyPress(target, markers, floatingElement) {
      if (!markers.length || !isElement(target) || isRootElement(target) ||
      // Clicked on a direct ancestor (e.g. FloatingOverlay).
      contains(target, floatingElement)) {
        return false;
      }
      let ancestor = target;
      while (ancestor && !isLastTraversableNode(ancestor)) {
        const nextParent = getParentNode(ancestor);
        if (isLastTraversableNode(nextParent) || !isElement(nextParent)) {
          break;
        }
        ancestor = nextParent;
      }

      // If the target root contains none of the markers, the element
      // was injected after the floating element rendered.
      return markers.every(marker => !contains(ancestor, marker));
    }

    // A press on the target element's scrollbar gutter (rather than its
    // content). Skipped for touch — scrollbars don't receive touch
    // events on most platforms.
    function isPressOnScrollbar(event, target) {
      if (!isHTMLElement(target) || "touches" in event) {
        return false;
      }
      const el = target;
      const lastTraversableNode = isLastTraversableNode(el);
      const style = getComputedStyle(el);
      const scrollRe = /auto|scroll/;
      const isScrollableX = lastTraversableNode || scrollRe.test(style.overflowX);
      const isScrollableY = lastTraversableNode || scrollRe.test(style.overflowY);
      const canScrollX = isScrollableX && el.clientWidth > 0 && el.scrollWidth > el.clientWidth;
      const canScrollY = isScrollableY && el.clientHeight > 0 && el.scrollHeight > el.clientHeight;
      const isRTL = style.direction === "rtl";
      const mouseEvent = event;
      const pressedVerticalScrollbar = canScrollY && (isRTL ? mouseEvent.offsetX <= el.offsetWidth - el.clientWidth : mouseEvent.offsetX > el.clientWidth);
      const pressedHorizontalScrollbar = canScrollX && mouseEvent.offsetY > el.clientHeight;
      return pressedVerticalScrollbar || pressedHorizontalScrollbar;
    }
    function closeOnPressOutside(event) {
      if (shouldIgnoreEvent(event)) {
        clearInsideReactTree();
        return;
      }
      if (dataRef.current.insideReactTree) {
        clearInsideReactTree();
        return;
      }
      const target = getTarget(event);
      const inertSelector = `[${createAttribute("inert")}]`;
      const targetRoot = isElement(target) ? target.getRootNode() : null;
      const markers = Array.from((isShadowRoot(targetRoot) ? targetRoot : ownerDocument(store.state.floatingElement)).querySelectorAll(inertSelector));
      const triggers = store.context.triggerElements;

      // If another trigger is clicked, don't close the floating element.
      if (target && (triggers.hasElement(target) || triggers.hasMatchingElement(trigger => contains(trigger, target)))) {
        return;
      }

      // Check if the click occurred on a third-party element injected
      // after the floating element rendered.
      if (isInjectedThirdPartyPress(target, markers, store.state.floatingElement)) {
        return;
      }

      // Check if the click occurred on the scrollbar.
      if (isPressOnScrollbar(event, target)) {
        return;
      }
      if (isEventWithinFloatingTree(event)) {
        return;
      }

      // In intentional mode, a press that starts inside and ends
      // outside gets one suppressed outside click. Run this after
      // inside-target checks so inside clicks don't consume the
      // one-shot suppression.
      if (getOutsidePressEvent() === "intentional" && suppressNextOutsideClickRef.current) {
        preventedPressSuppressionTimeout.clear();
        suppressNextOutsideClickRef.current = false;
        return;
      }
      if (typeof outsidePress === "function" && !outsidePress(event)) {
        return;
      }
      const nodeId = dataRef.current.floatingContext?.nodeId;
      const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];
      if (children.length > 0) {
        let shouldDismiss = true;
        for (const child of children) {
          if (child.context?.open && !child.context.dataRef.current.__outsidePressBubbles) {
            shouldDismiss = false;
          }
        }
        if (!shouldDismiss) {
          return;
        }
      }
      store.setOpen(false, createChangeEventDetails(REASONS.outsidePress, event));
      clearInsideReactTree();
    }
    function handlePointerDown(event) {
      if (getOutsidePressEvent() !== "sloppy" || event.pointerType === "touch" || !store.state.open || !enabled || isEventTargetWithin(event, store.state.floatingElement) || isEventTargetWithin(event, store.state.domReferenceElement)) {
        return;
      }
      closeOnPressOutside(event);
    }
    function handleTouchStart(event) {
      if (getOutsidePressEvent() !== "sloppy" || !store.state.open || !enabled || isEventTargetWithin(event, store.state.floatingElement) || isEventTargetWithin(event, store.state.domReferenceElement)) {
        return;
      }
      const touch = event.touches[0];
      if (touch) {
        touchStateRef.current = {
          startTime: Date.now(),
          startX: touch.clientX,
          startY: touch.clientY,
          dismissOnTouchEnd: false,
          dismissOnMouseDown: true
        };
        cancelDismissOnEndTimeout.start(1000, () => {
          if (touchStateRef.current) {
            touchStateRef.current.dismissOnTouchEnd = false;
            touchStateRef.current.dismissOnMouseDown = false;
          }
        });
      }
    }
    function addTargetEventListenerOnce(event, listener) {
      const target = getTarget(event);
      if (!target) {
        return;
      }
      const handler = () => {
        listener(event);
        target.removeEventListener(event.type, handler);
      };
      target.addEventListener(event.type, handler);
    }
    function handleTouchStartCapture(event) {
      currentPointerTypeRef.current = "touch";
      addTargetEventListenerOnce(event, handleTouchStart);
    }
    function closeOnPressOutsideCapture(event) {
      cancelDismissOnEndTimeout.clear();
      if (event.type === "pointerdown") {
        currentPointerTypeRef.current = event.pointerType;
      }
      if (event.type === "mousedown" && touchStateRef.current && !touchStateRef.current.dismissOnMouseDown) {
        return;
      }
      addTargetEventListenerOnce(event, targetEvent => {
        if (targetEvent.type === "pointerdown") {
          handlePointerDown(targetEvent);
        } else {
          closeOnPressOutside(targetEvent);
        }
      });
    }
    function handlePressEndCapture(event) {
      if (!pressStartedInsideRef.current) {
        return;
      }
      const pressStartedInsideDefaultPrevented = pressStartPreventedRef.current;
      resetPressStartState();
      if (getOutsidePressEvent() !== "intentional") {
        return;
      }
      if (event.type === "pointercancel") {
        if (pressStartedInsideDefaultPrevented) {
          suppressImmediateOutsideClickAfterPreventedStart();
        }
        return;
      }
      if (isEventWithinFloatingTree(event)) {
        return;
      }

      // If pointerdown was prevented, no click may be generated for
      // that interaction. However, Firefox may still emit an
      // immediate click after pointerup (e.g. NumberField scrub with
      // pointer lock), so suppress for one tick.
      if (pressStartedInsideDefaultPrevented) {
        suppressImmediateOutsideClickAfterPreventedStart();
        return;
      }

      // Avoid suppressing when outsidePress explicitly ignores this
      // target.
      if (typeof outsidePress === "function" && !outsidePress(event)) {
        return;
      }
      preventedPressSuppressionTimeout.clear();
      suppressNextOutsideClickRef.current = true;
      clearInsideReactTree();
    }
    function handleTouchMove(event) {
      if (getOutsidePressEvent() !== "sloppy" || !touchStateRef.current || isEventTargetWithin(event, store.state.floatingElement) || isEventTargetWithin(event, store.state.domReferenceElement)) {
        return;
      }
      const touch = event.touches[0];
      if (!touch) {
        return;
      }
      const deltaX = Math.abs(touch.clientX - touchStateRef.current.startX);
      const deltaY = Math.abs(touch.clientY - touchStateRef.current.startY);
      const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
      if (distance > 5) {
        touchStateRef.current.dismissOnTouchEnd = true;
      }
      if (distance > 10) {
        closeOnPressOutside(event);
        cancelDismissOnEndTimeout.clear();
        touchStateRef.current = null;
      }
    }
    function handleTouchMoveCapture(event) {
      addTargetEventListenerOnce(event, handleTouchMove);
    }
    function handleTouchEnd(event) {
      if (getOutsidePressEvent() !== "sloppy" || !touchStateRef.current || isEventTargetWithin(event, store.state.floatingElement) || isEventTargetWithin(event, store.state.domReferenceElement)) {
        return;
      }
      if (touchStateRef.current.dismissOnTouchEnd) {
        closeOnPressOutside(event);
      }
      cancelDismissOnEndTimeout.clear();
      touchStateRef.current = null;
    }
    function handleTouchEndCapture(event) {
      addTargetEventListenerOnce(event, handleTouchEnd);
    }

    // Solid analogue of React's synthetic capture propagation across
    // portals for the `insideReactTree` latch. The floating bag's
    // `on*Capture: markInsideReactTree` handlers (attached to the
    // floating element) only fire for DOM descendants; in React they
    // also fire for presses inside popups PORTALED out of the DOM
    // subtree but logically nested under us, because synthetic events
    // climb the component tree across portals. Without this, a press
    // that starts on a nested trigger and ends inside the nested
    // popup fires its `click` on the nearest common DOM ancestor —
    // our own portal wrapper — which fails every within-tree check
    // and dismisses the ancestor overlay (Select inside a Sheet:
    // press trigger, release over an item, sheet closes).
    function markNestedPortalPressCapture(event) {
      if (isInsideNestedPortal(getTarget(event), store.state.floatingElement)) {
        markInsideReactTree();
      }
    }
    // Same press-phase events the floating bag latches on.
    const nestedPortalLatchEvents = ["click", "mousedown", "pointerdown", "mouseup", "touchend", "touchmove"];
    const doc = ownerDocument(snapshot.floatingElement);
    const cleanups = [];
    if (escapeKey) {
      doc.addEventListener("keydown", closeOnEscapeKeyDown);
      doc.addEventListener("compositionstart", handleCompositionStart);
      doc.addEventListener("compositionend", handleCompositionEnd);
      cleanups.push(() => {
        doc.removeEventListener("keydown", closeOnEscapeKeyDown);
        doc.removeEventListener("compositionstart", handleCompositionStart);
        doc.removeEventListener("compositionend", handleCompositionEnd);
      });
    }
    if (outsidePressEnabled) {
      for (const type of nestedPortalLatchEvents) {
        doc.addEventListener(type, markNestedPortalPressCapture, true);
      }
      cleanups.push(() => {
        for (const type of nestedPortalLatchEvents) {
          doc.removeEventListener(type, markNestedPortalPressCapture, true);
        }
      });
      doc.addEventListener("click", closeOnPressOutsideCapture, true);
      doc.addEventListener("pointerdown", closeOnPressOutsideCapture, true);
      doc.addEventListener("pointerup", handlePressEndCapture, true);
      doc.addEventListener("pointercancel", handlePressEndCapture, true);
      doc.addEventListener("mousedown", closeOnPressOutsideCapture, true);
      doc.addEventListener("mouseup", handlePressEndCapture, true);
      doc.addEventListener("touchstart", handleTouchStartCapture, true);
      doc.addEventListener("touchmove", handleTouchMoveCapture, true);
      doc.addEventListener("touchend", handleTouchEndCapture, true);
      cleanups.push(() => {
        doc.removeEventListener("click", closeOnPressOutsideCapture, true);
        doc.removeEventListener("pointerdown", closeOnPressOutsideCapture, true);
        doc.removeEventListener("pointerup", handlePressEndCapture, true);
        doc.removeEventListener("pointercancel", handlePressEndCapture, true);
        doc.removeEventListener("mousedown", closeOnPressOutsideCapture, true);
        doc.removeEventListener("mouseup", handlePressEndCapture, true);
        doc.removeEventListener("touchstart", handleTouchStartCapture, true);
        doc.removeEventListener("touchmove", handleTouchMoveCapture, true);
        doc.removeEventListener("touchend", handleTouchEndCapture, true);
      });
    }
    return () => {
      for (const cleanup of cleanups) {
        cleanup();
      }
      compositionTimeout.clear();
      preventedPressSuppressionTimeout.clear();
      resetPressStartState();
      suppressNextOutsideClickRef.current = false;
    };
  });

  // React runs `useEffect(clearInsideReactTree, [outsidePress, clearInsideReactTree])`
  // to reset the latch whenever `outsidePress` identity changes. In Solid,
  // the effect fires once per mount — `outsidePress` identity is stable
  // for the lifetime of the hook instance (we wrap fn form in a stable
  // callback), so a single reset suffices.
  createEffect(() => undefined, () => {
    clearInsideReactTree();
  });

  // Build reference bag: bubble-phase handler keyed by press type, plus
  // an always-on `onKeyDown` for escape. When press type is not
  // `intentional`, also wire `onClick` so keyboard activation still
  // dismisses.
  function buildReference() {
    const handlePress = event => {
      if (!isReferencePressEnabled() || !getEnabled()) {
        return;
      }
      resolveStore().setOpen(false, createChangeEventDetails(REASONS.triggerPress, event));
    };
    const bag = {
      onKeyDown: closeOnEscapeKeyDown
    };
    bag[bubbleHandlerKeys[referencePressEvent]] = handlePress;
    if (referencePressEvent !== "intentional") {
      bag.onClick = event => {
        if (!isReferencePressEnabled() || !getEnabled()) {
          return;
        }
        resolveStore().setOpen(false, createChangeEventDetails(REASONS.triggerPress, event));
      };
    }
    return bag;
  }
  const markPressStartedInsideReactTree = createStableCallback(() => event => {
    const liveStore = resolveStore();
    if (!liveStore.state.open || !getEnabled() || event.button !== 0) {
      return;
    }
    const target = getTarget(event);
    // Only treat presses that start within the floating DOM subtree as
    // inside. This avoids suppressing parent dismissal when interacting
    // with nested portals.
    if (!contains(liveStore.state.floatingElement, target)) {
      return;
    }
    if (!pressStartedInsideRef.current) {
      pressStartedInsideRef.current = true;
      pressStartPreventedRef.current = false;
    }
  });
  const markInsidePressStartPrevented = createStableCallback(() => event => {
    const liveStore = resolveStore();
    if (!liveStore.state.open || !getEnabled()) {
      return;
    }
    if (!event.defaultPrevented) {
      return;
    }
    if (pressStartedInsideRef.current) {
      pressStartPreventedRef.current = true;
    }
  });
  const floating = {
    onKeyDown: closeOnEscapeKeyDown,
    // `onMouseDown` may be blocked if `event.preventDefault()` is
    // called in `onPointerDown`, such as with `<NumberField.ScrubArea>`.
    onPointerDown: markInsidePressStartPrevented,
    onMouseDown: markInsidePressStartPrevented,
    onClickCapture: markInsideReactTree,
    onMouseDownCapture(event) {
      markInsideReactTree();
      markPressStartedInsideReactTree(event);
    },
    onPointerDownCapture(event) {
      markInsideReactTree();
      markPressStartedInsideReactTree(event);
    },
    onMouseUpCapture: markInsideReactTree,
    onTouchEndCapture: markInsideReactTree,
    onTouchMoveCapture: markInsideReactTree
  };

  // When `enabled` is a literal `false`, mirror React and return an
  // empty bag. When it's an accessor, always return the bag so handlers
  // can reactively short-circuit via `getEnabled()`. The body-time
  // `props.enabled` access is wrapped in `untrack` so the strict-read
  // scope established by `devComponent(...)` does not warn — accessor
  // reads happen later inside already-tracked event handlers / effects.
  const isStaticallyDisabled = untrack(() => typeof props.enabled !== "function" && !getEnabled());
  if (isStaticallyDisabled) {
    return {};
  }
  const reference = buildReference();
  return {
    reference,
    floating,
    trigger: reference
  };
}
//# sourceMappingURL=use-dismiss.jsx.map
