{
  "name": "masonry",
  "type": "registry:ui",
  "dependencies": [
    "@diceui/masonry",
    "radix-ui"
  ],
  "registryDependencies": [
    "@diceui/use-isomorphic-layout-effect"
  ],
  "files": [
    {
      "path": "ui/masonry.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { useIsomorphicLayoutEffect } from \"@/registry/bases/radix/hooks/use-isomorphic-layout-effect\";\n\nconst NODE_COLOR = {\n  RED: 0,\n  BLACK: 1,\n  SENTINEL: 2,\n} as const;\n\nconst NODE_OPERATION = {\n  REMOVE: 0,\n  PRESERVE: 1,\n} as const;\n\ntype NodeColor = (typeof NODE_COLOR)[keyof typeof NODE_COLOR];\ntype NodeOperation = (typeof NODE_OPERATION)[keyof typeof NODE_OPERATION];\n\ninterface ListNode {\n  index: number;\n  high: number;\n  next: ListNode | null;\n}\n\ninterface TreeNode {\n  max: number;\n  low: number;\n  high: number;\n  color: NodeColor;\n  parent: TreeNode;\n  right: TreeNode;\n  left: TreeNode;\n  list: ListNode;\n}\n\ninterface Tree {\n  root: TreeNode;\n  size: number;\n}\n\nfunction addInterval(treeNode: TreeNode, high: number, index: number): boolean {\n  let node: ListNode | null = treeNode.list;\n  let prevNode: ListNode | undefined;\n\n  while (node) {\n    if (node.index === index) return false;\n    if (high > node.high) break;\n    prevNode = node;\n    node = node.next;\n  }\n\n  if (!prevNode) treeNode.list = { index, high, next: node };\n  if (prevNode) prevNode.next = { index, high, next: prevNode.next };\n\n  return true;\n}\n\nfunction removeInterval(\n  treeNode: TreeNode,\n  index: number,\n): NodeOperation | undefined {\n  let node: ListNode | null = treeNode.list;\n  if (node.index === index) {\n    if (node.next === null) return NODE_OPERATION.REMOVE;\n    treeNode.list = node.next;\n    return NODE_OPERATION.PRESERVE;\n  }\n\n  let prevNode: ListNode | undefined = node;\n  node = node.next;\n\n  while (node !== null) {\n    if (node.index === index) {\n      prevNode.next = node.next;\n      return NODE_OPERATION.PRESERVE;\n    }\n    prevNode = node;\n    node = node.next;\n  }\n}\n\nconst SENTINEL_NODE: TreeNode = {\n  low: 0,\n  max: 0,\n  high: 0,\n  color: NODE_COLOR.SENTINEL,\n  parent: undefined as unknown as TreeNode,\n  right: undefined as unknown as TreeNode,\n  left: undefined as unknown as TreeNode,\n  list: undefined as unknown as ListNode,\n};\n\nSENTINEL_NODE.parent = SENTINEL_NODE;\nSENTINEL_NODE.left = SENTINEL_NODE;\nSENTINEL_NODE.right = SENTINEL_NODE;\n\nfunction updateMax(node: TreeNode) {\n  const max = node.high;\n  if (node.left === SENTINEL_NODE && node.right === SENTINEL_NODE)\n    node.max = max;\n  else if (node.left === SENTINEL_NODE)\n    node.max = Math.max(node.right.max, max);\n  else if (node.right === SENTINEL_NODE)\n    node.max = Math.max(node.left.max, max);\n  else node.max = Math.max(Math.max(node.left.max, node.right.max), max);\n}\n\nfunction updateMaxUp(node: TreeNode) {\n  let x = node;\n\n  while (x.parent !== SENTINEL_NODE) {\n    updateMax(x.parent);\n    x = x.parent;\n  }\n}\n\nfunction rotateLeft(tree: Tree, x: TreeNode) {\n  if (x.right === SENTINEL_NODE) return;\n  const y = x.right;\n  x.right = y.left;\n  if (y.left !== SENTINEL_NODE) y.left.parent = x;\n  y.parent = x.parent;\n\n  if (x.parent === SENTINEL_NODE) tree.root = y;\n  else if (x === x.parent.left) x.parent.left = y;\n  else x.parent.right = y;\n\n  y.left = x;\n  x.parent = y;\n\n  updateMax(x);\n  updateMax(y);\n}\n\nfunction rotateRight(tree: Tree, x: TreeNode) {\n  if (x.left === SENTINEL_NODE) return;\n  const y = x.left;\n  x.left = y.right;\n  if (y.right !== SENTINEL_NODE) y.right.parent = x;\n  y.parent = x.parent;\n\n  if (x.parent === SENTINEL_NODE) tree.root = y;\n  else if (x === x.parent.right) x.parent.right = y;\n  else x.parent.left = y;\n\n  y.right = x;\n  x.parent = y;\n\n  updateMax(x);\n  updateMax(y);\n}\n\nfunction replaceNode(tree: Tree, x: TreeNode, y: TreeNode) {\n  if (x.parent === SENTINEL_NODE) tree.root = y;\n  else if (x === x.parent.left) x.parent.left = y;\n  else x.parent.right = y;\n  y.parent = x.parent;\n}\n\nfunction fixRemove(tree: Tree, node: TreeNode) {\n  let x = node;\n  let w: TreeNode;\n\n  while (x !== SENTINEL_NODE && x.color === NODE_COLOR.BLACK) {\n    if (x === x.parent.left) {\n      w = x.parent.right;\n\n      if (w.color === NODE_COLOR.RED) {\n        w.color = NODE_COLOR.BLACK;\n        x.parent.color = NODE_COLOR.RED;\n        rotateLeft(tree, x.parent);\n        w = x.parent.right;\n      }\n\n      if (\n        w.left.color === NODE_COLOR.BLACK &&\n        w.right.color === NODE_COLOR.BLACK\n      ) {\n        w.color = NODE_COLOR.RED;\n        x = x.parent;\n      } else {\n        if (w.right.color === NODE_COLOR.BLACK) {\n          w.left.color = NODE_COLOR.BLACK;\n          w.color = NODE_COLOR.RED;\n          rotateRight(tree, w);\n          w = x.parent.right;\n        }\n\n        w.color = x.parent.color;\n        x.parent.color = NODE_COLOR.BLACK;\n        w.right.color = NODE_COLOR.BLACK;\n        rotateLeft(tree, x.parent);\n        x = tree.root;\n      }\n    } else {\n      w = x.parent.left;\n\n      if (w.color === NODE_COLOR.RED) {\n        w.color = NODE_COLOR.BLACK;\n        x.parent.color = NODE_COLOR.RED;\n        rotateRight(tree, x.parent);\n        w = x.parent.left;\n      }\n\n      if (\n        w.right.color === NODE_COLOR.BLACK &&\n        w.left.color === NODE_COLOR.BLACK\n      ) {\n        w.color = NODE_COLOR.RED;\n        x = x.parent;\n      } else {\n        if (w.left.color === NODE_COLOR.BLACK) {\n          w.right.color = NODE_COLOR.BLACK;\n          w.color = NODE_COLOR.RED;\n          rotateLeft(tree, w);\n          w = x.parent.left;\n        }\n\n        w.color = x.parent.color;\n        x.parent.color = NODE_COLOR.BLACK;\n        w.left.color = NODE_COLOR.BLACK;\n        rotateRight(tree, x.parent);\n        x = tree.root;\n      }\n    }\n  }\n\n  x.color = NODE_COLOR.BLACK;\n}\n\nfunction minimumTree(node: TreeNode) {\n  let current = node;\n  while (current.left !== SENTINEL_NODE) {\n    current = current.left;\n  }\n  return current;\n}\n\nfunction fixInsert(tree: Tree, node: TreeNode) {\n  let current = node;\n  let y: TreeNode;\n\n  while (current.parent.color === NODE_COLOR.RED) {\n    if (current.parent === current.parent.parent.left) {\n      y = current.parent.parent.right;\n\n      if (y.color === NODE_COLOR.RED) {\n        current.parent.color = NODE_COLOR.BLACK;\n        y.color = NODE_COLOR.BLACK;\n        current.parent.parent.color = NODE_COLOR.RED;\n        current = current.parent.parent;\n      } else {\n        if (current === current.parent.right) {\n          current = current.parent;\n          rotateLeft(tree, current);\n        }\n\n        current.parent.color = NODE_COLOR.BLACK;\n        current.parent.parent.color = NODE_COLOR.RED;\n        rotateRight(tree, current.parent.parent);\n      }\n    } else {\n      y = current.parent.parent.left;\n\n      if (y.color === NODE_COLOR.RED) {\n        current.parent.color = NODE_COLOR.BLACK;\n        y.color = NODE_COLOR.BLACK;\n        current.parent.parent.color = NODE_COLOR.RED;\n        current = current.parent.parent;\n      } else {\n        if (current === current.parent.left) {\n          current = current.parent;\n          rotateRight(tree, current);\n        }\n\n        current.parent.color = NODE_COLOR.BLACK;\n        current.parent.parent.color = NODE_COLOR.RED;\n        rotateLeft(tree, current.parent.parent);\n      }\n    }\n  }\n  tree.root.color = NODE_COLOR.BLACK;\n}\n\ninterface IntervalTree {\n  insert(low: number, high: number, index: number): void;\n  remove(index: number): void;\n  search(\n    low: number,\n    high: number,\n    onCallback: (index: number, low: number) => void,\n  ): void;\n  size: number;\n}\n\nfunction createIntervalTree(): IntervalTree {\n  const tree: Tree = {\n    root: SENTINEL_NODE,\n    size: 0,\n  };\n\n  const indexMap: Record<number, TreeNode> = {};\n\n  return {\n    insert(low, high, index) {\n      let x: TreeNode = tree.root;\n      let y: TreeNode = SENTINEL_NODE;\n\n      while (x !== SENTINEL_NODE) {\n        y = x;\n        if (low === y.low) break;\n        if (low < x.low) x = x.left;\n        else x = x.right;\n      }\n\n      if (low === y.low && y !== SENTINEL_NODE) {\n        if (!addInterval(y, high, index)) return;\n        y.high = Math.max(y.high, high);\n        updateMax(y);\n        updateMaxUp(y);\n        indexMap[index] = y;\n        tree.size++;\n        return;\n      }\n\n      const z: TreeNode = {\n        low,\n        high,\n        max: high,\n        color: NODE_COLOR.RED,\n        parent: y,\n        left: SENTINEL_NODE,\n        right: SENTINEL_NODE,\n        list: { index, high, next: null },\n      };\n\n      if (y === SENTINEL_NODE) {\n        tree.root = z;\n      } else {\n        if (z.low < y.low) y.left = z;\n        else y.right = z;\n        updateMaxUp(z);\n      }\n\n      fixInsert(tree, z);\n      indexMap[index] = z;\n      tree.size++;\n    },\n\n    remove(index) {\n      const z = indexMap[index];\n      if (z === void 0) return;\n      delete indexMap[index];\n\n      const intervalResult = removeInterval(z, index);\n      if (intervalResult === void 0) return;\n      if (intervalResult === NODE_OPERATION.PRESERVE) {\n        z.high = z.list.high;\n        updateMax(z);\n        updateMaxUp(z);\n        tree.size--;\n        return;\n      }\n\n      let y = z;\n      let originalYColor = y.color;\n      let x: TreeNode;\n\n      if (z.left === SENTINEL_NODE) {\n        x = z.right;\n        replaceNode(tree, z, z.right);\n      } else if (z.right === SENTINEL_NODE) {\n        x = z.left;\n        replaceNode(tree, z, z.left);\n      } else {\n        y = minimumTree(z.right);\n        originalYColor = y.color;\n        x = y.right;\n\n        if (y.parent === z) {\n          x.parent = y;\n        } else {\n          replaceNode(tree, y, y.right);\n          y.right = z.right;\n          y.right.parent = y;\n        }\n\n        replaceNode(tree, z, y);\n        y.left = z.left;\n        y.left.parent = y;\n        y.color = z.color;\n      }\n\n      updateMax(x);\n      updateMaxUp(x);\n\n      if (originalYColor === NODE_COLOR.BLACK) fixRemove(tree, x);\n      tree.size--;\n    },\n\n    search(low, high, onCallback) {\n      const stack = [tree.root];\n      while (stack.length !== 0) {\n        const node = stack.pop();\n        if (!node) continue;\n        if (node === SENTINEL_NODE || low > node.max) continue;\n        if (node.left !== SENTINEL_NODE) stack.push(node.left);\n        if (node.right !== SENTINEL_NODE) stack.push(node.right);\n        if (node.low <= high && node.high >= low) {\n          let curr: ListNode | null = node.list;\n          while (curr !== null) {\n            if (curr.high >= low) onCallback(curr.index, node.low);\n            curr = curr.next;\n          }\n        }\n      }\n    },\n\n    get size() {\n      return tree.size;\n    },\n  };\n}\n\ntype CacheKey = string | number | symbol;\ntype CacheConstructor = (new () => Cache) | Record<CacheKey, unknown>;\n\ninterface Cache<K = CacheKey, V = unknown> {\n  set: (k: K, v: V) => V;\n  get: (k: K) => V | undefined;\n}\n\nfunction onDeepMemo<T extends unknown[], U>(\n  constructors: CacheConstructor[],\n  fn: (...args: T) => U,\n): (...args: T) => U {\n  if (!constructors.length || !constructors[0]) {\n    throw new Error(\"At least one constructor is required\");\n  }\n\n  function createCache(obj: CacheConstructor): Cache {\n    let cache: Cache;\n    if (typeof obj === \"function\") {\n      try {\n        cache = new (obj as new () => Cache)();\n      } catch (_err) {\n        cache = new Map<CacheKey, unknown>();\n      }\n    } else {\n      cache = obj as unknown as Cache;\n    }\n    return {\n      set(k: CacheKey, v: unknown): unknown {\n        cache.set(k, v);\n        return v;\n      },\n      get(k: CacheKey): unknown | undefined {\n        return cache.get(k);\n      },\n    };\n  }\n\n  const depth = constructors.length;\n  const baseCache = createCache(constructors[0]);\n\n  let base: Cache | undefined;\n  let map: Cache | undefined;\n  let node: Cache;\n  let i: number;\n  const one = depth === 1;\n\n  function get(args: unknown[]): unknown {\n    if (depth < 3) {\n      const key = args[0] as CacheKey;\n      base = baseCache.get(key) as Cache | undefined;\n      return one ? base : base?.get(args[1] as CacheKey);\n    }\n\n    node = baseCache;\n    for (i = 0; i < depth; i++) {\n      const next = node.get(args[i] as CacheKey);\n      if (!next) return undefined;\n      node = next as Cache;\n    }\n    return node;\n  }\n\n  function set(args: unknown[], value: unknown): unknown {\n    if (depth < 3) {\n      if (one) {\n        baseCache.set(args[0] as CacheKey, value);\n      } else {\n        base = baseCache.get(args[0] as CacheKey) as Cache | undefined;\n        if (!base) {\n          if (!constructors[1]) {\n            throw new Error(\n              \"Second constructor is required for non-single depth cache\",\n            );\n          }\n          map = createCache(constructors[1]);\n          map.set(args[1] as CacheKey, value);\n          baseCache.set(args[0] as CacheKey, map);\n        } else {\n          base.set(args[1] as CacheKey, value);\n        }\n      }\n      return value;\n    }\n\n    node = baseCache;\n    for (i = 0; i < depth - 1; i++) {\n      map = node.get(args[i] as CacheKey) as Cache | undefined;\n      if (!map) {\n        const nextConstructor = constructors[i + 1];\n        if (!nextConstructor) {\n          throw new Error(`Constructor at index ${i + 1} is required`);\n        }\n        map = createCache(nextConstructor);\n        node.set(args[i] as CacheKey, map);\n        node = map;\n      } else {\n        node = map;\n      }\n    }\n    node.set(args[depth - 1] as CacheKey, value);\n    return value;\n  }\n\n  return (...args: T): U => {\n    const cached = get(args);\n    if (cached === undefined) {\n      return set(args, fn(...args)) as U;\n    }\n    return cached as U;\n  };\n}\n\nconst COLUMN_WIDTH = 200;\nconst GAP = 0;\nconst ITEM_HEIGHT = 300;\nconst OVERSCAN = 2;\nconst SCROLL_FPS = 12;\nconst DEBOUNCE_DELAY = 300;\n\ninterface Positioner {\n  columnCount: number;\n  columnWidth: number;\n  set: (index: number, height: number) => void;\n  get: (index: number) => PositionerItem | undefined;\n  update: (updates: number[]) => void;\n  range: (\n    low: number,\n    high: number,\n    onItemRender: (index: number, left: number, top: number) => void,\n  ) => void;\n  size: () => number;\n  estimateHeight: (itemCount: number, defaultItemHeight: number) => number;\n  shortestColumn: () => number;\n  all: () => PositionerItem[];\n}\n\ninterface PositionerItem {\n  top: number;\n  left: number;\n  height: number;\n  columnIndex: number;\n}\n\ninterface UsePositionerOptions {\n  width: number;\n  columnWidth?: number;\n  columnGap?: number;\n  rowGap?: number;\n  columnCount?: number;\n  maxColumnCount?: number;\n  linear?: boolean;\n}\n\nfunction usePositioner(\n  {\n    width,\n    columnWidth = COLUMN_WIDTH,\n    columnGap = GAP,\n    rowGap,\n    columnCount,\n    maxColumnCount,\n    linear = false,\n  }: UsePositionerOptions,\n  deps: React.DependencyList = [],\n): Positioner {\n  const initPositioner = React.useCallback((): Positioner => {\n    function binarySearch(a: number[], y: number): number {\n      let l = 0;\n      let h = a.length - 1;\n\n      while (l <= h) {\n        const m = (l + h) >>> 1;\n        const x = a[m];\n        if (x === y) return m;\n        if (x === undefined || x <= y) l = m + 1;\n        else h = m - 1;\n      }\n\n      return -1;\n    }\n\n    const computedColumnCount =\n      columnCount ||\n      Math.min(\n        Math.floor((width + columnGap) / (columnWidth + columnGap)),\n        maxColumnCount || Number.POSITIVE_INFINITY,\n      ) ||\n      1;\n    const computedColumnWidth = Math.floor(\n      (width - columnGap * (computedColumnCount - 1)) / computedColumnCount,\n    );\n\n    const intervalTree = createIntervalTree();\n    const columnHeights: number[] = new Array(computedColumnCount).fill(0);\n    const items: (PositionerItem | undefined)[] = [];\n    const columnItems: number[][] = new Array(computedColumnCount)\n      .fill(0)\n      .map(() => []);\n\n    for (let i = 0; i < computedColumnCount; i++) {\n      columnHeights[i] = 0;\n      columnItems[i] = [];\n    }\n\n    return {\n      columnCount: computedColumnCount,\n      columnWidth: computedColumnWidth,\n      set: (index: number, height = 0) => {\n        let columnIndex = 0;\n\n        if (linear) {\n          const preferredColumn = index % computedColumnCount;\n\n          let shortestHeight = columnHeights[0] ?? 0;\n          let tallestHeight = shortestHeight;\n          let shortestIndex = 0;\n\n          for (let i = 0; i < columnHeights.length; i++) {\n            const currentHeight = columnHeights[i] ?? 0;\n            if (currentHeight < shortestHeight) {\n              shortestHeight = currentHeight;\n              shortestIndex = i;\n            }\n            if (currentHeight > tallestHeight) {\n              tallestHeight = currentHeight;\n            }\n          }\n\n          const preferredHeight =\n            (columnHeights[preferredColumn] ?? 0) + height;\n\n          const maxAllowedHeight = shortestHeight + height * 2.5;\n          columnIndex =\n            preferredHeight <= maxAllowedHeight\n              ? preferredColumn\n              : shortestIndex;\n        } else {\n          for (let i = 1; i < columnHeights.length; i++) {\n            const currentHeight = columnHeights[i];\n            const shortestHeight = columnHeights[columnIndex];\n            if (\n              currentHeight !== undefined &&\n              shortestHeight !== undefined &&\n              currentHeight < shortestHeight\n            ) {\n              columnIndex = i;\n            }\n          }\n        }\n\n        const columnHeight = columnHeights[columnIndex];\n        if (columnHeight === undefined) return;\n\n        const top = columnHeight;\n        columnHeights[columnIndex] = top + height + (rowGap ?? columnGap);\n\n        const columnItemsList = columnItems[columnIndex];\n        if (!columnItemsList) return;\n        columnItemsList.push(index);\n\n        items[index] = {\n          left: columnIndex * (computedColumnWidth + columnGap),\n          top,\n          height,\n          columnIndex,\n        };\n        intervalTree.insert(top, top + height, index);\n      },\n      get: (index: number) => items[index],\n      update: (updates: number[]) => {\n        const columns: (number | undefined)[] = new Array(computedColumnCount);\n        let i = 0;\n        let j = 0;\n\n        for (; i < updates.length - 1; i++) {\n          const currentIndex = updates[i];\n          if (typeof currentIndex !== \"number\") continue;\n\n          const item = items[currentIndex];\n          if (!item) continue;\n\n          const nextHeight = updates[++i];\n          if (typeof nextHeight !== \"number\") continue;\n\n          item.height = nextHeight;\n          intervalTree.remove(currentIndex);\n          intervalTree.insert(item.top, item.top + item.height, currentIndex);\n          columns[item.columnIndex] =\n            columns[item.columnIndex] === void 0\n              ? currentIndex\n              : Math.min(\n                  currentIndex,\n                  columns[item.columnIndex] ?? currentIndex,\n                );\n        }\n\n        for (i = 0; i < columns.length; i++) {\n          const currentColumn = columns[i];\n          if (currentColumn === void 0) continue;\n\n          const itemsInColumn = columnItems[i];\n          if (!itemsInColumn) continue;\n\n          const startIndex = binarySearch(itemsInColumn, currentColumn);\n          if (startIndex === -1) continue;\n\n          const currentItemIndex = itemsInColumn[startIndex];\n          if (typeof currentItemIndex !== \"number\") continue;\n\n          const startItem = items[currentItemIndex];\n          if (!startItem) continue;\n\n          const currentHeight = columnHeights[i];\n          if (typeof currentHeight !== \"number\") continue;\n\n          columnHeights[i] =\n            startItem.top + startItem.height + (rowGap ?? columnGap);\n\n          for (j = startIndex + 1; j < itemsInColumn.length; j++) {\n            const currentIndex = itemsInColumn[j];\n            if (typeof currentIndex !== \"number\") continue;\n\n            const item = items[currentIndex];\n            if (!item) continue;\n\n            const columnHeight = columnHeights[i];\n            if (typeof columnHeight !== \"number\") continue;\n\n            item.top = columnHeight;\n            columnHeights[i] = item.top + item.height + (rowGap ?? columnGap);\n            intervalTree.remove(currentIndex);\n            intervalTree.insert(item.top, item.top + item.height, currentIndex);\n          }\n        }\n      },\n      range: (low, high, onItemRender) =>\n        intervalTree.search(low, high, (index: number, top: number) => {\n          const item = items[index];\n          if (!item) return;\n          onItemRender(index, item.left, top);\n        }),\n      estimateHeight: (itemCount, defaultItemHeight): number => {\n        const tallestColumn = Math.max(0, Math.max.apply(null, columnHeights));\n\n        return itemCount === intervalTree.size\n          ? tallestColumn\n          : tallestColumn +\n              Math.ceil((itemCount - intervalTree.size) / computedColumnCount) *\n                defaultItemHeight;\n      },\n      shortestColumn: () => {\n        if (columnHeights.length > 1)\n          return Math.min.apply(null, columnHeights);\n        return columnHeights[0] ?? 0;\n      },\n      size(): number {\n        return intervalTree.size;\n      },\n      all(): PositionerItem[] {\n        return items.filter(Boolean) as PositionerItem[];\n      },\n    };\n  }, [\n    width,\n    columnWidth,\n    columnGap,\n    rowGap,\n    columnCount,\n    maxColumnCount,\n    linear,\n  ]);\n\n  const positionerRef = React.useRef<Positioner | null>(null);\n  if (positionerRef.current === null) positionerRef.current = initPositioner();\n\n  const prevDepsRef = React.useRef(deps);\n  const opts = [\n    width,\n    columnWidth,\n    columnGap,\n    rowGap,\n    columnCount,\n    maxColumnCount,\n    linear,\n  ];\n  const prevOptsRef = React.useRef(opts);\n  const optsChanged = !opts.every((item, i) => prevOptsRef.current[i] === item);\n\n  if (\n    optsChanged ||\n    !deps.every((item, i) => prevDepsRef.current[i] === item)\n  ) {\n    const prevPositioner = positionerRef.current;\n    const positioner = initPositioner();\n    prevDepsRef.current = deps;\n    prevOptsRef.current = opts;\n\n    if (optsChanged) {\n      const cacheSize = prevPositioner.size();\n      for (let index = 0; index < cacheSize; index++) {\n        const pos = prevPositioner.get(index);\n        positioner.set(index, pos !== void 0 ? pos.height : 0);\n      }\n    }\n\n    positionerRef.current = positioner;\n  }\n\n  return positionerRef.current;\n}\n\ninterface DebouncedWindowSizeOptions {\n  containerRef: React.RefObject<RootElement | null>;\n  defaultWidth?: number;\n  defaultHeight?: number;\n  delayMs?: number;\n}\n\nfunction useDebouncedWindowSize(options: DebouncedWindowSizeOptions) {\n  const {\n    containerRef,\n    defaultWidth = 0,\n    defaultHeight = 0,\n    delayMs = DEBOUNCE_DELAY,\n  } = options;\n\n  const getDocumentSize = React.useCallback(() => {\n    if (typeof document === \"undefined\") {\n      return { width: defaultWidth, height: defaultHeight };\n    }\n    return {\n      width: document.documentElement.clientWidth,\n      height: document.documentElement.clientHeight,\n    };\n  }, [defaultWidth, defaultHeight]);\n\n  const [size, setSize] = React.useState(getDocumentSize());\n  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);\n\n  const setDebouncedSize = React.useCallback(\n    (value: { width: number; height: number }) => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n\n      timeoutRef.current = setTimeout(() => {\n        setSize(value);\n      }, delayMs);\n    },\n    [delayMs],\n  );\n\n  React.useEffect(() => {\n    function onResize() {\n      if (containerRef.current) {\n        setDebouncedSize({\n          width: containerRef.current.offsetWidth,\n          height: document.documentElement.clientHeight,\n        });\n      } else {\n        setDebouncedSize(getDocumentSize());\n      }\n    }\n\n    window?.addEventListener(\"resize\", onResize, { passive: true });\n    window?.addEventListener(\"orientationchange\", onResize);\n    window.visualViewport?.addEventListener(\"resize\", onResize);\n\n    return () => {\n      window?.removeEventListener(\"resize\", onResize);\n      window?.removeEventListener(\"orientationchange\", onResize);\n      window.visualViewport?.removeEventListener(\"resize\", onResize);\n      if (timeoutRef.current) clearTimeout(timeoutRef.current);\n    };\n  }, [setDebouncedSize, containerRef, getDocumentSize]);\n\n  return size;\n}\n\ntype OnRafScheduleReturn<T extends unknown[]> = {\n  (...args: T): void;\n  cancel: () => void;\n};\n\nfunction onRafSchedule<T extends unknown[]>(\n  callback: (...args: T) => void,\n): OnRafScheduleReturn<T> {\n  let lastArgs: T = [] as unknown as T;\n  let frameId: number | null = null;\n\n  function onCallback(...args: T) {\n    lastArgs = args;\n\n    if (frameId)\n      frameId = requestAnimationFrame(() => {\n        frameId = null;\n        callback(...lastArgs);\n      });\n  }\n\n  onCallback.cancel = () => {\n    if (!frameId) return;\n    cancelAnimationFrame(frameId);\n    frameId = null;\n  };\n\n  return onCallback;\n}\n\nfunction useResizeObserver(positioner: Positioner) {\n  const [, setLayoutVersion] = React.useState(0);\n\n  const createResizeObserver = React.useMemo(() => {\n    if (typeof window === \"undefined\") {\n      return () => ({\n        disconnect: () => {},\n        observe: () => {},\n        unobserve: () => {},\n      });\n    }\n\n    return onDeepMemo(\n      [WeakMap],\n      (positioner: Positioner, onUpdate: () => void) => {\n        const updates: number[] = [];\n        const itemMap = new WeakMap<Element, number>();\n\n        const update = onRafSchedule(() => {\n          if (updates.length > 0) {\n            positioner.update(updates);\n            onUpdate();\n          }\n          updates.length = 0;\n        });\n\n        function onItemResize(target: ItemElement) {\n          const height = target.offsetHeight;\n          if (height > 0) {\n            const index = itemMap.get(target);\n            if (index !== void 0) {\n              const position = positioner.get(index);\n              if (position !== void 0 && height !== position.height) {\n                updates.push(index, height);\n              }\n            }\n          }\n          update();\n        }\n\n        const scheduledItemMap = new Map<\n          number,\n          OnRafScheduleReturn<[ItemElement]>\n        >();\n        function onResizeObserver(entries: ResizeObserverEntry[]) {\n          for (const entry of entries) {\n            if (!entry) continue;\n            const index = itemMap.get(entry.target);\n\n            if (index === void 0) continue;\n            let handler = scheduledItemMap.get(index);\n            if (!handler) {\n              handler = onRafSchedule(onItemResize);\n              scheduledItemMap.set(index, handler);\n            }\n            handler(entry.target as ItemElement);\n          }\n        }\n\n        const observer = new ResizeObserver(onResizeObserver);\n        const disconnect = observer.disconnect.bind(observer);\n        observer.disconnect = () => {\n          disconnect();\n          for (const [, scheduleItem] of scheduledItemMap) {\n            scheduleItem.cancel();\n          }\n        };\n\n        return observer;\n      },\n    );\n  }, []);\n\n  const resizeObserver = createResizeObserver(positioner, () =>\n    setLayoutVersion((prev) => prev + 1),\n  );\n\n  React.useEffect(() => () => resizeObserver.disconnect(), [resizeObserver]);\n\n  return resizeObserver;\n}\n\nfunction useScroller({\n  offset = 0,\n  fps = SCROLL_FPS,\n}: {\n  offset?: number;\n  fps?: number;\n} = {}): { scrollTop: number; isScrolling: boolean } {\n  const [scrollY, setScrollY] = useThrottle(\n    typeof globalThis.window === \"undefined\"\n      ? 0\n      : (globalThis.window.scrollY ?? document.documentElement.scrollTop ?? 0),\n    { fps, leading: true },\n  );\n\n  const onScroll = React.useCallback(() => {\n    setScrollY(\n      globalThis.window.scrollY ?? document.documentElement.scrollTop ?? 0,\n    );\n  }, [setScrollY]);\n\n  React.useEffect(() => {\n    if (typeof globalThis.window === \"undefined\") return;\n    globalThis.window.addEventListener(\"scroll\", onScroll, { passive: true });\n\n    return () => globalThis.window.removeEventListener(\"scroll\", onScroll);\n  }, [onScroll]);\n\n  const [isScrolling, setIsScrolling] = React.useState(false);\n  const hasMountedRef = React.useRef(0);\n\n  React.useEffect(() => {\n    if (hasMountedRef.current === 1) setIsScrolling(true);\n    let didUnsubscribe = false;\n\n    function requestTimeout(fn: () => void, delay: number) {\n      const start = performance.now();\n      const handle = {\n        id: requestAnimationFrame(function tick(timestamp) {\n          if (timestamp - start >= delay) {\n            fn();\n          } else {\n            handle.id = requestAnimationFrame(tick);\n          }\n        }),\n      };\n      return handle;\n    }\n\n    const timeout = requestTimeout(\n      () => {\n        if (didUnsubscribe) return;\n        setIsScrolling(false);\n      },\n      40 + 1000 / fps,\n    );\n    hasMountedRef.current = 1;\n    return () => {\n      didUnsubscribe = true;\n      cancelAnimationFrame(timeout.id);\n    };\n  }, [fps]);\n\n  return { scrollTop: Math.max(0, scrollY - offset), isScrolling };\n}\n\nfunction useThrottle<State>(\n  initialState: State | (() => State),\n  options: {\n    fps?: number;\n    leading?: boolean;\n  } = {},\n): [State, React.Dispatch<React.SetStateAction<State>>] {\n  const { fps = 30, leading = false } = options;\n  const [state, setState] = React.useState(initialState);\n  const latestSetState = React.useRef(setState);\n  latestSetState.current = setState;\n\n  const ms = 1000 / fps;\n  const prevCountRef = React.useRef(0);\n  const trailingTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(\n    null,\n  );\n\n  const clearTrailing = React.useCallback(() => {\n    if (trailingTimeout.current) {\n      clearTimeout(trailingTimeout.current);\n    }\n  }, []);\n\n  React.useEffect(() => {\n    return () => {\n      prevCountRef.current = 0;\n      clearTrailing();\n    };\n  }, [clearTrailing]);\n\n  const throttledSetState = React.useCallback(\n    (action: React.SetStateAction<State>) => {\n      const perf = typeof performance !== \"undefined\" ? performance : Date;\n      const now = () => perf.now();\n      const rightNow = now();\n      const call = () => {\n        prevCountRef.current = rightNow;\n        clearTrailing();\n        latestSetState.current(action);\n      };\n      const current = prevCountRef.current;\n\n      if (leading && current === 0) {\n        return call();\n      }\n\n      if (rightNow - current > ms) {\n        if (current > 0) {\n          return call();\n        }\n        prevCountRef.current = rightNow;\n      }\n\n      clearTrailing();\n      trailingTimeout.current = setTimeout(() => {\n        call();\n        prevCountRef.current = 0;\n      }, ms);\n    },\n    [leading, ms, clearTrailing],\n  );\n\n  return [state, throttledSetState];\n}\n\nconst ROOT_NAME = \"MasonryRoot\";\nconst VIEWPORT_NAME = \"MasonryViewport\";\nconst ITEM_NAME = \"MasonryItem\";\n\nconst MASONRY_ERROR = {\n  [ROOT_NAME]: `\\`${ROOT_NAME}\\` components must be within \\`${ROOT_NAME}\\``,\n  [VIEWPORT_NAME]: `\\`${VIEWPORT_NAME}\\` components must be within \\`${ROOT_NAME}\\``,\n  [ITEM_NAME]: `\\`${ITEM_NAME}\\` must be within \\`${VIEWPORT_NAME}\\``,\n} as const;\n\ninterface DivProps extends React.ComponentProps<\"div\"> {}\n\ntype RootElement = React.ComponentRef<typeof Masonry>;\ntype ItemElement = React.ComponentRef<typeof MasonryItem>;\n\ninterface MasonryContextValue {\n  positioner: Positioner;\n  resizeObserver?: ResizeObserver;\n  columnWidth: number;\n  onItemRegister: (index: number) => (node: ItemElement | null) => void;\n  scrollTop: number;\n  windowHeight: number;\n  itemHeight: number;\n  overscan: number;\n  isScrolling?: boolean;\n  fallback?: React.ReactNode;\n}\n\nconst MasonryContext = React.createContext<MasonryContextValue | null>(null);\n\nfunction useMasonryContext(name: keyof typeof MASONRY_ERROR) {\n  const context = React.useContext(MasonryContext);\n  if (!context) {\n    throw new Error(MASONRY_ERROR[name]);\n  }\n  return context;\n}\n\ninterface MasonryProps extends DivProps {\n  columnWidth?: number;\n  columnCount?: number;\n  maxColumnCount?: number;\n  gap?: number | { column: number; row: number };\n  itemHeight?: number;\n  defaultWidth?: number;\n  defaultHeight?: number;\n  overscan?: number;\n  scrollFps?: number;\n  fallback?: React.ReactNode;\n  linear?: boolean;\n  asChild?: boolean;\n}\n\nfunction Masonry(props: MasonryProps) {\n  const {\n    columnWidth = COLUMN_WIDTH,\n    columnCount,\n    maxColumnCount,\n    gap = GAP,\n    itemHeight = ITEM_HEIGHT,\n    defaultWidth,\n    defaultHeight,\n    overscan = OVERSCAN,\n    scrollFps = SCROLL_FPS,\n    fallback,\n    linear = false,\n    asChild,\n    children,\n    style,\n    ref,\n    ...rootProps\n  } = props;\n\n  const gapValue = typeof gap === \"object\" ? gap : { column: gap, row: gap };\n  const columnGap = gapValue.column;\n  const rowGap = gapValue.row;\n\n  const containerRef = React.useRef<RootElement | null>(null);\n  const composedRef = useComposedRefs(ref, containerRef);\n\n  const size = useDebouncedWindowSize({\n    containerRef,\n    defaultWidth,\n    defaultHeight,\n    delayMs: DEBOUNCE_DELAY,\n  });\n\n  const [containerPosition, setContainerPosition] = React.useState<{\n    offset: number;\n    width: number;\n  }>({ offset: 0, width: 0 });\n\n  useIsomorphicLayoutEffect(() => {\n    if (!containerRef.current) return;\n\n    let offset = 0;\n    let container = containerRef.current;\n\n    do {\n      offset += container.offsetTop ?? 0;\n      container = container.offsetParent as RootElement;\n    } while (container);\n\n    if (\n      offset !== containerPosition.offset ||\n      containerRef.current.offsetWidth !== containerPosition.width\n    ) {\n      setContainerPosition({\n        offset,\n        width: containerRef.current.offsetWidth,\n      });\n    }\n  }, [containerPosition, size]);\n\n  const positioner = usePositioner({\n    width: containerPosition.width ?? size.width,\n    columnWidth,\n    columnGap,\n    rowGap,\n    columnCount,\n    maxColumnCount,\n    linear,\n  });\n  const resizeObserver = useResizeObserver(positioner);\n  const { scrollTop, isScrolling } = useScroller({\n    offset: containerPosition.offset,\n    fps: scrollFps,\n  });\n\n  const itemMap = React.useRef(new WeakMap<ItemElement, number>()).current;\n\n  const onItemRegister = React.useCallback(\n    (index: number) => (node: ItemElement | null) => {\n      if (!node) return;\n\n      itemMap.set(node, index);\n      if (resizeObserver) {\n        resizeObserver.observe(node);\n      }\n      if (positioner.get(index) === void 0) {\n        positioner.set(index, node.offsetHeight);\n      }\n    },\n    [itemMap, positioner, resizeObserver],\n  );\n\n  const contextValue = React.useMemo<MasonryContextValue>(\n    () => ({\n      positioner,\n      resizeObserver,\n      columnWidth: positioner.columnWidth,\n      onItemRegister,\n      scrollTop,\n      windowHeight: size.height,\n      itemHeight,\n      overscan,\n      fallback,\n      isScrolling,\n    }),\n    [\n      positioner,\n      resizeObserver,\n      onItemRegister,\n      scrollTop,\n      size.height,\n      itemHeight,\n      overscan,\n      fallback,\n      isScrolling,\n    ],\n  );\n\n  const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <MasonryContext.Provider value={contextValue}>\n      <RootPrimitive\n        {...rootProps}\n        data-slot=\"masonry\"\n        ref={composedRef}\n        style={{\n          position: \"relative\",\n          width: \"100%\",\n          height: \"100%\",\n          ...style,\n        }}\n      >\n        <MasonryViewport>{children}</MasonryViewport>\n      </RootPrimitive>\n    </MasonryContext.Provider>\n  );\n}\n\ninterface MasonryItemPropsWithRef extends MasonryItemProps {\n  ref: React.Ref<ItemElement | null>;\n}\n\nfunction MasonryViewport(props: DivProps) {\n  const { children, style, ref, ...viewportProps } = props;\n  const context = useMasonryContext(VIEWPORT_NAME);\n  const [layoutVersion, setLayoutVersion] = React.useState(0);\n  const rafId = React.useRef<number | null>(null);\n  const [mounted, setMounted] = React.useState(false);\n\n  useIsomorphicLayoutEffect(() => {\n    setMounted(true);\n  }, []);\n\n  let startIndex = 0;\n  let stopIndex: number | undefined;\n\n  const validChildren = React.Children.toArray(children).filter(\n    (child): child is React.ReactElement<MasonryItemPropsWithRef> =>\n      React.isValidElement(child) && child.type === MasonryItem,\n  );\n  const itemCount = validChildren.length;\n\n  const shortestColumnSize = context.positioner.shortestColumn();\n  const measuredCount = context.positioner.size();\n  const overscanPixels = context.windowHeight * context.overscan;\n  const rangeStart = Math.max(0, context.scrollTop - overscanPixels / 2);\n  const rangeEnd = context.scrollTop + overscanPixels;\n  const layoutOutdated =\n    shortestColumnSize < rangeEnd && measuredCount < itemCount;\n\n  const positionedChildren: React.ReactElement[] = [];\n\n  const visibleItemStyle = React.useMemo(\n    (): React.CSSProperties => ({\n      position: \"absolute\",\n      writingMode: \"horizontal-tb\",\n      visibility: \"visible\",\n      width: context.columnWidth,\n      transform: context.isScrolling ? \"translateZ(0)\" : undefined,\n      willChange: context.isScrolling ? \"transform\" : undefined,\n    }),\n    [context.columnWidth, context.isScrolling],\n  );\n\n  const hiddenItemStyle = React.useMemo(\n    (): React.CSSProperties => ({\n      position: \"absolute\",\n      writingMode: \"horizontal-tb\",\n      visibility: \"hidden\",\n      width: context.columnWidth,\n      zIndex: -1000,\n    }),\n    [context.columnWidth],\n  );\n\n  context.positioner.range(rangeStart, rangeEnd, (index, left, top) => {\n    const child = validChildren[index];\n    if (!child) return;\n\n    const itemStyle = {\n      ...visibleItemStyle,\n      top,\n      left,\n      ...child.props.style,\n    };\n\n    positionedChildren.push(\n      React.cloneElement(child, {\n        key: child.key ?? index,\n        ref: context.onItemRegister(index),\n        style: itemStyle,\n      }),\n    );\n\n    if (stopIndex === undefined) {\n      startIndex = index;\n      stopIndex = index;\n    } else {\n      startIndex = Math.min(startIndex, index);\n      stopIndex = Math.max(stopIndex, index);\n    }\n  });\n\n  if (layoutOutdated && mounted) {\n    const batchSize = Math.min(\n      itemCount - measuredCount,\n      Math.ceil(\n        ((context.scrollTop + overscanPixels - shortestColumnSize) /\n          context.itemHeight) *\n          context.positioner.columnCount,\n      ),\n    );\n\n    for (\n      let index = measuredCount;\n      index < measuredCount + batchSize;\n      index++\n    ) {\n      const child = validChildren[index];\n      if (!child) continue;\n\n      const itemStyle = {\n        ...hiddenItemStyle,\n        ...child.props.style,\n      };\n\n      positionedChildren.push(\n        React.cloneElement(child, {\n          key: child.key ?? index,\n          ref: context.onItemRegister(index),\n          style: itemStyle,\n        }),\n      );\n    }\n  }\n\n  React.useEffect(() => {\n    if (layoutOutdated && mounted) {\n      if (rafId.current) {\n        cancelAnimationFrame(rafId.current);\n      }\n      rafId.current = requestAnimationFrame(() => {\n        setLayoutVersion((v) => v + 1);\n      });\n    }\n    return () => {\n      if (rafId.current) {\n        cancelAnimationFrame(rafId.current);\n      }\n    };\n  }, [layoutOutdated, mounted]);\n\n  const estimatedHeight = React.useMemo(() => {\n    const measuredHeight = context.positioner.estimateHeight(\n      measuredCount,\n      context.itemHeight,\n    );\n    if (measuredCount === itemCount) {\n      return measuredHeight;\n    }\n    const remainingItems = itemCount - measuredCount;\n    const estimatedRemainingHeight = Math.ceil(\n      (remainingItems / context.positioner.columnCount) * context.itemHeight,\n    );\n    return measuredHeight + estimatedRemainingHeight;\n  }, [context.positioner, context.itemHeight, measuredCount, itemCount]);\n\n  const containerStyle = React.useMemo(\n    () => ({\n      position: \"relative\" as const,\n      width: \"100%\",\n      maxWidth: \"100%\",\n      height: Math.ceil(estimatedHeight),\n      maxHeight: Math.ceil(estimatedHeight),\n      willChange: context.isScrolling ? \"contents\" : undefined,\n      pointerEvents: context.isScrolling ? (\"none\" as const) : undefined,\n      ...style,\n    }),\n    [context.isScrolling, estimatedHeight, style],\n  );\n\n  if (!mounted && context.fallback) {\n    return context.fallback;\n  }\n\n  return (\n    <div\n      {...viewportProps}\n      ref={ref}\n      style={containerStyle}\n      data-version={mounted ? layoutVersion : undefined}\n    >\n      {positionedChildren}\n    </div>\n  );\n}\n\ninterface MasonryItemProps extends DivProps {\n  asChild?: boolean;\n}\n\nfunction MasonryItem(props: MasonryItemProps) {\n  const { asChild, ref, ...itemProps } = props;\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return <ItemPrimitive data-slot=\"masonry-item\" {...itemProps} ref={ref} />;\n}\n\nexport { Masonry, MasonryItem, type MasonryProps };\n",
      "target": ""
    },
    {
      "path": "lib/compose-refs.ts",
      "type": "registry:lib",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // oxlint-disable-next-line react/exhaustive-deps -- we want to memoize by all values\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "target": ""
    }
  ]
}