{
  "mappings": "AACA,cAAgD,gBAAgB,iBAAiB;AAmCjF,OAAO,cAAM,8BAA+B,EAC1C,SACA,YACC;CACD;CACA,UAAU;MACR;KAiBC,+BAA+B;CAClC;;KAGG,4BAA4B,QAAQ,SAAS;AAIlD,OAAO,iBAAS,oBAAoB,OAAO;AAI3C,YAAY,cAAc;CACxB;CACA;CACA;CACA;CACA;CACA;;AAGF,YAAY,cAAc;CACxB,aAAa;EACX,QAAQ;EACR;;CAEF;;AASF,OAAO,iBAAS;AAiOhB,OAAO,cAAM,wBACX,UAAU,iBACV,YAAY,iBACZ,OAAO,gBACN;AA6CH,OAAO,iBAAS,mBACd,MAAM,aACN,sBACA;AA0BF,OAAO,iBAAS,iBACd,KAAK,UAAU,+BACf,aAAa,GAAG;AA+FlB,OAAO,cAAM,6BACX,MAAM,uBACL,QAAQ,kBAAkB;AAiB7B,OAAO,cAAM,cACX,MAAM,aACN,aAAa,uBACZ,eAAe;KAcb,qBAAqB,WAAW,WAAW,eAAe;KAE1D,aACH,WACA,WACA,eACA,gBACA,eACA;AAGF,OAAO,cAAM,UACX,MAAM,aACN,UAAU,cACT,QAAQ;AAWX,OAAO,iBAAS,cACd,MAAM,eACJ,UAAU,cAAc,QAAQ;KAI/B,eAAe;CAAE;CAAe;CAAe;CAAe;;AAEnE,OAAO,cAAM,kBACX,MAAM,aACN,UAAU,sBACT,QAAQ;AAQX,OAAO,cAAM,wBACX,MAAM,kBACH,UAAU,sBAAsB,QAAQ;AAI7C,OAAO,cAAM,gBACX,MAAM,aACN,cAAc,aACd,UAAU,cACT,QAAQ;AAQX,OAAO,iBAAS,oBACd,MAAM,eACJ,YAAY,aAAa,UAAU,cAAc,QAAQ",
  "names": [],
  "sources": [
    "src/index.tsx"
  ],
  "version": 3,
  "sourcesContent": [
    "import { useIsomorphicLayoutEffect } from '@tamagui/constants'\nimport { createContext, useContext, useId, type ReactNode, type RefObject } from 'react'\n\nconst LayoutHandlers = new WeakMap<HTMLElement, Function>()\nconst LayoutDisableKey = new WeakMap<HTMLElement, string>()\nconst Nodes = new Set<HTMLElement>()\nconst IntersectionState = new WeakMap<HTMLElement, boolean>()\n\n// feature flag to enable pre-transform dimension reporting (matches RN behavior)\n// can be set via env var at build time or runtime global for testing\n// see: https://github.com/tamagui/tamagui/pull/2329\nconst usePretransformDimensions = () =>\n  (globalThis as any).__TAMAGUI_ONLAYOUT_PRETRANSFORM === true ||\n  process.env.TAMAGUI_ONLAYOUT_PRETRANSFORM === '1'\n\nlet _debugLayout: boolean | undefined\n\nfunction isDebugLayout() {\n  if (_debugLayout === undefined) {\n    _debugLayout =\n      typeof window !== 'undefined' &&\n      new URLSearchParams(window.location.search).has('__tamaDebugLayout')\n  }\n  return _debugLayout\n}\n\n// separating to avoid all re-rendering\nconst DisableLayoutContextValues: Record<string, boolean> = {}\nconst DisableLayoutContextKey = createContext<string>('')\n\nconst ENABLE =\n  process.env.TAMAGUI_TARGET === 'web' && typeof IntersectionObserver !== 'undefined'\n\n// internal testing - advanced helper to turn off layout measurement for extra performance\n// TODO document!\n// TODO could add frame skip control here\nexport const LayoutMeasurementController = ({\n  disable,\n  children,\n}: {\n  disable: boolean\n  children: ReactNode\n}): ReactNode => {\n  const id = useId()\n\n  useIsomorphicLayoutEffect(() => {\n    DisableLayoutContextValues[id] = disable\n  }, [disable, id])\n\n  return (\n    <DisableLayoutContextKey.Provider value={id}>\n      {children}\n    </DisableLayoutContextKey.Provider>\n  )\n}\n\n// Single persistent IntersectionObserver for visibility tracking\nlet globalIntersectionObserver: IntersectionObserver | null = null\n\ntype TamaguiComponentStatePartial = {\n  host?: any\n}\n\ntype LayoutMeasurementStrategy = 'off' | 'sync' | 'async'\n\nlet strategy: LayoutMeasurementStrategy = 'async'\n\nexport function setOnLayoutStrategy(state: LayoutMeasurementStrategy): void {\n  strategy = state\n}\n\nexport type LayoutValue = {\n  x: number\n  y: number\n  width: number\n  height: number\n  pageX: number\n  pageY: number\n}\n\nexport type LayoutEvent = {\n  nativeEvent: {\n    layout: LayoutValue\n    target: any\n  }\n  timeStamp: number\n}\n\nconst NodeRectCache = new WeakMap<HTMLElement, DOMRect>()\n\n// prevent thrashing during first hydration (somewhat, streaming gets trickier)\nlet avoidUpdates = true\nconst queuedUpdates = new Map<HTMLElement, Function>()\n\nexport function enable(): void {\n  if (avoidUpdates) {\n    avoidUpdates = false\n    if (queuedUpdates) {\n      queuedUpdates.forEach((cb) => cb())\n      queuedUpdates.clear()\n    }\n  }\n}\n\nfunction startGlobalObservers() {\n  if (!ENABLE || globalIntersectionObserver) return\n\n  globalIntersectionObserver = new IntersectionObserver(\n    (entries) => {\n      for (let i = 0; i < entries.length; i++) {\n        const entry = entries[i]\n        const node = entry.target as HTMLElement\n        if (IntersectionState.get(node) !== entry.isIntersecting) {\n          IntersectionState.set(node, entry.isIntersecting)\n        }\n      }\n    },\n    {\n      threshold: 0,\n    }\n  )\n}\n\n// optimization: inline rect comparison to avoid function call overhead on hot path\nfunction rectsEqual(a: DOMRectReadOnly, b: DOMRectReadOnly): boolean {\n  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nif (ENABLE) {\n  const BoundingRects = new WeakMap<Element, DOMRectReadOnly>()\n\n  // optimization: persistent IO for rect fetching, reused across cycles\n  let rectFetchObserver: IntersectionObserver | null = null\n  let rectFetchResolve: ((value: boolean) => void) | null = null\n  let rectFetchStartTime = 0\n  let lastCallbackDelay = 0\n\n  function ensureRectFetchObserver() {\n    if (rectFetchObserver) return rectFetchObserver\n\n    rectFetchObserver = new IntersectionObserver(\n      (entries) => {\n        lastCallbackDelay = Math.round(performance.now() - rectFetchStartTime)\n\n        // store all rects\n        for (let i = 0; i < entries.length; i++) {\n          BoundingRects.set(entries[i].target, entries[i].boundingClientRect)\n        }\n\n        if (\n          process.env.NODE_ENV === 'development' &&\n          isDebugLayout() &&\n          lastCallbackDelay > 50\n        ) {\n          console.warn(\n            '[onLayout-io-delay]',\n            lastCallbackDelay + 'ms',\n            entries.length,\n            'entries'\n          )\n        }\n\n        if (rectFetchResolve) {\n          rectFetchResolve(true)\n          rectFetchResolve = null\n        }\n      },\n      {\n        threshold: 0,\n      }\n    )\n\n    return rectFetchObserver\n  }\n\n  async function updateLayoutIfChanged(node: HTMLElement) {\n    const onLayout = LayoutHandlers.get(node)\n    if (typeof onLayout !== 'function') return\n\n    const parentNode = node.parentElement\n    if (!parentNode) return\n\n    let nodeRect: DOMRectReadOnly | undefined\n    let parentRect: DOMRectReadOnly | undefined\n\n    // respect the strategy contract\n    if (strategy === 'async') {\n      nodeRect = BoundingRects.get(node)\n      parentRect = BoundingRects.get(parentNode)\n\n      if (!nodeRect || !parentRect) {\n        return\n      }\n    } else {\n      nodeRect = node.getBoundingClientRect()\n      parentRect = parentNode.getBoundingClientRect()\n    }\n\n    const cachedRect = NodeRectCache.get(node)\n    const cachedParentRect = NodeRectCache.get(parentNode)\n\n    // optimization: inline comparison instead of isEqualShallow\n    const nodeChanged = !cachedRect || !rectsEqual(cachedRect, nodeRect)\n    const parentChanged = !cachedParentRect || !rectsEqual(cachedParentRect, parentRect)\n\n    if (nodeChanged || parentChanged) {\n      NodeRectCache.set(node, nodeRect as DOMRect)\n      NodeRectCache.set(parentNode, parentRect as DOMRect)\n\n      const event = getElementLayoutEvent(nodeRect, parentRect, node)\n\n      if (process.env.NODE_ENV === 'development' && isDebugLayout()) {\n        console.log('[useElementLayout] change', {\n          tag: node.tagName,\n          id: node.id || undefined,\n          className: (node.className || '').slice(0, 60) || undefined,\n          layout: event.nativeEvent.layout,\n          first: !cachedRect,\n        })\n      }\n\n      if (avoidUpdates) {\n        queuedUpdates.set(node, () => onLayout(event))\n      } else {\n        onLayout(event)\n      }\n    }\n  }\n\n  const rAF =\n    typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : undefined\n\n  // adaptive frame skipping with backoff\n  const userSkipVal = process.env.TAMAGUI_LAYOUT_FRAME_SKIP\n  const BASE_SKIP_FRAMES = userSkipVal ? +userSkipVal : 10\n  const MAX_SKIP_FRAMES = 20\n  let skipFrames = BASE_SKIP_FRAMES\n  let frameCount = 0\n\n  async function layoutOnAnimationFrame() {\n    // skip frames based on adaptive rate\n    if (frameCount++ % skipFrames !== 0) {\n      rAF ? rAF(layoutOnAnimationFrame) : setTimeout(layoutOnAnimationFrame, 16)\n      return\n    }\n\n    // reset frame count to avoid overflow\n    if (frameCount >= Number.MAX_SAFE_INTEGER) {\n      frameCount = 0\n    }\n\n    if (strategy !== 'off') {\n      const visibleNodes: HTMLElement[] = []\n      // optimization: deduplicate parent observations\n      const parentsToObserve = new Set<HTMLElement>()\n\n      // collect visible nodes and their unique parents\n      for (const node of Nodes) {\n        const parentElement = node.parentElement\n        if (!(parentElement instanceof HTMLElement)) {\n          cleanupNode(node)\n          continue\n        }\n        const disableKey = LayoutDisableKey.get(node)\n        if (disableKey && DisableLayoutContextValues[disableKey] === true) continue\n        if (IntersectionState.get(node) === false) continue\n\n        visibleNodes.push(node)\n        parentsToObserve.add(parentElement)\n      }\n\n      if (visibleNodes.length > 0) {\n        const io = ensureRectFetchObserver()\n        rectFetchStartTime = performance.now()\n\n        // observe all nodes\n        for (let i = 0; i < visibleNodes.length; i++) {\n          io.observe(visibleNodes[i])\n        }\n        // optimization: observe unique parents only (not N times for N children)\n        for (const parent of parentsToObserve) {\n          io.observe(parent)\n        }\n\n        // wait for callback\n        await new Promise<boolean>((res) => {\n          rectFetchResolve = res\n        })\n\n        // unobserve all to reset for next cycle\n        for (let i = 0; i < visibleNodes.length; i++) {\n          io.unobserve(visibleNodes[i])\n        }\n        for (const parent of parentsToObserve) {\n          io.unobserve(parent)\n        }\n\n        // adaptive backoff: if IO was slow, skip more frames next cycle\n        if (lastCallbackDelay > 50) {\n          skipFrames = Math.min(skipFrames + 2, MAX_SKIP_FRAMES)\n        } else if (lastCallbackDelay < 20) {\n          // recover back to base rate when things are fast\n          skipFrames = Math.max(skipFrames - 1, BASE_SKIP_FRAMES)\n        }\n\n        // process updates\n        for (let i = 0; i < visibleNodes.length; i++) {\n          updateLayoutIfChanged(visibleNodes[i])\n        }\n      }\n    }\n\n    // schedule next frame\n    rAF ? rAF(layoutOnAnimationFrame) : setTimeout(layoutOnAnimationFrame, 16)\n  }\n\n  layoutOnAnimationFrame()\n}\n\nexport const getElementLayoutEvent = (\n  nodeRect: DOMRectReadOnly,\n  parentRect: DOMRectReadOnly,\n  node?: HTMLElement\n): LayoutEvent => {\n  return {\n    nativeEvent: {\n      layout: getRelativeDimensions(nodeRect, parentRect, node),\n      target: nodeRect,\n    },\n    timeStamp: Date.now(),\n  }\n}\n\n/**\n * get pre-transform dimensions for a node.\n * uses offsetWidth/offsetHeight which report CSS layout dimensions\n * unaffected by transforms - this matches React Native's onLayout behavior.\n *\n * see: https://github.com/tamagui/tamagui/pull/2329\n */\nconst getPreTransformDimensions = (\n  node: HTMLElement\n): { width: number; height: number } => {\n  return {\n    width: node.offsetWidth,\n    height: node.offsetHeight,\n  }\n}\n\nconst getRelativeDimensions = (\n  a: DOMRectReadOnly,\n  b: DOMRectReadOnly,\n  aNode?: HTMLElement\n) => {\n  const { left, top } = a\n  const x = left - b.left\n  const y = top - b.top\n\n  // get pre-transform dimensions when flag is enabled and node is available\n  const { width, height } =\n    usePretransformDimensions() && aNode\n      ? getPreTransformDimensions(aNode)\n      : { width: a.width, height: a.height }\n\n  return { x, y, width, height, pageX: a.left, pageY: a.top }\n}\n\n// register an arbitrary DOM element into the measurement loop without React lifecycle\nexport function registerLayoutNode(\n  node: HTMLElement,\n  onChange: () => void,\n  disableKey?: string\n): () => void {\n  Nodes.add(node)\n  LayoutHandlers.set(node, onChange)\n  if (disableKey) LayoutDisableKey.set(node, disableKey)\n  startGlobalObservers()\n  if (globalIntersectionObserver) {\n    globalIntersectionObserver.observe(node)\n    IntersectionState.set(node, true)\n  }\n  return () => cleanupNode(node)\n}\n\nfunction cleanupNode(node: HTMLElement) {\n  Nodes.delete(node)\n  LayoutHandlers.delete(node)\n  LayoutDisableKey.delete(node)\n  NodeRectCache.delete(node)\n  IntersectionState.delete(node)\n  if (globalIntersectionObserver) {\n    globalIntersectionObserver.unobserve(node)\n  }\n}\n\nconst PrevHostNode = new WeakMap<object, HTMLElement | undefined>()\n\nexport function useElementLayout(\n  ref: RefObject<TamaguiComponentStatePartial>,\n  onLayout?: ((e: LayoutEvent) => void) | null\n): void {\n  const disableKey = useContext(DisableLayoutContextKey)\n\n  // keep handlers up to date so polling always calls the latest callback\n  const node = ensureWebElement(ref.current?.host)\n  if (node && onLayout) {\n    LayoutHandlers.set(node, onLayout)\n    LayoutDisableKey.set(node, disableKey)\n  }\n\n  // detect host swaps after commit and fire immediate sync layout\n  useIsomorphicLayoutEffect(() => {\n    if (!onLayout) return\n    const nextNode = ensureWebElement(ref.current?.host)\n    const prevNode = PrevHostNode.get(ref)\n    if (nextNode === prevNode) return\n\n    if (prevNode) cleanupNode(prevNode)\n    PrevHostNode.set(ref, nextNode)\n    if (!nextNode) return\n\n    Nodes.add(nextNode)\n    startGlobalObservers()\n    if (globalIntersectionObserver) {\n      globalIntersectionObserver.observe(nextNode)\n      IntersectionState.set(nextNode, true)\n    }\n\n    const handler = LayoutHandlers.get(nextNode)\n    if (typeof handler !== 'function') return\n    const parentNode = nextNode.parentElement\n    if (!parentNode) return\n\n    const nodeRect = nextNode.getBoundingClientRect()\n    const parentRect = parentNode.getBoundingClientRect()\n    NodeRectCache.set(nextNode, nodeRect)\n    NodeRectCache.set(parentNode, parentRect)\n    handler(getElementLayoutEvent(nodeRect, parentRect, nextNode))\n  })\n\n  useIsomorphicLayoutEffect(() => {\n    if (!onLayout) return\n    const node = ref.current?.host\n    if (!node) return\n\n    Nodes.add(node)\n\n    startGlobalObservers()\n    if (globalIntersectionObserver) {\n      globalIntersectionObserver.observe(node)\n      IntersectionState.set(node, true)\n    }\n\n    if (process.env.NODE_ENV === 'development' && isDebugLayout()) {\n      console.log('[useElementLayout] register', {\n        tag: node.tagName,\n        id: node.id || undefined,\n        className: (node.className || '').slice(0, 60) || undefined,\n        totalNodes: Nodes.size,\n      })\n    }\n\n    // always do one immediate sync layout event for accuracy\n    const parentNode = node.parentNode as HTMLElement | null\n    if (parentNode) {\n      onLayout(\n        getElementLayoutEvent(\n          node.getBoundingClientRect(),\n          parentNode.getBoundingClientRect(),\n          node\n        )\n      )\n    }\n\n    return () => {\n      cleanupNode(node)\n\n      // also clean up any node from a mid-lifecycle host swap\n      const swappedNode = PrevHostNode.get(ref)\n      if (swappedNode && swappedNode !== node) {\n        cleanupNode(swappedNode)\n      }\n      PrevHostNode.delete(ref)\n    }\n  }, [ref, !!onLayout])\n}\n\nfunction ensureWebElement<X>(x: X): HTMLElement | undefined {\n  if (typeof HTMLElement === 'undefined') {\n    return undefined\n  }\n  return x instanceof HTMLElement ? x : undefined\n}\n\nexport const getBoundingClientRectAsync = (\n  node: HTMLElement | null\n): Promise<DOMRectReadOnly | false> => {\n  return new Promise<DOMRectReadOnly | false>((res) => {\n    if (!node || node.nodeType !== 1) return res(false)\n\n    const io = new IntersectionObserver(\n      (entries) => {\n        io.disconnect()\n        return res(entries[0].boundingClientRect)\n      },\n      {\n        threshold: 0,\n      }\n    )\n    io.observe(node)\n  })\n}\n\nexport const measureNode = async (\n  node: HTMLElement,\n  relativeTo?: HTMLElement | null\n): Promise<null | LayoutValue> => {\n  const relativeNode = relativeTo || node?.parentElement\n  if (relativeNode instanceof HTMLElement) {\n    const [nodeDim, relativeNodeDim] = await Promise.all([\n      getBoundingClientRectAsync(node),\n      getBoundingClientRectAsync(relativeNode),\n    ])\n    if (relativeNodeDim && nodeDim) {\n      return getRelativeDimensions(nodeDim, relativeNodeDim, node)\n    }\n  }\n  return null\n}\n\ntype MeasureInWindowCb = (x: number, y: number, width: number, height: number) => void\n\ntype MeasureCb = (\n  x: number,\n  y: number,\n  width: number,\n  height: number,\n  pageX: number,\n  pageY: number\n) => void\n\nexport const measure = async (\n  node: HTMLElement,\n  callback: MeasureCb\n): Promise<LayoutValue | null> => {\n  const out = await measureNode(\n    node,\n    node.parentNode instanceof HTMLElement ? node.parentNode : null\n  )\n  if (out) {\n    callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)\n  }\n  return out\n}\n\nexport function createMeasure(\n  node: HTMLElement\n): (callback: MeasureCb) => Promise<LayoutValue | null> {\n  return (callback) => measure(node, callback)\n}\n\ntype WindowLayout = { pageX: number; pageY: number; width: number; height: number }\n\nexport const measureInWindow = async (\n  node: HTMLElement,\n  callback: MeasureInWindowCb\n): Promise<WindowLayout | null> => {\n  const out = await measureNode(node, null)\n  if (out) {\n    callback?.(out.pageX, out.pageY, out.width, out.height)\n  }\n  return out\n}\n\nexport const createMeasureInWindow = (\n  node: HTMLElement\n): ((callback: MeasureInWindowCb) => Promise<WindowLayout | null>) => {\n  return (callback) => measureInWindow(node, callback)\n}\n\nexport const measureLayout = async (\n  node: HTMLElement,\n  relativeNode: HTMLElement,\n  callback: MeasureCb\n): Promise<LayoutValue | null> => {\n  const out = await measureNode(node, relativeNode)\n  if (out) {\n    callback?.(out.x, out.y, out.width, out.height, out.pageX, out.pageY)\n  }\n  return out\n}\n\nexport function createMeasureLayout(\n  node: HTMLElement\n): (relativeTo: HTMLElement, callback: MeasureCb) => Promise<LayoutValue | null> {\n  return (relativeTo, callback) => measureLayout(node, relativeTo, callback)\n}\n"
  ]
}