{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-virtualization",
  "type": "registry:component",
  "title": "Virtualization",
  "description": "A long list and table that mount one window of rows",
  "author": "sudo-kw <https://github.com/sudo-kw>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "@tanstack/virtual-core@3.19.6",
    "effect@4.1.2",
    "react@^19.2.4"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/data/src/internal/size.ts",
      "target": "@ui/gpuixui/data/internal/size.ts",
      "type": "registry:lib",
      "content": "import * as Option from \"effect/Option\";\n\nexport const paintableSize = (\n  size: number | undefined,\n): Option.Option<number> =>\n  Option.filter(\n    Option.fromNullishOr(size),\n    (value) => Number.isFinite(value) && value >= 0,\n  );\n"
    },
    {
      "path": "packages/data/src/recipe.ts",
      "target": "@ui/gpuixui/data/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(256);\n"
    },
    {
      "path": "packages/data/src/theme.ts",
      "target": "@ui/gpuixui/data/theme.ts",
      "type": "registry:lib",
      "content": "import type { Theme } from \"../tokens/index.js\";\nimport { useTheme as useTokenTheme } from \"../tokens/react.js\";\n\nexport {\n  controlFocusRing,\n  dividerOn,\n  pointerWash,\n  textStyle,\n  themeColors,\n} from \"../tokens/index.js\";\nexport type { Theme };\n\nexport const useTheme = (): Theme => useTokenTheme();\n"
    },
    {
      "path": "packages/data/src/virtual/list.tsx",
      "target": "@ui/gpuixui/data/virtual/list.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { EventPayload, Props, StyleDesc } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Option from \"effect/Option\";\nimport type { ReactNode } from \"react\";\nimport { recipeStyle } from \"../recipe.js\";\nimport { useTheme } from \"../theme.js\";\nimport { paintableSize } from \"../internal/size.js\";\nimport { rowCount, rowHeightOf, rowIndex, overdrawRows } from \"./window.js\";\n\nexport interface VisibleRange {\n  readonly first: number;\n\n  readonly last: number;\n}\n\nexport const visibleRange = (\n  event: EventPayload,\n): Option.Option<VisibleRange> =>\n  Option.flatMap(Option.fromNullishOr(event.startIndex), (first) =>\n    Option.map(\n      Option.fromNullishOr(event.endIndex),\n      (last): VisibleRange => ({ first, last }),\n    ),\n  );\n\ninterface VirtualListShared extends Omit<Props, \"children\" | \"onVisibleRange\"> {\n  readonly height?: number;\n\n  readonly followTail?: boolean;\n\n  readonly alignment?: \"top\" | \"bottom\";\n\n  readonly overdraw?: number;\n\n  readonly onVisibleRange?: (range: VisibleRange) => void;\n\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport type VirtualListProps =\n  | (VirtualListShared & {\n      readonly itemCount?: undefined;\n      readonly estimatedItemHeight?: number;\n      readonly windowStart?: undefined;\n    })\n  | (VirtualListShared & {\n      readonly itemCount: number;\n\n      readonly estimatedItemHeight: number;\n      readonly windowStart?: number;\n    });\n\nconst listBox = (height?: number): StyleDesc =>\n  height === undefined\n    ? {}\n    : { height, flexGrow: 0, flexShrink: 0, flexBasis: height };\n\nexport const VirtualList = (props: VirtualListProps) => {\n  const theme = useTheme();\n  const {\n    height,\n    followTail,\n    alignment,\n    overdraw,\n    onVisibleRange,\n    className,\n    children,\n    style,\n    ...rest\n  } = props;\n  const handleRange =\n    onVisibleRange === undefined\n      ? undefined\n      : (event: EventPayload) =>\n          Option.match(visibleRange(event), {\n            onNone: () => {},\n            onSome: onVisibleRange,\n          });\n  const shared = {\n    ...rest,\n    followTail,\n    alignment,\n    overdraw: overdraw === undefined ? undefined : overdrawRows(overdraw),\n    onVisibleRange: handleRange,\n    style: mergeStyles(\n      recipeStyle(cn(className ?? \"\"), theme),\n      mergeStyles(listBox(Option.getOrUndefined(paintableSize(height))), style),\n    ),\n  };\n  const windowed =\n    props.itemCount === undefined\n      ? undefined\n      : {\n          itemCount: rowCount(props.itemCount),\n\n          estimatedItemHeight: rowHeightOf(props.estimatedItemHeight),\n          windowStart:\n            props.windowStart === undefined\n              ? undefined\n              : rowIndex(props.windowStart),\n        };\n  return windowed === undefined ? (\n    <virtual-list {...shared}>{children}</virtual-list>\n  ) : (\n    <virtual-list\n      {...shared}\n      itemCount={windowed.itemCount}\n      estimatedItemHeight={windowed.estimatedItemHeight}\n      windowStart={windowed.windowStart}\n    >\n      {children}\n    </virtual-list>\n  );\n};\n"
    },
    {
      "path": "packages/data/src/virtual/window.ts",
      "target": "@ui/gpuixui/data/virtual/window.ts",
      "type": "registry:lib",
      "content": "import { Virtualizer } from \"@tanstack/virtual-core\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\n\nexport const DEFAULT_OVERDRAW = 4;\n\nexport const DEFAULT_ROW_HEIGHT = 37;\n\nexport interface VirtualWindow {\n  readonly start: number;\n  readonly end: number;\n}\n\nexport interface WindowSpec {\n  readonly count: number;\n\n  readonly rowHeight: number;\n\n  readonly viewportHeight: number;\n\n  readonly overdraw?: number;\n}\n\nexport interface WindowCalculator {\n  readonly indicesAt: (scrollOffset: number) => ReadonlyArray<number>;\n\n  readonly at: (scrollOffset: number) => VirtualWindow;\n\n  readonly around: (index: number) => VirtualWindow;\n}\n\nexport const rowHeightOf = (\n  height: number | undefined,\n  fallback: number = DEFAULT_ROW_HEIGHT,\n): number =>\n  height !== undefined && Number.isFinite(height) && height > 0\n    ? height\n    : fallback;\n\nconst viewportOf = (spec: WindowSpec): number =>\n  Number.isFinite(spec.viewportHeight) && spec.viewportHeight > 0\n    ? spec.viewportHeight\n    : rowHeightOf(spec.rowHeight);\n\nexport const overdrawRows = (overdraw: number | undefined): number =>\n  overdraw === undefined || !Number.isFinite(overdraw)\n    ? DEFAULT_OVERDRAW\n    : Math.max(0, Math.floor(overdraw));\n\nexport const rowCount = (count: number): number =>\n  Number.isFinite(count) ? Math.max(0, Math.floor(count)) : 0;\n\nexport const maxScrollOffset = (spec: WindowSpec): number =>\n  Math.max(\n    0,\n    rowCount(spec.count) * rowHeightOf(spec.rowHeight) - viewportOf(spec),\n  );\n\nexport const clampScrollOffset = (\n  spec: WindowSpec,\n  scrollOffset: number,\n): number => {\n  const offset = Number.isFinite(scrollOffset) ? Math.max(0, scrollOffset) : 0;\n  return Math.min(offset, maxScrollOffset(spec));\n};\n\nexport const createWindowCalculator = (spec: WindowSpec): WindowCalculator => {\n  const rowHeight = rowHeightOf(spec.rowHeight);\n  const virtualizer = new Virtualizer<never, never>({\n    count: rowCount(spec.count),\n    estimateSize: () => rowHeight,\n    getScrollElement: () => null,\n    observeElementRect: () => undefined,\n    observeElementOffset: () => undefined,\n    scrollToFn: () => undefined,\n    overscan: overdrawRows(spec.overdraw),\n  });\n\n  const indicesAt = (scrollOffset: number): ReadonlyArray<number> => {\n    virtualizer.scrollRect = { width: 0, height: viewportOf(spec) };\n    virtualizer.scrollOffset = clampScrollOffset(spec, scrollOffset);\n    return Arr.filter(\n      virtualizer.getVirtualIndexes(),\n      (index) => index >= 0 && index < rowCount(spec.count),\n    );\n  };\n\n  const at = (scrollOffset: number): VirtualWindow => {\n    const indices = indicesAt(scrollOffset);\n    const first = Option.getOrUndefined(Arr.head(indices));\n    const last = Option.getOrUndefined(Arr.last(indices));\n    return first === undefined || last === undefined\n      ? { start: 0, end: 0 }\n      : { start: first, end: last + 1 };\n  };\n\n  return {\n    indicesAt,\n    at,\n    around: (index) => at(rowIndex(index) * rowHeight),\n  };\n};\n\nexport const rowIndex = (index: number): number =>\n  Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;\n\nexport const anchorOffset = (\n  anchor: Option.Option<ReadonlyArray<number>>,\n  rowHeight: number,\n): Option.Option<number> =>\n  Option.flatMap(anchor, (values) => {\n    const index = values[0];\n    const offsetInItem = values[1];\n    if (index === undefined || offsetInItem === undefined) return Option.none();\n    const into = Number.isFinite(offsetInItem) ? Math.max(0, offsetInItem) : 0;\n    return Option.some(rowIndex(index) * rowHeightOf(rowHeight) + into);\n  });\n"
    }
  ],
  "docs": "[Virtualization](https://gpuixui.sudokw.com/data-display/virtualization) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/data-display/virtualization",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}