{"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;AAC/D,KAAI,CAAC,GAAI,QAAO;AAEhB,KAAI,CAAC,GAAG,YAAa,QAAO;AAK5B,KAAI;EAEF,IAAI,UAA0B;AAC9B,SAAO,SAAS;AACd,OAAI,iBAAiB,QAAQ,CAAC,YAAY,OACxC,QAAO;AAET,aAAU,QAAQ;;AAEpB,SAAO;SACD;AACN,SAAO;;;;;;;;;;AAWX,MAAa,2CACX,OACA,SACA,YAMG;CACH,MAAM,UAAU,SAAS,WAAW;CAIpC,MAAM,eACH,MAAM,WAAW,OAAO,IAA4B,QAAQ,MAAM,MAAM,KAAK;CAChF,MAAM,cAAc,WAAW;AAI/B,uBAAsB;AACpB,MAAI,CAAC,YAAa;EAElB,IAAI,MAAM;EAEV,MAAM,aAAa;AACjB,OAAI,wBAAwB,MAAM,MAAM,wBAAwB,KAAK,EAAE;AACrE,aAAS;AACT;;AAEF,SAAM,OAAO,sBAAsB,KAAK;;AAG1C,QAAM;AACN,eAAa,OAAO,qBAAqB,IAAI;IAC5C;EAAC;EAAS;EAAa;EAAM,CAAC;;;;;;AAOnC,MAAa,sCACX,OACA,YAWG;CACH,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,eACH,MAAM,WAAW,OAAO,IAA4B,QAAQ,MAAM,MAAM,KAAK;CAChF,MAAM,4BACH,MAAM,WAAW,oBAAoB,IACtC,MAAM,MAAM,qBACZ;AAEF,uBAAsB;EACpB,MAAM,eAAe,MAAM,MAAM,qBAAqB;AAEtD,MAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,cAAc;AAC9C,OAAI,aAAc,QAAO,aAAa,QAAQ;AAC9C;;EAGF,IAAI,MAAM;EACV,MAAM,aAAa;GACjB,MAAM,UAAU,MAAM,MAAM,qBAAqB;AACjD,OAAI,CAAC,QAAS;GAEd,MAAM,OAAO,QAAQ,uBAAuB;AAE5C,OADiB,KAAK,IAAI,KAAK,KAAK,IAAI,aAAa,KAAK,IAAI,KAAK,IAAI,IAAI,UAC7D,SAAQ,QAAQ,aAAa;OACtC,QAAO,QAAQ,QAAQ;AAE5B,SAAM,OAAO,sBAAsB,KAAK;;AAG1C,QAAM;AACN,eAAa;AACX,UAAO,qBAAqB,IAAI;GAChC,MAAM,UAAU,MAAM,MAAM,qBAAqB;AACjD,OAAI,QAAS,QAAO,QAAQ,QAAQ;;IAErC;EAAC;EAAS;EAAc;EAA2B;EAAO;EAAU,CAAC"}