{"version":3,"file":"destroyOnInvalidActiveTriggerElement.mjs","names":[],"sources":["../../src/utils/destroyOnInvalidActiveTriggerElement.ts"],"sourcesContent":["import { useLayoutEffect } from 'react';\n\ntype PopupStoreLike = {\n  // Base UI store's `useState` has a strongly-typed key union; we keep it loose here on purpose.\n  state: {\n    activeTriggerElement?: Element | null;\n    open?: boolean;\n    positionerElement?: HTMLElement | null;\n  };\n  useState?: (...args: any[]) => unknown;\n};\n\nconst isInvalidTriggerElement = (el: Element | null): boolean => {\n  if (!el) return true;\n\n  if (!el.isConnected) return true;\n\n  // \"display: none\" on self or an ancestor effectively hides the trigger.\n  // `getComputedStyle` can throw in some edge cases (e.g. non-Element in old envs),\n  // so we guard it defensively.\n  try {\n    // Check self and all ancestors for display: none\n    let current: Element | null = el;\n    while (current) {\n      if (getComputedStyle(current).display === 'none') {\n        return true;\n      }\n      current = current.parentElement;\n    }\n    return false;\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Destroys (hard reset) a group popup (Tooltip/Popover) when its active trigger element becomes\n * disconnected from the DOM or is effectively hidden via `display: none`.\n *\n * We intentionally poll while open to also catch CSS-driven visibility changes that won't\n * necessarily trigger DOM mutation observers.\n */\nexport const useDestroyOnInvalidActiveTriggerElement = (\n  store: PopupStoreLike,\n  destroy: () => void,\n  options?: {\n    /**\n     * @default true\n     */\n    enabled?: boolean;\n  },\n) => {\n  const enabled = options?.enabled ?? true;\n\n  // Subscribe with `useState` (reactive), but read from `state` (immediate) inside the loop.\n  // Base UI note: `state` updates immediately, while `useState` reflects updates before the next render.\n  const openReactive =\n    (store.useState?.('open') as boolean | undefined) ?? Boolean(store.state.open);\n  const shouldWatch = enabled && openReactive;\n\n  // Use layout effect so the first check runs right after React commits DOM updates.\n  // Then keep watching via rAF while open to also capture CSS-driven visibility changes.\n  useLayoutEffect(() => {\n    if (!shouldWatch) return;\n\n    let raf = 0;\n\n    const loop = () => {\n      if (isInvalidTriggerElement(store.state.activeTriggerElement ?? null)) {\n        destroy();\n        return;\n      }\n      raf = window.requestAnimationFrame(loop);\n    };\n\n    loop();\n    return () => window.cancelAnimationFrame(raf);\n  }, [destroy, shouldWatch, store]);\n};\n\n/**\n * UI-only fallback: If the positioner ends up at viewport (0,0), hide it to avoid a visible flash\n * in the corner. This doesn't replace \"destroy on invalid trigger\"; it's purely a visual guard.\n */\nexport const useHidePopupWhenPositionerAtOrigin = (\n  store: PopupStoreLike,\n  options?: {\n    /**\n     * @default true\n     */\n    enabled?: boolean;\n    /**\n     * Pixel threshold to consider the element \"at origin\".\n     * @default 0.5\n     */\n    threshold?: number;\n  },\n) => {\n  const enabled = options?.enabled ?? true;\n  const threshold = options?.threshold ?? 0.5;\n\n  const openReactive =\n    (store.useState?.('open') as boolean | undefined) ?? Boolean(store.state.open);\n  const positionerElementReactive =\n    (store.useState?.('positionerElement') as HTMLElement | null | undefined) ??\n    store.state.positionerElement ??\n    null;\n\n  useLayoutEffect(() => {\n    const positionerEl = store.state.positionerElement ?? positionerElementReactive;\n\n    if (!enabled || !openReactive || !positionerEl) {\n      if (positionerEl) delete positionerEl.dataset.zeroOrigin;\n      return;\n    }\n\n    let raf = 0;\n    const loop = () => {\n      const current = store.state.positionerElement ?? positionerEl;\n      if (!current) return;\n\n      const rect = current.getBoundingClientRect();\n      const atOrigin = Math.abs(rect.left) <= threshold && Math.abs(rect.top) <= threshold;\n      if (atOrigin) current.dataset.zeroOrigin = 'true';\n      else delete current.dataset.zeroOrigin;\n\n      raf = window.requestAnimationFrame(loop);\n    };\n\n    loop();\n    return () => {\n      window.cancelAnimationFrame(raf);\n      const current = store.state.positionerElement ?? positionerEl;\n      if (current) delete current.dataset.zeroOrigin;\n    };\n  }, [enabled, openReactive, positionerElementReactive, store, threshold]);\n};\n"],"mappings":";;AAYA,MAAM,2BAA2B,OAAgC;CAC/D,IAAI,CAAC,IAAI,OAAO;CAEhB,IAAI,CAAC,GAAG,aAAa,OAAO;CAK5B,IAAI;EAEF,IAAI,UAA0B;EAC9B,OAAO,SAAS;GACd,IAAI,iBAAiB,OAAO,CAAC,CAAC,YAAY,QACxC,OAAO;GAET,UAAU,QAAQ;EACpB;EACA,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;AASA,MAAa,2CACX,OACA,SACA,YAMG;CACH,MAAM,UAAU,SAAS,WAAW;CAIpC,MAAM,eACH,MAAM,WAAW,MAAM,KAA6B,QAAQ,MAAM,MAAM,IAAI;CAC/E,MAAM,cAAc,WAAW;CAI/B,sBAAsB;EACpB,IAAI,CAAC,aAAa;EAElB,IAAI,MAAM;EAEV,MAAM,aAAa;GACjB,IAAI,wBAAwB,MAAM,MAAM,wBAAwB,IAAI,GAAG;IACrE,QAAQ;IACR;GACF;GACA,MAAM,OAAO,sBAAsB,IAAI;EACzC;EAEA,KAAK;EACL,aAAa,OAAO,qBAAqB,GAAG;CAC9C,GAAG;EAAC;EAAS;EAAa;CAAK,CAAC;AAClC;;;;;AAMA,MAAa,sCACX,OACA,YAWG;CACH,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,eACH,MAAM,WAAW,MAAM,KAA6B,QAAQ,MAAM,MAAM,IAAI;CAC/E,MAAM,4BACH,MAAM,WAAW,mBAAmB,KACrC,MAAM,MAAM,qBACZ;CAEF,sBAAsB;EACpB,MAAM,eAAe,MAAM,MAAM,qBAAqB;EAEtD,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,cAAc;GAC9C,IAAI,cAAc,OAAO,aAAa,QAAQ;GAC9C;EACF;EAEA,IAAI,MAAM;EACV,MAAM,aAAa;GACjB,MAAM,UAAU,MAAM,MAAM,qBAAqB;GACjD,IAAI,CAAC,SAAS;GAEd,MAAM,OAAO,QAAQ,sBAAsB;GAE3C,IADiB,KAAK,IAAI,KAAK,IAAI,KAAK,aAAa,KAAK,IAAI,KAAK,GAAG,KAAK,WAC7D,QAAQ,QAAQ,aAAa;QACtC,OAAO,QAAQ,QAAQ;GAE5B,MAAM,OAAO,sBAAsB,IAAI;EACzC;EAEA,KAAK;EACL,aAAa;GACX,OAAO,qBAAqB,GAAG;GAC/B,MAAM,UAAU,MAAM,MAAM,qBAAqB;GACjD,IAAI,SAAS,OAAO,QAAQ,QAAQ;EACtC;CACF,GAAG;EAAC;EAAS;EAAc;EAA2B;EAAO;CAAS,CAAC;AACzE"}