{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-grid",
  "title": "Data Grid",
  "description": "A high-performance editable data grid component with virtualization, keyboard navigation, and cell editing",
  "dependencies": [
    "@tanstack/react-table",
    "@tanstack/react-virtual",
    "lucide-react",
    "sonner"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "calendar",
    "checkbox",
    "command",
    "dialog",
    "dropdown-menu",
    "input",
    "popover",
    "select",
    "separator",
    "skeleton",
    "textarea",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/components/data-grid/data-grid.tsx",
      "content": "\"use client\";\n\nimport { Plus } from \"lucide-react\";\nimport * as React from \"react\";\nimport { DataGridColumnHeader } from \"@/components/data-grid/data-grid-column-header\";\nimport { DataGridContextMenu } from \"@/components/data-grid/data-grid-context-menu\";\nimport { DataGridPasteDialog } from \"@/components/data-grid/data-grid-paste-dialog\";\nimport { DataGridRow } from \"@/components/data-grid/data-grid-row\";\nimport { DataGridSearch } from \"@/components/data-grid/data-grid-search\";\nimport { useAsRef } from \"@/hooks/use-as-ref\";\nimport type { useDataGrid } from \"@/hooks/use-data-grid\";\nimport {\n  flexRender,\n  getColumnBorderVisibility,\n  getColumnPinningStyle,\n} from \"@/lib/data-grid\";\nimport { cn } from \"@/lib/utils\";\nimport type { Direction } from \"@/types/data-grid\";\n\nconst EMPTY_CELL_SELECTION_SET = new Set<string>();\n\ninterface DataGridProps<TData>\n  extends Omit<ReturnType<typeof useDataGrid<TData>>, \"dir\">,\n    Omit<React.ComponentProps<\"div\">, \"contextMenu\"> {\n  dir?: Direction;\n  height?: number;\n  stretchColumns?: boolean;\n}\n\nexport function DataGrid<TData>({\n  dataGridRef,\n  headerRef,\n  rowMapRef,\n  footerRef,\n  dir = \"ltr\",\n  table,\n  tableMeta,\n  virtualTotalSize,\n  virtualItems,\n  measureElement,\n  columns,\n  columnSizeVars,\n  searchState,\n  searchMatchesByRow,\n  activeSearchMatch,\n  cellSelectionMap,\n  focusedCell,\n  editingCell,\n  rowHeight,\n  contextMenu,\n  pasteDialog,\n  onRowAdd: onRowAddProp,\n  height = 600,\n  stretchColumns = false,\n  adjustLayout = false,\n  className,\n  ...props\n}: DataGridProps<TData>) {\n  const rows = table.getRowModel().rows;\n  const readOnly = tableMeta?.readOnly ?? false;\n  const columnVisibility = table.getState().columnVisibility;\n  const columnPinning = table.getState().columnPinning;\n\n  const onRowAddRef = useAsRef(onRowAddProp);\n\n  const onRowAdd = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      onRowAddRef.current?.(event);\n    },\n    [onRowAddRef],\n  );\n\n  const onDataGridContextMenu = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      event.preventDefault();\n    },\n    [],\n  );\n\n  const onFooterCellKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (!onRowAddRef.current) return;\n\n      if (event.key === \"Enter\" || event.key === \" \") {\n        event.preventDefault();\n        onRowAddRef.current();\n      }\n    },\n    [onRowAddRef],\n  );\n\n  return (\n    <div\n      data-slot=\"grid-wrapper\"\n      dir={dir}\n      {...props}\n      className={cn(\"relative flex w-full flex-col\", className)}\n    >\n      {searchState && <DataGridSearch {...searchState} />}\n      <DataGridContextMenu\n        tableMeta={tableMeta}\n        columns={columns}\n        contextMenu={contextMenu}\n      />\n      <DataGridPasteDialog tableMeta={tableMeta} pasteDialog={pasteDialog} />\n      <div\n        role=\"grid\"\n        aria-label=\"Data grid\"\n        aria-rowcount={rows.length + (onRowAddProp ? 1 : 0)}\n        aria-colcount={columns.length}\n        data-slot=\"grid\"\n        tabIndex={0}\n        ref={dataGridRef}\n        className=\"relative grid select-none overflow-auto rounded-md border focus:outline-none\"\n        style={{\n          ...columnSizeVars,\n          maxHeight: `${height}px`,\n        }}\n        onContextMenu={onDataGridContextMenu}\n      >\n        <div\n          role=\"rowgroup\"\n          data-slot=\"grid-header\"\n          ref={headerRef}\n          className=\"sticky top-0 z-10 grid border-b bg-background\"\n        >\n          {table.getHeaderGroups().map((headerGroup, rowIndex) => (\n            <div\n              key={headerGroup.id}\n              role=\"row\"\n              aria-rowindex={rowIndex + 1}\n              data-slot=\"grid-header-row\"\n              tabIndex={-1}\n              className=\"flex w-full\"\n            >\n              {headerGroup.headers.map((header, colIndex) => {\n                const sorting = table.getState().sorting;\n                const currentSort = sorting.find(\n                  (sort) => sort.id === header.column.id,\n                );\n                const isSortable = header.column.getCanSort();\n\n                const nextHeader = headerGroup.headers[colIndex + 1];\n                const isLastColumn =\n                  colIndex === headerGroup.headers.length - 1;\n\n                const { showEndBorder, showStartBorder } =\n                  getColumnBorderVisibility({\n                    column: header.column,\n                    nextColumn: nextHeader?.column,\n                    isLastColumn,\n                  });\n\n                return (\n                  <div\n                    key={header.id}\n                    role=\"columnheader\"\n                    aria-colindex={colIndex + 1}\n                    aria-sort={\n                      currentSort?.desc === false\n                        ? \"ascending\"\n                        : currentSort?.desc === true\n                          ? \"descending\"\n                          : isSortable\n                            ? \"none\"\n                            : undefined\n                    }\n                    data-slot=\"grid-header-cell\"\n                    tabIndex={-1}\n                    className={cn(\"relative\", {\n                      grow: stretchColumns && header.column.id !== \"select\",\n                      \"border-e\":\n                        showEndBorder && header.column.id !== \"select\",\n                      \"border-s\":\n                        showStartBorder && header.column.id !== \"select\",\n                    })}\n                    style={{\n                      ...getColumnPinningStyle({ column: header.column, dir }),\n                      width: `calc(var(--header-${header.id}-size) * 1px)`,\n                    }}\n                  >\n                    {header.isPlaceholder ? null : typeof header.column\n                        .columnDef.header === \"function\" ? (\n                      <div className=\"size-full px-3 py-1.5\">\n                        {flexRender(\n                          header.column.columnDef.header,\n                          header.getContext(),\n                        )}\n                      </div>\n                    ) : (\n                      <DataGridColumnHeader header={header} table={table} />\n                    )}\n                  </div>\n                );\n              })}\n            </div>\n          ))}\n        </div>\n        <div\n          role=\"rowgroup\"\n          data-slot=\"grid-body\"\n          className=\"relative grid\"\n          style={{\n            height: `${virtualTotalSize}px`,\n            contain: adjustLayout ? \"layout paint\" : \"strict\",\n          }}\n        >\n          {virtualItems.map((virtualItem) => {\n            const row = rows[virtualItem.index];\n            if (!row) return null;\n\n            const cellSelectionKeys =\n              cellSelectionMap?.get(virtualItem.index) ??\n              EMPTY_CELL_SELECTION_SET;\n\n            const searchMatchColumns =\n              searchMatchesByRow?.get(virtualItem.index) ?? null;\n            const isActiveSearchRow =\n              activeSearchMatch?.rowIndex === virtualItem.index;\n\n            return (\n              <DataGridRow\n                key={row.id}\n                row={row}\n                tableMeta={tableMeta}\n                rowMapRef={rowMapRef}\n                virtualItem={virtualItem}\n                measureElement={measureElement}\n                rowHeight={rowHeight}\n                columnVisibility={columnVisibility}\n                columnPinning={columnPinning}\n                focusedCell={focusedCell}\n                editingCell={editingCell}\n                cellSelectionKeys={cellSelectionKeys}\n                searchMatchColumns={searchMatchColumns}\n                activeSearchMatch={isActiveSearchRow ? activeSearchMatch : null}\n                dir={dir}\n                adjustLayout={adjustLayout}\n                stretchColumns={stretchColumns}\n                readOnly={readOnly}\n              />\n            );\n          })}\n        </div>\n        {!readOnly && onRowAdd && (\n          <div\n            role=\"rowgroup\"\n            data-slot=\"grid-footer\"\n            ref={footerRef}\n            className=\"sticky bottom-0 z-10 grid border-t bg-background\"\n          >\n            <div\n              role=\"row\"\n              aria-rowindex={rows.length + 2}\n              data-slot=\"grid-add-row\"\n              tabIndex={-1}\n              className=\"flex w-full\"\n            >\n              <div\n                role=\"gridcell\"\n                tabIndex={0}\n                className=\"relative flex h-9 grow items-center bg-muted/30 transition-colors hover:bg-muted/50 focus:bg-muted/50 focus:outline-none\"\n                style={{\n                  width: table.getTotalSize(),\n                  minWidth: table.getTotalSize(),\n                }}\n                onClick={onRowAdd}\n                onKeyDown={onFooterCellKeyDown}\n              >\n                <div className=\"sticky start-0 flex items-center gap-2 px-3 text-muted-foreground\">\n                  <Plus className=\"size-3.5\" />\n                  <span className=\"text-sm\">Add row</span>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-presence.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\ninterface DataGridCellPresence {\n  color: string;\n  name: string;\n}\n\nconst DataGridCellPresenceContext = React.createContext<Map<\n  string,\n  DataGridCellPresence\n> | null>(null);\n\ninterface DataGridPresenceProviderProps {\n  value: Map<string, DataGridCellPresence>;\n  children: React.ReactNode;\n}\n\nfunction DataGridPresenceProvider({\n  value,\n  children,\n}: DataGridPresenceProviderProps) {\n  return (\n    <DataGridCellPresenceContext.Provider value={value}>\n      {children}\n    </DataGridCellPresenceContext.Provider>\n  );\n}\n\nfunction useDataGridPresence(cellKey: string): DataGridCellPresence | null {\n  const map = React.useContext(DataGridCellPresenceContext);\n  return map?.get(cellKey) ?? null;\n}\n\nexport {\n  type DataGridCellPresence,\n  DataGridPresenceProvider,\n  useDataGridPresence,\n};\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-presence.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-cell.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  CheckboxCell,\n  DateCell,\n  FileCell,\n  LongTextCell,\n  MultiSelectCell,\n  NumberCell,\n  SelectCell,\n  ShortTextCell,\n  UrlCell,\n} from \"@/components/data-grid/data-grid-cell-variants\";\nimport type { DataGridCellProps } from \"@/types/data-grid\";\n\nexport const DataGridCell = React.memo(DataGridCellImpl, (prev, next) => {\n  // Fast path: check stable primitive props first\n  if (prev.isFocused !== next.isFocused) return false;\n  if (prev.isEditing !== next.isEditing) return false;\n  if (prev.isSelected !== next.isSelected) return false;\n  if (prev.isSearchMatch !== next.isSearchMatch) return false;\n  if (prev.isActiveSearchMatch !== next.isActiveSearchMatch) return false;\n  if (prev.readOnly !== next.readOnly) return false;\n  if (prev.rowIndex !== next.rowIndex) return false;\n  if (prev.columnId !== next.columnId) return false;\n  if (prev.rowHeight !== next.rowHeight) return false;\n\n  // Check cell value using row.original instead of getValue() for stability\n  // getValue() is unstable and recreates on every render, breaking memoization\n  const prevValue = (prev.cell.row.original as Record<string, unknown>)[\n    prev.columnId\n  ];\n  const nextValue = (next.cell.row.original as Record<string, unknown>)[\n    next.columnId\n  ];\n  if (prevValue !== nextValue) {\n    return false;\n  }\n\n  // Check cell/row identity\n  if (prev.cell.row.id !== next.cell.row.id) return false;\n\n  return true;\n}) as typeof DataGridCellImpl;\n\nfunction DataGridCellImpl<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n  rowHeight,\n}: DataGridCellProps<TData>) {\n  const cellOpts = cell.column.columnDef.meta?.cell;\n  const variant = cellOpts?.variant ?? \"text\";\n\n  let Comp: React.ComponentType<DataGridCellProps<TData>>;\n\n  switch (variant) {\n    case \"short-text\":\n      Comp = ShortTextCell;\n      break;\n    case \"long-text\":\n      Comp = LongTextCell;\n      break;\n    case \"number\":\n      Comp = NumberCell;\n      break;\n    case \"url\":\n      Comp = UrlCell;\n      break;\n    case \"checkbox\":\n      Comp = CheckboxCell;\n      break;\n    case \"select\":\n      Comp = SelectCell;\n      break;\n    case \"multi-select\":\n      Comp = MultiSelectCell;\n      break;\n    case \"date\":\n      Comp = DateCell;\n      break;\n    case \"file\":\n      Comp = FileCell;\n      break;\n\n    default:\n      Comp = ShortTextCell;\n      break;\n  }\n\n  return (\n    <Comp\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-cell.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-cell-wrapper.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useDataGridPresence } from \"@/components/data-grid/data-grid-presence\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { getCellKey } from \"@/lib/data-grid\";\nimport { cn } from \"@/lib/utils\";\nimport type { DataGridCellProps } from \"@/types/data-grid\";\n\ninterface DataGridCellWrapperProps<TData>\n  extends DataGridCellProps<TData>,\n    React.ComponentProps<\"div\"> {}\n\nexport function DataGridCellWrapper<TData>({\n  tableMeta,\n  rowIndex,\n  columnId,\n  isEditing,\n  isFocused,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n  rowHeight,\n  className,\n  onClick: onClickProp,\n  onKeyDown: onKeyDownProp,\n  ref,\n  ...props\n}: DataGridCellWrapperProps<TData>) {\n  const cellMapRef = tableMeta?.cellMapRef;\n  const cellPresence = useDataGridPresence(getCellKey(rowIndex, columnId));\n\n  const onCellChange = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      if (!cellMapRef) return;\n\n      const cellKey = getCellKey(rowIndex, columnId);\n\n      if (node) {\n        cellMapRef.current.set(cellKey, node);\n      } else {\n        cellMapRef.current.delete(cellKey);\n      }\n    },\n    [rowIndex, columnId, cellMapRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onCellChange);\n\n  const onClick = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      if (!isEditing) {\n        event.preventDefault();\n        onClickProp?.(event);\n        if (isFocused && !readOnly) {\n          tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n        } else {\n          tableMeta?.onCellClick?.(rowIndex, columnId, event);\n        }\n      }\n    },\n    [\n      tableMeta,\n      rowIndex,\n      columnId,\n      isEditing,\n      isFocused,\n      readOnly,\n      onClickProp,\n    ],\n  );\n\n  const onContextMenu = React.useCallback(\n    (event: React.MouseEvent) => {\n      if (!isEditing) {\n        tableMeta?.onCellContextMenu?.(rowIndex, columnId, event);\n      }\n    },\n    [tableMeta, rowIndex, columnId, isEditing],\n  );\n\n  const onDoubleClick = React.useCallback(\n    (event: React.MouseEvent) => {\n      if (!isEditing) {\n        event.preventDefault();\n        tableMeta?.onCellDoubleClick?.(rowIndex, columnId);\n      }\n    },\n    [tableMeta, rowIndex, columnId, isEditing],\n  );\n\n  const onKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      onKeyDownProp?.(event);\n\n      if (event.defaultPrevented) return;\n\n      if (\n        event.key === \"ArrowUp\" ||\n        event.key === \"ArrowDown\" ||\n        event.key === \"ArrowLeft\" ||\n        event.key === \"ArrowRight\" ||\n        event.key === \"Home\" ||\n        event.key === \"End\" ||\n        event.key === \"PageUp\" ||\n        event.key === \"PageDown\" ||\n        event.key === \"Tab\"\n      ) {\n        return;\n      }\n\n      if (isFocused && !isEditing && !readOnly) {\n        if (event.key === \"F2\" || event.key === \"Enter\") {\n          event.preventDefault();\n          event.stopPropagation();\n          tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n          return;\n        }\n\n        if (event.key === \" \") {\n          event.preventDefault();\n          event.stopPropagation();\n          tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n          return;\n        }\n\n        if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {\n          event.preventDefault();\n          event.stopPropagation();\n          tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n        }\n      }\n    },\n    [\n      onKeyDownProp,\n      isFocused,\n      isEditing,\n      readOnly,\n      tableMeta,\n      rowIndex,\n      columnId,\n    ],\n  );\n\n  const onMouseDown = React.useCallback(\n    (event: React.MouseEvent) => {\n      if (!isEditing) {\n        tableMeta?.onCellMouseDown?.(rowIndex, columnId, event);\n      }\n    },\n    [tableMeta, rowIndex, columnId, isEditing],\n  );\n\n  const onMouseEnter = React.useCallback(() => {\n    if (!isEditing) {\n      tableMeta?.onCellMouseEnter?.(rowIndex, columnId);\n    }\n  }, [tableMeta, rowIndex, columnId, isEditing]);\n\n  const onMouseUp = React.useCallback(() => {\n    if (!isEditing) {\n      tableMeta?.onCellMouseUp?.();\n    }\n  }, [tableMeta, isEditing]);\n\n  return (\n    <div\n      role=\"button\"\n      data-slot=\"grid-cell-wrapper\"\n      data-editing={isEditing ? \"\" : undefined}\n      data-focused={isFocused ? \"\" : undefined}\n      data-selected={isSelected ? \"\" : undefined}\n      tabIndex={isFocused && !isEditing ? 0 : -1}\n      {...props}\n      ref={composedRef}\n      className={cn(\n        \"size-full px-2 py-1.5 text-start text-sm outline-none has-data-[slot=checkbox]:pt-2.5\",\n        {\n          \"ring-1 ring-inset\": isFocused || !!cellPresence,\n          \"ring-ring\": isFocused && !cellPresence,\n          \"bg-yellow-100 dark:bg-yellow-900/30\":\n            isSearchMatch && !isActiveSearchMatch,\n          \"bg-orange-200 dark:bg-orange-900/50\": isActiveSearchMatch,\n          \"bg-primary/10\": isSelected && !isEditing,\n          \"cursor-default\": !isEditing,\n          \"**:data-[slot=grid-cell-content]:line-clamp-1\":\n            !isEditing && rowHeight === \"short\",\n          \"**:data-[slot=grid-cell-content]:line-clamp-2\":\n            !isEditing && rowHeight === \"medium\",\n          \"**:data-[slot=grid-cell-content]:line-clamp-3\":\n            !isEditing && rowHeight === \"tall\",\n          \"**:data-[slot=grid-cell-content]:line-clamp-4\":\n            !isEditing && rowHeight === \"extra-tall\",\n        },\n        className,\n      )}\n      style={\n        cellPresence\n          ? ({ \"--tw-ring-color\": cellPresence.color } as React.CSSProperties)\n          : undefined\n      }\n      onClick={onClick}\n      onContextMenu={onContextMenu}\n      onDoubleClick={onDoubleClick}\n      onMouseDown={onMouseDown}\n      onMouseEnter={onMouseEnter}\n      onMouseUp={onMouseUp}\n      onKeyDown={onKeyDown}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-cell-wrapper.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-cell-variants.tsx",
      "content": "\"use client\";\n\nimport { Check, Upload, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { toast } from \"sonner\";\nimport { DataGridCellWrapper } from \"@/components/data-grid/data-grid-cell-wrapper\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverAnchor,\n  PopoverContent,\n} from \"@/components/ui/popover\";\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useBadgeOverflow } from \"@/hooks/use-badge-overflow\";\nimport { useDebouncedCallback } from \"@/hooks/use-debounced-callback\";\nimport {\n  formatDateForDisplay,\n  formatDateToString,\n  formatFileSize,\n  getCellKey,\n  getFileIcon,\n  getLineCount,\n  getUrlHref,\n  parseLocalDate,\n} from \"@/lib/data-grid\";\nimport { cn } from \"@/lib/utils\";\nimport type { DataGridCellProps, FileCellData } from \"@/types/data-grid\";\n\nexport function ShortTextCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isEditing,\n  isFocused,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = cell.getValue() as string;\n  const [value, setValue] = React.useState(initialValue);\n  const cellRef = React.useRef<HTMLDivElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const prevIsEditingRef = React.useRef(false);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(initialValue);\n    if (cellRef.current && !isEditing) {\n      cellRef.current.textContent = initialValue;\n    }\n  }\n\n  const onBlur = React.useCallback(() => {\n    // Read the current value directly from the DOM to avoid stale state\n    const currentValue = cellRef.current?.textContent ?? \"\";\n    if (!readOnly && currentValue !== initialValue) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: currentValue });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, rowIndex, columnId, initialValue, readOnly]);\n\n  const onInput = React.useCallback(\n    (event: React.FormEvent<HTMLDivElement>) => {\n      const currentValue = event.currentTarget.textContent ?? \"\";\n      setValue(currentValue);\n    },\n    [],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing) {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          const currentValue = cellRef.current?.textContent ?? \"\";\n          if (currentValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({\n              rowIndex,\n              columnId,\n              value: currentValue,\n            });\n          }\n          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });\n        } else if (event.key === \"Tab\") {\n          event.preventDefault();\n          const currentValue = cellRef.current?.textContent ?? \"\";\n          if (currentValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({\n              rowIndex,\n              columnId,\n              value: currentValue,\n            });\n          }\n          tableMeta?.onCellEditingStop?.({\n            direction: event.shiftKey ? \"left\" : \"right\",\n          });\n        } else if (event.key === \"Escape\") {\n          event.preventDefault();\n          setValue(initialValue);\n          cellRef.current?.blur();\n        }\n      } else if (\n        isFocused &&\n        event.key.length === 1 &&\n        !event.ctrlKey &&\n        !event.metaKey\n      ) {\n        // Handle typing to pre-fill the value when editing starts\n        setValue(event.key);\n\n        queueMicrotask(() => {\n          if (cellRef.current && cellRef.current.contentEditable === \"true\") {\n            cellRef.current.textContent = event.key;\n            const range = document.createRange();\n            const selection = window.getSelection();\n            range.selectNodeContents(cellRef.current);\n            range.collapse(false);\n            selection?.removeAllRanges();\n            selection?.addRange(range);\n          }\n        });\n      }\n    },\n    [isEditing, isFocused, initialValue, tableMeta, rowIndex, columnId],\n  );\n\n  React.useEffect(() => {\n    const wasEditing = prevIsEditingRef.current;\n    prevIsEditingRef.current = isEditing;\n\n    if (isEditing && !wasEditing && cellRef.current) {\n      cellRef.current.focus();\n\n      if (!cellRef.current.textContent && value) {\n        cellRef.current.textContent = value;\n      }\n\n      if (cellRef.current.textContent) {\n        const range = document.createRange();\n        const selection = window.getSelection();\n        range.selectNodeContents(cellRef.current);\n        range.collapse(false);\n        selection?.removeAllRanges();\n        selection?.addRange(range);\n      }\n    }\n  }, [isEditing, value]);\n\n  const displayValue = !isEditing ? (value ?? \"\") : \"\";\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      <div\n        role=\"textbox\"\n        data-slot=\"grid-cell-content\"\n        contentEditable={isEditing}\n        tabIndex={-1}\n        ref={cellRef}\n        onBlur={onBlur}\n        onInput={onInput}\n        suppressContentEditableWarning\n        className={cn(\"size-full overflow-hidden outline-none\", {\n          \"whitespace-nowrap **:inline **:whitespace-nowrap [&_br]:hidden\":\n            isEditing,\n        })}\n      >\n        {displayValue}\n      </div>\n    </DataGridCellWrapper>\n  );\n}\n\nexport function LongTextCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = cell.getValue() as string;\n  const [value, setValue] = React.useState(initialValue ?? \"\");\n  const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const pendingCharRef = React.useRef<string | null>(null);\n  const sideOffset = -(containerRef.current?.clientHeight ?? 0);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(initialValue ?? \"\");\n  }\n\n  const debouncedSave = useDebouncedCallback((newValue: string) => {\n    if (!readOnly) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValue });\n    }\n  }, 300);\n\n  const onSave = React.useCallback(() => {\n    // Immediately save any pending changes and close the popover\n    if (!readOnly && value !== initialValue) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, value, initialValue, rowIndex, columnId, readOnly]);\n\n  const onCancel = React.useCallback(() => {\n    // Restore the original value\n    setValue(initialValue ?? \"\");\n    if (!readOnly) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: initialValue });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, initialValue, rowIndex, columnId, readOnly]);\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open && !readOnly) {\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else {\n        // Immediately save any pending changes when closing\n        if (!readOnly && value !== initialValue) {\n          tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });\n        }\n        tableMeta?.onCellEditingStop?.();\n      }\n    },\n    [tableMeta, value, initialValue, rowIndex, columnId, readOnly],\n  );\n\n  const onOpenAutoFocus: NonNullable<\n    React.ComponentProps<typeof PopoverContent>[\"onOpenAutoFocus\"]\n  > = React.useCallback((event) => {\n    event.preventDefault();\n    if (textareaRef.current) {\n      textareaRef.current.focus();\n      const length = textareaRef.current.value.length;\n      textareaRef.current.setSelectionRange(length, length);\n\n      // Insert pending character using execCommand so it's part of undo history\n      // Use requestAnimationFrame to ensure focus has fully settled\n      if (pendingCharRef.current) {\n        const char = pendingCharRef.current;\n        pendingCharRef.current = null;\n        requestAnimationFrame(() => {\n          if (\n            textareaRef.current &&\n            document.activeElement === textareaRef.current\n          ) {\n            document.execCommand(\"insertText\", false, char);\n            textareaRef.current.scrollTop = textareaRef.current.scrollHeight;\n          }\n        });\n      } else {\n        textareaRef.current.scrollTop = textareaRef.current.scrollHeight;\n      }\n    }\n  }, []);\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (\n        isFocused &&\n        !isEditing &&\n        !readOnly &&\n        event.key.length === 1 &&\n        !event.ctrlKey &&\n        !event.metaKey\n      ) {\n        // Store the character to be inserted after textarea focuses\n        // This ensures it's part of the textarea's undo history\n        pendingCharRef.current = event.key;\n      }\n    },\n    [isFocused, isEditing, readOnly],\n  );\n\n  const onBlur = React.useCallback(() => {\n    // Immediately save any pending changes on blur\n    if (!readOnly && value !== initialValue) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, value, initialValue, rowIndex, columnId, readOnly]);\n\n  const onChange = React.useCallback(\n    (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n      const newValue = event.target.value;\n      setValue(newValue);\n      debouncedSave(newValue);\n    },\n    [debouncedSave],\n  );\n\n  const onKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n      if (event.key === \"Escape\") {\n        event.preventDefault();\n        onCancel();\n      } else if (event.key === \"Enter\" && (event.ctrlKey || event.metaKey)) {\n        event.preventDefault();\n        onSave();\n      } else if (event.key === \"Tab\") {\n        event.preventDefault();\n        // Save any pending changes\n        if (value !== initialValue) {\n          tableMeta?.onDataUpdate?.({ rowIndex, columnId, value });\n        }\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n        return;\n      }\n      // Stop propagation to prevent grid navigation\n      event.stopPropagation();\n    },\n    [onSave, onCancel, value, initialValue, tableMeta, rowIndex, columnId],\n  );\n\n  return (\n    <Popover open={isEditing} onOpenChange={onOpenChange}>\n      <PopoverAnchor asChild>\n        <DataGridCellWrapper<TData>\n          ref={containerRef}\n          cell={cell}\n          tableMeta={tableMeta}\n          rowIndex={rowIndex}\n          columnId={columnId}\n          rowHeight={rowHeight}\n          isEditing={isEditing}\n          isFocused={isFocused}\n          isSelected={isSelected}\n          isSearchMatch={isSearchMatch}\n          isActiveSearchMatch={isActiveSearchMatch}\n          readOnly={readOnly}\n          onKeyDown={onWrapperKeyDown}\n        >\n          <span data-slot=\"grid-cell-content\">{value}</span>\n        </DataGridCellWrapper>\n      </PopoverAnchor>\n      <PopoverContent\n        data-grid-cell-editor=\"\"\n        align=\"start\"\n        side=\"bottom\"\n        sideOffset={sideOffset}\n        className=\"w-[400px] rounded-none p-0\"\n        onOpenAutoFocus={onOpenAutoFocus}\n      >\n        <Textarea\n          placeholder=\"Enter text...\"\n          className=\"max-h-[300px] min-h-[150px] resize-none overflow-y-auto rounded-none border-0 shadow-none focus-visible:ring-1 focus-visible:ring-ring\"\n          ref={textareaRef}\n          value={value}\n          onBlur={onBlur}\n          onChange={onChange}\n          onKeyDown={onKeyDown}\n        />\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nexport function NumberCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = cell.getValue() as number;\n  const [value, setValue] = React.useState(String(initialValue ?? \"\"));\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  const cellOpts = cell.column.columnDef.meta?.cell;\n  const numberCellOpts = cellOpts?.variant === \"number\" ? cellOpts : null;\n  const min = numberCellOpts?.min;\n  const max = numberCellOpts?.max;\n  const step = numberCellOpts?.step;\n\n  const prevIsEditingRef = React.useRef(false);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(String(initialValue ?? \"\"));\n  }\n\n  const onBlur = React.useCallback(() => {\n    const numValue = value === \"\" ? null : Number(value);\n    if (!readOnly && numValue !== initialValue) {\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, rowIndex, columnId, initialValue, value, readOnly]);\n\n  const onChange = React.useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      setValue(event.target.value);\n    },\n    [],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing) {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          const numValue = value === \"\" ? null : Number(value);\n          if (numValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });\n          }\n          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });\n        } else if (event.key === \"Tab\") {\n          event.preventDefault();\n          const numValue = value === \"\" ? null : Number(value);\n          if (numValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: numValue });\n          }\n          tableMeta?.onCellEditingStop?.({\n            direction: event.shiftKey ? \"left\" : \"right\",\n          });\n        } else if (event.key === \"Escape\") {\n          event.preventDefault();\n          setValue(String(initialValue ?? \"\"));\n          inputRef.current?.blur();\n        }\n      } else if (isFocused) {\n        // Handle Backspace to start editing with empty value\n        if (event.key === \"Backspace\") {\n          setValue(\"\");\n        } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {\n          // Handle typing to pre-fill the value when editing starts\n          setValue(event.key);\n        }\n      }\n    },\n    [isEditing, isFocused, initialValue, tableMeta, rowIndex, columnId, value],\n  );\n\n  React.useEffect(() => {\n    const wasEditing = prevIsEditingRef.current;\n    prevIsEditingRef.current = isEditing;\n\n    // Only focus when we start editing (transition from false to true)\n    if (isEditing && !wasEditing && inputRef.current) {\n      inputRef.current.focus();\n    }\n  }, [isEditing]);\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      {isEditing ? (\n        <input\n          type=\"number\"\n          ref={inputRef}\n          value={value}\n          min={min}\n          max={max}\n          step={step}\n          className=\"w-full border-none bg-transparent p-0 outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n          onBlur={onBlur}\n          onChange={onChange}\n        />\n      ) : (\n        <span data-slot=\"grid-cell-content\">{value}</span>\n      )}\n    </DataGridCellWrapper>\n  );\n}\n\nexport function UrlCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isEditing,\n  isFocused,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = cell.getValue() as string;\n  const [value, setValue] = React.useState(initialValue ?? \"\");\n  const cellRef = React.useRef<HTMLDivElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const prevIsEditingRef = React.useRef(false);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(initialValue ?? \"\");\n    if (cellRef.current && !isEditing) {\n      cellRef.current.textContent = initialValue ?? \"\";\n    }\n  }\n\n  const onBlur = React.useCallback(() => {\n    const currentValue = cellRef.current?.textContent?.trim() ?? \"\";\n\n    if (!readOnly && currentValue !== initialValue) {\n      tableMeta?.onDataUpdate?.({\n        rowIndex,\n        columnId,\n        value: currentValue || null,\n      });\n    }\n    tableMeta?.onCellEditingStop?.();\n  }, [tableMeta, rowIndex, columnId, initialValue, readOnly]);\n\n  const onInput = React.useCallback(\n    (event: React.FormEvent<HTMLDivElement>) => {\n      const currentValue = event.currentTarget.textContent ?? \"\";\n      setValue(currentValue);\n    },\n    [],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing) {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          const currentValue = cellRef.current?.textContent?.trim() ?? \"\";\n          if (!readOnly && currentValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({\n              rowIndex,\n              columnId,\n              value: currentValue || null,\n            });\n          }\n          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });\n        } else if (event.key === \"Tab\") {\n          event.preventDefault();\n          const currentValue = cellRef.current?.textContent?.trim() ?? \"\";\n          if (!readOnly && currentValue !== initialValue) {\n            tableMeta?.onDataUpdate?.({\n              rowIndex,\n              columnId,\n              value: currentValue || null,\n            });\n          }\n          tableMeta?.onCellEditingStop?.({\n            direction: event.shiftKey ? \"left\" : \"right\",\n          });\n        } else if (event.key === \"Escape\") {\n          event.preventDefault();\n          setValue(initialValue ?? \"\");\n          cellRef.current?.blur();\n        }\n      } else if (\n        isFocused &&\n        !readOnly &&\n        event.key.length === 1 &&\n        !event.ctrlKey &&\n        !event.metaKey\n      ) {\n        // Handle typing to pre-fill the value when editing starts\n        setValue(event.key);\n\n        queueMicrotask(() => {\n          if (cellRef.current && cellRef.current.contentEditable === \"true\") {\n            cellRef.current.textContent = event.key;\n            const range = document.createRange();\n            const selection = window.getSelection();\n            range.selectNodeContents(cellRef.current);\n            range.collapse(false);\n            selection?.removeAllRanges();\n            selection?.addRange(range);\n          }\n        });\n      }\n    },\n    [\n      isEditing,\n      isFocused,\n      initialValue,\n      tableMeta,\n      rowIndex,\n      columnId,\n      readOnly,\n    ],\n  );\n\n  const onLinkClick = React.useCallback(\n    (event: React.MouseEvent<HTMLAnchorElement>) => {\n      if (isEditing) {\n        event.preventDefault();\n        return;\n      }\n\n      // Check if URL was rejected due to dangerous protocol\n      const href = getUrlHref(value);\n      if (!href) {\n        event.preventDefault();\n        toast.error(\"Invalid URL\", {\n          description:\n            \"URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)\",\n        });\n        return;\n      }\n\n      // Stop propagation to prevent grid from interfering with link navigation\n      event.stopPropagation();\n    },\n    [isEditing, value],\n  );\n\n  React.useEffect(() => {\n    const wasEditing = prevIsEditingRef.current;\n    prevIsEditingRef.current = isEditing;\n\n    if (isEditing && !wasEditing && cellRef.current) {\n      cellRef.current.focus();\n\n      if (!cellRef.current.textContent && value) {\n        cellRef.current.textContent = value;\n      }\n\n      if (cellRef.current.textContent) {\n        const range = document.createRange();\n        const selection = window.getSelection();\n        range.selectNodeContents(cellRef.current);\n        range.collapse(false);\n        selection?.removeAllRanges();\n        selection?.addRange(range);\n      }\n    }\n  }, [isEditing, value]);\n\n  const displayValue = !isEditing ? (value ?? \"\") : \"\";\n  const urlHref = displayValue ? getUrlHref(displayValue) : \"\";\n  const isDangerousUrl = displayValue && !urlHref;\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      {!isEditing && displayValue ? (\n        <div\n          data-slot=\"grid-cell-content\"\n          className=\"size-full overflow-hidden\"\n        >\n          <a\n            data-focused={isFocused && !isDangerousUrl ? \"\" : undefined}\n            data-invalid={isDangerousUrl ? \"\" : undefined}\n            href={urlHref}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"truncate text-primary underline decoration-primary/30 underline-offset-2 hover:decoration-primary/60 data-invalid:cursor-not-allowed data-focused:text-foreground data-invalid:text-destructive data-focused:decoration-foreground/50 data-invalid:decoration-destructive/50 data-focused:hover:decoration-foreground/70 data-invalid:hover:decoration-destructive/70\"\n            onClick={onLinkClick}\n          >\n            {displayValue}\n          </a>\n        </div>\n      ) : (\n        <div\n          role=\"textbox\"\n          data-slot=\"grid-cell-content\"\n          contentEditable={isEditing}\n          tabIndex={-1}\n          ref={cellRef}\n          onBlur={onBlur}\n          onInput={onInput}\n          suppressContentEditableWarning\n          className={cn(\"size-full overflow-hidden outline-none\", {\n            \"whitespace-nowrap **:inline **:whitespace-nowrap [&_br]:hidden\":\n              isEditing,\n          })}\n        >\n          {displayValue}\n        </div>\n      )}\n    </DataGridCellWrapper>\n  );\n}\n\nexport function CheckboxCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: Omit<DataGridCellProps<TData>, \"isEditing\">) {\n  const initialValue = cell.getValue() as boolean;\n  const [value, setValue] = React.useState(Boolean(initialValue));\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(Boolean(initialValue));\n  }\n\n  const onCheckedChange = React.useCallback(\n    (checked: boolean) => {\n      if (readOnly) return;\n      setValue(checked);\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: checked });\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (\n        isFocused &&\n        !readOnly &&\n        (event.key === \" \" || event.key === \"Enter\")\n      ) {\n        event.preventDefault();\n        event.stopPropagation();\n        onCheckedChange(!value);\n      } else if (isFocused && event.key === \"Tab\") {\n        event.preventDefault();\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n      }\n    },\n    [isFocused, value, onCheckedChange, tableMeta, readOnly],\n  );\n\n  const onWrapperClick = React.useCallback(\n    (event: React.MouseEvent) => {\n      if (isFocused && !readOnly) {\n        event.preventDefault();\n        event.stopPropagation();\n        onCheckedChange(!value);\n      }\n    },\n    [isFocused, value, onCheckedChange, readOnly],\n  );\n\n  const onCheckboxClick = React.useCallback((event: React.MouseEvent) => {\n    event.stopPropagation();\n  }, []);\n\n  const onCheckboxMouseDown = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      event.stopPropagation();\n    },\n    [],\n  );\n\n  const onCheckboxDoubleClick = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      event.stopPropagation();\n    },\n    [],\n  );\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={false}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      className=\"flex size-full justify-center\"\n      onClick={onWrapperClick}\n      onKeyDown={onWrapperKeyDown}\n    >\n      <Checkbox\n        checked={value}\n        onCheckedChange={onCheckedChange}\n        disabled={readOnly}\n        className=\"border-primary\"\n        onClick={onCheckboxClick}\n        onMouseDown={onCheckboxMouseDown}\n        onDoubleClick={onCheckboxDoubleClick}\n      />\n    </DataGridCellWrapper>\n  );\n}\n\nexport function SelectCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = (cell.getValue() ?? undefined) as string | undefined;\n\n  const [value, setValue] = React.useState(initialValue);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const cellOpts = cell.column.columnDef.meta?.cell;\n  const options = React.useMemo(\n    () => (cellOpts?.variant === \"select\" ? cellOpts.options : []),\n    [cellOpts],\n  );\n  const optionByValue = React.useMemo(\n    () => new Map(options.map((option) => [option.value, option])),\n    [options],\n  );\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(initialValue);\n  }\n\n  const onValueChange = React.useCallback(\n    (newValue: string) => {\n      if (readOnly) return;\n      setValue(newValue);\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValue });\n      tableMeta?.onCellEditingStop?.();\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open && !readOnly) {\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else {\n        tableMeta?.onCellEditingStop?.();\n      }\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing && event.key === \"Escape\") {\n        event.preventDefault();\n        setValue(initialValue);\n        tableMeta?.onCellEditingStop?.();\n      } else if (isFocused && event.key === \"Tab\") {\n        event.preventDefault();\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n      }\n    },\n    [isEditing, isFocused, initialValue, tableMeta],\n  );\n\n  const displayLabel = value\n    ? (optionByValue.get(value)?.label ?? value)\n    : null;\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      {isEditing ? (\n        <Select\n          value={value}\n          onValueChange={onValueChange}\n          open={isEditing}\n          onOpenChange={onOpenChange}\n        >\n          <SelectTrigger className=\"size-full items-start border-none p-0 shadow-none focus-visible:ring-0 dark:bg-transparent [&_svg]:hidden\">\n            {displayLabel ? (\n              <Badge\n                variant=\"secondary\"\n                className=\"whitespace-pre-wrap px-1.5 py-px\"\n              >\n                <SelectValue />\n              </Badge>\n            ) : (\n              <SelectValue />\n            )}\n          </SelectTrigger>\n          <SelectContent\n            data-grid-cell-editor=\"\"\n            // compensate for the wrapper padding\n            align=\"start\"\n            alignOffset={-8}\n            sideOffset={-8}\n            className=\"min-w-[calc(var(--radix-select-trigger-width)+16px)]\"\n          >\n            <SelectGroup>\n              {options.map((option) => (\n                <SelectItem key={option.value} value={option.value}>\n                  {option.label}\n                </SelectItem>\n              ))}\n            </SelectGroup>\n          </SelectContent>\n        </Select>\n      ) : displayLabel ? (\n        <Badge\n          data-slot=\"grid-cell-content\"\n          variant=\"secondary\"\n          className=\"whitespace-pre-wrap px-1.5 py-px\"\n        >\n          {displayLabel}\n        </Badge>\n      ) : null}\n    </DataGridCellWrapper>\n  );\n}\n\nexport function MultiSelectCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const cellValue = React.useMemo(() => {\n    const value = cell.getValue() as string[];\n    return value ?? [];\n  }, [cell]);\n\n  const cellKey = getCellKey(rowIndex, columnId);\n  const prevCellKeyRef = React.useRef(cellKey);\n\n  const [selectedValues, setSelectedValues] =\n    React.useState<string[]>(cellValue);\n  const [searchValue, setSearchValue] = React.useState(\"\");\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const cellOpts = cell.column.columnDef.meta?.cell;\n  const options = React.useMemo(\n    () => (cellOpts?.variant === \"multi-select\" ? cellOpts.options : []),\n    [cellOpts],\n  );\n  const optionByValue = React.useMemo(\n    () => new Map(options.map((option) => [option.value, option])),\n    [options],\n  );\n  const sideOffset = -(containerRef.current?.clientHeight ?? 0);\n\n  const prevCellValueRef = React.useRef(cellValue);\n  if (cellValue !== prevCellValueRef.current) {\n    prevCellValueRef.current = cellValue;\n    setSelectedValues(cellValue);\n  }\n\n  if (prevCellKeyRef.current !== cellKey) {\n    prevCellKeyRef.current = cellKey;\n    setSearchValue(\"\");\n  }\n\n  const onValueChange = React.useCallback(\n    (value: string) => {\n      if (readOnly) return;\n      let newValues: string[] = [];\n      setSelectedValues((curr) => {\n        newValues = curr.includes(value)\n          ? curr.filter((v) => v !== value)\n          : [...curr, value];\n        return newValues;\n      });\n      queueMicrotask(() => {\n        tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });\n        inputRef.current?.focus();\n      });\n      setSearchValue(\"\");\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const removeValue = React.useCallback(\n    (valueToRemove: string, event?: React.MouseEvent) => {\n      if (readOnly) return;\n      event?.stopPropagation();\n      event?.preventDefault();\n      let newValues: string[] = [];\n      setSelectedValues((curr) => {\n        newValues = curr.filter((v) => v !== valueToRemove);\n        return newValues;\n      });\n      queueMicrotask(() => {\n        tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });\n        inputRef.current?.focus();\n      });\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const clearAll = React.useCallback(() => {\n    if (readOnly) return;\n    setSelectedValues([]);\n    tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: [] });\n    queueMicrotask(() => inputRef.current?.focus());\n  }, [tableMeta, rowIndex, columnId, readOnly]);\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open && !readOnly) {\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else {\n        setSearchValue(\"\");\n        tableMeta?.onCellEditingStop?.();\n      }\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onOpenAutoFocus: NonNullable<\n    React.ComponentProps<typeof PopoverContent>[\"onOpenAutoFocus\"]\n  > = React.useCallback((event) => {\n    event.preventDefault();\n    inputRef.current?.focus();\n  }, []);\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing && event.key === \"Escape\") {\n        event.preventDefault();\n        setSelectedValues(cellValue);\n        setSearchValue(\"\");\n        tableMeta?.onCellEditingStop?.();\n      } else if (isFocused && event.key === \"Tab\") {\n        event.preventDefault();\n        setSearchValue(\"\");\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n      }\n    },\n    [isEditing, isFocused, cellValue, tableMeta],\n  );\n\n  const onInputKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLInputElement>) => {\n      if (event.key === \"Backspace\" && searchValue === \"\") {\n        event.preventDefault();\n        let newValues: string[] | null = null;\n        setSelectedValues((curr) => {\n          if (curr.length === 0) return curr;\n          newValues = curr.slice(0, -1);\n          return newValues;\n        });\n        queueMicrotask(() => {\n          if (newValues !== null) {\n            tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: newValues });\n          }\n          inputRef.current?.focus();\n        });\n      }\n      if (event.key === \"Escape\") {\n        event.stopPropagation();\n      }\n    },\n    [searchValue, tableMeta, rowIndex, columnId],\n  );\n\n  const displayLabels = selectedValues\n    .map((val) => optionByValue.get(val)?.label ?? val)\n    .filter(Boolean);\n\n  const selectedValuesSet = React.useMemo(\n    () => new Set(selectedValues),\n    [selectedValues],\n  );\n\n  const lineCount = getLineCount(rowHeight);\n\n  const { visibleItems: visibleLabels, hiddenCount: hiddenBadgeCount } =\n    useBadgeOverflow({\n      items: displayLabels,\n      getLabel: (label) => label,\n      containerRef,\n      lineCount,\n    });\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      {isEditing ? (\n        <Popover open={isEditing} onOpenChange={onOpenChange}>\n          <PopoverAnchor asChild>\n            <div className=\"absolute inset-0\" />\n          </PopoverAnchor>\n          <PopoverContent\n            data-grid-cell-editor=\"\"\n            align=\"start\"\n            sideOffset={sideOffset}\n            className=\"w-[300px] rounded-none p-0\"\n            onOpenAutoFocus={onOpenAutoFocus}\n          >\n            <Command className=\"**:data-[slot=command-input-wrapper]:h-auto **:data-[slot=command-input-wrapper]:border-none **:data-[slot=command-input-wrapper]:p-0 [&_[data-slot=command-input-wrapper]_svg]:hidden\">\n              <div className=\"flex min-h-9 flex-wrap items-center gap-1 border-b px-3 py-1.5\">\n                {selectedValues.map((value) => {\n                  const label = optionByValue.get(value)?.label ?? value;\n\n                  return (\n                    <Badge\n                      key={value}\n                      variant=\"secondary\"\n                      className=\"gap-1 px-1.5 py-px\"\n                    >\n                      {label}\n                      <button\n                        type=\"button\"\n                        onClick={(event) => removeValue(value, event)}\n                        onPointerDown={(event) => {\n                          event.preventDefault();\n                          event.stopPropagation();\n                        }}\n                      >\n                        <X className=\"size-3\" />\n                      </button>\n                    </Badge>\n                  );\n                })}\n                <CommandInput\n                  ref={inputRef}\n                  value={searchValue}\n                  onValueChange={setSearchValue}\n                  onKeyDown={onInputKeyDown}\n                  placeholder=\"Search...\"\n                  className=\"h-auto flex-1 p-0\"\n                />\n              </div>\n              <CommandList className=\"max-h-full\">\n                <CommandEmpty>No options found.</CommandEmpty>\n                <CommandGroup className=\"max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden\">\n                  {options.map((option) => {\n                    const isSelected = selectedValuesSet.has(option.value);\n\n                    return (\n                      <CommandItem\n                        key={option.value}\n                        value={option.label}\n                        onSelect={() => onValueChange(option.value)}\n                      >\n                        <div\n                          className={cn(\n                            \"flex size-4 items-center justify-center rounded-sm border border-primary\",\n                            isSelected\n                              ? \"bg-primary text-primary-foreground\"\n                              : \"opacity-50 [&_svg]:invisible\",\n                          )}\n                        >\n                          <Check className=\"size-3\" />\n                        </div>\n                        <span>{option.label}</span>\n                      </CommandItem>\n                    );\n                  })}\n                </CommandGroup>\n                {selectedValues.length > 0 && (\n                  <>\n                    <CommandSeparator />\n                    <CommandGroup>\n                      <CommandItem\n                        onSelect={clearAll}\n                        className=\"justify-center text-muted-foreground\"\n                      >\n                        Clear all\n                      </CommandItem>\n                    </CommandGroup>\n                  </>\n                )}\n              </CommandList>\n            </Command>\n          </PopoverContent>\n        </Popover>\n      ) : null}\n      {displayLabels.length > 0 ? (\n        <div className=\"flex flex-wrap items-center gap-1 overflow-hidden\">\n          {visibleLabels.map((label, index) => (\n            <Badge\n              key={selectedValues[index]}\n              variant=\"secondary\"\n              className=\"px-1.5 py-px\"\n            >\n              {label}\n            </Badge>\n          ))}\n          {hiddenBadgeCount > 0 && (\n            <Badge\n              variant=\"outline\"\n              className=\"px-1.5 py-px text-muted-foreground\"\n            >\n              +{hiddenBadgeCount}\n            </Badge>\n          )}\n        </div>\n      ) : null}\n    </DataGridCellWrapper>\n  );\n}\n\nexport function DateCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const initialValue = cell.getValue() as string;\n  const [value, setValue] = React.useState(initialValue ?? \"\");\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  const prevInitialValueRef = React.useRef(initialValue);\n  if (initialValue !== prevInitialValueRef.current) {\n    prevInitialValueRef.current = initialValue;\n    setValue(initialValue ?? \"\");\n  }\n\n  // Parse date as local time to avoid timezone shifts\n  const selectedDate = value ? (parseLocalDate(value) ?? undefined) : undefined;\n\n  const onDateSelect = React.useCallback(\n    (date: Date | undefined) => {\n      if (!date || readOnly) return;\n\n      // Format using local date components to avoid timezone issues\n      const formattedDate = formatDateToString(date);\n      setValue(formattedDate);\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: formattedDate });\n      tableMeta?.onCellEditingStop?.();\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open && !readOnly) {\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else {\n        tableMeta?.onCellEditingStop?.();\n      }\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing && event.key === \"Escape\") {\n        event.preventDefault();\n        setValue(initialValue);\n        tableMeta?.onCellEditingStop?.();\n      } else if (isFocused && event.key === \"Tab\") {\n        event.preventDefault();\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n      }\n    },\n    [isEditing, isFocused, initialValue, tableMeta],\n  );\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      onKeyDown={onWrapperKeyDown}\n    >\n      <Popover open={isEditing} onOpenChange={onOpenChange}>\n        <PopoverAnchor asChild>\n          <span data-slot=\"grid-cell-content\">\n            {formatDateForDisplay(value)}\n          </span>\n        </PopoverAnchor>\n        {isEditing && (\n          <PopoverContent\n            data-grid-cell-editor=\"\"\n            align=\"start\"\n            alignOffset={-8}\n            className=\"w-auto p-0\"\n          >\n            <Calendar\n              autoFocus\n              captionLayout=\"dropdown\"\n              mode=\"single\"\n              defaultMonth={selectedDate ?? new Date()}\n              selected={selectedDate}\n              onSelect={onDateSelect}\n            />\n          </PopoverContent>\n        )}\n      </Popover>\n    </DataGridCellWrapper>\n  );\n}\n\nexport function FileCell<TData>({\n  cell,\n  tableMeta,\n  rowIndex,\n  columnId,\n  rowHeight,\n  isFocused,\n  isEditing,\n  isSelected,\n  isSearchMatch,\n  isActiveSearchMatch,\n  readOnly,\n}: DataGridCellProps<TData>) {\n  const cellValue = React.useMemo(\n    () => (cell.getValue() as FileCellData[]) ?? [],\n    [cell],\n  );\n\n  const cellKey = getCellKey(rowIndex, columnId);\n  const prevCellKeyRef = React.useRef(cellKey);\n\n  const labelId = React.useId();\n  const descriptionId = React.useId();\n\n  const [files, setFiles] = React.useState<FileCellData[]>(cellValue);\n  const [uploadingFiles, setUploadingFiles] = React.useState<Set<string>>(\n    new Set(),\n  );\n  const [deletingFiles, setDeletingFiles] = React.useState<Set<string>>(\n    new Set(),\n  );\n  const [isDraggingOver, setIsDraggingOver] = React.useState(false);\n  const [isDragging, setIsDragging] = React.useState(false);\n  const [error, setError] = React.useState<string | null>(null);\n\n  const isUploading = uploadingFiles.size > 0;\n  const isDeleting = deletingFiles.size > 0;\n  const isPending = isUploading || isDeleting;\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const fileInputRef = React.useRef<HTMLInputElement>(null);\n  const dropzoneRef = React.useRef<HTMLDivElement>(null);\n  const cellOpts = cell.column.columnDef.meta?.cell;\n  const sideOffset = -(containerRef.current?.clientHeight ?? 0);\n\n  const fileCellOpts = cellOpts?.variant === \"file\" ? cellOpts : null;\n  const maxFileSize = fileCellOpts?.maxFileSize ?? 10 * 1024 * 1024;\n  const maxFiles = fileCellOpts?.maxFiles ?? 10;\n  const accept = fileCellOpts?.accept;\n  const multiple = fileCellOpts?.multiple ?? false;\n\n  const acceptedTypes = React.useMemo(\n    () => (accept ? accept.split(\",\").map((t) => t.trim()) : null),\n    [accept],\n  );\n\n  const prevCellValueRef = React.useRef(cellValue);\n  if (cellValue !== prevCellValueRef.current) {\n    prevCellValueRef.current = cellValue;\n    for (const file of files) {\n      if (file.url) {\n        URL.revokeObjectURL(file.url);\n      }\n    }\n    setFiles(cellValue);\n    setError(null);\n  }\n\n  if (prevCellKeyRef.current !== cellKey) {\n    prevCellKeyRef.current = cellKey;\n    setError(null);\n  }\n\n  const validateFile = React.useCallback(\n    (file: File): string | null => {\n      if (maxFileSize && file.size > maxFileSize) {\n        return `File size exceeds ${formatFileSize(maxFileSize)}`;\n      }\n      if (acceptedTypes) {\n        const fileExtension = `.${file.name.split(\".\").pop()}`;\n        const isAccepted = acceptedTypes.some((type) => {\n          if (type.endsWith(\"/*\")) {\n            const baseType = type.slice(0, -2);\n            return file.type.startsWith(`${baseType}/`);\n          }\n          if (type.startsWith(\".\")) {\n            return fileExtension.toLowerCase() === type.toLowerCase();\n          }\n          return file.type === type;\n        });\n        if (!isAccepted) {\n          return \"File type not accepted\";\n        }\n      }\n      return null;\n    },\n    [maxFileSize, acceptedTypes],\n  );\n\n  const addFiles = React.useCallback(\n    async (newFiles: File[], skipUpload = false) => {\n      if (readOnly || isPending) return;\n      setError(null);\n\n      if (maxFiles && files.length + newFiles.length > maxFiles) {\n        const errorMessage = `Maximum ${maxFiles} files allowed`;\n        setError(errorMessage);\n        toast(errorMessage);\n        setTimeout(() => {\n          setError(null);\n        }, 2000);\n        return;\n      }\n\n      const rejectedFiles: Array<{ name: string; reason: string }> = [];\n      const filesToValidate: File[] = [];\n\n      for (const file of newFiles) {\n        const validationError = validateFile(file);\n        if (validationError) {\n          rejectedFiles.push({ name: file.name, reason: validationError });\n          continue;\n        }\n        filesToValidate.push(file);\n      }\n\n      if (rejectedFiles.length > 0) {\n        const firstError = rejectedFiles[0];\n        if (firstError) {\n          setError(firstError.reason);\n\n          const truncatedName =\n            firstError.name.length > 20\n              ? `${firstError.name.slice(0, 20)}...`\n              : firstError.name;\n\n          if (rejectedFiles.length === 1) {\n            toast(firstError.reason, {\n              description: `\"${truncatedName}\" has been rejected`,\n            });\n          } else {\n            toast(firstError.reason, {\n              description: `\"${truncatedName}\" and ${rejectedFiles.length - 1} more rejected`,\n            });\n          }\n\n          setTimeout(() => {\n            setError(null);\n          }, 2000);\n        }\n      }\n\n      if (filesToValidate.length > 0) {\n        if (!skipUpload) {\n          const tempFiles = filesToValidate.map((f) => ({\n            id: crypto.randomUUID(),\n            name: f.name,\n            size: f.size,\n            type: f.type,\n            url: undefined,\n          }));\n          const filesWithTemp = [...files, ...tempFiles];\n          setFiles(filesWithTemp);\n\n          const uploadingIds = new Set(tempFiles.map((f) => f.id));\n          setUploadingFiles(uploadingIds);\n\n          let uploadedFiles: FileCellData[] = [];\n\n          if (tableMeta?.onFilesUpload) {\n            try {\n              uploadedFiles = await tableMeta.onFilesUpload({\n                files: filesToValidate,\n                rowIndex,\n                columnId,\n              });\n            } catch (error) {\n              toast.error(\n                error instanceof Error\n                  ? error.message\n                  : `Failed to upload ${filesToValidate.length} file${filesToValidate.length !== 1 ? \"s\" : \"\"}`,\n              );\n              setFiles((prev) => prev.filter((f) => !uploadingIds.has(f.id)));\n              setUploadingFiles(new Set());\n              return;\n            }\n          } else {\n            uploadedFiles = filesToValidate.map((f, i) => ({\n              id: tempFiles[i]?.id ?? crypto.randomUUID(),\n              name: f.name,\n              size: f.size,\n              type: f.type,\n              url: URL.createObjectURL(f),\n            }));\n          }\n\n          const finalFiles = filesWithTemp\n            .map((f) => {\n              if (uploadingIds.has(f.id)) {\n                return uploadedFiles.find((uf) => uf.name === f.name) ?? f;\n              }\n              return f;\n            })\n            .filter((f) => f.url !== undefined);\n\n          setFiles(finalFiles);\n          setUploadingFiles(new Set());\n          tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: finalFiles });\n        } else {\n          const newFilesData: FileCellData[] = filesToValidate.map((f) => ({\n            id: crypto.randomUUID(),\n            name: f.name,\n            size: f.size,\n            type: f.type,\n            url: URL.createObjectURL(f),\n          }));\n          const updatedFiles = [...files, ...newFilesData];\n          setFiles(updatedFiles);\n          tableMeta?.onDataUpdate?.({\n            rowIndex,\n            columnId,\n            value: updatedFiles,\n          });\n        }\n      }\n    },\n    [\n      files,\n      maxFiles,\n      validateFile,\n      tableMeta,\n      rowIndex,\n      columnId,\n      readOnly,\n      isPending,\n    ],\n  );\n\n  const removeFile = React.useCallback(\n    async (fileId: string) => {\n      if (readOnly || isPending) return;\n      setError(null);\n\n      const fileToRemove = files.find((f) => f.id === fileId);\n      if (!fileToRemove) return;\n\n      setDeletingFiles((prev) => new Set(prev).add(fileId));\n\n      if (tableMeta?.onFilesDelete) {\n        try {\n          await tableMeta.onFilesDelete({\n            fileIds: [fileId],\n            rowIndex,\n            columnId,\n          });\n        } catch (error) {\n          toast.error(\n            error instanceof Error\n              ? error.message\n              : `Failed to delete ${fileToRemove.name}`,\n          );\n          setDeletingFiles((prev) => {\n            const next = new Set(prev);\n            next.delete(fileId);\n            return next;\n          });\n          return;\n        }\n      }\n\n      if (fileToRemove.url?.startsWith(\"blob:\")) {\n        URL.revokeObjectURL(fileToRemove.url);\n      }\n\n      const updatedFiles = files.filter((f) => f.id !== fileId);\n      setFiles(updatedFiles);\n      setDeletingFiles((prev) => {\n        const next = new Set(prev);\n        next.delete(fileId);\n        return next;\n      });\n      tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: updatedFiles });\n    },\n    [files, tableMeta, rowIndex, columnId, readOnly, isPending],\n  );\n\n  const clearAll = React.useCallback(async () => {\n    if (readOnly || isPending) return;\n    setError(null);\n\n    const fileIds = files.map((f) => f.id);\n    setDeletingFiles(new Set(fileIds));\n\n    if (tableMeta?.onFilesDelete && files.length > 0) {\n      try {\n        await tableMeta.onFilesDelete({\n          fileIds,\n          rowIndex,\n          columnId,\n        });\n      } catch (error) {\n        toast.error(\n          error instanceof Error ? error.message : \"Failed to delete files\",\n        );\n        setDeletingFiles(new Set());\n        return;\n      }\n    }\n\n    for (const file of files) {\n      if (file.url?.startsWith(\"blob:\")) {\n        URL.revokeObjectURL(file.url);\n      }\n    }\n    setFiles([]);\n    setDeletingFiles(new Set());\n    tableMeta?.onDataUpdate?.({ rowIndex, columnId, value: [] });\n  }, [files, tableMeta, rowIndex, columnId, readOnly, isPending]);\n\n  const onCellDragEnter = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    if (event.dataTransfer.types.includes(\"Files\")) {\n      setIsDraggingOver(true);\n    }\n  }, []);\n\n  const onCellDragLeave = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    const rect = event.currentTarget.getBoundingClientRect();\n    const x = event.clientX;\n    const y = event.clientY;\n\n    if (\n      x <= rect.left ||\n      x >= rect.right ||\n      y <= rect.top ||\n      y >= rect.bottom\n    ) {\n      setIsDraggingOver(false);\n    }\n  }, []);\n\n  const onCellDragOver = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n  }, []);\n\n  const onCellDrop = React.useCallback(\n    (event: React.DragEvent) => {\n      event.preventDefault();\n      event.stopPropagation();\n      setIsDraggingOver(false);\n\n      const droppedFiles = Array.from(event.dataTransfer.files);\n      if (droppedFiles.length > 0) {\n        addFiles(droppedFiles, false);\n      }\n    },\n    [addFiles],\n  );\n\n  const onDropzoneDragEnter = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    setIsDragging(true);\n  }, []);\n\n  const onDropzoneDragLeave = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    const rect = event.currentTarget.getBoundingClientRect();\n    const x = event.clientX;\n    const y = event.clientY;\n\n    if (\n      x <= rect.left ||\n      x >= rect.right ||\n      y <= rect.top ||\n      y >= rect.bottom\n    ) {\n      setIsDragging(false);\n    }\n  }, []);\n\n  const onDropzoneDragOver = React.useCallback((event: React.DragEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n  }, []);\n\n  const onDropzoneDrop = React.useCallback(\n    (event: React.DragEvent) => {\n      event.preventDefault();\n      event.stopPropagation();\n      setIsDragging(false);\n\n      const droppedFiles = Array.from(event.dataTransfer.files);\n      addFiles(droppedFiles, false);\n    },\n    [addFiles],\n  );\n\n  const onDropzoneClick = React.useCallback(() => {\n    fileInputRef.current?.click();\n  }, []);\n\n  const onDropzoneKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.key === \"Enter\" || event.key === \" \") {\n        event.preventDefault();\n        onDropzoneClick();\n      }\n    },\n    [onDropzoneClick],\n  );\n\n  const onFileInputChange = React.useCallback(\n    (event: React.ChangeEvent<HTMLInputElement>) => {\n      const selectedFiles = Array.from(event.target.files ?? []);\n      addFiles(selectedFiles, false);\n      event.target.value = \"\";\n    },\n    [addFiles],\n  );\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open && !readOnly) {\n        setError(null);\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else {\n        setError(null);\n        tableMeta?.onCellEditingStop?.();\n      }\n    },\n    [tableMeta, rowIndex, columnId, readOnly],\n  );\n\n  const onEscapeKeyDown: NonNullable<\n    React.ComponentProps<typeof PopoverContent>[\"onEscapeKeyDown\"]\n  > = React.useCallback((event) => {\n    // Prevent the escape key from propagating to the data grid's keyboard handler\n    // which would call blurCell() and remove focus from the cell\n    event.stopPropagation();\n  }, []);\n\n  const onOpenAutoFocus: NonNullable<\n    React.ComponentProps<typeof PopoverContent>[\"onOpenAutoFocus\"]\n  > = React.useCallback((event) => {\n    event.preventDefault();\n    queueMicrotask(() => {\n      dropzoneRef.current?.focus();\n    });\n  }, []);\n\n  const onWrapperKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (isEditing) {\n        if (event.key === \"Escape\") {\n          event.preventDefault();\n          setFiles(cellValue);\n          setError(null);\n          tableMeta?.onCellEditingStop?.();\n        } else if (event.key === \" \") {\n          event.preventDefault();\n          onDropzoneClick();\n        } else if (event.key === \"Tab\") {\n          event.preventDefault();\n          tableMeta?.onCellEditingStop?.({\n            direction: event.shiftKey ? \"left\" : \"right\",\n          });\n        }\n      } else if (isFocused && event.key === \"Enter\") {\n        event.preventDefault();\n        tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n      } else if (isFocused && event.key === \"Tab\") {\n        event.preventDefault();\n        tableMeta?.onCellEditingStop?.({\n          direction: event.shiftKey ? \"left\" : \"right\",\n        });\n      }\n    },\n    [\n      isEditing,\n      isFocused,\n      cellValue,\n      tableMeta,\n      onDropzoneClick,\n      rowIndex,\n      columnId,\n    ],\n  );\n\n  React.useEffect(() => {\n    return () => {\n      for (const file of files) {\n        if (file.url) {\n          URL.revokeObjectURL(file.url);\n        }\n      }\n    };\n  }, [files]);\n\n  const lineCount = getLineCount(rowHeight);\n\n  const { visibleItems: visibleFiles, hiddenCount: hiddenFileCount } =\n    useBadgeOverflow({\n      items: files,\n      getLabel: (file) => file.name,\n      containerRef,\n      lineCount,\n      cacheKeyPrefix: \"file\",\n      iconSize: 12,\n      maxWidth: 100,\n    });\n\n  return (\n    <DataGridCellWrapper<TData>\n      ref={containerRef}\n      cell={cell}\n      tableMeta={tableMeta}\n      rowIndex={rowIndex}\n      columnId={columnId}\n      rowHeight={rowHeight}\n      isEditing={isEditing}\n      isFocused={isFocused}\n      isSelected={isSelected}\n      isSearchMatch={isSearchMatch}\n      isActiveSearchMatch={isActiveSearchMatch}\n      readOnly={readOnly}\n      className={cn({\n        \"ring-1 ring-primary/80 ring-inset\": isDraggingOver,\n      })}\n      onDragEnter={onCellDragEnter}\n      onDragLeave={onCellDragLeave}\n      onDragOver={onCellDragOver}\n      onDrop={onCellDrop}\n      onKeyDown={onWrapperKeyDown}\n    >\n      {isEditing ? (\n        <Popover open={isEditing} onOpenChange={onOpenChange}>\n          <PopoverAnchor asChild>\n            <div className=\"absolute inset-0\" />\n          </PopoverAnchor>\n          <PopoverContent\n            data-grid-cell-editor=\"\"\n            align=\"start\"\n            sideOffset={sideOffset}\n            className=\"w-[400px] rounded-none p-0\"\n            onEscapeKeyDown={onEscapeKeyDown}\n            onOpenAutoFocus={onOpenAutoFocus}\n          >\n            <div className=\"flex flex-col gap-2 p-3\">\n              <span id={labelId} className=\"sr-only\">\n                File upload\n              </span>\n              <div\n                role=\"region\"\n                aria-labelledby={labelId}\n                aria-describedby={descriptionId}\n                aria-invalid={!!error}\n                aria-disabled={isPending}\n                data-dragging={isDragging ? \"\" : undefined}\n                data-invalid={error ? \"\" : undefined}\n                data-disabled={isPending ? \"\" : undefined}\n                tabIndex={isDragging || isPending ? -1 : 0}\n                className=\"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed p-6 outline-none transition-colors hover:bg-accent/30 focus-visible:border-ring/50 data-disabled:pointer-events-none data-dragging:border-primary/30 data-invalid:border-destructive data-dragging:bg-accent/30 data-disabled:opacity-50 data-invalid:ring-destructive/20\"\n                ref={dropzoneRef}\n                onClick={onDropzoneClick}\n                onDragEnter={onDropzoneDragEnter}\n                onDragLeave={onDropzoneDragLeave}\n                onDragOver={onDropzoneDragOver}\n                onDrop={onDropzoneDrop}\n                onKeyDown={onDropzoneKeyDown}\n              >\n                <Upload className=\"size-8 text-muted-foreground\" />\n                <div className=\"text-center text-sm\">\n                  <p className=\"font-medium\">\n                    {isDragging ? \"Drop files here\" : \"Drag files here\"}\n                  </p>\n                  <p className=\"text-muted-foreground text-xs\">\n                    or click to browse\n                  </p>\n                </div>\n                <p id={descriptionId} className=\"text-muted-foreground text-xs\">\n                  {maxFileSize\n                    ? `Max size: ${formatFileSize(maxFileSize)}${maxFiles ? ` • Max ${maxFiles} files` : \"\"}`\n                    : maxFiles\n                      ? `Max ${maxFiles} files`\n                      : \"Select files to upload\"}\n                </p>\n              </div>\n              <input\n                type=\"file\"\n                aria-labelledby={labelId}\n                aria-describedby={descriptionId}\n                multiple={multiple}\n                accept={accept}\n                className=\"sr-only\"\n                ref={fileInputRef}\n                onChange={onFileInputChange}\n              />\n              {files.length > 0 && (\n                <div className=\"flex flex-col gap-2\">\n                  <div className=\"flex items-center justify-between\">\n                    <p className=\"font-medium text-muted-foreground text-xs\">\n                      {files.length} {files.length === 1 ? \"file\" : \"files\"}\n                    </p>\n                    <Button\n                      type=\"button\"\n                      variant=\"ghost\"\n                      className=\"h-6 text-muted-foreground text-xs\"\n                      onClick={clearAll}\n                      disabled={isPending}\n                    >\n                      Clear all\n                    </Button>\n                  </div>\n                  <div className=\"max-h-[200px] space-y-1 overflow-y-auto\">\n                    {files.map((file) => {\n                      const FileIcon = getFileIcon(file.type);\n                      const isFileUploading = uploadingFiles.has(file.id);\n                      const isFileDeleting = deletingFiles.has(file.id);\n                      const isFilePending = isFileUploading || isFileDeleting;\n\n                      return (\n                        <div\n                          key={file.id}\n                          data-pending={isFilePending ? \"\" : undefined}\n                          className=\"flex items-center gap-2 rounded-md border bg-muted/50 px-2 py-1.5 data-pending:opacity-60\"\n                        >\n                          {FileIcon && (\n                            <FileIcon className=\"size-4 shrink-0 text-muted-foreground\" />\n                          )}\n                          <div className=\"flex-1 overflow-hidden\">\n                            <p className=\"truncate text-sm\">{file.name}</p>\n                            <p className=\"text-muted-foreground text-xs\">\n                              {isFileUploading\n                                ? \"Uploading...\"\n                                : isFileDeleting\n                                  ? \"Deleting...\"\n                                  : formatFileSize(file.size)}\n                            </p>\n                          </div>\n                          <Button\n                            type=\"button\"\n                            variant=\"ghost\"\n                            size=\"icon\"\n                            className=\"size-5 rounded-sm\"\n                            onClick={() => removeFile(file.id)}\n                            disabled={isPending}\n                          >\n                            <X className=\"size-3\" />\n                          </Button>\n                        </div>\n                      );\n                    })}\n                  </div>\n                </div>\n              )}\n            </div>\n          </PopoverContent>\n        </Popover>\n      ) : null}\n      {isDraggingOver ? (\n        <div className=\"flex items-center justify-center gap-2 text-primary text-sm\">\n          <Upload className=\"size-4\" />\n          <span>Drop files here</span>\n        </div>\n      ) : files.length > 0 ? (\n        <div className=\"flex flex-wrap items-center gap-1 overflow-hidden\">\n          {visibleFiles.map((file) => {\n            const isUploading = uploadingFiles.has(file.id);\n\n            if (isUploading) {\n              return (\n                <Skeleton\n                  key={file.id}\n                  className=\"h-5 shrink-0 px-1.5\"\n                  style={{\n                    width: `${Math.min(file.name.length * 8 + 30, 100)}px`,\n                  }}\n                />\n              );\n            }\n\n            const FileIcon = getFileIcon(file.type);\n\n            return (\n              <Badge\n                key={file.id}\n                variant=\"secondary\"\n                className=\"gap-1 px-1.5 py-px\"\n              >\n                {FileIcon && <FileIcon className=\"size-3 shrink-0\" />}\n                <span className=\"max-w-[100px] truncate\">{file.name}</span>\n              </Badge>\n            );\n          })}\n          {hiddenFileCount > 0 && (\n            <Badge\n              variant=\"outline\"\n              className=\"px-1.5 py-px text-muted-foreground\"\n            >\n              +{hiddenFileCount}\n            </Badge>\n          )}\n        </div>\n      ) : null}\n    </DataGridCellWrapper>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-cell-variants.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-column-header.tsx",
      "content": "\"use client\";\n\nimport type {\n  ColumnSort,\n  Header,\n  SortDirection,\n  SortingState,\n  Table,\n} from \"@tanstack/react-table\";\nimport {\n  ChevronDownIcon,\n  ChevronUpIcon,\n  EyeOffIcon,\n  PinIcon,\n  PinOffIcon,\n  XIcon,\n} from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { getColumnVariant } from \"@/lib/data-grid\";\nimport { cn } from \"@/lib/utils\";\n\ninterface DataGridColumnHeaderProps<TData, TValue>\n  extends React.ComponentProps<typeof DropdownMenuTrigger> {\n  header: Header<TData, TValue>;\n  table: Table<TData>;\n}\n\nexport function DataGridColumnHeader<TData, TValue>({\n  header,\n  table,\n  className,\n  onPointerDown,\n  ...props\n}: DataGridColumnHeaderProps<TData, TValue>) {\n  const column = header.column;\n  const label = column.columnDef.meta?.label\n    ? column.columnDef.meta.label\n    : typeof column.columnDef.header === \"string\"\n      ? column.columnDef.header\n      : column.id;\n\n  const isAnyColumnResizing =\n    table.getState().columnSizingInfo.isResizingColumn;\n\n  const cellVariant = column.columnDef.meta?.cell;\n  const columnVariant = getColumnVariant(cellVariant?.variant);\n\n  const pinnedPosition = column.getIsPinned();\n  const isPinnedLeft = pinnedPosition === \"left\";\n  const isPinnedRight = pinnedPosition === \"right\";\n\n  const onSortingChange = React.useCallback(\n    (direction: SortDirection) => {\n      table.setSorting((prev: SortingState) => {\n        const existingSortIndex = prev.findIndex(\n          (sort) => sort.id === column.id,\n        );\n        const newSort: ColumnSort = {\n          id: column.id,\n          desc: direction === \"desc\",\n        };\n\n        if (existingSortIndex >= 0) {\n          const updated = [...prev];\n          updated[existingSortIndex] = newSort;\n          return updated;\n        } else {\n          return [...prev, newSort];\n        }\n      });\n    },\n    [column.id, table],\n  );\n\n  const onSortRemove = React.useCallback(() => {\n    table.setSorting((prev: SortingState) =>\n      prev.filter((sort) => sort.id !== column.id),\n    );\n  }, [column.id, table]);\n\n  const onLeftPin = React.useCallback(() => {\n    column.pin(\"left\");\n  }, [column]);\n\n  const onRightPin = React.useCallback(() => {\n    column.pin(\"right\");\n  }, [column]);\n\n  const onUnpin = React.useCallback(() => {\n    column.pin(false);\n  }, [column]);\n\n  const onTriggerPointerDown = React.useCallback(\n    (event: React.PointerEvent<HTMLButtonElement>) => {\n      onPointerDown?.(event);\n      if (event.defaultPrevented) return;\n\n      if (event.button !== 0) {\n        return;\n      }\n      table.options.meta?.onColumnClick?.(column.id);\n    },\n    [table.options.meta, column.id, onPointerDown],\n  );\n\n  return (\n    <>\n      <DropdownMenu modal={false}>\n        <DropdownMenuTrigger\n          className={cn(\n            \"flex size-full items-center justify-between gap-2 p-2 text-sm hover:bg-accent/40 data-[state=open]:bg-accent/40 [&_svg]:size-4\",\n            isAnyColumnResizing && \"pointer-events-none\",\n            className,\n          )}\n          onPointerDown={onTriggerPointerDown}\n          {...props}\n        >\n          <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n            {columnVariant && (\n              <Tooltip delayDuration={100}>\n                <TooltipTrigger asChild>\n                  <columnVariant.icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n                </TooltipTrigger>\n                <TooltipContent side=\"top\">\n                  <p>{columnVariant.label}</p>\n                </TooltipContent>\n              </Tooltip>\n            )}\n            <span className=\"truncate\">{label}</span>\n          </div>\n          <ChevronDownIcon className=\"shrink-0 text-muted-foreground\" />\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"start\" sideOffset={0} className=\"w-60\">\n          {column.getCanSort() && (\n            <>\n              <DropdownMenuCheckboxItem\n                className=\"relative ltr:pr-8 ltr:pl-2 rtl:pr-2 rtl:pl-8 [&>span:first-child]:ltr:right-2 [&>span:first-child]:ltr:left-auto [&>span:first-child]:rtl:right-auto [&>span:first-child]:rtl:left-2 [&_svg]:text-muted-foreground\"\n                checked={column.getIsSorted() === \"asc\"}\n                onSelect={() => onSortingChange(\"asc\")}\n              >\n                <ChevronUpIcon />\n                Sort asc\n              </DropdownMenuCheckboxItem>\n              <DropdownMenuCheckboxItem\n                className=\"relative ltr:pr-8 ltr:pl-2 rtl:pr-2 rtl:pl-8 [&>span:first-child]:ltr:right-2 [&>span:first-child]:ltr:left-auto [&>span:first-child]:rtl:right-auto [&>span:first-child]:rtl:left-2 [&_svg]:text-muted-foreground\"\n                checked={column.getIsSorted() === \"desc\"}\n                onSelect={() => onSortingChange(\"desc\")}\n              >\n                <ChevronDownIcon />\n                Sort desc\n              </DropdownMenuCheckboxItem>\n              {column.getIsSorted() && (\n                <DropdownMenuItem onSelect={onSortRemove}>\n                  <XIcon />\n                  Remove sort\n                </DropdownMenuItem>\n              )}\n            </>\n          )}\n          {column.getCanPin() && (\n            <>\n              {column.getCanSort() && <DropdownMenuSeparator />}\n\n              {isPinnedLeft ? (\n                <DropdownMenuItem\n                  className=\"[&_svg]:text-muted-foreground\"\n                  onSelect={onUnpin}\n                >\n                  <PinOffIcon />\n                  Unpin from left\n                </DropdownMenuItem>\n              ) : (\n                <DropdownMenuItem\n                  className=\"[&_svg]:text-muted-foreground\"\n                  onSelect={onLeftPin}\n                >\n                  <PinIcon />\n                  Pin to left\n                </DropdownMenuItem>\n              )}\n              {isPinnedRight ? (\n                <DropdownMenuItem\n                  className=\"[&_svg]:text-muted-foreground\"\n                  onSelect={onUnpin}\n                >\n                  <PinOffIcon />\n                  Unpin from right\n                </DropdownMenuItem>\n              ) : (\n                <DropdownMenuItem\n                  className=\"[&_svg]:text-muted-foreground\"\n                  onSelect={onRightPin}\n                >\n                  <PinIcon />\n                  Pin to right\n                </DropdownMenuItem>\n              )}\n            </>\n          )}\n          {column.getCanHide() && (\n            <>\n              <DropdownMenuSeparator />\n              <DropdownMenuItem\n                className=\"[&_svg]:text-muted-foreground\"\n                onSelect={() => column.toggleVisibility(false)}\n              >\n                <EyeOffIcon />\n                Hide column\n              </DropdownMenuItem>\n            </>\n          )}\n        </DropdownMenuContent>\n      </DropdownMenu>\n      {header.column.getCanResize() && (\n        <DataGridColumnResizer header={header} table={table} label={label} />\n      )}\n    </>\n  );\n}\n\nconst DataGridColumnResizer = React.memo(\n  DataGridColumnResizerImpl,\n  (prev, next) => {\n    const prevColumn = prev.header.column;\n    const nextColumn = next.header.column;\n\n    if (\n      prevColumn.getIsResizing() !== nextColumn.getIsResizing() ||\n      prevColumn.getSize() !== nextColumn.getSize()\n    ) {\n      return false;\n    }\n\n    if (prev.label !== next.label) return false;\n\n    return true;\n  },\n) as typeof DataGridColumnResizerImpl;\n\ninterface DataGridColumnResizerProps<TData, TValue>\n  extends DataGridColumnHeaderProps<TData, TValue> {\n  label: string;\n}\n\nfunction DataGridColumnResizerImpl<TData, TValue>({\n  header,\n  table,\n  label,\n}: DataGridColumnResizerProps<TData, TValue>) {\n  const defaultColumnDef = table._getDefaultColumnDef();\n\n  const onDoubleClick = React.useCallback(() => {\n    header.column.resetSize();\n  }, [header.column]);\n\n  return (\n    <div\n      role=\"separator\"\n      aria-orientation=\"vertical\"\n      aria-label={`Resize ${label} column`}\n      aria-valuenow={header.column.getSize()}\n      aria-valuemin={defaultColumnDef.minSize}\n      aria-valuemax={defaultColumnDef.maxSize}\n      tabIndex={0}\n      className={cn(\n        \"absolute -end-px top-0 z-50 h-full w-0.5 cursor-ew-resize touch-none select-none bg-border transition-opacity after:absolute after:inset-y-0 after:start-1/2 after:h-full after:w-[18px] after:-translate-x-1/2 after:content-[''] hover:bg-primary focus:bg-primary focus:outline-none\",\n        header.column.getIsResizing()\n          ? \"bg-primary\"\n          : \"opacity-0 hover:opacity-100\",\n      )}\n      onDoubleClick={onDoubleClick}\n      onMouseDown={header.getResizeHandler()}\n      onTouchStart={header.getResizeHandler()}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-column-header.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-context-menu.tsx",
      "content": "\"use client\";\n\nimport type { ColumnDef, TableMeta } from \"@tanstack/react-table\";\nimport { CopyIcon, EraserIcon, ScissorsIcon, Trash2Icon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { toast } from \"sonner\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { useAsRef } from \"@/hooks/use-as-ref\";\nimport { getEmptyCellValue, parseCellKey } from \"@/lib/data-grid\";\nimport type { CellUpdate, ContextMenuState } from \"@/types/data-grid\";\n\ninterface DataGridContextMenuProps<TData> {\n  tableMeta: TableMeta<TData>;\n  columns: Array<ColumnDef<TData>>;\n  contextMenu: ContextMenuState;\n}\n\nexport function DataGridContextMenu<TData>({\n  tableMeta,\n  columns,\n  contextMenu,\n}: DataGridContextMenuProps<TData>) {\n  const onContextMenuOpenChange = tableMeta?.onContextMenuOpenChange;\n  const selectionState = tableMeta?.selectionState;\n  const dataGridRef = tableMeta?.dataGridRef;\n  const onDataUpdate = tableMeta?.onDataUpdate;\n  const onRowsDelete = tableMeta?.onRowsDelete;\n  const onCellsCopy = tableMeta?.onCellsCopy;\n  const onCellsCut = tableMeta?.onCellsCut;\n\n  if (!contextMenu.open) return null;\n\n  return (\n    <ContextMenu\n      tableMeta={tableMeta}\n      columns={columns}\n      dataGridRef={dataGridRef}\n      contextMenu={contextMenu}\n      onContextMenuOpenChange={onContextMenuOpenChange}\n      selectionState={selectionState}\n      onDataUpdate={onDataUpdate}\n      onRowsDelete={onRowsDelete}\n      onCellsCopy={onCellsCopy}\n      onCellsCut={onCellsCut}\n    />\n  );\n}\n\ninterface ContextMenuProps<TData>\n  extends Pick<\n      TableMeta<TData>,\n      | \"dataGridRef\"\n      | \"onContextMenuOpenChange\"\n      | \"selectionState\"\n      | \"onDataUpdate\"\n      | \"onRowsDelete\"\n      | \"onCellsCopy\"\n      | \"onCellsCut\"\n      | \"readOnly\"\n    >,\n    Required<Pick<TableMeta<TData>, \"contextMenu\">> {\n  tableMeta: TableMeta<TData>;\n  columns: Array<ColumnDef<TData>>;\n}\n\nconst ContextMenu = React.memo(ContextMenuImpl, (prev, next) => {\n  if (prev.contextMenu.open !== next.contextMenu.open) return false;\n  if (!next.contextMenu.open) return true;\n  if (prev.contextMenu.x !== next.contextMenu.x) return false;\n  if (prev.contextMenu.y !== next.contextMenu.y) return false;\n\n  const prevSize = prev.selectionState?.selectedCells?.size ?? 0;\n  const nextSize = next.selectionState?.selectedCells?.size ?? 0;\n  if (prevSize !== nextSize) return false;\n\n  return true;\n}) as typeof ContextMenuImpl;\n\nfunction ContextMenuImpl<TData>({\n  tableMeta,\n  columns,\n  dataGridRef,\n  contextMenu,\n  onContextMenuOpenChange,\n  selectionState,\n  onDataUpdate,\n  onRowsDelete,\n  onCellsCopy,\n  onCellsCut,\n}: ContextMenuProps<TData>) {\n  const propsRef = useAsRef({\n    dataGridRef,\n    selectionState,\n    onDataUpdate,\n    onRowsDelete,\n    onCellsCopy,\n    onCellsCut,\n    columns,\n  });\n\n  const triggerStyle = React.useMemo<React.CSSProperties>(\n    () => ({\n      position: \"fixed\",\n      left: `${contextMenu.x}px`,\n      top: `${contextMenu.y}px`,\n      width: \"1px\",\n      height: \"1px\",\n      padding: 0,\n      margin: 0,\n      border: \"none\",\n      background: \"transparent\",\n      pointerEvents: \"none\",\n      opacity: 0,\n    }),\n    [contextMenu.x, contextMenu.y],\n  );\n\n  const onCloseAutoFocus: NonNullable<\n    React.ComponentProps<typeof DropdownMenuContent>[\"onCloseAutoFocus\"]\n  > = React.useCallback(\n    (event) => {\n      event.preventDefault();\n      propsRef.current.dataGridRef?.current?.focus();\n    },\n    [propsRef],\n  );\n\n  const onCopy = React.useCallback(() => {\n    propsRef.current.onCellsCopy?.();\n  }, [propsRef]);\n\n  const onCut = React.useCallback(() => {\n    propsRef.current.onCellsCut?.();\n  }, [propsRef]);\n\n  const onClear = React.useCallback(() => {\n    const { selectionState, columns, onDataUpdate } = propsRef.current;\n\n    if (\n      !selectionState?.selectedCells ||\n      selectionState.selectedCells.size === 0\n    )\n      return;\n\n    const updates: Array<CellUpdate> = [];\n\n    for (const cellKey of selectionState.selectedCells) {\n      const { rowIndex, columnId } = parseCellKey(cellKey);\n\n      // Get column from columns array\n      const column = columns.find((col) => {\n        if (col.id) return col.id === columnId;\n        if (\"accessorKey\" in col) return col.accessorKey === columnId;\n        return false;\n      });\n      const cellVariant = column?.meta?.cell?.variant;\n\n      const emptyValue = getEmptyCellValue(cellVariant);\n\n      updates.push({ rowIndex, columnId, value: emptyValue });\n    }\n\n    onDataUpdate?.(updates);\n\n    toast.success(\n      `${updates.length} cell${updates.length !== 1 ? \"s\" : \"\"} cleared`,\n    );\n  }, [propsRef]);\n\n  const onDelete = React.useCallback(async () => {\n    const { selectionState, onRowsDelete } = propsRef.current;\n\n    if (\n      !selectionState?.selectedCells ||\n      selectionState.selectedCells.size === 0\n    )\n      return;\n\n    const rowIndices = new Set<number>();\n    for (const cellKey of selectionState.selectedCells) {\n      const { rowIndex } = parseCellKey(cellKey);\n      rowIndices.add(rowIndex);\n    }\n\n    const rowIndicesArray = Array.from(rowIndices).sort((a, b) => a - b);\n    const rowCount = rowIndicesArray.length;\n\n    await onRowsDelete?.(rowIndicesArray);\n\n    toast.success(`${rowCount} row${rowCount !== 1 ? \"s\" : \"\"} deleted`);\n  }, [propsRef]);\n\n  return (\n    <DropdownMenu\n      open={contextMenu.open}\n      onOpenChange={onContextMenuOpenChange}\n    >\n      <DropdownMenuTrigger style={triggerStyle} />\n      <DropdownMenuContent\n        data-grid-popover=\"\"\n        align=\"start\"\n        className=\"w-48\"\n        onCloseAutoFocus={onCloseAutoFocus}\n      >\n        <DropdownMenuItem onSelect={onCopy}>\n          <CopyIcon />\n          Copy\n        </DropdownMenuItem>\n        <DropdownMenuItem onSelect={onCut} disabled={tableMeta?.readOnly}>\n          <ScissorsIcon />\n          Cut\n        </DropdownMenuItem>\n        <DropdownMenuItem onSelect={onClear} disabled={tableMeta?.readOnly}>\n          <EraserIcon />\n          Clear\n        </DropdownMenuItem>\n        {onRowsDelete && (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem variant=\"destructive\" onSelect={onDelete}>\n              <Trash2Icon />\n              Delete rows\n            </DropdownMenuItem>\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-context-menu.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-paste-dialog.tsx",
      "content": "\"use client\";\n\nimport type { TableMeta } from \"@tanstack/react-table\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { useAsRef } from \"@/hooks/use-as-ref\";\nimport { cn } from \"@/lib/utils\";\nimport type { PasteDialogState } from \"@/types/data-grid\";\n\ninterface DataGridPasteDialogProps<TData> {\n  tableMeta: TableMeta<TData>;\n  pasteDialog: PasteDialogState;\n}\n\nexport function DataGridPasteDialog<TData>({\n  tableMeta,\n  pasteDialog,\n}: DataGridPasteDialogProps<TData>) {\n  const onPasteDialogOpenChange = tableMeta?.onPasteDialogOpenChange;\n  const onCellsPaste = tableMeta?.onCellsPaste;\n\n  if (!pasteDialog.open) return null;\n\n  return (\n    <PasteDialog\n      pasteDialog={pasteDialog}\n      onPasteDialogOpenChange={onPasteDialogOpenChange}\n      onCellsPaste={onCellsPaste}\n    />\n  );\n}\n\ninterface PasteDialogProps\n  extends Pick<TableMeta<unknown>, \"onPasteDialogOpenChange\" | \"onCellsPaste\">,\n    Required<Pick<TableMeta<unknown>, \"pasteDialog\">> {}\n\nconst PasteDialog = React.memo(PasteDialogImpl, (prev, next) => {\n  if (prev.pasteDialog.open !== next.pasteDialog.open) return false;\n  if (!next.pasteDialog.open) return true;\n  if (prev.pasteDialog.rowsNeeded !== next.pasteDialog.rowsNeeded) return false;\n\n  return true;\n});\n\nfunction PasteDialogImpl({\n  pasteDialog,\n  onPasteDialogOpenChange,\n  onCellsPaste,\n}: PasteDialogProps) {\n  const propsRef = useAsRef({\n    onPasteDialogOpenChange,\n    onCellsPaste,\n  });\n\n  const expandRadioRef = React.useRef<HTMLInputElement | null>(null);\n\n  const onOpenChange = React.useCallback(\n    (open: boolean) => {\n      propsRef.current.onPasteDialogOpenChange?.(open);\n    },\n    [propsRef],\n  );\n\n  const onCancel = React.useCallback(() => {\n    propsRef.current.onPasteDialogOpenChange?.(false);\n  }, [propsRef]);\n\n  const onContinue = React.useCallback(() => {\n    propsRef.current.onCellsPaste?.(expandRadioRef.current?.checked ?? false);\n  }, [propsRef]);\n\n  return (\n    <Dialog open={pasteDialog.open} onOpenChange={onOpenChange}>\n      <DialogContent data-grid-popover=\"\">\n        <DialogHeader>\n          <DialogTitle>Do you want to add more rows?</DialogTitle>\n          <DialogDescription>\n            We need <strong>{pasteDialog.rowsNeeded}</strong> additional row\n            {pasteDialog.rowsNeeded !== 1 ? \"s\" : \"\"} to paste everything from\n            your clipboard.\n          </DialogDescription>\n        </DialogHeader>\n        <div className=\"flex flex-col gap-3 py-1\">\n          <label className=\"flex cursor-pointer items-start gap-3\">\n            <RadioItem\n              ref={expandRadioRef}\n              name=\"expand-option\"\n              value=\"expand\"\n              defaultChecked\n            />\n            <div className=\"flex flex-col gap-1\">\n              <span className=\"font-medium text-sm leading-none\">\n                Create new rows\n              </span>\n              <span className=\"text-muted-foreground text-sm\">\n                Add {pasteDialog.rowsNeeded} new row\n                {pasteDialog.rowsNeeded !== 1 ? \"s\" : \"\"} to the table and paste\n                all data\n              </span>\n            </div>\n          </label>\n          <label className=\"flex cursor-pointer items-start gap-3\">\n            <RadioItem name=\"expand-option\" value=\"no-expand\" />\n            <div className=\"flex flex-col gap-1\">\n              <span className=\"font-medium text-sm leading-none\">\n                Keep current rows\n              </span>\n              <span className=\"text-muted-foreground text-sm\">\n                Paste only what fits in the existing rows\n              </span>\n            </div>\n          </label>\n        </div>\n        <DialogFooter>\n          <Button variant=\"outline\" onClick={onCancel}>\n            Cancel\n          </Button>\n          <Button onClick={onContinue}>Continue</Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction RadioItem({ className, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type=\"radio\"\n      className={cn(\n        \"relative size-4 shrink-0 appearance-none rounded-full border border-input bg-background shadow-xs outline-none transition-[color,box-shadow]\",\n        \"text-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        \"disabled:cursor-not-allowed disabled:opacity-50\",\n        \"checked:before:absolute checked:before:start-1/2 checked:before:top-1/2 checked:before:size-2 checked:before:-translate-x-1/2 checked:before:-translate-y-1/2 checked:before:rounded-full checked:before:bg-primary checked:before:content-['']\",\n        \"dark:bg-input/30\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-paste-dialog.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-row.tsx",
      "content": "\"use client\";\n\nimport type {\n  ColumnPinningState,\n  Row,\n  TableMeta,\n  VisibilityState,\n} from \"@tanstack/react-table\";\nimport type { VirtualItem } from \"@tanstack/react-virtual\";\nimport * as React from \"react\";\nimport { DataGridCell } from \"@/components/data-grid/data-grid-cell\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport {\n  flexRender,\n  getCellKey,\n  getColumnBorderVisibility,\n  getColumnPinningStyle,\n  getRowHeightValue,\n} from \"@/lib/data-grid\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n  CellPosition,\n  Direction,\n  RowHeightValue,\n} from \"@/types/data-grid\";\n\ninterface DataGridRowProps<TData> extends React.ComponentProps<\"div\"> {\n  row: Row<TData>;\n  tableMeta: TableMeta<TData>;\n  virtualItem: VirtualItem;\n  measureElement: (node: Element | null) => void;\n  rowMapRef: React.RefObject<Map<number, HTMLDivElement>>;\n  rowHeight: RowHeightValue;\n  columnVisibility: VisibilityState;\n  columnPinning: ColumnPinningState;\n  focusedCell: CellPosition | null;\n  editingCell: CellPosition | null;\n  cellSelectionKeys: Set<string>;\n  searchMatchColumns: Set<string> | null;\n  activeSearchMatch: CellPosition | null;\n  dir: Direction;\n  readOnly: boolean;\n  stretchColumns: boolean;\n  adjustLayout: boolean;\n}\n\nexport const DataGridRow = React.memo(DataGridRowImpl, (prev, next) => {\n  const prevRowIndex = prev.virtualItem.index;\n  const nextRowIndex = next.virtualItem.index;\n\n  // Re-render if row identity changed\n  if (prev.row.id !== next.row.id) {\n    return false;\n  }\n\n  // Re-render if row data (original) reference changed\n  if (prev.row.original !== next.row.original) {\n    return false;\n  }\n\n  // Re-render if virtual position changed (handles transform updates)\n  if (prev.virtualItem.start !== next.virtualItem.start) {\n    return false;\n  }\n\n  // Re-render if focus state changed for this row\n  const prevHasFocus = prev.focusedCell?.rowIndex === prevRowIndex;\n  const nextHasFocus = next.focusedCell?.rowIndex === nextRowIndex;\n\n  if (prevHasFocus !== nextHasFocus) {\n    return false;\n  }\n\n  // Re-render if focused column changed within this row\n  if (nextHasFocus && prevHasFocus) {\n    if (prev.focusedCell?.columnId !== next.focusedCell?.columnId) {\n      return false;\n    }\n  }\n\n  // Re-render if editing state changed for this row\n  const prevHasEditing = prev.editingCell?.rowIndex === prevRowIndex;\n  const nextHasEditing = next.editingCell?.rowIndex === nextRowIndex;\n\n  if (prevHasEditing !== nextHasEditing) {\n    return false;\n  }\n\n  // Re-render if editing column changed within this row\n  if (nextHasEditing && prevHasEditing) {\n    if (prev.editingCell?.columnId !== next.editingCell?.columnId) {\n      return false;\n    }\n  }\n\n  // Re-render if this row's selected cells changed\n  // Using stable Set reference that only includes this row's cells\n  if (prev.cellSelectionKeys !== next.cellSelectionKeys) {\n    return false;\n  }\n\n  // Re-render if column visibility changed\n  if (prev.columnVisibility !== next.columnVisibility) {\n    return false;\n  }\n\n  // Re-render if row height changed\n  if (prev.rowHeight !== next.rowHeight) {\n    return false;\n  }\n\n  // Re-render if column pinning state changed\n  if (prev.columnPinning !== next.columnPinning) {\n    return false;\n  }\n\n  // Re-render if readOnly changed\n  if (prev.readOnly !== next.readOnly) {\n    return false;\n  }\n\n  // Re-render if search match columns changed for this row\n  if (prev.searchMatchColumns !== next.searchMatchColumns) {\n    return false;\n  }\n\n  // Re-render if active search match changed for this row\n  if (prev.activeSearchMatch?.columnId !== next.activeSearchMatch?.columnId) {\n    return false;\n  }\n\n  // Re-render if direction changed\n  if (prev.dir !== next.dir) {\n    return false;\n  }\n\n  // Re-render if adjustLayout state changed\n  if (prev.adjustLayout !== next.adjustLayout) {\n    return false;\n  }\n\n  // Re-render if stretchColumns changed\n  if (prev.stretchColumns !== next.stretchColumns) {\n    return false;\n  }\n\n  // Skip re-render - props are equal\n  return true;\n}) as typeof DataGridRowImpl;\n\nfunction DataGridRowImpl<TData>({\n  row,\n  tableMeta,\n  virtualItem,\n  measureElement,\n  rowMapRef,\n  rowHeight,\n  columnVisibility,\n  columnPinning,\n  focusedCell,\n  editingCell,\n  cellSelectionKeys,\n  searchMatchColumns,\n  activeSearchMatch,\n  dir,\n  readOnly,\n  stretchColumns,\n  adjustLayout,\n  className,\n  style,\n  ref,\n  ...props\n}: DataGridRowProps<TData>) {\n  const virtualRowIndex = virtualItem.index;\n\n  const onRowChange = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      if (typeof virtualRowIndex === \"undefined\") return;\n\n      if (node) {\n        measureElement(node);\n        rowMapRef.current?.set(virtualRowIndex, node);\n      } else {\n        rowMapRef.current?.delete(virtualRowIndex);\n      }\n    },\n    [virtualRowIndex, measureElement, rowMapRef],\n  );\n\n  const rowRef = useComposedRefs(ref, onRowChange);\n\n  const isRowSelected = row.getIsSelected();\n\n  // Memoize visible cells to avoid recreating cell array on every render\n  // Though TanStack returns new Cell wrappers, memoizing the array helps React's reconciliation\n  // biome-ignore lint/correctness/useExhaustiveDependencies: columnVisibility and columnPinning are used for calculating the visible cells\n  const visibleCells = React.useMemo(\n    () => row.getVisibleCells(),\n    [row, columnVisibility, columnPinning],\n  );\n\n  return (\n    <div\n      key={row.id}\n      role=\"row\"\n      aria-rowindex={virtualRowIndex + 2}\n      aria-selected={isRowSelected}\n      data-index={virtualRowIndex}\n      data-slot=\"grid-row\"\n      tabIndex={-1}\n      {...props}\n      ref={rowRef}\n      className={cn(\n        \"absolute flex w-full border-b [content-visibility:auto]\",\n        !adjustLayout && \"will-change-transform\",\n        className,\n      )}\n      style={{\n        height: `${getRowHeightValue(rowHeight)}px`,\n        ...(adjustLayout\n          ? { top: `${virtualItem.start}px` }\n          : { transform: `translateY(${virtualItem.start}px)` }),\n        ...style,\n      }}\n    >\n      {visibleCells.map((cell, colIndex) => {\n        const columnId = cell.column.id;\n\n        const isCellFocused =\n          focusedCell?.rowIndex === virtualRowIndex &&\n          focusedCell?.columnId === columnId;\n        const isCellEditing =\n          editingCell?.rowIndex === virtualRowIndex &&\n          editingCell?.columnId === columnId;\n        const isCellSelected =\n          cellSelectionKeys?.has(getCellKey(virtualRowIndex, columnId)) ??\n          false;\n\n        const isSearchMatch = searchMatchColumns?.has(columnId) ?? false;\n        const isActiveSearchMatch = activeSearchMatch?.columnId === columnId;\n\n        const nextCell = visibleCells[colIndex + 1];\n        const isLastColumn = colIndex === visibleCells.length - 1;\n        const { showEndBorder, showStartBorder } = getColumnBorderVisibility({\n          column: cell.column,\n          nextColumn: nextCell?.column,\n          isLastColumn,\n        });\n\n        return (\n          <div\n            key={cell.id}\n            role=\"gridcell\"\n            aria-colindex={colIndex + 1}\n            data-highlighted={isCellFocused ? \"\" : undefined}\n            data-slot=\"grid-cell\"\n            tabIndex={-1}\n            className={cn({\n              grow: stretchColumns && columnId !== \"select\",\n              \"border-e\": showEndBorder && columnId !== \"select\",\n              \"border-s\": showStartBorder && columnId !== \"select\",\n            })}\n            style={{\n              ...getColumnPinningStyle({ column: cell.column, dir }),\n              width: `calc(var(--col-${columnId}-size) * 1px)`,\n            }}\n          >\n            {typeof cell.column.columnDef.header === \"function\" ? (\n              <div\n                className={cn(\"size-full px-3 py-1.5\", {\n                  \"bg-primary/10\": isRowSelected,\n                })}\n              >\n                {flexRender(cell.column.columnDef.cell, cell.getContext())}\n              </div>\n            ) : (\n              <DataGridCell\n                cell={cell}\n                tableMeta={tableMeta}\n                rowIndex={virtualRowIndex}\n                columnId={columnId}\n                rowHeight={rowHeight}\n                isFocused={isCellFocused}\n                isEditing={isCellEditing}\n                isSelected={isCellSelected}\n                isSearchMatch={isSearchMatch}\n                isActiveSearchMatch={isActiveSearchMatch}\n                readOnly={readOnly}\n              />\n            )}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-row.tsx"
    },
    {
      "path": "src/components/data-grid/data-grid-search.tsx",
      "content": "\"use client\";\n\nimport { ChevronDown, ChevronUp, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { useAsRef } from \"@/hooks/use-as-ref\";\nimport { useDebouncedCallback } from \"@/hooks/use-debounced-callback\";\nimport type { SearchState } from \"@/types/data-grid\";\n\nfunction onTriggerPointerDown(event: React.PointerEvent<HTMLButtonElement>) {\n  const target = event.target;\n  if (!(target instanceof HTMLElement)) return;\n  if (target.hasPointerCapture(event.pointerId)) {\n    target.releasePointerCapture(event.pointerId);\n  }\n\n  // Prevent the trigger from stealing focus away from the input\n  if (\n    event.button === 0 &&\n    event.ctrlKey === false &&\n    event.pointerType === \"mouse\" &&\n    !(event.target instanceof HTMLInputElement)\n  ) {\n    event.preventDefault();\n  }\n}\n\ninterface DataGridSearchProps extends SearchState {}\n\nexport const DataGridSearch = React.memo(DataGridSearchImpl, (prev, next) => {\n  if (prev.searchOpen !== next.searchOpen) return false;\n\n  if (!next.searchOpen) return true;\n\n  // Exclude searchQuery because the input is uncontrolled, and hasQuery state handles the status text\n  if (prev.matchIndex !== next.matchIndex) return false;\n\n  if (prev.searchMatches.length !== next.searchMatches.length) return false;\n\n  for (let i = 0; i < prev.searchMatches.length; i++) {\n    const prevMatch = prev.searchMatches[i];\n    const nextMatch = next.searchMatches[i];\n\n    if (!prevMatch || !nextMatch) return false;\n\n    if (\n      prevMatch.rowIndex !== nextMatch.rowIndex ||\n      prevMatch.columnId !== nextMatch.columnId\n    ) {\n      return false;\n    }\n  }\n\n  return true;\n});\n\nfunction DataGridSearchImpl({\n  searchMatches,\n  matchIndex,\n  searchOpen,\n  onSearchOpenChange,\n  searchQuery,\n  onSearchQueryChange,\n  onSearch,\n  onNavigateToNextMatch,\n  onNavigateToPrevMatch,\n}: DataGridSearchProps) {\n  const propsRef = useAsRef({\n    onSearchOpenChange,\n    onSearchQueryChange,\n    onSearch,\n    onNavigateToNextMatch,\n    onNavigateToPrevMatch,\n  });\n\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const isComposingRef = React.useRef(false);\n  const [hasQuery, setHasQuery] = React.useState(searchQuery.length > 0);\n\n  React.useEffect(() => {\n    if (searchOpen) {\n      requestAnimationFrame(() => {\n        inputRef.current?.focus();\n      });\n      return;\n    }\n\n    isComposingRef.current = false;\n    setHasQuery(false);\n  }, [searchOpen]);\n\n  React.useEffect(() => {\n    if (!searchOpen) return;\n\n    function onEscape(event: KeyboardEvent) {\n      if (event.key === \"Escape\") {\n        event.preventDefault();\n        propsRef.current.onSearchOpenChange(false);\n      }\n    }\n\n    document.addEventListener(\"keydown\", onEscape);\n    return () => document.removeEventListener(\"keydown\", onEscape);\n  }, [searchOpen, propsRef]);\n\n  const debouncedSearch = useDebouncedCallback((query: string) => {\n    propsRef.current.onSearch(query);\n  }, 150);\n\n  function onCompositionStart() {\n    isComposingRef.current = true;\n  }\n\n  function onCompositionEnd(event: React.CompositionEvent<HTMLInputElement>) {\n    isComposingRef.current = false;\n    const value = event.currentTarget.value;\n    setHasQuery(value.length > 0);\n    propsRef.current.onSearchQueryChange(value);\n    debouncedSearch(value);\n  }\n\n  function onKeyDown(event: React.KeyboardEvent) {\n    event.stopPropagation();\n\n    if (event.key === \"Enter\") {\n      if (event.nativeEvent.isComposing) return;\n      event.preventDefault();\n      if (event.shiftKey) {\n        propsRef.current.onNavigateToPrevMatch();\n      } else {\n        propsRef.current.onNavigateToNextMatch();\n      }\n    }\n  }\n\n  function onChange(event: React.ChangeEvent<HTMLInputElement>) {\n    if (isComposingRef.current) return;\n    const value = event.target.value;\n    setHasQuery(value.length > 0);\n    propsRef.current.onSearchQueryChange(value);\n    debouncedSearch(value);\n  }\n\n  function onClose() {\n    propsRef.current.onSearchOpenChange(false);\n  }\n\n  function onPrevMatch() {\n    propsRef.current.onNavigateToPrevMatch();\n  }\n\n  function onNextMatch() {\n    propsRef.current.onNavigateToNextMatch();\n  }\n\n  if (!searchOpen) return null;\n\n  return (\n    <div\n      role=\"search\"\n      data-slot=\"grid-search\"\n      className=\"fade-in-0 slide-in-from-top-2 absolute end-4 top-4 z-50 flex animate-in flex-col gap-2 rounded-lg border bg-background p-2 shadow-lg\"\n    >\n      <div className=\"flex items-center gap-2\">\n        <Input\n          autoComplete=\"off\"\n          autoCorrect=\"off\"\n          autoCapitalize=\"off\"\n          spellCheck={false}\n          placeholder=\"Find in table...\"\n          className=\"h-8 w-64\"\n          ref={inputRef}\n          defaultValue={searchQuery}\n          onChange={onChange}\n          onKeyDown={onKeyDown}\n          onCompositionStart={onCompositionStart}\n          onCompositionEnd={onCompositionEnd}\n        />\n        <div className=\"flex items-center gap-1\">\n          <Button\n            aria-label=\"Previous match\"\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7\"\n            onClick={onPrevMatch}\n            onPointerDown={onTriggerPointerDown}\n            disabled={searchMatches.length === 0}\n          >\n            <ChevronUp />\n          </Button>\n          <Button\n            aria-label=\"Next match\"\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7\"\n            onClick={onNextMatch}\n            onPointerDown={onTriggerPointerDown}\n            disabled={searchMatches.length === 0}\n          >\n            <ChevronDown />\n          </Button>\n          <Button\n            aria-label=\"Close search\"\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7\"\n            onClick={onClose}\n          >\n            <X />\n          </Button>\n        </div>\n      </div>\n      <div className=\"flex items-center gap-1 whitespace-nowrap text-muted-foreground text-xs\">\n        {searchMatches.length > 0 ? (\n          <span>\n            {matchIndex + 1} of {searchMatches.length}\n          </span>\n        ) : hasQuery ? (\n          <span>No results</span>\n        ) : (\n          <span>Type to search</span>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/components/data-grid/data-grid-search.tsx"
    },
    {
      "path": "src/components/ui/direction.tsx",
      "content": "\"use client\";\n\nimport { Direction } from \"radix-ui\";\nimport type * as React from \"react\";\n\nfunction DirectionProvider({\n  dir,\n  direction,\n  children,\n}: React.ComponentProps<typeof Direction.DirectionProvider> & {\n  direction?: React.ComponentProps<typeof Direction.DirectionProvider>[\"dir\"];\n}) {\n  return (\n    <Direction.DirectionProvider dir={direction ?? dir}>\n      {children}\n    </Direction.DirectionProvider>\n  );\n}\n\nconst useDirection = Direction.useDirection;\n\nexport { DirectionProvider, useDirection };\n",
      "type": "registry:ui",
      "target": "src/components/ui/direction.tsx"
    },
    {
      "path": "src/hooks/use-as-ref.ts",
      "content": "import * as React from \"react\";\n\nimport { useIsomorphicLayoutEffect } from \"@/hooks/use-isomorphic-layout-effect\";\n\nfunction useAsRef<T>(props: T) {\n  const ref = React.useRef<T>(props);\n\n  useIsomorphicLayoutEffect(() => {\n    ref.current = props;\n  });\n\n  return ref;\n}\n\nexport { useAsRef };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-badge-overflow.ts",
      "content": "import * as React from \"react\";\n\nconst badgeWidthCache = new Map<string, number>();\n\nconst DEFAULT_CONTAINER_PADDING = 16; // px-2 = 8px * 2\nconst DEFAULT_BADGE_GAP = 4; // gap-1 = 4px\nconst DEFAULT_OVERFLOW_BADGE_WIDTH = 40; // Approximate width of \"+N\" badge\n\ninterface MeasureBadgeWidthProps {\n  label: string;\n  cacheKey: string;\n  iconSize?: number;\n  maxWidth?: number;\n  className?: string;\n}\n\nfunction measureBadgeWidth({\n  label,\n  cacheKey,\n  iconSize,\n  maxWidth,\n  className,\n}: MeasureBadgeWidthProps): number {\n  const cached = badgeWidthCache.get(cacheKey);\n  if (cached !== undefined) {\n    return cached;\n  }\n\n  const measureEl = document.createElement(\"div\");\n  measureEl.className = `inline-flex items-center rounded-md border px-1.5 text-xs font-semibold h-5 gap-1 shrink-0 absolute invisible pointer-events-none ${\n    className ?? \"\"\n  }`;\n  measureEl.style.whiteSpace = \"nowrap\";\n\n  if (iconSize) {\n    const icon = document.createElement(\"span\");\n    icon.className = \"shrink-0\";\n    icon.style.width = `${iconSize}px`;\n    icon.style.height = `${iconSize}px`;\n    measureEl.appendChild(icon);\n  }\n\n  if (maxWidth) {\n    const text = document.createElement(\"span\");\n    text.className = \"truncate\";\n    text.style.maxWidth = `${maxWidth}px`;\n    text.textContent = label;\n    measureEl.appendChild(text);\n  } else {\n    measureEl.textContent = label;\n  }\n\n  document.body.appendChild(measureEl);\n  const width = measureEl.offsetWidth;\n  document.body.removeChild(measureEl);\n\n  badgeWidthCache.set(cacheKey, width);\n  return width;\n}\n\ninterface UseBadgeOverflowProps<T> {\n  items: T[];\n  getLabel: (item: T) => string;\n  containerRef: React.RefObject<HTMLElement | null>;\n  lineCount: number;\n  cacheKeyPrefix?: string;\n  iconSize?: number;\n  maxWidth?: number;\n  className?: string;\n  containerPadding?: number;\n  badgeGap?: number;\n  overflowBadgeWidth?: number;\n}\n\ninterface UseBadgeOverflowReturn<T> {\n  visibleItems: T[];\n  hiddenCount: number;\n  containerWidth: number;\n}\n\nexport function useBadgeOverflow<T>({\n  items,\n  getLabel,\n  containerRef,\n  lineCount,\n  cacheKeyPrefix = \"\",\n  containerPadding = DEFAULT_CONTAINER_PADDING,\n  badgeGap = DEFAULT_BADGE_GAP,\n  overflowBadgeWidth = DEFAULT_OVERFLOW_BADGE_WIDTH,\n  iconSize,\n  maxWidth,\n  className,\n}: UseBadgeOverflowProps<T>): UseBadgeOverflowReturn<T> {\n  const [containerWidth, setContainerWidth] = React.useState(0);\n\n  React.useEffect(() => {\n    if (!containerRef.current) return;\n\n    function measureWidth() {\n      if (containerRef.current) {\n        const width = containerRef.current.clientWidth - containerPadding;\n        setContainerWidth(width);\n      }\n    }\n\n    measureWidth();\n\n    const resizeObserver = new ResizeObserver(measureWidth);\n    resizeObserver.observe(containerRef.current);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [containerRef, containerPadding]);\n\n  const result = React.useMemo(() => {\n    if (!containerWidth || items.length === 0) {\n      return { visibleItems: items, hiddenCount: 0, containerWidth };\n    }\n\n    let currentLineWidth = 0;\n    let currentLine = 1;\n    const visible: T[] = [];\n\n    for (const item of items) {\n      const label = getLabel(item);\n      const cacheKey = cacheKeyPrefix ? `${cacheKeyPrefix}:${label}` : label;\n      const badgeWidth = measureBadgeWidth({\n        label,\n        cacheKey,\n        iconSize,\n        maxWidth,\n        className,\n      });\n      const widthWithGap = badgeWidth + badgeGap;\n\n      if (currentLineWidth + widthWithGap <= containerWidth) {\n        currentLineWidth += widthWithGap;\n        visible.push(item);\n      } else if (currentLine < lineCount) {\n        currentLine++;\n        currentLineWidth = widthWithGap;\n        visible.push(item);\n      } else {\n        if (\n          currentLineWidth + overflowBadgeWidth > containerWidth &&\n          visible.length > 0\n        ) {\n          visible.pop();\n        }\n\n        break;\n      }\n    }\n\n    return {\n      visibleItems: visible,\n      hiddenCount: Math.max(0, items.length - visible.length),\n      containerWidth,\n    };\n  }, [\n    items,\n    getLabel,\n    containerWidth,\n    lineCount,\n    cacheKeyPrefix,\n    iconSize,\n    maxWidth,\n    className,\n    badgeGap,\n    overflowBadgeWidth,\n  ]);\n\n  return result;\n}\n\nexport function clearBadgeWidthCache(): void {\n  badgeWidthCache.clear();\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-callback-ref.ts",
      "content": "import * as React from \"react\";\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx\n */\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef<T extends (...args: never[]) => unknown>(\n  callback: T | undefined,\n): T {\n  const callbackRef = React.useRef(callback);\n\n  React.useEffect(() => {\n    callbackRef.current = callback;\n  });\n\n  // https://github.com/facebook/react/issues/19240\n  return React.useMemo(\n    () => ((...args) => callbackRef.current?.(...args)) as T,\n    [],\n  );\n}\n\nexport { useCallbackRef };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-data-grid.ts",
      "content": "import {\n  type ColumnDef,\n  type ColumnFiltersState,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getSortedRowModel,\n  type Row,\n  type RowSelectionState,\n  type SortingState,\n  type TableMeta,\n  type TableOptions,\n  type TableState,\n  type Updater,\n  useReactTable,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer, type Virtualizer } from \"@tanstack/react-virtual\";\nimport * as React from \"react\";\nimport { toast } from \"sonner\";\nimport { useDirection } from \"@/components/ui/direction\";\n\nimport { useAsRef } from \"@/hooks/use-as-ref\";\nimport { useIsomorphicLayoutEffect } from \"@/hooks/use-isomorphic-layout-effect\";\nimport { useLazyRef } from \"@/hooks/use-lazy-ref\";\nimport {\n  getCellKey,\n  getEmptyCellValue,\n  getIsFileCellData,\n  getIsInPopover,\n  getRowHeightValue,\n  getScrollDirection,\n  matchSelectOption,\n  parseCellKey,\n  parseTsv,\n  scrollCellIntoView,\n} from \"@/lib/data-grid\";\nimport type {\n  CellPosition,\n  CellUpdate,\n  ContextMenuState,\n  Direction,\n  FileCellData,\n  NavigationDirection,\n  PasteDialogState,\n  RowHeightValue,\n  SearchState,\n  SelectionState,\n} from \"@/types/data-grid\";\n\nconst DEFAULT_ROW_HEIGHT = \"short\";\nconst OVERSCAN = 6;\nconst VIEWPORT_OFFSET = 1;\nconst HORIZONTAL_PAGE_SIZE = 5;\nconst SCROLL_SYNC_RETRY_COUNT = 16;\nconst MIN_COLUMN_SIZE = 60;\nconst MAX_COLUMN_SIZE = 800;\nconst SEARCH_SHORTCUT_KEY = \"f\";\nconst NON_NAVIGABLE_COLUMN_IDS = new Set([\"select\", \"actions\"]);\nconst AUTO_SCROLL_EDGE_ZONE = 50;\nconst AUTO_SCROLL_SPEED_RAMP_ZONE = AUTO_SCROLL_EDGE_ZONE * 3;\nconst AUTO_SCROLL_MIN_SPEED = 8;\nconst AUTO_SCROLL_MAX_SPEED = 40;\nconst AUTO_SCROLL_SELECTION_THROTTLE_MS = 32;\n\nconst DOMAIN_REGEX = /^[\\w.-]+\\.[a-z]{2,}(\\/\\S*)?$/i;\nconst ISO_DATE_REGEX = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}.*)?$/;\nconst TRUTHY_BOOLEANS = new Set([\"true\", \"1\", \"yes\", \"checked\"]);\nconst VALID_BOOLEANS = new Set([\n  \"true\",\n  \"false\",\n  \"1\",\n  \"0\",\n  \"yes\",\n  \"no\",\n  \"checked\",\n  \"unchecked\",\n]);\n\ninterface DataGridState {\n  sorting: SortingState;\n  columnFilters: ColumnFiltersState;\n  rowHeight: RowHeightValue;\n  rowSelection: RowSelectionState;\n  selectionState: SelectionState;\n  focusedCell: CellPosition | null;\n  editingCell: CellPosition | null;\n  cutCells: Set<string>;\n  contextMenu: ContextMenuState;\n  searchQuery: string;\n  searchMatches: CellPosition[];\n  matchIndex: number;\n  searchOpen: boolean;\n  lastClickedRowId: string | null;\n  pasteDialog: PasteDialogState;\n}\n\ninterface DataGridStore {\n  subscribe: (callback: () => void) => () => void;\n  getState: () => DataGridState;\n  setState: <K extends keyof DataGridState>(\n    key: K,\n    value: DataGridState[K],\n  ) => void;\n  notify: () => void;\n  batch: (fn: () => void) => void;\n}\n\nfunction useStore<T>(\n  store: DataGridStore,\n  selector: (state: DataGridState) => T,\n): T {\n  const getSnapshot = React.useCallback(\n    () => selector(store.getState()),\n    [store, selector],\n  );\n\n  return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface UseDataGridProps<TData>\n  extends Omit<TableOptions<TData>, \"pageCount\" | \"getCoreRowModel\"> {\n  onDataChange?: (data: TData[]) => void;\n  onRowAdd?: (\n    event?: React.MouseEvent<HTMLDivElement>,\n  ) => Partial<CellPosition> | Promise<Partial<CellPosition> | null> | null;\n  onRowsAdd?: (count: number) => void | Promise<void>;\n  onRowsDelete?: (rows: TData[], rowIndices: number[]) => void | Promise<void>;\n  onPaste?: (updates: Array<CellUpdate>) => void | Promise<void>;\n  onFilesUpload?: (params: {\n    files: File[];\n    rowIndex: number;\n    columnId: string;\n  }) => Promise<FileCellData[]>;\n  onFilesDelete?: (params: {\n    fileIds: string[];\n    rowIndex: number;\n    columnId: string;\n  }) => void | Promise<void>;\n  rowHeight?: RowHeightValue;\n  onRowHeightChange?: (rowHeight: RowHeightValue) => void;\n  overscan?: number;\n  dir?: Direction;\n  autoFocus?: boolean | Partial<CellPosition>;\n  enableSingleCellSelection?: boolean;\n  enableColumnSelection?: boolean;\n  enableSearch?: boolean;\n  enablePaste?: boolean;\n  readOnly?: boolean;\n}\n\nfunction useDataGrid<TData>({\n  data,\n  columns,\n  rowHeight: rowHeightProp = DEFAULT_ROW_HEIGHT,\n  overscan = OVERSCAN,\n  dir: dirProp,\n  initialState,\n  ...props\n}: UseDataGridProps<TData>) {\n  const dir = useDirection(dirProp);\n  const dataGridRef = React.useRef<HTMLDivElement>(null);\n  const tableRef = React.useRef<ReturnType<typeof useReactTable<TData>>>(null);\n  const rowVirtualizerRef =\n    React.useRef<Virtualizer<HTMLDivElement, Element>>(null);\n  const headerRef = React.useRef<HTMLDivElement>(null);\n  const rowMapRef = React.useRef<Map<number, HTMLDivElement>>(new Map());\n  const cellMapRef = React.useRef<Map<string, HTMLDivElement>>(new Map());\n  const footerRef = React.useRef<HTMLDivElement>(null);\n  const focusGuardRef = React.useRef(false);\n\n  const propsRef = useAsRef({\n    ...props,\n    data,\n    columns,\n    initialState,\n  });\n\n  const listenersRef = useLazyRef(() => new Set<() => void>());\n\n  const stateRef = useLazyRef<DataGridState>(() => {\n    return {\n      sorting: initialState?.sorting ?? [],\n      columnFilters: initialState?.columnFilters ?? [],\n      rowHeight: rowHeightProp,\n      rowSelection: initialState?.rowSelection ?? {},\n      selectionState: {\n        selectedCells: new Set(),\n        selectionRange: null,\n        isSelecting: false,\n      },\n      focusedCell: null,\n      editingCell: null,\n      cutCells: new Set(),\n      contextMenu: {\n        open: false,\n        x: 0,\n        y: 0,\n      },\n      searchQuery: \"\",\n      searchMatches: [],\n      matchIndex: -1,\n      searchOpen: false,\n      lastClickedRowId: null,\n      pasteDialog: {\n        open: false,\n        rowsNeeded: 0,\n        clipboardText: \"\",\n      },\n    };\n  });\n\n  const store = React.useMemo<DataGridStore>(() => {\n    let isBatching = false;\n    let pendingNotification = false;\n\n    return {\n      subscribe: (callback) => {\n        listenersRef.current.add(callback);\n        return () => listenersRef.current.delete(callback);\n      },\n      getState: () => stateRef.current,\n      setState: (key, value) => {\n        if (Object.is(stateRef.current[key], value)) return;\n        stateRef.current[key] = value;\n\n        if (isBatching) {\n          pendingNotification = true;\n        } else {\n          if (!pendingNotification) {\n            pendingNotification = true;\n            queueMicrotask(() => {\n              pendingNotification = false;\n              store.notify();\n            });\n          }\n        }\n      },\n      notify: () => {\n        for (const listener of listenersRef.current) {\n          listener();\n        }\n      },\n      batch: (fn) => {\n        if (isBatching) {\n          fn();\n          return;\n        }\n\n        isBatching = true;\n        const wasPending = pendingNotification;\n        pendingNotification = false;\n\n        try {\n          fn();\n        } finally {\n          isBatching = false;\n          if (pendingNotification || wasPending) {\n            pendingNotification = false;\n            store.notify();\n          }\n        }\n      },\n    };\n  }, [listenersRef, stateRef]);\n\n  const focusedCell = useStore(store, (state) => state.focusedCell);\n  const editingCell = useStore(store, (state) => state.editingCell);\n  const selectionState = useStore(store, (state) => state.selectionState);\n  const searchQuery = useStore(store, (state) => state.searchQuery);\n  const searchMatches = useStore(store, (state) => state.searchMatches);\n  const matchIndex = useStore(store, (state) => state.matchIndex);\n  const searchOpen = useStore(store, (state) => state.searchOpen);\n  const sorting = useStore(store, (state) => state.sorting);\n  const columnFilters = useStore(store, (state) => state.columnFilters);\n  const rowSelection = useStore(store, (state) => state.rowSelection);\n  const rowHeight = useStore(store, (state) => state.rowHeight);\n  const contextMenu = useStore(store, (state) => state.contextMenu);\n  const pasteDialog = useStore(store, (state) => state.pasteDialog);\n\n  const rowHeightValue = getRowHeightValue(rowHeight);\n\n  const prevCellSelectionMapRef = useLazyRef(\n    () => new Map<number, Set<string>>(),\n  );\n\n  // Memoize per-row selection sets to prevent unnecessary row re-renders\n  // Each row gets a stable Set reference that only changes when its cells' selection changes\n  const cellSelectionMap = React.useMemo(() => {\n    const selectedCells = selectionState.selectedCells;\n\n    if (selectedCells.size === 0) {\n      prevCellSelectionMapRef.current.clear();\n      return null;\n    }\n\n    const newRowCells = new Map<number, Set<string>>();\n    for (const cellKey of selectedCells) {\n      const { rowIndex } = parseCellKey(cellKey);\n      let rowSet = newRowCells.get(rowIndex);\n      if (!rowSet) {\n        rowSet = new Set<string>();\n        newRowCells.set(rowIndex, rowSet);\n      }\n      rowSet.add(cellKey);\n    }\n\n    const stableMap = new Map<number, Set<string>>();\n    for (const [rowIndex, newSet] of newRowCells) {\n      const prevSet = prevCellSelectionMapRef.current.get(rowIndex);\n      if (\n        prevSet &&\n        prevSet.size === newSet.size &&\n        [...newSet].every((key) => prevSet.has(key))\n      ) {\n        stableMap.set(rowIndex, prevSet);\n      } else {\n        stableMap.set(rowIndex, newSet);\n      }\n    }\n\n    prevCellSelectionMapRef.current = stableMap;\n    return stableMap;\n  }, [selectionState.selectedCells, prevCellSelectionMapRef]);\n\n  const visualRowIndexCacheRef = React.useRef<{\n    rows: Row<TData>[] | null;\n    map: Map<string, number>;\n  } | null>(null);\n\n  // Pre-compute visual row index map for O(1) lookups (used by select column)\n  // Cache is invalidated when row model identity changes (sorting/filtering)\n  const getVisualRowIndex = React.useCallback(\n    (rowId: string): number | undefined => {\n      const rows = tableRef.current?.getRowModel().rows;\n      if (!rows) return undefined;\n\n      if (visualRowIndexCacheRef.current?.rows !== rows) {\n        const map = new Map<string, number>();\n        for (const [i, row] of rows.entries()) {\n          map.set(row.id, i + 1);\n        }\n        visualRowIndexCacheRef.current = { rows, map };\n      }\n\n      return visualRowIndexCacheRef.current.map.get(rowId);\n    },\n    [],\n  );\n\n  const columnIds = React.useMemo(() => {\n    return columns\n      .map((c) => {\n        if (c.id) return c.id;\n        if (\"accessorKey\" in c) return c.accessorKey as string;\n        return undefined;\n      })\n      .filter((id): id is string => Boolean(id));\n  }, [columns]);\n\n  const navigableColumnIds = React.useMemo(() => {\n    return columnIds.filter((c) => !NON_NAVIGABLE_COLUMN_IDS.has(c));\n  }, [columnIds]);\n\n  const onDataUpdate = React.useCallback(\n    (updates: CellUpdate | Array<CellUpdate>) => {\n      if (propsRef.current.readOnly) return;\n\n      const updateArray = Array.isArray(updates) ? updates : [updates];\n\n      if (updateArray.length === 0) return;\n\n      const currentTable = tableRef.current;\n      const currentData = propsRef.current.data;\n      const rows = currentTable?.getRowModel().rows;\n\n      const rowUpdatesMap = new Map<\n        number,\n        Array<Omit<CellUpdate, \"rowIndex\">>\n      >();\n\n      for (const update of updateArray) {\n        if (!rows || !currentTable) {\n          const existingUpdates = rowUpdatesMap.get(update.rowIndex) ?? [];\n          existingUpdates.push({\n            columnId: update.columnId,\n            value: update.value,\n          });\n          rowUpdatesMap.set(update.rowIndex, existingUpdates);\n        } else {\n          const row = rows[update.rowIndex];\n          if (!row) continue;\n\n          const originalData = row.original;\n          const originalRowIndex = currentData.indexOf(originalData);\n\n          const targetIndex =\n            originalRowIndex !== -1 ? originalRowIndex : update.rowIndex;\n\n          const existingUpdates = rowUpdatesMap.get(targetIndex) ?? [];\n          existingUpdates.push({\n            columnId: update.columnId,\n            value: update.value,\n          });\n          rowUpdatesMap.set(targetIndex, existingUpdates);\n        }\n      }\n\n      const maxUpdateIndex =\n        rowUpdatesMap.size > 0\n          ? Math.max(...Array.from(rowUpdatesMap.keys()))\n          : -1;\n      const dataLength = Math.max(currentData.length, maxUpdateIndex + 1);\n\n      const newData: TData[] = new Array(dataLength);\n\n      for (let i = 0; i < dataLength; i++) {\n        const updates = rowUpdatesMap.get(i);\n        // Fall back to the table's row data for rows not yet in currentData\n        const existingRow = currentData[i] ?? rows?.[i]?.original;\n\n        if (existingRow == null) continue;\n\n        if (updates) {\n          const updatedRow = { ...existingRow } as Record<string, unknown>;\n          for (const { columnId, value } of updates) {\n            updatedRow[columnId] = value;\n          }\n          newData[i] = updatedRow as TData;\n        } else {\n          newData[i] = existingRow;\n        }\n      }\n\n      propsRef.current.onDataChange?.(newData);\n    },\n    [propsRef],\n  );\n\n  const getIsCellSelected = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      const currentSelectionState = store.getState().selectionState;\n      return currentSelectionState.selectedCells.has(\n        getCellKey(rowIndex, columnId),\n      );\n    },\n    [store],\n  );\n\n  const onSelectionClear = React.useCallback(() => {\n    store.batch(() => {\n      store.setState(\"selectionState\", {\n        selectedCells: new Set(),\n        selectionRange: null,\n        isSelecting: false,\n      });\n      store.setState(\"rowSelection\", {});\n    });\n  }, [store]);\n\n  const selectAll = React.useCallback(() => {\n    const allCells = new Set<string>();\n    const currentTable = tableRef.current;\n    const rows = currentTable?.getRowModel().rows ?? [];\n    const rowCount = rows.length ?? propsRef.current.data.length;\n\n    for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {\n      for (const columnId of columnIds) {\n        allCells.add(getCellKey(rowIndex, columnId));\n      }\n    }\n\n    const firstColumnId = columnIds[0];\n    const lastColumnId = columnIds[columnIds.length - 1];\n\n    store.setState(\"selectionState\", {\n      selectedCells: allCells,\n      selectionRange:\n        columnIds.length > 0 && rowCount > 0 && firstColumnId && lastColumnId\n          ? {\n              start: { rowIndex: 0, columnId: firstColumnId },\n              end: { rowIndex: rowCount - 1, columnId: lastColumnId },\n            }\n          : null,\n      isSelecting: false,\n    });\n  }, [columnIds, propsRef, store]);\n\n  const selectColumn = React.useCallback(\n    (columnId: string) => {\n      const currentTable = tableRef.current;\n      const rows = currentTable?.getRowModel().rows ?? [];\n      const rowCount = rows.length ?? propsRef.current.data.length;\n\n      if (rowCount === 0) return;\n\n      const selectedCells = new Set<string>();\n\n      for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {\n        selectedCells.add(getCellKey(rowIndex, columnId));\n      }\n\n      store.setState(\"selectionState\", {\n        selectedCells,\n        selectionRange: {\n          start: { rowIndex: 0, columnId },\n          end: { rowIndex: rowCount - 1, columnId },\n        },\n        isSelecting: false,\n      });\n    },\n    [propsRef, store],\n  );\n\n  const selectRange = React.useCallback(\n    (start: CellPosition, end: CellPosition, isSelecting = false) => {\n      const startColIndex = columnIds.indexOf(start.columnId);\n      const endColIndex = columnIds.indexOf(end.columnId);\n\n      const minRow = Math.min(start.rowIndex, end.rowIndex);\n      const maxRow = Math.max(start.rowIndex, end.rowIndex);\n      const minCol = Math.min(startColIndex, endColIndex);\n      const maxCol = Math.max(startColIndex, endColIndex);\n\n      const selectedCells = new Set<string>();\n\n      for (let rowIndex = minRow; rowIndex <= maxRow; rowIndex++) {\n        for (let colIndex = minCol; colIndex <= maxCol; colIndex++) {\n          const columnId = columnIds[colIndex];\n          if (columnId) {\n            selectedCells.add(getCellKey(rowIndex, columnId));\n          }\n        }\n      }\n\n      store.setState(\"selectionState\", {\n        selectedCells,\n        selectionRange: { start, end },\n        isSelecting,\n      });\n    },\n    [columnIds, store],\n  );\n\n  const dragDepsRef = useAsRef({\n    selectRange,\n    dir,\n    rowHeightValue,\n    columnIds,\n  });\n\n  const serializeCellsToTsv = React.useCallback(() => {\n    const currentState = store.getState();\n\n    let selectedCellsArray: string[];\n    if (!currentState.selectionState.selectedCells.size) {\n      if (!currentState.focusedCell) return null;\n      const focusedCellKey = getCellKey(\n        currentState.focusedCell.rowIndex,\n        currentState.focusedCell.columnId,\n      );\n      selectedCellsArray = [focusedCellKey];\n    } else {\n      selectedCellsArray = Array.from(\n        currentState.selectionState.selectedCells,\n      );\n    }\n\n    const currentTable = tableRef.current;\n    const rows = currentTable?.getRowModel().rows;\n    if (!rows) return null;\n\n    const selectedColumnIds: string[] = [];\n    const seenColumnIds = new Set<string>();\n    const cellData = new Map<string, string>();\n    const rowIndices = new Set<number>();\n    const rowCellMaps = new Map<\n      number,\n      Map<string, ReturnType<Row<TData>[\"getVisibleCells\"]>[number]>\n    >();\n    const navigableCells: string[] = [];\n\n    for (const cellKey of selectedCellsArray) {\n      const { rowIndex, columnId } = parseCellKey(cellKey);\n\n      if (columnId && NON_NAVIGABLE_COLUMN_IDS.has(columnId)) {\n        continue;\n      }\n\n      navigableCells.push(cellKey);\n\n      if (columnId && !seenColumnIds.has(columnId)) {\n        seenColumnIds.add(columnId);\n        selectedColumnIds.push(columnId);\n      }\n\n      rowIndices.add(rowIndex);\n\n      const row = rows[rowIndex];\n      if (row) {\n        let cellMap = rowCellMaps.get(rowIndex);\n        if (!cellMap) {\n          cellMap = new Map(row.getVisibleCells().map((c) => [c.column.id, c]));\n          rowCellMaps.set(rowIndex, cellMap);\n        }\n        const cell = cellMap.get(columnId);\n        if (cell) {\n          const value = cell.getValue();\n          const cellVariant = cell.column.columnDef?.meta?.cell?.variant;\n\n          let serializedValue = \"\";\n          if (cellVariant === \"file\" || cellVariant === \"multi-select\") {\n            serializedValue = value ? JSON.stringify(value) : \"\";\n          } else if (value instanceof Date) {\n            serializedValue = value.toISOString();\n          } else {\n            serializedValue = String(value ?? \"\");\n          }\n\n          cellData.set(cellKey, serializedValue);\n        }\n      }\n    }\n\n    const colIndices = new Set<number>();\n    for (const cellKey of navigableCells) {\n      const { columnId } = parseCellKey(cellKey);\n      const colIndex = selectedColumnIds.indexOf(columnId);\n      if (colIndex >= 0) {\n        colIndices.add(colIndex);\n      }\n    }\n\n    const sortedRowIndices = Array.from(rowIndices).sort((a, b) => a - b);\n    const sortedColIndices = Array.from(colIndices).sort((a, b) => a - b);\n    const sortedColumnIds = sortedColIndices.map((i) => selectedColumnIds[i]);\n\n    const tsvData = sortedRowIndices\n      .map((rowIndex) =>\n        sortedColumnIds\n          .map((columnId) => {\n            const cellKey = `${rowIndex}:${columnId}`;\n            return cellData.get(cellKey) ?? \"\";\n          })\n          .join(\"\\t\"),\n      )\n      .join(\"\\n\");\n\n    return { tsvData, selectedCellsArray: navigableCells };\n  }, [store]);\n\n  const onCellsCopy = React.useCallback(async () => {\n    const result = serializeCellsToTsv();\n    if (!result) return;\n\n    const { tsvData, selectedCellsArray } = result;\n\n    try {\n      await navigator.clipboard.writeText(tsvData);\n\n      const currentState = store.getState();\n      if (currentState.cutCells.size > 0) {\n        store.setState(\"cutCells\", new Set());\n      }\n\n      toast.success(\n        `${selectedCellsArray.length} cell${\n          selectedCellsArray.length !== 1 ? \"s\" : \"\"\n        } copied`,\n      );\n    } catch (error) {\n      toast.error(\n        error instanceof Error ? error.message : \"Failed to copy to clipboard\",\n      );\n    }\n  }, [store, serializeCellsToTsv]);\n\n  const onCellsCut = React.useCallback(async () => {\n    if (propsRef.current.readOnly) return;\n\n    const result = serializeCellsToTsv();\n    if (!result) return;\n\n    const { tsvData, selectedCellsArray } = result;\n\n    try {\n      await navigator.clipboard.writeText(tsvData);\n\n      store.setState(\"cutCells\", new Set(selectedCellsArray));\n\n      toast.success(\n        `${selectedCellsArray.length} cell${\n          selectedCellsArray.length !== 1 ? \"s\" : \"\"\n        } cut`,\n      );\n    } catch (error) {\n      toast.error(\n        error instanceof Error ? error.message : \"Failed to cut to clipboard\",\n      );\n    }\n  }, [store, propsRef, serializeCellsToTsv]);\n\n  const restoreFocus = React.useCallback((element: HTMLDivElement | null) => {\n    if (element && document.activeElement !== element) {\n      requestAnimationFrame(() => {\n        element.focus();\n      });\n    }\n  }, []);\n\n  const onCellsPaste = React.useCallback(\n    async (expandRows = false) => {\n      if (propsRef.current.readOnly) return;\n\n      const currentState = store.getState();\n      if (!currentState.focusedCell) return;\n\n      const currentTable = tableRef.current;\n      const rows = currentTable?.getRowModel().rows;\n      if (!rows) return;\n\n      try {\n        let clipboardText = currentState.pasteDialog.clipboardText;\n\n        if (!clipboardText) {\n          clipboardText = await navigator.clipboard.readText();\n          if (!clipboardText) return;\n        }\n\n        const rawPastedData = parseTsv(\n          clipboardText,\n          navigableColumnIds.length,\n        );\n\n        // Fill entire selection when clipboard has a single value and multiple cells are selected\n        const selectionCells = currentState.selectionState.selectedCells;\n        const isSingleCellClipboard =\n          rawPastedData.length === 1 && (rawPastedData[0]?.length ?? 0) === 1;\n\n        let pastedData = rawPastedData;\n        let startRowIndex = currentState.focusedCell.rowIndex;\n        let startColIndex = navigableColumnIds.indexOf(\n          currentState.focusedCell.columnId,\n        );\n\n        if (isSingleCellClipboard && selectionCells.size > 1) {\n          const singleValue = rawPastedData[0]?.[0] ?? \"\";\n          let minRow = Infinity;\n          let maxRow = -Infinity;\n          let minColIdx = Infinity;\n          let maxColIdx = -Infinity;\n\n          for (const cellKey of selectionCells) {\n            const { rowIndex, columnId } = parseCellKey(cellKey);\n            const colIdx = navigableColumnIds.indexOf(columnId);\n            if (colIdx === -1) continue;\n            minRow = Math.min(minRow, rowIndex);\n            maxRow = Math.max(maxRow, rowIndex);\n            minColIdx = Math.min(minColIdx, colIdx);\n            maxColIdx = Math.max(maxColIdx, colIdx);\n          }\n\n          if (minRow !== Infinity) {\n            startRowIndex = minRow;\n            startColIndex = minColIdx;\n            const numRows = maxRow - minRow + 1;\n            const numCols = maxColIdx - minColIdx + 1;\n            pastedData = Array.from({ length: numRows }, () =>\n              Array.from({ length: numCols }, () => singleValue),\n            );\n          }\n        }\n\n        if (startColIndex === -1) return;\n\n        const rowCount = rows.length ?? propsRef.current.data.length;\n        const rowsNeeded = startRowIndex + pastedData.length - rowCount;\n\n        if (\n          rowsNeeded > 0 &&\n          !expandRows &&\n          propsRef.current.onRowAdd &&\n          !currentState.pasteDialog.clipboardText\n        ) {\n          store.setState(\"pasteDialog\", {\n            open: true,\n            rowsNeeded,\n            clipboardText,\n          });\n          return;\n        }\n\n        if (expandRows && rowsNeeded > 0) {\n          const expectedRowCount = rowCount + rowsNeeded;\n\n          if (propsRef.current.onRowsAdd) {\n            await propsRef.current.onRowsAdd(rowsNeeded);\n          } else if (propsRef.current.onRowAdd) {\n            for (let i = 0; i < rowsNeeded; i++) {\n              await propsRef.current.onRowAdd();\n            }\n          }\n\n          let attempts = 0;\n          const maxAttempts = 50;\n          let currentTableRowCount =\n            tableRef.current?.getRowModel().rows.length ?? 0;\n\n          while (\n            currentTableRowCount < expectedRowCount &&\n            attempts < maxAttempts\n          ) {\n            await new Promise((resolve) => setTimeout(resolve, 100));\n            currentTableRowCount =\n              tableRef.current?.getRowModel().rows.length ?? 0;\n            attempts++;\n          }\n        }\n\n        const updates: Array<CellUpdate> = [];\n        const tableColumns = currentTable?.getAllColumns() ?? [];\n        let cellsUpdated = 0;\n        let endRowIndex = startRowIndex;\n        let endColIndex = startColIndex;\n\n        const updatedTable = tableRef.current;\n        const updatedRows = updatedTable?.getRowModel().rows;\n        const currentRowCount = updatedRows?.length ?? 0;\n\n        let cellsSkipped = 0;\n\n        const columnMap = new Map(tableColumns.map((c) => [c.id, c]));\n\n        for (\n          let pasteRowIdx = 0;\n          pasteRowIdx < pastedData.length;\n          pasteRowIdx++\n        ) {\n          const pasteRow = pastedData[pasteRowIdx];\n          if (!pasteRow) continue;\n\n          const targetRowIndex = startRowIndex + pasteRowIdx;\n          if (targetRowIndex >= currentRowCount) break;\n\n          for (\n            let pasteColIdx = 0;\n            pasteColIdx < pasteRow.length;\n            pasteColIdx++\n          ) {\n            const targetColIndex = startColIndex + pasteColIdx;\n            if (targetColIndex >= navigableColumnIds.length) break;\n\n            const targetColumnId = navigableColumnIds[targetColIndex];\n            if (!targetColumnId) continue;\n\n            const pastedValue = pasteRow[pasteColIdx] ?? \"\";\n            const column = columnMap.get(targetColumnId);\n            const cellOpts = column?.columnDef?.meta?.cell;\n            const cellVariant = cellOpts?.variant;\n\n            let processedValue: unknown = pastedValue;\n            let shouldSkip = false;\n\n            switch (cellVariant) {\n              case \"number\": {\n                if (!pastedValue) {\n                  processedValue = null;\n                } else {\n                  const num = Number.parseFloat(pastedValue);\n                  if (Number.isNaN(num)) shouldSkip = true;\n                  else processedValue = num;\n                }\n                break;\n              }\n\n              case \"checkbox\": {\n                if (!pastedValue) {\n                  processedValue = false;\n                } else {\n                  const lower = pastedValue.toLowerCase();\n                  if (VALID_BOOLEANS.has(lower)) {\n                    processedValue = TRUTHY_BOOLEANS.has(lower);\n                  } else {\n                    shouldSkip = true;\n                  }\n                }\n                break;\n              }\n\n              case \"date\": {\n                if (!pastedValue) {\n                  processedValue = null;\n                } else {\n                  const date = new Date(pastedValue);\n                  if (Number.isNaN(date.getTime())) shouldSkip = true;\n                  else processedValue = date;\n                }\n                break;\n              }\n\n              case \"select\": {\n                const options = cellOpts?.options ?? [];\n                if (!pastedValue) {\n                  processedValue = null;\n                } else {\n                  const matched = matchSelectOption(pastedValue, options);\n                  if (matched) processedValue = matched;\n                  else shouldSkip = true;\n                }\n                break;\n              }\n\n              case \"multi-select\": {\n                const options = cellOpts?.options ?? [];\n                let values: string[] = [];\n                try {\n                  const parsed = JSON.parse(pastedValue);\n                  if (Array.isArray(parsed)) {\n                    values = parsed.filter(\n                      (v): v is string => typeof v === \"string\",\n                    );\n                  }\n                } catch {\n                  values = pastedValue\n                    ? pastedValue.split(\",\").map((v) => v.trim())\n                    : [];\n                }\n\n                const validated = values\n                  .map((v) => matchSelectOption(v, options))\n                  .filter(Boolean) as string[];\n\n                if (values.length > 0 && validated.length === 0) {\n                  shouldSkip = true;\n                } else {\n                  processedValue = validated;\n                }\n                break;\n              }\n\n              case \"file\": {\n                if (!pastedValue) {\n                  processedValue = [];\n                } else {\n                  try {\n                    const parsed = JSON.parse(pastedValue);\n                    if (!Array.isArray(parsed)) {\n                      shouldSkip = true;\n                    } else {\n                      const validFiles = parsed.filter(getIsFileCellData);\n                      if (parsed.length > 0 && validFiles.length === 0) {\n                        shouldSkip = true;\n                      } else {\n                        processedValue = validFiles;\n                      }\n                    }\n                  } catch {\n                    shouldSkip = true;\n                  }\n                }\n                break;\n              }\n\n              case \"url\": {\n                if (!pastedValue) {\n                  processedValue = \"\";\n                } else {\n                  const firstChar = pastedValue[0];\n                  if (firstChar === \"[\" || firstChar === \"{\") {\n                    shouldSkip = true;\n                  } else {\n                    try {\n                      new URL(pastedValue);\n                      processedValue = pastedValue;\n                    } catch {\n                      if (DOMAIN_REGEX.test(pastedValue)) {\n                        processedValue = pastedValue;\n                      } else {\n                        shouldSkip = true;\n                      }\n                    }\n                  }\n                }\n                break;\n              }\n\n              default: {\n                if (!pastedValue) {\n                  processedValue = \"\";\n                  break;\n                }\n\n                if (ISO_DATE_REGEX.test(pastedValue)) {\n                  const date = new Date(pastedValue);\n                  if (!Number.isNaN(date.getTime())) {\n                    processedValue = date.toLocaleDateString();\n                    break;\n                  }\n                }\n\n                const firstChar = pastedValue[0];\n                if (\n                  firstChar === \"[\" ||\n                  firstChar === \"{\" ||\n                  firstChar === \"t\" ||\n                  firstChar === \"f\"\n                ) {\n                  try {\n                    const parsed = JSON.parse(pastedValue);\n\n                    if (Array.isArray(parsed)) {\n                      if (\n                        parsed.length > 0 &&\n                        parsed.every(getIsFileCellData)\n                      ) {\n                        processedValue = parsed.map((f) => f.name).join(\", \");\n                      } else if (parsed.every((v) => typeof v === \"string\")) {\n                        processedValue = (parsed as string[]).join(\", \");\n                      }\n                    } else if (typeof parsed === \"boolean\") {\n                      processedValue = parsed ? \"Checked\" : \"Unchecked\";\n                    }\n                  } catch {\n                    const lower = pastedValue.toLowerCase();\n                    if (lower === \"true\" || lower === \"false\") {\n                      processedValue =\n                        lower === \"true\" ? \"Checked\" : \"Unchecked\";\n                    }\n                  }\n                }\n              }\n            }\n\n            if (shouldSkip) {\n              cellsSkipped++;\n              endRowIndex = Math.max(endRowIndex, targetRowIndex);\n              endColIndex = Math.max(endColIndex, targetColIndex);\n              continue;\n            }\n\n            updates.push({\n              rowIndex: targetRowIndex,\n              columnId: targetColumnId,\n              value: processedValue,\n            });\n            cellsUpdated++;\n\n            endRowIndex = Math.max(endRowIndex, targetRowIndex);\n            endColIndex = Math.max(endColIndex, targetColIndex);\n          }\n        }\n\n        if (updates.length > 0) {\n          if (propsRef.current.onPaste) {\n            await propsRef.current.onPaste(updates);\n          }\n\n          const allUpdates = [...updates];\n\n          if (currentState.cutCells.size > 0) {\n            const columnById = new Map(tableColumns.map((c) => [c.id, c]));\n\n            for (const cellKey of currentState.cutCells) {\n              const { rowIndex, columnId } = parseCellKey(cellKey);\n              const column = columnById.get(columnId);\n              const cellVariant = column?.columnDef?.meta?.cell?.variant;\n              const emptyValue = getEmptyCellValue(cellVariant);\n              allUpdates.push({ rowIndex, columnId, value: emptyValue });\n            }\n\n            store.setState(\"cutCells\", new Set());\n          }\n\n          onDataUpdate(allUpdates);\n\n          if (cellsSkipped > 0) {\n            toast.success(\n              `${cellsUpdated} cell${\n                cellsUpdated !== 1 ? \"s\" : \"\"\n              } pasted, ${cellsSkipped} skipped`,\n            );\n          } else {\n            toast.success(\n              `${cellsUpdated} cell${cellsUpdated !== 1 ? \"s\" : \"\"} pasted`,\n            );\n          }\n\n          const endColumnId = navigableColumnIds[endColIndex];\n          if (endColumnId) {\n            selectRange(\n              {\n                rowIndex: startRowIndex,\n                columnId: currentState.focusedCell.columnId,\n              },\n              { rowIndex: endRowIndex, columnId: endColumnId },\n            );\n          }\n\n          restoreFocus(dataGridRef.current);\n        } else if (cellsSkipped > 0) {\n          toast.error(\n            `${cellsSkipped} cell${\n              cellsSkipped !== 1 ? \"s\" : \"\"\n            } skipped pasting for invalid data`,\n          );\n        }\n\n        if (currentState.pasteDialog.open) {\n          store.setState(\"pasteDialog\", {\n            open: false,\n            rowsNeeded: 0,\n            clipboardText: \"\",\n          });\n        }\n      } catch (error) {\n        toast.error(\n          error instanceof Error\n            ? error.message\n            : \"Failed to paste. Please try again.\",\n        );\n      }\n    },\n    [\n      store,\n      navigableColumnIds,\n      propsRef,\n      onDataUpdate,\n      selectRange,\n      restoreFocus,\n    ],\n  );\n\n  // Release focus guard after delay to allow async data re-renders to settle.\n  // 300ms accounts for db sync and virtualized cell mounting\n  const releaseFocusGuard = React.useCallback((immediate = false) => {\n    if (immediate) {\n      focusGuardRef.current = false;\n      return;\n    }\n\n    setTimeout(() => {\n      focusGuardRef.current = false;\n    }, 300);\n  }, []);\n\n  const focusCellWrapper = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      focusGuardRef.current = true;\n\n      requestAnimationFrame(() => {\n        const cellKey = getCellKey(rowIndex, columnId);\n        const cellWrapperElement = cellMapRef.current.get(cellKey);\n\n        if (!cellWrapperElement) {\n          const container = dataGridRef.current;\n          if (container) {\n            container.focus();\n          }\n          releaseFocusGuard();\n          return;\n        }\n\n        cellWrapperElement.focus();\n        releaseFocusGuard();\n      });\n    },\n    [releaseFocusGuard],\n  );\n\n  const focusCell = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      store.batch(() => {\n        store.setState(\"focusedCell\", { rowIndex, columnId });\n        store.setState(\"editingCell\", null);\n      });\n\n      const currentState = store.getState();\n\n      if (currentState.searchOpen) return;\n\n      focusCellWrapper(rowIndex, columnId);\n    },\n    [store, focusCellWrapper],\n  );\n\n  const onRowsDelete = React.useCallback(\n    async (rowIndices: number[]) => {\n      if (\n        propsRef.current.readOnly ||\n        !propsRef.current.onRowsDelete ||\n        rowIndices.length === 0\n      )\n        return;\n\n      const currentTable = tableRef.current;\n      const rows = currentTable?.getRowModel().rows;\n\n      if (!rows || rows.length === 0) return;\n\n      const currentState = store.getState();\n      const currentFocusedColumn =\n        currentState.focusedCell?.columnId ?? navigableColumnIds[0];\n\n      const minDeletedRowIndex = Math.min(...rowIndices);\n\n      const rowsToDelete: TData[] = [];\n      for (const rowIndex of rowIndices) {\n        const row = rows[rowIndex];\n        if (row) {\n          rowsToDelete.push(row.original);\n        }\n      }\n\n      await propsRef.current.onRowsDelete(rowsToDelete, rowIndices);\n\n      store.batch(() => {\n        store.setState(\"selectionState\", {\n          selectedCells: new Set(),\n          selectionRange: null,\n          isSelecting: false,\n        });\n        store.setState(\"rowSelection\", {});\n        store.setState(\"editingCell\", null);\n      });\n\n      requestAnimationFrame(() => {\n        const currentTable = tableRef.current;\n        const currentRows = currentTable?.getRowModel().rows ?? [];\n        const newRowCount = currentRows.length ?? propsRef.current.data.length;\n\n        if (newRowCount > 0 && currentFocusedColumn) {\n          const targetRowIndex = Math.min(minDeletedRowIndex, newRowCount - 1);\n          focusCell(targetRowIndex, currentFocusedColumn);\n        }\n      });\n    },\n    [propsRef, store, navigableColumnIds, focusCell],\n  );\n\n  const navigateCell = React.useCallback(\n    (direction: NavigationDirection) => {\n      const currentState = store.getState();\n      if (!currentState.focusedCell) return;\n\n      const { rowIndex, columnId } = currentState.focusedCell;\n      const currentColIndex = navigableColumnIds.indexOf(columnId);\n      const rowVirtualizer = rowVirtualizerRef.current;\n      const currentTable = tableRef.current;\n      const rows = currentTable?.getRowModel().rows ?? [];\n      const rowCount = rows.length ?? propsRef.current.data.length;\n\n      let newRowIndex = rowIndex;\n      let newColumnId = columnId;\n\n      const isRtl = dir === \"rtl\";\n\n      switch (direction) {\n        case \"up\":\n          newRowIndex = Math.max(0, rowIndex - 1);\n          break;\n        case \"down\":\n          newRowIndex = Math.min(rowCount - 1, rowIndex + 1);\n          break;\n        case \"left\":\n          if (isRtl) {\n            if (currentColIndex < navigableColumnIds.length - 1) {\n              const nextColumnId = navigableColumnIds[currentColIndex + 1];\n              if (nextColumnId) newColumnId = nextColumnId;\n            }\n          } else {\n            if (currentColIndex > 0) {\n              const prevColumnId = navigableColumnIds[currentColIndex - 1];\n              if (prevColumnId) newColumnId = prevColumnId;\n            }\n          }\n          break;\n        case \"right\":\n          if (isRtl) {\n            if (currentColIndex > 0) {\n              const prevColumnId = navigableColumnIds[currentColIndex - 1];\n              if (prevColumnId) newColumnId = prevColumnId;\n            }\n          } else {\n            if (currentColIndex < navigableColumnIds.length - 1) {\n              const nextColumnId = navigableColumnIds[currentColIndex + 1];\n              if (nextColumnId) newColumnId = nextColumnId;\n            }\n          }\n          break;\n        case \"home\":\n          if (navigableColumnIds.length > 0) {\n            newColumnId = navigableColumnIds[0] ?? columnId;\n          }\n          break;\n        case \"end\":\n          if (navigableColumnIds.length > 0) {\n            newColumnId =\n              navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;\n          }\n          break;\n        case \"ctrl+home\":\n          newRowIndex = 0;\n          if (navigableColumnIds.length > 0) {\n            newColumnId = navigableColumnIds[0] ?? columnId;\n          }\n          break;\n        case \"ctrl+end\":\n          newRowIndex = Math.max(0, rowCount - 1);\n          if (navigableColumnIds.length > 0) {\n            newColumnId =\n              navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;\n          }\n          break;\n        case \"ctrl+up\":\n          newRowIndex = 0;\n          break;\n        case \"ctrl+down\":\n          newRowIndex = Math.max(0, rowCount - 1);\n          break;\n        case \"pageup\":\n          if (rowVirtualizer) {\n            const visibleRange = rowVirtualizer.getVirtualItems();\n            const pageSize = visibleRange.length ?? 10;\n            newRowIndex = Math.max(0, rowIndex - pageSize);\n          } else {\n            newRowIndex = Math.max(0, rowIndex - 10);\n          }\n          break;\n        case \"pagedown\":\n          if (rowVirtualizer) {\n            const visibleRange = rowVirtualizer.getVirtualItems();\n            const pageSize = visibleRange.length ?? 10;\n            newRowIndex = Math.min(rowCount - 1, rowIndex + pageSize);\n          } else {\n            newRowIndex = Math.min(rowCount - 1, rowIndex + 10);\n          }\n          break;\n        case \"pageleft\":\n          if (currentColIndex > 0) {\n            const targetIndex = Math.max(\n              0,\n              currentColIndex - HORIZONTAL_PAGE_SIZE,\n            );\n            const targetColumnId = navigableColumnIds[targetIndex];\n            if (targetColumnId) newColumnId = targetColumnId;\n          }\n          break;\n        case \"pageright\":\n          if (currentColIndex < navigableColumnIds.length - 1) {\n            const targetIndex = Math.min(\n              navigableColumnIds.length - 1,\n              currentColIndex + HORIZONTAL_PAGE_SIZE,\n            );\n            const targetColumnId = navigableColumnIds[targetIndex];\n            if (targetColumnId) newColumnId = targetColumnId;\n          }\n          break;\n      }\n\n      if (newRowIndex !== rowIndex || newColumnId !== columnId) {\n        focusCell(newRowIndex, newColumnId);\n\n        // Calculate and apply scrolls synchronously to avoid flashing\n        const container = dataGridRef.current;\n        if (!container) return;\n\n        const targetRow = rowMapRef.current.get(newRowIndex);\n        const cellKey = getCellKey(newRowIndex, newColumnId);\n        const targetCell = cellMapRef.current.get(cellKey);\n\n        // If target row is not rendered, scroll it into view first\n        if (!targetRow) {\n          if (rowVirtualizer) {\n            const align =\n              direction === \"up\" ||\n              direction === \"pageup\" ||\n              direction === \"ctrl+up\" ||\n              direction === \"ctrl+home\"\n                ? \"start\"\n                : direction === \"down\" ||\n                    direction === \"pagedown\" ||\n                    direction === \"ctrl+down\" ||\n                    direction === \"ctrl+end\"\n                  ? \"end\"\n                  : \"center\";\n\n            rowVirtualizer.scrollToIndex(newRowIndex, { align });\n\n            // Wait for row to render before horizontal scroll\n            if (newColumnId !== columnId) {\n              requestAnimationFrame(() => {\n                const cellKeyRetry = getCellKey(newRowIndex, newColumnId);\n                const targetCellRetry = cellMapRef.current.get(cellKeyRetry);\n\n                if (targetCellRetry) {\n                  const scrollDirection = getScrollDirection(direction);\n\n                  scrollCellIntoView({\n                    container,\n                    targetCell: targetCellRetry,\n                    tableRef,\n                    viewportOffset: VIEWPORT_OFFSET,\n                    direction: scrollDirection,\n                    isRtl: dir === \"rtl\",\n                  });\n                }\n              });\n            }\n          } else {\n            // Use direct scroll calculation when virtualizer is not available\n            const rowHeightValue = getRowHeightValue(rowHeight);\n            const estimatedScrollTop = newRowIndex * rowHeightValue;\n            container.scrollTop = estimatedScrollTop;\n          }\n\n          return;\n        }\n\n        // Vertical scrolling for rendered rows that changed\n        if (newRowIndex !== rowIndex && targetRow) {\n          requestAnimationFrame(() => {\n            const containerRect = container.getBoundingClientRect();\n            const headerHeight =\n              headerRef.current?.getBoundingClientRect().height ?? 0;\n            const footerHeight =\n              footerRef.current?.getBoundingClientRect().height ?? 0;\n            const viewportTop =\n              containerRect.top + headerHeight + VIEWPORT_OFFSET;\n            const viewportBottom =\n              containerRect.bottom - footerHeight - VIEWPORT_OFFSET;\n\n            const rowRect = targetRow.getBoundingClientRect();\n            const isFullyVisible =\n              rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;\n\n            if (!isFullyVisible) {\n              // Only apply vertical scroll for vertical navigation\n              const isVerticalNavigation =\n                direction === \"up\" ||\n                direction === \"down\" ||\n                direction === \"pageup\" ||\n                direction === \"pagedown\" ||\n                direction === \"ctrl+up\" ||\n                direction === \"ctrl+down\" ||\n                direction === \"ctrl+home\" ||\n                direction === \"ctrl+end\";\n\n              if (isVerticalNavigation) {\n                if (\n                  direction === \"down\" ||\n                  direction === \"pagedown\" ||\n                  direction === \"ctrl+down\" ||\n                  direction === \"ctrl+end\"\n                ) {\n                  container.scrollTop += rowRect.bottom - viewportBottom;\n                } else {\n                  container.scrollTop -= viewportTop - rowRect.top;\n                }\n              }\n            }\n          });\n        }\n\n        // Horizontal scrolling for rendered cells\n        if (newColumnId !== columnId && targetCell) {\n          requestAnimationFrame(() => {\n            const scrollDirection = getScrollDirection(direction);\n\n            scrollCellIntoView({\n              container,\n              targetCell,\n              tableRef,\n              viewportOffset: VIEWPORT_OFFSET,\n              direction: scrollDirection,\n              isRtl: dir === \"rtl\",\n            });\n          });\n        }\n      }\n    },\n    [dir, store, navigableColumnIds, focusCell, propsRef, rowHeight],\n  );\n\n  const onCellEditingStart = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      if (propsRef.current.readOnly) return;\n\n      store.batch(() => {\n        store.setState(\"focusedCell\", { rowIndex, columnId });\n        store.setState(\"editingCell\", { rowIndex, columnId });\n      });\n    },\n    [store, propsRef],\n  );\n\n  const onCellEditingStop = React.useCallback(\n    (opts?: { moveToNextRow?: boolean; direction?: NavigationDirection }) => {\n      const currentState = store.getState();\n      const currentEditing = currentState.editingCell;\n\n      store.setState(\"editingCell\", null);\n\n      if (opts?.moveToNextRow && currentEditing) {\n        const { rowIndex, columnId } = currentEditing;\n        const currentTable = tableRef.current;\n        const rows = currentTable?.getRowModel().rows ?? [];\n        const rowCount = rows.length ?? propsRef.current.data.length;\n\n        const nextRowIndex = rowIndex + 1;\n        if (nextRowIndex < rowCount) {\n          requestAnimationFrame(() => {\n            focusCell(nextRowIndex, columnId);\n          });\n        }\n      } else if (opts?.direction && currentEditing) {\n        const { rowIndex, columnId } = currentEditing;\n        focusCell(rowIndex, columnId);\n        requestAnimationFrame(() => {\n          navigateCell(opts.direction ?? \"right\");\n        });\n      } else if (currentEditing) {\n        const { rowIndex, columnId } = currentEditing;\n        focusCellWrapper(rowIndex, columnId);\n      }\n    },\n    [store, propsRef, focusCell, navigateCell, focusCellWrapper],\n  );\n\n  const onSearchOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (open) {\n        store.setState(\"searchOpen\", true);\n        return;\n      }\n\n      const currentState = store.getState();\n      const currentMatch =\n        currentState.matchIndex >= 0 &&\n        currentState.searchMatches[currentState.matchIndex];\n\n      store.batch(() => {\n        store.setState(\"searchOpen\", false);\n        store.setState(\"searchQuery\", \"\");\n        store.setState(\"searchMatches\", []);\n        store.setState(\"matchIndex\", -1);\n\n        if (currentMatch) {\n          store.setState(\"focusedCell\", {\n            rowIndex: currentMatch.rowIndex,\n            columnId: currentMatch.columnId,\n          });\n        }\n      });\n\n      if (\n        dataGridRef.current &&\n        document.activeElement !== dataGridRef.current\n      ) {\n        dataGridRef.current.focus();\n      }\n    },\n    [store],\n  );\n\n  const onSearch = React.useCallback(\n    (query: string) => {\n      if (!query.trim()) {\n        store.batch(() => {\n          store.setState(\"searchMatches\", []);\n          store.setState(\"matchIndex\", -1);\n        });\n        return;\n      }\n\n      const matches: CellPosition[] = [];\n      const currentTable = tableRef.current;\n      const rows = currentTable?.getRowModel().rows ?? [];\n\n      const lowerQuery = query.toLowerCase();\n\n      for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n        const row = rows[rowIndex];\n        if (!row) continue;\n\n        const cellById = new Map(\n          row.getVisibleCells().map((c) => [c.column.id, c]),\n        );\n\n        for (const columnId of columnIds) {\n          const cell = cellById.get(columnId);\n          if (!cell) continue;\n\n          const value = cell.getValue();\n          const stringValue = String(value ?? \"\").toLowerCase();\n\n          if (stringValue.includes(lowerQuery)) {\n            matches.push({ rowIndex, columnId });\n          }\n        }\n      }\n\n      store.batch(() => {\n        store.setState(\"searchMatches\", matches);\n        store.setState(\"matchIndex\", matches.length > 0 ? 0 : -1);\n      });\n\n      if (matches.length > 0 && matches[0]) {\n        const firstMatch = matches[0];\n        rowVirtualizerRef.current?.scrollToIndex(firstMatch.rowIndex, {\n          align: \"center\",\n        });\n      }\n    },\n    [columnIds, store],\n  );\n\n  const onSearchQueryChange = React.useCallback(\n    (query: string) => store.setState(\"searchQuery\", query),\n    [store],\n  );\n\n  const onNavigateToPrevMatch = React.useCallback(() => {\n    const currentState = store.getState();\n    if (currentState.searchMatches.length === 0) return;\n\n    const prevIndex =\n      currentState.matchIndex - 1 < 0\n        ? currentState.searchMatches.length - 1\n        : currentState.matchIndex - 1;\n    const match = currentState.searchMatches[prevIndex];\n\n    if (match) {\n      rowVirtualizerRef.current?.scrollToIndex(match.rowIndex, {\n        align: \"center\",\n      });\n\n      requestAnimationFrame(() => {\n        store.setState(\"matchIndex\", prevIndex);\n        requestAnimationFrame(() => {\n          focusCell(match.rowIndex, match.columnId);\n        });\n      });\n    }\n  }, [store, focusCell]);\n\n  const onNavigateToNextMatch = React.useCallback(() => {\n    const currentState = store.getState();\n    if (currentState.searchMatches.length === 0) return;\n\n    const nextIndex =\n      (currentState.matchIndex + 1) % currentState.searchMatches.length;\n    const match = currentState.searchMatches[nextIndex];\n\n    if (match) {\n      rowVirtualizerRef.current?.scrollToIndex(match.rowIndex, {\n        align: \"center\",\n      });\n\n      requestAnimationFrame(() => {\n        store.setState(\"matchIndex\", nextIndex);\n        requestAnimationFrame(() => {\n          focusCell(match.rowIndex, match.columnId);\n        });\n      });\n    }\n  }, [store, focusCell]);\n\n  const searchMatchSet = React.useMemo(() => {\n    return new Set(\n      searchMatches.map((m) => getCellKey(m.rowIndex, m.columnId)),\n    );\n  }, [searchMatches]);\n\n  const getIsSearchMatch = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      return searchMatchSet.has(getCellKey(rowIndex, columnId));\n    },\n    [searchMatchSet],\n  );\n\n  const getIsActiveSearchMatch = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      const currentState = store.getState();\n      if (currentState.matchIndex < 0) return false;\n      const currentMatch = currentState.searchMatches[currentState.matchIndex];\n      return (\n        currentMatch?.rowIndex === rowIndex &&\n        currentMatch?.columnId === columnId\n      );\n    },\n    [store],\n  );\n\n  // Compute search match data for targeted row re-renders\n  const searchMatchesByRow = React.useMemo(() => {\n    if (searchMatches.length === 0) return null;\n    const rowMap = new Map<number, Set<string>>();\n    for (const match of searchMatches) {\n      let columnSet = rowMap.get(match.rowIndex);\n      if (!columnSet) {\n        columnSet = new Set<string>();\n        rowMap.set(match.rowIndex, columnSet);\n      }\n      columnSet.add(match.columnId);\n    }\n    return rowMap;\n  }, [searchMatches]);\n\n  const activeSearchMatch = React.useMemo<CellPosition | null>(() => {\n    if (matchIndex < 0 || searchMatches.length === 0) return null;\n    return searchMatches[matchIndex] ?? null;\n  }, [searchMatches, matchIndex]);\n\n  const blurCell = React.useCallback(() => {\n    const currentState = store.getState();\n    if (\n      currentState.editingCell &&\n      document.activeElement instanceof HTMLElement\n    ) {\n      document.activeElement.blur();\n    }\n\n    store.batch(() => {\n      store.setState(\"focusedCell\", null);\n      store.setState(\"editingCell\", null);\n    });\n  }, [store]);\n\n  const scrollToCell = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      requestAnimationFrame(() => {\n        const container = dataGridRef.current;\n        const cellKey = getCellKey(rowIndex, columnId);\n        const targetCell = cellMapRef.current.get(cellKey);\n\n        if (container && targetCell) {\n          scrollCellIntoView({\n            container,\n            targetCell,\n            tableRef,\n            viewportOffset: VIEWPORT_OFFSET,\n            isRtl: dir === \"rtl\",\n          });\n        }\n      });\n    },\n    [dir],\n  );\n\n  const onCellClick = React.useCallback(\n    (rowIndex: number, columnId: string, event?: React.MouseEvent) => {\n      if (event?.button === 2) return;\n\n      const currentState = store.getState();\n      const currentFocused = currentState.focusedCell;\n\n      if (event) {\n        if (event.ctrlKey || event.metaKey) {\n          event.preventDefault();\n          const cellKey = getCellKey(rowIndex, columnId);\n          const newSelectedCells = new Set(\n            currentState.selectionState.selectedCells,\n          );\n\n          if (newSelectedCells.has(cellKey)) {\n            newSelectedCells.delete(cellKey);\n          } else {\n            newSelectedCells.add(cellKey);\n          }\n\n          store.setState(\"selectionState\", {\n            selectedCells: newSelectedCells,\n            selectionRange: null,\n            isSelecting: false,\n          });\n          focusCell(rowIndex, columnId);\n          scrollToCell(rowIndex, columnId);\n          return;\n        }\n\n        if (event.shiftKey && currentState.focusedCell) {\n          event.preventDefault();\n          selectRange(currentState.focusedCell, { rowIndex, columnId });\n          scrollToCell(rowIndex, columnId);\n          return;\n        }\n      }\n\n      const hasSelectedCells =\n        currentState.selectionState.selectedCells.size > 0;\n      const hasSelectedRows = Object.keys(currentState.rowSelection).length > 0;\n\n      if (hasSelectedCells && !currentState.selectionState.isSelecting) {\n        const cellKey = getCellKey(rowIndex, columnId);\n        const isClickingSelectedCell =\n          currentState.selectionState.selectedCells.has(cellKey);\n\n        if (!isClickingSelectedCell) {\n          onSelectionClear();\n        } else {\n          focusCell(rowIndex, columnId);\n          scrollToCell(rowIndex, columnId);\n          return;\n        }\n      } else if (hasSelectedRows && columnId !== \"select\") {\n        onSelectionClear();\n      }\n\n      if (\n        currentFocused?.rowIndex === rowIndex &&\n        currentFocused?.columnId === columnId\n      ) {\n        onCellEditingStart(rowIndex, columnId);\n      } else {\n        focusCell(rowIndex, columnId);\n        scrollToCell(rowIndex, columnId);\n      }\n    },\n    [\n      store,\n      focusCell,\n      scrollToCell,\n      onCellEditingStart,\n      selectRange,\n      onSelectionClear,\n    ],\n  );\n\n  const onCellDoubleClick = React.useCallback(\n    (rowIndex: number, columnId: string, event?: React.MouseEvent) => {\n      if (event?.defaultPrevented) return;\n\n      onCellEditingStart(rowIndex, columnId);\n    },\n    [onCellEditingStart],\n  );\n\n  const onCellMouseDown = React.useCallback(\n    (rowIndex: number, columnId: string, event: React.MouseEvent) => {\n      if (event.button === 2) {\n        return;\n      }\n\n      event.preventDefault();\n\n      if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {\n        const cellKey = getCellKey(rowIndex, columnId);\n        store.batch(() => {\n          store.setState(\"selectionState\", {\n            selectedCells: propsRef.current.enableSingleCellSelection\n              ? new Set([cellKey])\n              : new Set(),\n            selectionRange: {\n              start: { rowIndex, columnId },\n              end: { rowIndex, columnId },\n            },\n            isSelecting: true,\n          });\n          store.setState(\"rowSelection\", {});\n        });\n      }\n    },\n    [store, propsRef],\n  );\n\n  const onCellMouseEnter = React.useCallback(\n    (rowIndex: number, columnId: string) => {\n      const currentState = store.getState();\n      if (\n        currentState.selectionState.isSelecting &&\n        currentState.selectionState.selectionRange\n      ) {\n        const start = currentState.selectionState.selectionRange.start;\n        const end = { rowIndex, columnId };\n\n        if (\n          currentState.focusedCell?.rowIndex !== start.rowIndex ||\n          currentState.focusedCell?.columnId !== start.columnId\n        ) {\n          focusCell(start.rowIndex, start.columnId);\n        }\n\n        selectRange(start, end, true);\n      }\n    },\n    [store, selectRange, focusCell],\n  );\n\n  const onCellMouseUp = React.useCallback(() => {\n    const currentState = store.getState();\n    store.setState(\"selectionState\", {\n      ...currentState.selectionState,\n      isSelecting: false,\n    });\n  }, [store]);\n\n  const onCellContextMenu = React.useCallback(\n    (rowIndex: number, columnId: string, event: React.MouseEvent) => {\n      event.preventDefault();\n      event.stopPropagation();\n\n      const currentState = store.getState();\n      const cellKey = getCellKey(rowIndex, columnId);\n      const isTargetCellSelected =\n        currentState.selectionState.selectedCells.has(cellKey);\n\n      if (!isTargetCellSelected) {\n        store.batch(() => {\n          store.setState(\"selectionState\", {\n            selectedCells: new Set([cellKey]),\n            selectionRange: {\n              start: { rowIndex, columnId },\n              end: { rowIndex, columnId },\n            },\n            isSelecting: false,\n          });\n          store.setState(\"focusedCell\", { rowIndex, columnId });\n        });\n      }\n\n      store.setState(\"contextMenu\", {\n        open: true,\n        x: event.clientX,\n        y: event.clientY,\n      });\n    },\n    [store],\n  );\n\n  const onContextMenuOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (!open) {\n        const currentMenu = store.getState().contextMenu;\n        store.setState(\"contextMenu\", {\n          open: false,\n          x: currentMenu.x,\n          y: currentMenu.y,\n        });\n      }\n    },\n    [store],\n  );\n\n  const onSortingChange = React.useCallback(\n    (updater: Updater<SortingState>) => {\n      const currentState = store.getState();\n      const newSorting =\n        typeof updater === \"function\" ? updater(currentState.sorting) : updater;\n      store.setState(\"sorting\", newSorting);\n\n      propsRef.current.onSortingChange?.(newSorting);\n    },\n    [store, propsRef],\n  );\n\n  const onColumnFiltersChange = React.useCallback(\n    (updater: Updater<ColumnFiltersState>) => {\n      const currentState = store.getState();\n      const newColumnFilters =\n        typeof updater === \"function\"\n          ? updater(currentState.columnFilters)\n          : updater;\n      store.setState(\"columnFilters\", newColumnFilters);\n\n      propsRef.current.onColumnFiltersChange?.(newColumnFilters);\n    },\n    [store, propsRef],\n  );\n\n  const onRowSelectionChange = React.useCallback(\n    (updater: Updater<RowSelectionState>) => {\n      const currentState = store.getState();\n      const newRowSelection =\n        typeof updater === \"function\"\n          ? updater(currentState.rowSelection)\n          : updater;\n\n      const selectedRows = Object.keys(newRowSelection).filter(\n        (key) => newRowSelection[key],\n      );\n\n      const selectedCells = new Set<string>();\n      const rows = tableRef.current?.getRowModel().rows ?? [];\n\n      for (const rowId of selectedRows) {\n        const rowIndex = rows.findIndex((r) => r.id === rowId);\n        if (rowIndex === -1) continue;\n\n        for (const columnId of columnIds) {\n          selectedCells.add(getCellKey(rowIndex, columnId));\n        }\n      }\n\n      store.batch(() => {\n        store.setState(\"rowSelection\", newRowSelection);\n        store.setState(\"selectionState\", {\n          selectedCells,\n          selectionRange: null,\n          isSelecting: false,\n        });\n        store.setState(\"focusedCell\", null);\n        store.setState(\"editingCell\", null);\n      });\n\n      propsRef.current.onRowSelectionChange?.(updater);\n    },\n    [store, columnIds, propsRef],\n  );\n\n  const onRowSelect = React.useCallback(\n    (rowId: string, selected: boolean, shiftKey: boolean) => {\n      const currentState = store.getState();\n      const rows = tableRef.current?.getRowModel().rows ?? [];\n      const currentRowIndex = rows.findIndex((r) => r.id === rowId);\n      const currentRow = currentRowIndex >= 0 ? rows[currentRowIndex] : null;\n      if (!currentRow) return;\n\n      if (shiftKey && currentState.lastClickedRowId !== null) {\n        const lastClickedRowIndex = rows.findIndex(\n          (r) => r.id === currentState.lastClickedRowId,\n        );\n        if (lastClickedRowIndex >= 0) {\n          const startIndex = Math.min(lastClickedRowIndex, currentRowIndex);\n          const endIndex = Math.max(lastClickedRowIndex, currentRowIndex);\n\n          const newRowSelection: RowSelectionState = {\n            ...currentState.rowSelection,\n          };\n\n          for (let i = startIndex; i <= endIndex; i++) {\n            const row = rows[i];\n            if (row) {\n              newRowSelection[row.id] = selected;\n            }\n          }\n\n          onRowSelectionChange(newRowSelection);\n        } else {\n          onRowSelectionChange({\n            ...currentState.rowSelection,\n            [currentRow.id]: selected,\n          });\n        }\n      } else {\n        onRowSelectionChange({\n          ...currentState.rowSelection,\n          [currentRow.id]: selected,\n        });\n      }\n\n      store.setState(\"lastClickedRowId\", rowId);\n    },\n    [store, onRowSelectionChange],\n  );\n\n  const onRowHeightChange = React.useCallback(\n    (updater: Updater<RowHeightValue>) => {\n      const currentState = store.getState();\n      const newRowHeight =\n        typeof updater === \"function\"\n          ? updater(currentState.rowHeight)\n          : updater;\n      store.setState(\"rowHeight\", newRowHeight);\n      propsRef.current.onRowHeightChange?.(newRowHeight);\n    },\n    [store, propsRef],\n  );\n\n  const onColumnClick = React.useCallback(\n    (columnId: string) => {\n      if (!propsRef.current.enableColumnSelection) {\n        onSelectionClear();\n        return;\n      }\n\n      selectColumn(columnId);\n    },\n    [propsRef, selectColumn, onSelectionClear],\n  );\n\n  const onPasteDialogOpenChange = React.useCallback(\n    (open: boolean) => {\n      if (!open) {\n        store.setState(\"pasteDialog\", {\n          open: false,\n          rowsNeeded: 0,\n          clipboardText: \"\",\n        });\n      }\n    },\n    [store],\n  );\n\n  const defaultColumn: Partial<ColumnDef<TData>> = React.useMemo(\n    () => ({\n      // Cell is rendered directly in DataGridRow to bypass flexRender's\n      // unstable cell.getContext() (https://github.com/TanStack/table/issues/4794)\n      minSize: MIN_COLUMN_SIZE,\n      maxSize: MAX_COLUMN_SIZE,\n    }),\n    [],\n  );\n\n  const tableMeta = React.useMemo<TableMeta<TData>>(() => {\n    return {\n      ...propsRef.current.meta,\n      dataGridRef,\n      cellMapRef,\n      get focusedCell() {\n        return store.getState().focusedCell;\n      },\n      get editingCell() {\n        return store.getState().editingCell;\n      },\n      get selectionState() {\n        return store.getState().selectionState;\n      },\n      get searchOpen() {\n        return store.getState().searchOpen;\n      },\n      get contextMenu() {\n        return store.getState().contextMenu;\n      },\n      get pasteDialog() {\n        return store.getState().pasteDialog;\n      },\n      get rowHeight() {\n        return store.getState().rowHeight;\n      },\n      get readOnly() {\n        return propsRef.current.readOnly;\n      },\n      getIsCellSelected,\n      getIsSearchMatch,\n      getIsActiveSearchMatch,\n      getVisualRowIndex,\n      scrollToCell: (rowIndex, columnId, align = \"auto\") => {\n        const container = dataGridRef.current;\n        if (!container) return;\n\n        rowVirtualizerRef.current?.scrollToIndex(rowIndex, { align });\n\n        const scrollRowIntoView = (retries = 1) => {\n          requestAnimationFrame(() => {\n            const targetRow = rowMapRef.current.get(rowIndex);\n            if (!targetRow) {\n              if (retries > 0) scrollRowIntoView(retries - 1);\n              return;\n            }\n\n            const headerBottom =\n              headerRef.current?.getBoundingClientRect().bottom ??\n              container.getBoundingClientRect().top;\n\n            const viewportTop = headerBottom + VIEWPORT_OFFSET;\n\n            const rowRect = targetRow.getBoundingClientRect();\n\n            if (rowRect.top < viewportTop) {\n              container.scrollTop -= viewportTop - rowRect.top;\n            }\n\n            const cellKey = getCellKey(rowIndex, columnId);\n            const targetCell = cellMapRef.current.get(cellKey);\n            if (targetCell) {\n              scrollCellIntoView({\n                container,\n                targetCell,\n                tableRef,\n                viewportOffset: VIEWPORT_OFFSET,\n                isRtl: dir === \"rtl\",\n              });\n            }\n          });\n        };\n\n        scrollRowIntoView();\n      },\n      onRowHeightChange,\n      onRowSelect,\n      onDataUpdate,\n      onRowsDelete: propsRef.current.onRowsDelete ? onRowsDelete : undefined,\n      onColumnClick,\n      onCellClick,\n      onCellDoubleClick,\n      onCellMouseDown,\n      onCellMouseEnter,\n      onCellMouseUp,\n      onCellContextMenu,\n      onCellEditingStart,\n      onCellEditingStop,\n      onCellsCopy,\n      onCellsCut,\n      onCellsPaste,\n      onSelectionClear,\n      onFilesUpload: propsRef.current.onFilesUpload\n        ? propsRef.current.onFilesUpload\n        : undefined,\n      onFilesDelete: propsRef.current.onFilesDelete\n        ? propsRef.current.onFilesDelete\n        : undefined,\n      onContextMenuOpenChange,\n      onPasteDialogOpenChange,\n    };\n  }, [\n    propsRef,\n    store,\n    dir,\n    getIsCellSelected,\n    getIsSearchMatch,\n    getIsActiveSearchMatch,\n    getVisualRowIndex,\n    onRowHeightChange,\n    onRowSelect,\n    onDataUpdate,\n    onRowsDelete,\n    onColumnClick,\n    onCellClick,\n    onCellDoubleClick,\n    onCellMouseDown,\n    onCellMouseEnter,\n    onCellMouseUp,\n    onCellContextMenu,\n    onCellEditingStart,\n    onCellEditingStop,\n    onCellsCopy,\n    onCellsCut,\n    onCellsPaste,\n    onSelectionClear,\n    onContextMenuOpenChange,\n    onPasteDialogOpenChange,\n  ]);\n\n  const getMemoizedCoreRowModel = React.useMemo(() => getCoreRowModel(), []);\n  const getMemoizedFilteredRowModel = React.useMemo(\n    () => getFilteredRowModel(),\n    [],\n  );\n  const getMemoizedSortedRowModel = React.useMemo(\n    () => getSortedRowModel(),\n    [],\n  );\n\n  // Memoize state object to reduce shallow equality checks\n  const tableState = React.useMemo<Partial<TableState>>(\n    () => ({\n      ...propsRef.current.state,\n      sorting,\n      columnFilters,\n      rowSelection,\n    }),\n    [propsRef, sorting, columnFilters, rowSelection],\n  );\n\n  const tableOptions = React.useMemo<TableOptions<TData>>(() => {\n    return {\n      ...propsRef.current,\n      data,\n      columns,\n      defaultColumn,\n      initialState: propsRef.current.initialState,\n      state: tableState,\n      onRowSelectionChange,\n      onSortingChange,\n      onColumnFiltersChange,\n      columnResizeMode: \"onChange\",\n      columnResizeDirection: dir,\n      getCoreRowModel: getMemoizedCoreRowModel,\n      getFilteredRowModel: getMemoizedFilteredRowModel,\n      getSortedRowModel: getMemoizedSortedRowModel,\n      meta: tableMeta,\n    };\n  }, [\n    propsRef,\n    data,\n    columns,\n    defaultColumn,\n    tableState,\n    dir,\n    onRowSelectionChange,\n    onSortingChange,\n    onColumnFiltersChange,\n    getMemoizedCoreRowModel,\n    getMemoizedFilteredRowModel,\n    getMemoizedSortedRowModel,\n    tableMeta,\n  ]);\n\n  const table = useReactTable(tableOptions);\n\n  if (!tableRef.current) {\n    tableRef.current = table;\n  }\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: columnSizingInfo and columnSizing are used for calculating the column size vars\n  const columnSizeVars = React.useMemo(() => {\n    const headers = table.getFlatHeaders();\n    const colSizes: { [key: string]: number } = {};\n    for (const header of headers) {\n      colSizes[`--header-${header.id}-size`] = header.getSize();\n      colSizes[`--col-${header.column.id}-size`] = header.column.getSize();\n    }\n    return colSizes;\n  }, [table.getState().columnSizingInfo, table.getState().columnSizing]);\n\n  const isFirefox = React.useSyncExternalStore(\n    React.useCallback(() => () => {}, []),\n    React.useCallback(() => {\n      if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n        return false;\n      }\n      return navigator.userAgent.indexOf(\"Firefox\") !== -1;\n    }, []),\n    React.useCallback(() => false, []),\n  );\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: columnPinning is used for calculating the adjustLayout\n  const adjustLayout = React.useMemo(() => {\n    const columnPinning = table.getState().columnPinning;\n    return (\n      isFirefox &&\n      ((columnPinning.left?.length ?? 0) > 0 ||\n        (columnPinning.right?.length ?? 0) > 0)\n    );\n  }, [isFirefox, table.getState().columnPinning]);\n\n  const rowVirtualizer = useVirtualizer({\n    count: table.getRowModel().rows.length,\n    getScrollElement: () => dataGridRef.current,\n    estimateSize: () => rowHeightValue,\n    overscan,\n    measureElement: !isFirefox\n      ? (element) => element?.getBoundingClientRect().height\n      : undefined,\n    scrollPaddingStart:\n      (headerRef.current?.getBoundingClientRect().bottom ?? 0) -\n      (dataGridRef.current?.getBoundingClientRect().top ?? 0) +\n      VIEWPORT_OFFSET,\n    // Add extra row buffer to absorb virtual position drift after render measurements\n    scrollPaddingEnd:\n      (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) -\n      (footerRef.current?.getBoundingClientRect().top ??\n        dataGridRef.current?.getBoundingClientRect().bottom ??\n        0) +\n      rowHeightValue +\n      VIEWPORT_OFFSET,\n  });\n\n  if (!rowVirtualizerRef.current) {\n    rowVirtualizerRef.current = rowVirtualizer;\n  }\n\n  const onScrollToRow = React.useCallback(\n    async (opts: Partial<CellPosition>) => {\n      const rowIndex = opts?.rowIndex ?? 0;\n      const columnId = opts?.columnId;\n\n      focusGuardRef.current = true;\n\n      const navigableIds = propsRef.current.columns\n        .map((c) => {\n          if (c.id) return c.id;\n          if (\"accessorKey\" in c) return c.accessorKey as string;\n          return undefined;\n        })\n        .filter((id): id is string => Boolean(id))\n        .filter((c) => !NON_NAVIGABLE_COLUMN_IDS.has(c));\n\n      const targetColumnId = columnId ?? navigableIds[0];\n\n      if (!targetColumnId) {\n        releaseFocusGuard(true);\n        return;\n      }\n\n      async function onScrollAndFocus(retryCount: number) {\n        if (!targetColumnId) return;\n        const currentRowCount = propsRef.current.data.length;\n\n        // If the requested row doesn't exist yet, wait for data to update\n        if (rowIndex >= currentRowCount && retryCount > 0) {\n          await new Promise((resolve) => setTimeout(resolve, 50));\n          await onScrollAndFocus(retryCount - 1);\n          return;\n        }\n\n        const safeRowIndex = Math.min(\n          rowIndex,\n          Math.max(0, currentRowCount - 1),\n        );\n\n        const isBottomHalf = safeRowIndex > currentRowCount / 2;\n        rowVirtualizer.scrollToIndex(safeRowIndex, {\n          align: isBottomHalf ? \"end\" : \"start\",\n        });\n\n        await new Promise((resolve) => requestAnimationFrame(resolve));\n\n        // Adjust scroll position to account for sticky header/footer\n        const container = dataGridRef.current;\n        const targetRow = rowMapRef.current.get(safeRowIndex);\n\n        if (container && targetRow) {\n          const containerRect = container.getBoundingClientRect();\n          const headerHeight =\n            headerRef.current?.getBoundingClientRect().height ?? 0;\n          const footerHeight =\n            footerRef.current?.getBoundingClientRect().height ?? 0;\n\n          const viewportTop =\n            containerRect.top + headerHeight + VIEWPORT_OFFSET;\n          const viewportBottom =\n            containerRect.bottom - footerHeight - VIEWPORT_OFFSET;\n\n          const rowRect = targetRow.getBoundingClientRect();\n          const isFullyVisible =\n            rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;\n\n          if (!isFullyVisible) {\n            if (rowRect.top < viewportTop) {\n              // Scroll up as row is partially hidden by header\n              container.scrollTop -= viewportTop - rowRect.top;\n            } else if (rowRect.bottom > viewportBottom) {\n              // Scroll down as row is partially hidden by footer\n              container.scrollTop += rowRect.bottom - viewportBottom;\n            }\n          }\n        }\n\n        store.batch(() => {\n          store.setState(\"focusedCell\", {\n            rowIndex: safeRowIndex,\n            columnId: targetColumnId,\n          });\n          store.setState(\"editingCell\", null);\n        });\n\n        const cellKey = getCellKey(safeRowIndex, targetColumnId);\n        const cellElement = cellMapRef.current.get(cellKey);\n\n        if (cellElement) {\n          cellElement.focus();\n          releaseFocusGuard();\n        } else if (retryCount > 0) {\n          await new Promise((resolve) => requestAnimationFrame(resolve));\n          await onScrollAndFocus(retryCount - 1);\n        } else {\n          dataGridRef.current?.focus();\n          releaseFocusGuard();\n        }\n      }\n\n      await onScrollAndFocus(SCROLL_SYNC_RETRY_COUNT);\n    },\n    [rowVirtualizer, propsRef, store, releaseFocusGuard],\n  );\n\n  const onRowAdd = React.useCallback(\n    async (event?: React.MouseEvent<HTMLDivElement>) => {\n      if (propsRef.current.readOnly || !propsRef.current.onRowAdd) return;\n\n      const initialRowCount = propsRef.current.data.length;\n\n      let result: Partial<CellPosition> | null;\n      try {\n        result = await propsRef.current.onRowAdd(event);\n      } catch {\n        // Callback threw an error, don't proceed with scroll/focus\n        return;\n      }\n\n      if (result === null || event?.defaultPrevented) return;\n\n      onSelectionClear();\n\n      // onScrollToRow will handle retries if the row isn't rendered yet\n      const targetRowIndex = result.rowIndex ?? initialRowCount;\n      const targetColumnId = result.columnId;\n\n      onScrollToRow({\n        rowIndex: targetRowIndex,\n        columnId: targetColumnId,\n      });\n    },\n    [propsRef, onScrollToRow, onSelectionClear],\n  );\n\n  const onDataGridKeyDown = React.useCallback(\n    (event: KeyboardEvent) => {\n      const currentState = store.getState();\n      const { key, ctrlKey, metaKey, shiftKey, altKey } = event;\n      const isCtrlPressed = ctrlKey || metaKey;\n\n      if (\n        propsRef.current.enableSearch &&\n        isCtrlPressed &&\n        !shiftKey &&\n        key === SEARCH_SHORTCUT_KEY\n      ) {\n        event.preventDefault();\n        onSearchOpenChange(true);\n        return;\n      }\n\n      if (\n        propsRef.current.enableSearch &&\n        currentState.searchOpen &&\n        !currentState.editingCell\n      ) {\n        if (key === \"Enter\") {\n          event.preventDefault();\n          if (shiftKey) {\n            onNavigateToPrevMatch();\n          } else {\n            onNavigateToNextMatch();\n          }\n          return;\n        }\n        if (key === \"Escape\") {\n          event.preventDefault();\n          onSearchOpenChange(false);\n          return;\n        }\n        return;\n      }\n\n      // Cell editing keyboard events (Enter, Tab, Escape) are handled by the cell variants\n      // to ensure proper value commitment before navigation\n      if (currentState.editingCell) return;\n\n      if (\n        isCtrlPressed &&\n        (key === \"Backspace\" || key === \"Delete\") &&\n        !propsRef.current.readOnly &&\n        propsRef.current.onRowsDelete\n      ) {\n        const rowIndices = new Set<number>();\n\n        const selectedRowIds = Object.keys(currentState.rowSelection);\n        if (selectedRowIds.length > 0) {\n          const currentTable = tableRef.current;\n          const rows = currentTable?.getRowModel().rows ?? [];\n          for (const row of rows) {\n            if (currentState.rowSelection[row.id]) {\n              rowIndices.add(row.index);\n            }\n          }\n        } else if (currentState.selectionState.selectedCells.size > 0) {\n          for (const cellKey of currentState.selectionState.selectedCells) {\n            const { rowIndex } = parseCellKey(cellKey);\n            rowIndices.add(rowIndex);\n          }\n        } else if (currentState.focusedCell) {\n          rowIndices.add(currentState.focusedCell.rowIndex);\n        }\n\n        if (rowIndices.size > 0) {\n          event.preventDefault();\n          onRowsDelete(Array.from(rowIndices));\n        }\n        return;\n      }\n\n      if (!currentState.focusedCell) return;\n\n      let direction: NavigationDirection | null = null;\n\n      if (isCtrlPressed && !shiftKey && key === \"a\") {\n        event.preventDefault();\n        selectAll();\n        return;\n      }\n\n      if (isCtrlPressed && !shiftKey && key === \"c\") {\n        event.preventDefault();\n        onCellsCopy();\n        return;\n      }\n\n      if (\n        isCtrlPressed &&\n        !shiftKey &&\n        key === \"x\" &&\n        !propsRef.current.readOnly\n      ) {\n        event.preventDefault();\n        onCellsCut();\n        return;\n      }\n\n      if (\n        propsRef.current.enablePaste &&\n        isCtrlPressed &&\n        !shiftKey &&\n        key === \"v\" &&\n        !propsRef.current.readOnly\n      ) {\n        event.preventDefault();\n        onCellsPaste();\n        return;\n      }\n\n      if (\n        (key === \"Delete\" || key === \"Backspace\") &&\n        !isCtrlPressed &&\n        !propsRef.current.readOnly\n      ) {\n        const cellsToClear =\n          currentState.selectionState.selectedCells.size > 0\n            ? Array.from(currentState.selectionState.selectedCells)\n            : currentState.focusedCell\n              ? [\n                  getCellKey(\n                    currentState.focusedCell.rowIndex,\n                    currentState.focusedCell.columnId,\n                  ),\n                ]\n              : [];\n\n        if (cellsToClear.length > 0) {\n          event.preventDefault();\n\n          const updates: Array<{\n            rowIndex: number;\n            columnId: string;\n            value: unknown;\n          }> = [];\n\n          const currentTable = tableRef.current;\n          const tableColumns = currentTable?.getAllColumns() ?? [];\n          const columnById = new Map(tableColumns.map((c) => [c.id, c]));\n\n          for (const cellKey of cellsToClear) {\n            const { rowIndex, columnId } = parseCellKey(cellKey);\n            const column = columnById.get(columnId);\n            const cellVariant = column?.columnDef?.meta?.cell?.variant;\n            const emptyValue = getEmptyCellValue(cellVariant);\n            updates.push({ rowIndex, columnId, value: emptyValue });\n          }\n\n          onDataUpdate(updates);\n\n          if (currentState.selectionState.selectedCells.size > 0) {\n            onSelectionClear();\n          }\n\n          if (currentState.cutCells.size > 0) {\n            store.setState(\"cutCells\", new Set());\n          }\n        }\n        return;\n      }\n\n      if (\n        key === \"Enter\" &&\n        shiftKey &&\n        !propsRef.current.readOnly &&\n        propsRef.current.onRowAdd\n      ) {\n        event.preventDefault();\n        const initialRowCount = propsRef.current.data.length;\n        const currentColumnId = currentState.focusedCell.columnId;\n\n        Promise.resolve(propsRef.current.onRowAdd())\n          .then(async (result) => {\n            if (result === null) return;\n\n            onSelectionClear();\n\n            const targetRowIndex = result.rowIndex ?? initialRowCount;\n            const targetColumnId = result.columnId ?? currentColumnId;\n\n            onScrollToRow({\n              rowIndex: targetRowIndex,\n              columnId: targetColumnId,\n            });\n          })\n          .catch(() => {\n            // Callback threw an error, don't proceed with scroll/focus\n          });\n        return;\n      }\n\n      switch (key) {\n        case \"ArrowUp\":\n          if (altKey && !isCtrlPressed && !shiftKey) {\n            direction = \"pageup\";\n          } else if (isCtrlPressed && shiftKey) {\n            const selectionEdge =\n              currentState.selectionState.selectionRange?.end ||\n              currentState.focusedCell;\n            const currentColIndex = navigableColumnIds.indexOf(\n              selectionEdge.columnId,\n            );\n            const selectionStart =\n              currentState.selectionState.selectionRange?.start ||\n              currentState.focusedCell;\n\n            selectRange(selectionStart, {\n              rowIndex: 0,\n              columnId:\n                navigableColumnIds[currentColIndex] ?? selectionEdge.columnId,\n            });\n\n            const rowVirtualizer = rowVirtualizerRef.current;\n            if (rowVirtualizer) {\n              rowVirtualizer.scrollToIndex(0, { align: \"start\" });\n            }\n\n            restoreFocus(dataGridRef.current);\n\n            event.preventDefault();\n            return;\n          } else if (isCtrlPressed && !shiftKey) {\n            direction = \"ctrl+up\";\n          } else {\n            direction = \"up\";\n          }\n          break;\n        case \"ArrowDown\":\n          if (altKey && !isCtrlPressed && !shiftKey) {\n            direction = \"pagedown\";\n          } else if (isCtrlPressed && shiftKey) {\n            const rowCount =\n              tableRef.current?.getRowModel().rows.length ||\n              propsRef.current.data.length;\n            const selectionEdge =\n              currentState.selectionState.selectionRange?.end ||\n              currentState.focusedCell;\n            const currentColIndex = navigableColumnIds.indexOf(\n              selectionEdge.columnId,\n            );\n            const selectionStart =\n              currentState.selectionState.selectionRange?.start ||\n              currentState.focusedCell;\n\n            selectRange(selectionStart, {\n              rowIndex: Math.max(0, rowCount - 1),\n              columnId:\n                navigableColumnIds[currentColIndex] ?? selectionEdge.columnId,\n            });\n\n            const rowVirtualizer = rowVirtualizerRef.current;\n            if (rowVirtualizer) {\n              rowVirtualizer.scrollToIndex(Math.max(0, rowCount - 1), {\n                align: \"end\",\n              });\n            }\n\n            restoreFocus(dataGridRef.current);\n\n            event.preventDefault();\n            return;\n          } else if (isCtrlPressed && !shiftKey) {\n            direction = \"ctrl+down\";\n          } else {\n            direction = \"down\";\n          }\n          break;\n        case \"ArrowLeft\":\n          if (isCtrlPressed && shiftKey) {\n            const selectionEdge =\n              currentState.selectionState.selectionRange?.end ||\n              currentState.focusedCell;\n            const selectionStart =\n              currentState.selectionState.selectionRange?.start ||\n              currentState.focusedCell;\n            const targetColumnId =\n              dir === \"rtl\"\n                ? navigableColumnIds[navigableColumnIds.length - 1]\n                : navigableColumnIds[0];\n\n            if (targetColumnId) {\n              selectRange(selectionStart, {\n                rowIndex: selectionEdge.rowIndex,\n                columnId: targetColumnId,\n              });\n\n              const container = dataGridRef.current;\n              const cellKey = getCellKey(\n                selectionEdge.rowIndex,\n                targetColumnId,\n              );\n              const targetCell = cellMapRef.current.get(cellKey);\n              if (container && targetCell) {\n                scrollCellIntoView({\n                  container,\n                  targetCell,\n                  tableRef,\n                  viewportOffset: VIEWPORT_OFFSET,\n                  direction: \"home\",\n                  isRtl: dir === \"rtl\",\n                });\n              }\n\n              restoreFocus(container);\n            }\n            event.preventDefault();\n            return;\n          } else if (isCtrlPressed && !shiftKey) {\n            direction = \"home\";\n          } else {\n            direction = \"left\";\n          }\n          break;\n        case \"ArrowRight\":\n          if (isCtrlPressed && shiftKey) {\n            const selectionEdge =\n              currentState.selectionState.selectionRange?.end ||\n              currentState.focusedCell;\n            const selectionStart =\n              currentState.selectionState.selectionRange?.start ||\n              currentState.focusedCell;\n            const targetColumnId =\n              dir === \"rtl\"\n                ? navigableColumnIds[0]\n                : navigableColumnIds[navigableColumnIds.length - 1];\n\n            if (targetColumnId) {\n              selectRange(selectionStart, {\n                rowIndex: selectionEdge.rowIndex,\n                columnId: targetColumnId,\n              });\n\n              const container = dataGridRef.current;\n              const cellKey = getCellKey(\n                selectionEdge.rowIndex,\n                targetColumnId,\n              );\n              const targetCell = cellMapRef.current.get(cellKey);\n              if (container && targetCell) {\n                scrollCellIntoView({\n                  container,\n                  targetCell,\n                  tableRef,\n                  viewportOffset: VIEWPORT_OFFSET,\n                  direction: \"end\",\n                  isRtl: dir === \"rtl\",\n                });\n              }\n\n              restoreFocus(container);\n            }\n            event.preventDefault();\n            return;\n          } else if (isCtrlPressed && !shiftKey) {\n            direction = \"end\";\n          } else {\n            direction = \"right\";\n          }\n          break;\n        case \"Home\":\n          direction = isCtrlPressed ? \"ctrl+home\" : \"home\";\n          break;\n        case \"End\":\n          direction = isCtrlPressed ? \"ctrl+end\" : \"end\";\n          break;\n        case \"PageUp\":\n          direction = altKey ? \"pageleft\" : \"pageup\";\n          break;\n        case \"PageDown\":\n          direction = altKey ? \"pageright\" : \"pagedown\";\n          break;\n        case \"Escape\":\n          event.preventDefault();\n          if (\n            currentState.selectionState.selectedCells.size > 0 ||\n            Object.keys(currentState.rowSelection).length > 0\n          ) {\n            onSelectionClear();\n          } else {\n            blurCell();\n          }\n          return;\n        case \"Tab\":\n          event.preventDefault();\n          if (dir === \"rtl\") {\n            direction = event.shiftKey ? \"right\" : \"left\";\n          } else {\n            direction = event.shiftKey ? \"left\" : \"right\";\n          }\n          break;\n      }\n\n      if (direction) {\n        event.preventDefault();\n\n        if (shiftKey && key !== \"Tab\" && currentState.focusedCell) {\n          const selectionEdge =\n            currentState.selectionState.selectionRange?.end ||\n            currentState.focusedCell;\n\n          const currentColIndex = navigableColumnIds.indexOf(\n            selectionEdge.columnId,\n          );\n          let newRowIndex = selectionEdge.rowIndex;\n          let newColumnId = selectionEdge.columnId;\n\n          const isRtl = dir === \"rtl\";\n\n          const rowCount =\n            tableRef.current?.getRowModel().rows.length ||\n            propsRef.current.data.length;\n\n          switch (direction) {\n            case \"up\":\n              newRowIndex = Math.max(0, selectionEdge.rowIndex - 1);\n              break;\n            case \"down\":\n              newRowIndex = Math.min(rowCount - 1, selectionEdge.rowIndex + 1);\n              break;\n            case \"left\":\n              if (isRtl) {\n                if (currentColIndex < navigableColumnIds.length - 1) {\n                  const nextColumnId = navigableColumnIds[currentColIndex + 1];\n                  if (nextColumnId) newColumnId = nextColumnId;\n                }\n              } else {\n                if (currentColIndex > 0) {\n                  const prevColumnId = navigableColumnIds[currentColIndex - 1];\n                  if (prevColumnId) newColumnId = prevColumnId;\n                }\n              }\n              break;\n            case \"right\":\n              if (isRtl) {\n                if (currentColIndex > 0) {\n                  const prevColumnId = navigableColumnIds[currentColIndex - 1];\n                  if (prevColumnId) newColumnId = prevColumnId;\n                }\n              } else {\n                if (currentColIndex < navigableColumnIds.length - 1) {\n                  const nextColumnId = navigableColumnIds[currentColIndex + 1];\n                  if (nextColumnId) newColumnId = nextColumnId;\n                }\n              }\n              break;\n            case \"home\":\n              if (navigableColumnIds.length > 0) {\n                newColumnId = navigableColumnIds[0] ?? newColumnId;\n              }\n              break;\n            case \"end\":\n              if (navigableColumnIds.length > 0) {\n                newColumnId =\n                  navigableColumnIds[navigableColumnIds.length - 1] ??\n                  newColumnId;\n              }\n              break;\n          }\n\n          const selectionStart =\n            currentState.selectionState.selectionRange?.start ||\n            currentState.focusedCell;\n\n          selectRange(selectionStart, {\n            rowIndex: newRowIndex,\n            columnId: newColumnId,\n          });\n\n          const container = dataGridRef.current;\n          const targetRow = rowMapRef.current.get(newRowIndex);\n          const cellKey = getCellKey(newRowIndex, newColumnId);\n          const targetCell = cellMapRef.current.get(cellKey);\n\n          if (\n            newRowIndex !== selectionEdge.rowIndex &&\n            (direction === \"up\" || direction === \"down\")\n          ) {\n            if (container && targetRow) {\n              const containerRect = container.getBoundingClientRect();\n              const headerHeight =\n                headerRef.current?.getBoundingClientRect().height ?? 0;\n              const footerHeight =\n                footerRef.current?.getBoundingClientRect().height ?? 0;\n\n              const viewportTop =\n                containerRect.top + headerHeight + VIEWPORT_OFFSET;\n              const viewportBottom =\n                containerRect.bottom - footerHeight - VIEWPORT_OFFSET;\n\n              const rowRect = targetRow.getBoundingClientRect();\n              const isFullyVisible =\n                rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;\n\n              if (!isFullyVisible) {\n                const scrollNeeded =\n                  direction === \"down\"\n                    ? rowRect.bottom - viewportBottom\n                    : viewportTop - rowRect.top;\n\n                if (direction === \"down\") {\n                  container.scrollTop += scrollNeeded;\n                } else {\n                  container.scrollTop -= scrollNeeded;\n                }\n\n                restoreFocus(container);\n              }\n            } else {\n              const rowVirtualizer = rowVirtualizerRef.current;\n              if (rowVirtualizer) {\n                const align = direction === \"up\" ? \"start\" : \"end\";\n                rowVirtualizer.scrollToIndex(newRowIndex, { align });\n\n                restoreFocus(container);\n              }\n            }\n          }\n\n          if (\n            newColumnId !== selectionEdge.columnId &&\n            (direction === \"left\" ||\n              direction === \"right\" ||\n              direction === \"home\" ||\n              direction === \"end\")\n          ) {\n            if (container && targetCell) {\n              scrollCellIntoView({\n                container,\n                targetCell,\n                tableRef,\n                viewportOffset: VIEWPORT_OFFSET,\n                direction,\n                isRtl,\n              });\n            }\n          }\n        } else {\n          if (currentState.selectionState.selectedCells.size > 0) {\n            onSelectionClear();\n          }\n          navigateCell(direction);\n        }\n      }\n    },\n    [\n      dir,\n      store,\n      propsRef,\n      blurCell,\n      navigateCell,\n      selectAll,\n      onCellsCopy,\n      onCellsCut,\n      onCellsPaste,\n      onDataUpdate,\n      onSelectionClear,\n      navigableColumnIds,\n      selectRange,\n      onSearchOpenChange,\n      onNavigateToNextMatch,\n      onNavigateToPrevMatch,\n      onRowsDelete,\n      restoreFocus,\n      onScrollToRow,\n    ],\n  );\n\n  const searchState = React.useMemo<SearchState | undefined>(() => {\n    if (!propsRef.current.enableSearch) return undefined;\n\n    return {\n      searchMatches,\n      matchIndex,\n      searchOpen,\n      onSearchOpenChange,\n      searchQuery,\n      onSearchQueryChange,\n      onSearch,\n      onNavigateToNextMatch,\n      onNavigateToPrevMatch,\n    };\n  }, [\n    propsRef,\n    searchMatches,\n    matchIndex,\n    searchOpen,\n    onSearchOpenChange,\n    searchQuery,\n    onSearchQueryChange,\n    onSearch,\n    onNavigateToNextMatch,\n    onNavigateToPrevMatch,\n  ]);\n\n  React.useEffect(() => {\n    const dataGridElement = dataGridRef.current;\n    if (!dataGridElement) return;\n\n    dataGridElement.addEventListener(\"keydown\", onDataGridKeyDown);\n    return () => {\n      dataGridElement.removeEventListener(\"keydown\", onDataGridKeyDown);\n    };\n  }, [onDataGridKeyDown]);\n\n  React.useEffect(() => {\n    function onGlobalKeyDown(event: KeyboardEvent) {\n      const dataGridElement = dataGridRef.current;\n      if (!dataGridElement) return;\n\n      const target = event.target;\n      if (!(target instanceof HTMLElement)) return;\n\n      const { key, ctrlKey, metaKey, shiftKey } = event;\n      const isCommandPressed = ctrlKey || metaKey;\n\n      if (\n        propsRef.current.enableSearch &&\n        isCommandPressed &&\n        !shiftKey &&\n        key === SEARCH_SHORTCUT_KEY\n      ) {\n        const isInInput =\n          target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\";\n        const isInDataGrid = dataGridElement.contains(target);\n        const isInSearchInput = target.closest('[role=\"search\"]') !== null;\n\n        if (isInDataGrid || isInSearchInput || !isInInput) {\n          event.preventDefault();\n          event.stopPropagation();\n\n          const nextSearchOpen = !store.getState().searchOpen;\n          onSearchOpenChange(nextSearchOpen);\n\n          if (nextSearchOpen && !isInDataGrid && !isInSearchInput) {\n            requestAnimationFrame(() => {\n              dataGridElement.focus();\n            });\n          }\n          return;\n        }\n      }\n\n      const isInDataGrid = dataGridElement.contains(target);\n      if (!isInDataGrid) return;\n\n      if (key === \"Escape\") {\n        const currentState = store.getState();\n        const hasSelections =\n          currentState.selectionState.selectedCells.size > 0 ||\n          Object.keys(currentState.rowSelection).length > 0;\n\n        if (hasSelections) {\n          event.preventDefault();\n          event.stopPropagation();\n          onSelectionClear();\n        }\n      }\n    }\n\n    window.addEventListener(\"keydown\", onGlobalKeyDown, true);\n    return () => {\n      window.removeEventListener(\"keydown\", onGlobalKeyDown, true);\n    };\n  }, [propsRef, onSearchOpenChange, store, onSelectionClear]);\n\n  React.useEffect(() => {\n    const currentState = store.getState();\n    const autoFocus = propsRef.current.autoFocus;\n\n    if (\n      autoFocus &&\n      data.length > 0 &&\n      columns.length > 0 &&\n      !currentState.focusedCell\n    ) {\n      if (navigableColumnIds.length > 0) {\n        const rafId = requestAnimationFrame(() => {\n          if (typeof autoFocus === \"object\") {\n            const { rowIndex, columnId } = autoFocus;\n            if (columnId) {\n              focusCell(rowIndex ?? 0, columnId);\n            }\n            return;\n          }\n\n          const firstColumnId = navigableColumnIds[0];\n          if (firstColumnId) {\n            focusCell(0, firstColumnId);\n          }\n        });\n        return () => cancelAnimationFrame(rafId);\n      }\n    }\n  }, [store, propsRef, data, columns, navigableColumnIds, focusCell]);\n\n  // Restore focus to container when virtualized cells are unmounted\n  React.useEffect(() => {\n    const container = dataGridRef.current;\n    if (!container) return;\n\n    function onFocusOut(event: FocusEvent) {\n      if (focusGuardRef.current) return;\n\n      const currentContainer = dataGridRef.current;\n      if (!currentContainer) return;\n\n      const currentState = store.getState();\n\n      if (!currentState.focusedCell || currentState.editingCell) return;\n\n      const relatedTarget = event.relatedTarget;\n\n      const isFocusMovingOutsideGrid =\n        !relatedTarget || !currentContainer.contains(relatedTarget as Node);\n\n      const isFocusMovingToPopover = getIsInPopover(relatedTarget);\n\n      if (isFocusMovingOutsideGrid && !isFocusMovingToPopover) {\n        const { rowIndex, columnId } = currentState.focusedCell;\n        const cellKey = getCellKey(rowIndex, columnId);\n        const cellElement = cellMapRef.current.get(cellKey);\n\n        requestAnimationFrame(() => {\n          if (focusGuardRef.current) return;\n\n          if (cellElement && document.body.contains(cellElement)) {\n            cellElement.focus();\n          } else {\n            currentContainer.focus();\n          }\n        });\n      }\n    }\n\n    container.addEventListener(\"focusout\", onFocusOut);\n\n    return () => {\n      container.removeEventListener(\"focusout\", onFocusOut);\n    };\n  }, [store]);\n\n  React.useEffect(() => {\n    function onOutsideClick(event: MouseEvent) {\n      if (event.button === 2) {\n        return;\n      }\n\n      if (\n        dataGridRef.current &&\n        !dataGridRef.current.contains(event.target as Node)\n      ) {\n        const elements = document.elementsFromPoint(\n          event.clientX,\n          event.clientY,\n        );\n\n        // Compensate for event.target bubbling up\n        const isInsidePopover = elements.some((element) =>\n          getIsInPopover(element),\n        );\n\n        if (!isInsidePopover) {\n          blurCell();\n          const currentState = store.getState();\n          if (\n            currentState.selectionState.selectedCells.size > 0 ||\n            Object.keys(currentState.rowSelection).length > 0\n          ) {\n            onSelectionClear();\n          }\n        }\n      }\n    }\n\n    document.addEventListener(\"mousedown\", onOutsideClick);\n    return () => {\n      document.removeEventListener(\"mousedown\", onOutsideClick);\n    };\n  }, [store, blurCell, onSelectionClear]);\n\n  React.useEffect(() => {\n    function onSelectStart(event: Event) {\n      event.preventDefault();\n    }\n\n    function onContextMenu(event: Event) {\n      event.preventDefault();\n    }\n\n    function onCleanup() {\n      document.removeEventListener(\"selectstart\", onSelectStart);\n      document.removeEventListener(\"contextmenu\", onContextMenu);\n      document.body.style.userSelect = \"\";\n    }\n\n    const onUnsubscribe = store.subscribe(() => {\n      const currentState = store.getState();\n      if (currentState.selectionState.isSelecting) {\n        document.addEventListener(\"selectstart\", onSelectStart);\n        document.addEventListener(\"contextmenu\", onContextMenu);\n        document.body.style.userSelect = \"none\";\n      } else {\n        onCleanup();\n      }\n    });\n\n    return () => {\n      onCleanup();\n      onUnsubscribe();\n    };\n  }, [store]);\n\n  React.useEffect(() => {\n    let rafId: number | null = null;\n    let mouseX = 0;\n    let mouseY = 0;\n    let mouseReady = false;\n    let active = false;\n    let lastSelectionTime = 0;\n    let resizeObserver: ResizeObserver | null = null;\n\n    let cachedRect: DOMRect | null = null;\n    let cachedHdrH = 0;\n    let cachedFtrH = 0;\n    let cachedLpw = 0;\n    let cachedRpw = 0;\n\n    function getAutoScrollSpeed(dist: number): number {\n      const t = Math.min(dist / AUTO_SCROLL_SPEED_RAMP_ZONE, 1);\n      return Math.round(\n        AUTO_SCROLL_MIN_SPEED +\n          (AUTO_SCROLL_MAX_SPEED - AUTO_SCROLL_MIN_SPEED) * t,\n      );\n    }\n\n    function cacheLayout(container: HTMLDivElement) {\n      cachedRect = container.getBoundingClientRect();\n      cachedHdrH = headerRef.current?.getBoundingClientRect().height ?? 0;\n      cachedFtrH = footerRef.current?.getBoundingClientRect().height ?? 0;\n      const tbl = tableRef.current;\n      if (tbl) {\n        cachedLpw = tbl\n          .getLeftVisibleLeafColumns()\n          .reduce((s, c) => s + c.getSize(), 0);\n        cachedRpw = tbl\n          .getRightVisibleLeafColumns()\n          .reduce((s, c) => s + c.getSize(), 0);\n      }\n    }\n\n    function tick() {\n      if (!active) return;\n      const container = dataGridRef.current;\n      const tbl = tableRef.current;\n\n      if (!container || !tbl) {\n        onAutoScrollStop();\n        return;\n      }\n\n      if (!mouseReady || !cachedRect) {\n        rafId = requestAnimationFrame(tick);\n        return;\n      }\n\n      const rect = cachedRect;\n      const { dir } = dragDepsRef.current;\n      const hasNegativeScroll = container.scrollLeft < 0;\n      const isActuallyRtl = dir === \"rtl\" || hasNegativeScroll;\n\n      const dataTop = rect.top + cachedHdrH;\n      const dataBottom = rect.bottom - cachedFtrH;\n\n      const scrollAreaLeft = isActuallyRtl\n        ? rect.left + cachedRpw\n        : rect.left + cachedLpw;\n      const scrollAreaRight = isActuallyRtl\n        ? rect.right - cachedLpw\n        : rect.right - cachedRpw;\n\n      let dy = 0;\n      let dx = 0;\n\n      if (mouseY < dataTop) dy = -getAutoScrollSpeed(dataTop - mouseY);\n      else if (mouseY > dataBottom)\n        dy = getAutoScrollSpeed(mouseY - dataBottom);\n\n      if (mouseX < scrollAreaLeft)\n        dx = -getAutoScrollSpeed(scrollAreaLeft - mouseX);\n      else if (mouseX > scrollAreaRight)\n        dx = getAutoScrollSpeed(mouseX - scrollAreaRight);\n\n      if (dx === 0 && dy === 0) {\n        rafId = requestAnimationFrame(tick);\n        return;\n      }\n\n      container.scrollTop += dy;\n      container.scrollLeft += dx;\n\n      const now = performance.now();\n      if (now - lastSelectionTime < AUTO_SCROLL_SELECTION_THROTTLE_MS) {\n        rafId = requestAnimationFrame(tick);\n        return;\n      }\n\n      const { rowHeightValue: rh, columnIds } = dragDepsRef.current;\n      if (columnIds.length === 0) {\n        rafId = requestAnimationFrame(tick);\n        return;\n      }\n\n      const totalRows = tbl.getRowModel().rows.length;\n      const clampedY = Math.max(dataTop, Math.min(mouseY, dataBottom));\n      const absY = container.scrollTop + (clampedY - dataTop);\n      const rowIndex = Math.max(\n        0,\n        Math.min(Math.floor(absY / rh), totalRows - 1),\n      );\n\n      const st = store.getState();\n      const range = st.selectionState.selectionRange;\n\n      let columnId: string | undefined;\n\n      if (dx !== 0) {\n        const clampedX = Math.max(rect.left, Math.min(mouseX, rect.right));\n        const relX = clampedX - rect.left;\n\n        const leftZoneWidth = isActuallyRtl ? cachedRpw : cachedLpw;\n        const rightZoneWidth = isActuallyRtl ? cachedLpw : cachedRpw;\n\n        if (relX < leftZoneWidth) {\n          const columns = isActuallyRtl\n            ? tbl.getRightVisibleLeafColumns()\n            : tbl.getLeftVisibleLeafColumns();\n          columnId = columns[0]?.id ?? columnIds[0] ?? \"\";\n          let cx = 0;\n          for (const col of columns) {\n            if (relX < cx + col.getSize()) {\n              columnId = col.id;\n              break;\n            }\n            cx += col.getSize();\n          }\n        } else if (relX > rect.width - rightZoneWidth) {\n          const columns = isActuallyRtl\n            ? tbl.getLeftVisibleLeafColumns()\n            : tbl.getRightVisibleLeafColumns();\n          columnId = columns[0]?.id ?? columnIds[columnIds.length - 1] ?? \"\";\n          let cx = rect.width - rightZoneWidth;\n          for (const col of columns) {\n            if (relX < cx + col.getSize()) {\n              columnId = col.id;\n              break;\n            }\n            cx += col.getSize();\n          }\n        } else {\n          const center = tbl.getCenterVisibleLeafColumns();\n          const centerZoneWidth = rect.width - leftZoneWidth - rightZoneWidth;\n          const distFromVisualLeft = relX - leftZoneWidth;\n\n          let absX: number;\n          if (isActuallyRtl) {\n            const scrollFromRight = hasNegativeScroll\n              ? -container.scrollLeft\n              : container.scrollWidth -\n                container.clientWidth -\n                container.scrollLeft;\n            absX = scrollFromRight + (centerZoneWidth - distFromVisualLeft);\n          } else {\n            absX = container.scrollLeft + distFromVisualLeft;\n          }\n\n          columnId =\n            center[center.length - 1]?.id ??\n            columnIds[columnIds.length - 1] ??\n            \"\";\n          let cw = 0;\n          for (const col of center) {\n            cw += col.getSize();\n            if (absX < cw) {\n              columnId = col.id;\n              break;\n            }\n          }\n        }\n      }\n\n      if (!columnId) {\n        columnId = range?.end.columnId ?? columnIds[0] ?? \"\";\n      }\n\n      if (\n        range &&\n        (rowIndex !== range.end.rowIndex || columnId !== range.end.columnId)\n      ) {\n        dragDepsRef.current.selectRange(\n          range.start,\n          { rowIndex, columnId },\n          true,\n        );\n        lastSelectionTime = now;\n      }\n\n      rafId = requestAnimationFrame(tick);\n    }\n\n    function onMove(event: MouseEvent) {\n      mouseX = event.clientX;\n      mouseY = event.clientY;\n      mouseReady = true;\n    }\n\n    function onUp() {\n      onAutoScrollStop();\n      const st = store.getState();\n      if (st.selectionState.isSelecting) {\n        store.setState(\"selectionState\", {\n          ...st.selectionState,\n          isSelecting: false,\n        });\n      }\n    }\n\n    function onAutoScrollStart() {\n      if (active) return;\n\n      const container = dataGridRef.current;\n      if (!container) return;\n\n      active = true;\n      mouseReady = false;\n      cachedRect = null;\n      lastSelectionTime = 0;\n      document.addEventListener(\"mousemove\", onMove);\n      document.addEventListener(\"mouseup\", onUp);\n      resizeObserver = new ResizeObserver(() => {\n        const currentContainer = dataGridRef.current;\n        if (currentContainer) cacheLayout(currentContainer);\n      });\n      resizeObserver.observe(container);\n      rafId = requestAnimationFrame(() => {\n        const currentContainer = dataGridRef.current;\n        if (currentContainer) cacheLayout(currentContainer);\n        rafId = requestAnimationFrame(tick);\n      });\n    }\n\n    function onAutoScrollStop() {\n      if (!active) return;\n      active = false;\n      cachedRect = null;\n      document.removeEventListener(\"mousemove\", onMove);\n      document.removeEventListener(\"mouseup\", onUp);\n      resizeObserver?.disconnect();\n      resizeObserver = null;\n      if (rafId !== null) {\n        cancelAnimationFrame(rafId);\n        rafId = null;\n      }\n    }\n\n    if (store.getState().selectionState.isSelecting) onAutoScrollStart();\n\n    const onUnsubscribe = store.subscribe(() => {\n      const st = store.getState();\n      if (st.selectionState.isSelecting && !active) onAutoScrollStart();\n      else if (!st.selectionState.isSelecting && active) onAutoScrollStop();\n    });\n\n    return () => {\n      onAutoScrollStop();\n      onUnsubscribe();\n    };\n  }, [store, dragDepsRef]);\n\n  useIsomorphicLayoutEffect(() => {\n    const rafId = requestAnimationFrame(() => {\n      rowVirtualizer.measure();\n    });\n    return () => cancelAnimationFrame(rafId);\n  }, [\n    rowHeight,\n    table.getState().columnFilters,\n    table.getState().columnOrder,\n    table.getState().columnPinning,\n    table.getState().columnSizing,\n    table.getState().columnVisibility,\n    table.getState().expanded,\n    table.getState().globalFilter,\n    table.getState().grouping,\n    table.getState().rowSelection,\n    table.getState().sorting,\n  ]);\n\n  const virtualTotalSize = rowVirtualizer.getTotalSize();\n  const virtualItems = rowVirtualizer.getVirtualItems();\n  const measureElement = rowVirtualizer.measureElement;\n\n  return React.useMemo(\n    () => ({\n      dataGridRef,\n      headerRef,\n      rowMapRef,\n      footerRef,\n      dir,\n      table,\n      tableMeta,\n      virtualTotalSize,\n      virtualItems,\n      measureElement,\n      columns,\n      columnSizeVars,\n      searchState,\n      searchMatchesByRow,\n      activeSearchMatch,\n      cellSelectionMap,\n      focusedCell,\n      editingCell,\n      rowHeight,\n      contextMenu,\n      pasteDialog,\n      onRowAdd: propsRef.current.onRowAdd ? onRowAdd : undefined,\n      adjustLayout,\n    }),\n    [\n      propsRef,\n      dir,\n      table,\n      tableMeta,\n      virtualTotalSize,\n      virtualItems,\n      measureElement,\n      columns,\n      columnSizeVars,\n      searchState,\n      searchMatchesByRow,\n      activeSearchMatch,\n      cellSelectionMap,\n      focusedCell,\n      editingCell,\n      rowHeight,\n      contextMenu,\n      pasteDialog,\n      onRowAdd,\n      adjustLayout,\n    ],\n  );\n}\n\nexport { type UseDataGridProps, useDataGrid };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-debounced-callback.ts",
      "content": "/**\n * @see https://github.com/mantinedev/mantine/blob/master/packages/@mantine/hooks/src/use-debounced-callback/use-debounced-callback.ts\n */\n\nimport * as React from \"react\";\n\nimport { useCallbackRef } from \"@/hooks/use-callback-ref\";\n\nexport function useDebouncedCallback<T extends (...args: never[]) => unknown>(\n  callback: T,\n  delay: number,\n) {\n  const handleCallback = useCallbackRef(callback);\n  const debounceTimerRef = React.useRef(0);\n  React.useEffect(\n    () => () => window.clearTimeout(debounceTimerRef.current),\n    [],\n  );\n\n  const setValue = React.useCallback(\n    (...args: Parameters<T>) => {\n      window.clearTimeout(debounceTimerRef.current);\n      debounceTimerRef.current = window.setTimeout(\n        () => handleCallback(...args),\n        delay,\n      );\n    },\n    [handleCallback, delay],\n  );\n\n  return setValue;\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-isomorphic-layout-effect.ts",
      "content": "import * as React from \"react\";\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? React.useLayoutEffect : React.useEffect;\n\nexport { useIsomorphicLayoutEffect };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/hooks/use-lazy-ref.ts",
      "content": "import * as React from \"react\";\n\nfunction useLazyRef<T>(fn: () => T): React.RefObject<T> {\n  const ref = React.useRef<T | null>(null);\n  if (ref.current === null) {\n    ref.current = fn();\n  }\n  return ref as React.RefObject<T>;\n}\n\nexport { useLazyRef };\n",
      "type": "registry:hook"
    },
    {
      "path": "src/lib/compose-refs.ts",
      "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  // biome-ignore lint/correctness/useExhaustiveDependencies: we don't want to re-run this callback when the refs change\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/data-grid.ts",
      "content": "import type { Column, Table } from \"@tanstack/react-table\";\nimport {\n  BaselineIcon,\n  CalendarIcon,\n  CheckSquareIcon,\n  File,\n  FileArchive,\n  FileAudio,\n  FileIcon,\n  FileImage,\n  FileSpreadsheet,\n  FileText,\n  FileVideo,\n  HashIcon,\n  LinkIcon,\n  ListChecksIcon,\n  ListIcon,\n  Presentation,\n  TextInitialIcon,\n} from \"lucide-react\";\nimport type * as React from \"react\";\nimport type {\n  CellOpts,\n  CellPosition,\n  Direction,\n  FileCellData,\n  RowHeightValue,\n} from \"@/types/data-grid\";\n\nexport function flexRender<TProps extends object>(\n  Comp: ((props: TProps) => React.ReactNode) | string | undefined,\n  props: TProps,\n): React.ReactNode {\n  if (typeof Comp === \"string\") {\n    return Comp;\n  }\n  return Comp?.(props);\n}\n\nexport function getIsFileCellData(item: unknown): item is FileCellData {\n  return (\n    !!item &&\n    typeof item === \"object\" &&\n    \"id\" in item &&\n    \"name\" in item &&\n    \"size\" in item &&\n    \"type\" in item\n  );\n}\n\nexport function matchSelectOption(\n  value: string,\n  options: { value: string; label: string }[],\n): string | undefined {\n  return options.find(\n    (o) =>\n      o.value === value ||\n      o.value.toLowerCase() === value.toLowerCase() ||\n      o.label.toLowerCase() === value.toLowerCase(),\n  )?.value;\n}\n\nexport function getCellKey(rowIndex: number, columnId: string) {\n  return `${rowIndex}:${columnId}`;\n}\n\nexport function parseCellKey(cellKey: string): Required<CellPosition> {\n  const parts = cellKey.split(\":\");\n  const rowIndexStr = parts[0];\n  const columnId = parts[1];\n  if (rowIndexStr && columnId) {\n    const rowIndex = parseInt(rowIndexStr, 10);\n    if (!Number.isNaN(rowIndex)) {\n      return { rowIndex, columnId };\n    }\n  }\n  return { rowIndex: 0, columnId: \"\" };\n}\n\nexport function getRowHeightValue(rowHeight: RowHeightValue): number {\n  const rowHeightMap: Record<RowHeightValue, number> = {\n    short: 36,\n    medium: 56,\n    tall: 76,\n    \"extra-tall\": 96,\n  };\n\n  return rowHeightMap[rowHeight];\n}\n\nexport function getLineCount(rowHeight: RowHeightValue): number {\n  const lineCountMap: Record<RowHeightValue, number> = {\n    short: 1,\n    medium: 2,\n    tall: 3,\n    \"extra-tall\": 4,\n  };\n\n  return lineCountMap[rowHeight];\n}\n\nexport function getColumnBorderVisibility<TData>(params: {\n  column: Column<TData>;\n  nextColumn?: Column<TData>;\n  isLastColumn: boolean;\n}): {\n  showEndBorder: boolean;\n  showStartBorder: boolean;\n} {\n  const { column, nextColumn, isLastColumn } = params;\n\n  const isPinned = column.getIsPinned();\n  const isFirstRightPinnedColumn =\n    isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n  const isLastRightPinnedColumn =\n    isPinned === \"right\" && column.getIsLastColumn(\"right\");\n\n  const nextIsPinned = nextColumn?.getIsPinned();\n  const isBeforeRightPinned =\n    nextIsPinned === \"right\" && nextColumn?.getIsFirstColumn(\"right\");\n\n  const showEndBorder =\n    !isBeforeRightPinned && (isLastColumn || !isLastRightPinnedColumn);\n\n  const showStartBorder = isFirstRightPinnedColumn;\n\n  return {\n    showEndBorder,\n    showStartBorder,\n  };\n}\n\nexport function getColumnPinningStyle<TData>(params: {\n  column: Column<TData>;\n  withBorder?: boolean;\n  dir?: Direction;\n}): React.CSSProperties {\n  const { column, dir = \"ltr\", withBorder = false } = params;\n\n  const isPinned = column.getIsPinned();\n  const isLastLeftPinnedColumn =\n    isPinned === \"left\" && column.getIsLastColumn(\"left\");\n  const isFirstRightPinnedColumn =\n    isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n\n  const isRtl = dir === \"rtl\";\n\n  const leftPosition =\n    isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined;\n  const rightPosition =\n    isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined;\n\n  return {\n    boxShadow: withBorder\n      ? isLastLeftPinnedColumn\n        ? isRtl\n          ? \"4px 0 4px -4px var(--border) inset\"\n          : \"-4px 0 4px -4px var(--border) inset\"\n        : isFirstRightPinnedColumn\n          ? isRtl\n            ? \"-4px 0 4px -4px var(--border) inset\"\n            : \"4px 0 4px -4px var(--border) inset\"\n          : undefined\n      : undefined,\n    left: isRtl ? rightPosition : leftPosition,\n    right: isRtl ? leftPosition : rightPosition,\n    opacity: isPinned ? 0.97 : 1,\n    position: isPinned ? \"sticky\" : \"relative\",\n    background: isPinned ? \"var(--background)\" : \"var(--background)\",\n    width: column.getSize(),\n    zIndex: isPinned ? 1 : undefined,\n  };\n}\n\nexport function getScrollDirection(\n  direction: string,\n): \"left\" | \"right\" | \"home\" | \"end\" | undefined {\n  if (\n    direction === \"left\" ||\n    direction === \"right\" ||\n    direction === \"home\" ||\n    direction === \"end\"\n  ) {\n    return direction as \"left\" | \"right\" | \"home\" | \"end\";\n  }\n  if (direction === \"pageleft\") return \"left\";\n  if (direction === \"pageright\") return \"right\";\n  return undefined;\n}\n\nexport function scrollCellIntoView<TData>(params: {\n  container: HTMLDivElement;\n  targetCell: HTMLDivElement;\n  tableRef: React.RefObject<Table<TData> | null>;\n  viewportOffset: number;\n  direction?: \"left\" | \"right\" | \"home\" | \"end\";\n  isRtl: boolean;\n}): void {\n  const { container, targetCell, tableRef, direction, viewportOffset, isRtl } =\n    params;\n\n  const containerRect = container.getBoundingClientRect();\n  const cellRect = targetCell.getBoundingClientRect();\n\n  const hasNegativeScroll = container.scrollLeft < 0;\n  const isActuallyRtl = isRtl || hasNegativeScroll;\n\n  const currentTable = tableRef.current;\n  const leftPinnedColumns = currentTable?.getLeftVisibleLeafColumns() ?? [];\n  const rightPinnedColumns = currentTable?.getRightVisibleLeafColumns() ?? [];\n\n  const leftPinnedWidth = leftPinnedColumns.reduce(\n    (sum, c) => sum + c.getSize(),\n    0,\n  );\n  const rightPinnedWidth = rightPinnedColumns.reduce(\n    (sum, c) => sum + c.getSize(),\n    0,\n  );\n\n  const viewportLeft = isActuallyRtl\n    ? containerRect.left + rightPinnedWidth + viewportOffset\n    : containerRect.left + leftPinnedWidth + viewportOffset;\n  const viewportRight = isActuallyRtl\n    ? containerRect.right - leftPinnedWidth - viewportOffset\n    : containerRect.right - rightPinnedWidth - viewportOffset;\n\n  const isFullyVisible =\n    cellRect.left >= viewportLeft && cellRect.right <= viewportRight;\n\n  if (isFullyVisible) return;\n\n  const isClippedLeft = cellRect.left < viewportLeft;\n  const isClippedRight = cellRect.right > viewportRight;\n\n  let scrollDelta = 0;\n\n  if (!direction) {\n    if (isClippedRight) {\n      scrollDelta = cellRect.right - viewportRight;\n    } else if (isClippedLeft) {\n      scrollDelta = -(viewportLeft - cellRect.left);\n    }\n  } else {\n    const shouldScrollRight = isActuallyRtl\n      ? direction === \"right\" || direction === \"home\"\n      : direction === \"right\" || direction === \"end\";\n\n    if (shouldScrollRight) {\n      scrollDelta = cellRect.right - viewportRight;\n    } else {\n      scrollDelta = -(viewportLeft - cellRect.left);\n    }\n  }\n\n  container.scrollLeft += scrollDelta;\n}\n\nfunction countTabs(s: string): number {\n  let n = 0;\n  for (let i = 0; i < s.length; i++) if (s[i] === \"\\t\") n++;\n  return n;\n}\n\nexport function parseTsv(\n  text: string,\n  fallbackColumnCount: number,\n): string[][] {\n  if (text.startsWith('\"') || text.includes('\\t\"')) {\n    const rows: string[][] = [];\n    let currentRow: string[] = [];\n    let currentField = \"\";\n    let inQuotes = false;\n    let i = 0;\n\n    while (i < text.length) {\n      const char = text[i];\n      const nextChar = text[i + 1];\n\n      if (inQuotes) {\n        if (char === '\"' && nextChar === '\"') {\n          currentField += '\"';\n          i += 2;\n        } else if (char === '\"') {\n          inQuotes = false;\n          i++;\n        } else {\n          currentField += char;\n          i++;\n        }\n      } else {\n        if (char === '\"' && currentField === \"\") {\n          inQuotes = true;\n          i++;\n        } else if (char === \"\\t\") {\n          currentRow.push(currentField);\n          currentField = \"\";\n          i++;\n        } else if (char === \"\\n\") {\n          currentRow.push(currentField);\n          if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n            rows.push(currentRow);\n          }\n          currentRow = [];\n          currentField = \"\";\n          i++;\n        } else if (char === \"\\r\" && nextChar === \"\\n\") {\n          currentRow.push(currentField);\n          if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n            rows.push(currentRow);\n          }\n          currentRow = [];\n          currentField = \"\";\n          i += 2;\n        } else {\n          currentField += char;\n          i++;\n        }\n      }\n    }\n\n    currentRow.push(currentField);\n    if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n      rows.push(currentRow);\n    }\n\n    return rows;\n  }\n\n  const lines = text.split(\"\\n\").map((l) => l.replace(/\\r$/, \"\"));\n  let maxTabCount = 0;\n  for (const line of lines) {\n    const n = countTabs(line);\n    if (n > maxTabCount) maxTabCount = n;\n  }\n  const columnCount = maxTabCount > 0 ? maxTabCount + 1 : fallbackColumnCount;\n  if (columnCount <= 0) return [];\n\n  const expectedTabCount = columnCount - 1;\n  const rows: string[][] = [];\n  let buf = \"\";\n  let bufTabCount = 0;\n\n  for (const line of lines) {\n    const tc = countTabs(line);\n\n    if (tc === expectedTabCount) {\n      if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n      buf = \"\";\n      bufTabCount = 0;\n      rows.push(line.split(\"\\t\"));\n    } else {\n      buf = buf ? `${buf}\\n${line}` : line;\n      bufTabCount += tc;\n      if (bufTabCount === expectedTabCount) {\n        rows.push(buf.split(\"\\t\"));\n        buf = \"\";\n        bufTabCount = 0;\n      }\n    }\n  }\n\n  if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n\n  return rows.length > 0\n    ? rows\n    : lines.filter((l) => l.length > 0).map((l) => l.split(\"\\t\"));\n}\n\nexport function getIsInPopover(element: unknown): boolean {\n  if (!(element instanceof Element)) return false;\n\n  return (\n    element.closest(\"[data-grid-cell-editor]\") !== null ||\n    element.closest(\"[data-grid-popover]\") !== null ||\n    element.closest(\"[data-slot='dropdown-menu-content']\") !== null ||\n    element.closest(\"[data-slot='popover-content']\") !== null\n  );\n}\n\nexport function getColumnVariant(variant?: CellOpts[\"variant\"]): {\n  icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;\n  label: string;\n} | null {\n  switch (variant) {\n    case \"short-text\":\n      return { label: \"Short text\", icon: BaselineIcon };\n    case \"long-text\":\n      return { label: \"Long text\", icon: TextInitialIcon };\n    case \"number\":\n      return { label: \"Number\", icon: HashIcon };\n    case \"url\":\n      return { label: \"URL\", icon: LinkIcon };\n    case \"checkbox\":\n      return { label: \"Checkbox\", icon: CheckSquareIcon };\n    case \"select\":\n      return { label: \"Select\", icon: ListIcon };\n    case \"multi-select\":\n      return { label: \"Multi-select\", icon: ListChecksIcon };\n    case \"date\":\n      return { label: \"Date\", icon: CalendarIcon };\n    case \"file\":\n      return { label: \"File\", icon: FileIcon };\n    default:\n      return null;\n  }\n}\n\nexport function getEmptyCellValue(\n  variant: CellOpts[\"variant\"] | undefined,\n): unknown {\n  if (variant === \"multi-select\" || variant === \"file\") return [];\n  if (variant === \"number\" || variant === \"date\" || variant === \"select\")\n    return null;\n  if (variant === \"checkbox\") return false;\n  return \"\";\n}\n\nexport function getUrlHref(urlString: string): string {\n  if (!urlString || urlString.trim() === \"\") return \"\";\n\n  const trimmed = urlString.trim();\n\n  // Reject dangerous protocols (extra safety, though our http:// prefix would neutralize them)\n  if (/^(javascript|data|vbscript|file):/i.test(trimmed)) {\n    return \"\";\n  }\n\n  if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n    return trimmed;\n  }\n\n  return `http://${trimmed}`;\n}\n\nexport function parseLocalDate(dateStr: unknown): Date | null {\n  if (!dateStr) return null;\n  if (dateStr instanceof Date) return dateStr;\n  if (typeof dateStr !== \"string\") return null;\n  const [year, month, day] = dateStr.split(\"-\").map(Number);\n  if (!year || !month || !day) return null;\n  const date = new Date(year, month - 1, day);\n  // Verify date wasn't auto-corrected (e.g. Feb 30 -> Mar 1)\n  if (\n    date.getFullYear() !== year ||\n    date.getMonth() !== month - 1 ||\n    date.getDate() !== day\n  ) {\n    return null;\n  }\n  return date;\n}\n\nexport function formatDateToString(date: Date): string {\n  const year = date.getFullYear();\n  const month = String(date.getMonth() + 1).padStart(2, \"0\");\n  const day = String(date.getDate()).padStart(2, \"0\");\n  return `${year}-${month}-${day}`;\n}\n\nexport function formatDateForDisplay(dateStr: unknown): string {\n  if (!dateStr) return \"\";\n  const date = parseLocalDate(dateStr);\n  if (!date) return typeof dateStr === \"string\" ? dateStr : \"\";\n  return date.toLocaleDateString();\n}\n\nexport function formatFileSize(bytes: number): string {\n  if (bytes <= 0 || !Number.isFinite(bytes)) return \"0 B\";\n  const k = 1024;\n  const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n  const i = Math.min(\n    sizes.length - 1,\n    Math.floor(Math.log(bytes) / Math.log(k)),\n  );\n  return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;\n}\n\nexport function getFileIcon(\n  type: string,\n): React.ComponentType<React.SVGProps<SVGSVGElement>> {\n  if (type.startsWith(\"image/\")) return FileImage;\n  if (type.startsWith(\"video/\")) return FileVideo;\n  if (type.startsWith(\"audio/\")) return FileAudio;\n  if (type.includes(\"pdf\")) return FileText;\n  if (type.includes(\"zip\") || type.includes(\"rar\")) return FileArchive;\n  if (\n    type.includes(\"word\") ||\n    type.includes(\"document\") ||\n    type.includes(\"doc\")\n  )\n    return FileText;\n  if (type.includes(\"sheet\") || type.includes(\"excel\") || type.includes(\"xls\"))\n    return FileSpreadsheet;\n  if (\n    type.includes(\"presentation\") ||\n    type.includes(\"powerpoint\") ||\n    type.includes(\"ppt\")\n  )\n    return Presentation;\n  return File;\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/types/data-grid.ts",
      "content": "import type { Cell, RowData, TableMeta } from \"@tanstack/react-table\";\n\nexport type Direction = \"ltr\" | \"rtl\";\n\nexport type RowHeightValue = \"short\" | \"medium\" | \"tall\" | \"extra-tall\";\n\nexport interface CellSelectOption {\n  label: string;\n  value: string;\n  icon?: React.ComponentType<React.ComponentProps<\"svg\">>;\n  count?: number;\n}\n\nexport type CellOpts =\n  | {\n      variant: \"short-text\";\n    }\n  | {\n      variant: \"long-text\";\n    }\n  | {\n      variant: \"number\";\n      min?: number;\n      max?: number;\n      step?: number;\n    }\n  | {\n      variant: \"select\";\n      options: CellSelectOption[];\n    }\n  | {\n      variant: \"multi-select\";\n      options: CellSelectOption[];\n    }\n  | {\n      variant: \"checkbox\";\n    }\n  | {\n      variant: \"date\";\n    }\n  | {\n      variant: \"url\";\n    }\n  | {\n      variant: \"file\";\n      maxFileSize?: number;\n      maxFiles?: number;\n      accept?: string;\n      multiple?: boolean;\n    };\n\nexport interface CellUpdate {\n  rowIndex: number;\n  columnId: string;\n  value: unknown;\n}\n\ndeclare module \"@tanstack/react-table\" {\n  interface ColumnMeta<TData extends RowData, TValue> {\n    label?: string;\n    cell?: CellOpts;\n  }\n\n  interface TableMeta<TData extends RowData> {\n    dataGridRef?: React.RefObject<HTMLElement | null>;\n    cellMapRef?: React.RefObject<Map<string, HTMLDivElement>>;\n    focusedCell?: CellPosition | null;\n    editingCell?: CellPosition | null;\n    selectionState?: SelectionState;\n    searchOpen?: boolean;\n    getIsCellSelected?: (rowIndex: number, columnId: string) => boolean;\n    getIsSearchMatch?: (rowIndex: number, columnId: string) => boolean;\n    getIsActiveSearchMatch?: (rowIndex: number, columnId: string) => boolean;\n    getVisualRowIndex?: (rowId: string) => number | undefined;\n    scrollToCell?: (\n      rowIndex: number,\n      columnId: string,\n      align?: \"auto\" | \"start\" | \"center\" | \"end\",\n    ) => void;\n    rowHeight?: RowHeightValue;\n    onRowHeightChange?: (value: RowHeightValue) => void;\n    onRowSelect?: (rowId: string, checked: boolean, shiftKey: boolean) => void;\n    onDataUpdate?: (params: CellUpdate | Array<CellUpdate>) => void;\n    onRowsDelete?: (rowIndices: number[]) => void | Promise<void>;\n    onColumnClick?: (columnId: string) => void;\n    onCellClick?: (\n      rowIndex: number,\n      columnId: string,\n      event?: React.MouseEvent,\n    ) => void;\n    onCellDoubleClick?: (rowIndex: number, columnId: string) => void;\n    onCellMouseDown?: (\n      rowIndex: number,\n      columnId: string,\n      event: React.MouseEvent,\n    ) => void;\n    onCellMouseEnter?: (rowIndex: number, columnId: string) => void;\n    onCellMouseUp?: () => void;\n    onCellContextMenu?: (\n      rowIndex: number,\n      columnId: string,\n      event: React.MouseEvent,\n    ) => void;\n    onCellEditingStart?: (rowIndex: number, columnId: string) => void;\n    onCellEditingStop?: (opts?: {\n      direction?: NavigationDirection;\n      moveToNextRow?: boolean;\n    }) => void;\n    onCellsCopy?: () => void;\n    onCellsCut?: () => void;\n    onCellsPaste?: (expand?: boolean) => void;\n    onSelectionClear?: () => void;\n    onFilesUpload?: (params: {\n      files: File[];\n      rowIndex: number;\n      columnId: string;\n    }) => Promise<FileCellData[]>;\n    onFilesDelete?: (params: {\n      fileIds: string[];\n      rowIndex: number;\n      columnId: string;\n    }) => void | Promise<void>;\n    contextMenu?: ContextMenuState;\n    onContextMenuOpenChange?: (open: boolean) => void;\n    pasteDialog?: PasteDialogState;\n    onPasteDialogOpenChange?: (open: boolean) => void;\n    readOnly?: boolean;\n  }\n}\n\nexport interface CellPosition {\n  rowIndex: number;\n  columnId: string;\n}\n\nexport interface CellRange {\n  start: CellPosition;\n  end: CellPosition;\n}\n\nexport interface SelectionState {\n  selectedCells: Set<string>;\n  selectionRange: CellRange | null;\n  isSelecting: boolean;\n}\n\nexport interface ContextMenuState {\n  open: boolean;\n  x: number;\n  y: number;\n}\n\nexport interface PasteDialogState {\n  open: boolean;\n  rowsNeeded: number;\n  clipboardText: string;\n}\n\nexport type NavigationDirection =\n  | \"up\"\n  | \"down\"\n  | \"left\"\n  | \"right\"\n  | \"home\"\n  | \"end\"\n  | \"ctrl+up\"\n  | \"ctrl+down\"\n  | \"ctrl+home\"\n  | \"ctrl+end\"\n  | \"pageup\"\n  | \"pagedown\"\n  | \"pageleft\"\n  | \"pageright\";\n\nexport interface SearchState {\n  searchMatches: CellPosition[];\n  matchIndex: number;\n  searchOpen: boolean;\n  onSearchOpenChange: (open: boolean) => void;\n  searchQuery: string;\n  onSearchQueryChange: (query: string) => void;\n  onSearch: (query: string) => void;\n  onNavigateToNextMatch: () => void;\n  onNavigateToPrevMatch: () => void;\n}\n\nexport interface DataGridCellProps<TData> {\n  cell: Cell<TData, unknown>;\n  tableMeta: TableMeta<TData>;\n  rowIndex: number;\n  columnId: string;\n  rowHeight: RowHeightValue;\n  isEditing: boolean;\n  isFocused: boolean;\n  isSelected: boolean;\n  isSearchMatch: boolean;\n  isActiveSearchMatch: boolean;\n  readOnly: boolean;\n}\n\nexport interface FileCellData {\n  id: string;\n  name: string;\n  size: number;\n  type: string;\n  url?: string;\n}\n\nexport type TextFilterOperator =\n  | \"contains\"\n  | \"notContains\"\n  | \"equals\"\n  | \"notEquals\"\n  | \"startsWith\"\n  | \"endsWith\"\n  | \"isEmpty\"\n  | \"isNotEmpty\";\n\nexport type NumberFilterOperator =\n  | \"equals\"\n  | \"notEquals\"\n  | \"lessThan\"\n  | \"lessThanOrEqual\"\n  | \"greaterThan\"\n  | \"greaterThanOrEqual\"\n  | \"isBetween\"\n  | \"isEmpty\"\n  | \"isNotEmpty\";\n\nexport type DateFilterOperator =\n  | \"equals\"\n  | \"notEquals\"\n  | \"before\"\n  | \"after\"\n  | \"onOrBefore\"\n  | \"onOrAfter\"\n  | \"isBetween\"\n  | \"isEmpty\"\n  | \"isNotEmpty\";\n\nexport type SelectFilterOperator =\n  | \"is\"\n  | \"isNot\"\n  | \"isAnyOf\"\n  | \"isNoneOf\"\n  | \"isEmpty\"\n  | \"isNotEmpty\";\n\nexport type BooleanFilterOperator = \"isTrue\" | \"isFalse\";\n\nexport type FilterOperator =\n  | TextFilterOperator\n  | NumberFilterOperator\n  | DateFilterOperator\n  | SelectFilterOperator\n  | BooleanFilterOperator;\n\nexport interface FilterValue {\n  operator: FilterOperator;\n  value?: string | number | string[];\n  endValue?: string | number;\n}\n",
      "type": "registry:file",
      "target": "src/types/data-grid.ts"
    }
  ],
  "type": "registry:component"
}