shadimage
Shadcn Table
componentdata-gridsource: ok · hit

Data Grid

A high-performance editable data grid component with virtualization, keyboard navigation, and cell editing

npx shadcn add "https://tablecn.com/r/data-grid.json"
free

Where it goes

data-tablekanban

Use cases

dashboard

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react
toastssonner
ui-primitives@base-ui/react@base-ui/utils

Registry dependencies

badgebuttoncalendarcheckboxcommanddialogdropdown-menuinputpopoverselectskeletontextareatooltip

Files20 · source cached 10/14/2026, 3:07:59 PM

src/registry/bases/base/components/data-grid/data-grid.tsx
"use client";

import type { RowData } from "@tanstack/react-table";

import { useValueAsRef } from "@base-ui/utils/useValueAsRef";
import { cn } from "cn";
import * as React from "react";

import type { Direction } from "@/lib/data-grid-types";
import type { useDataGrid } from "@/registry/bases/base/hooks/use-data-grid";

import {
  flexRender,
  getColumnBorderVisibility,
  getColumnPinningStyle,
} from "@/lib/data-grid-utils";
import { DataGridColumnHeader } from "@/registry/bases/base/components/data-grid/data-grid-column-header";
import { DataGridContextMenu } from "@/registry/bases/base/components/data-grid/data-grid-context-menu";
import { DataGridPasteDialog } from "@/registry/bases/base/components/data-grid/data-grid-paste-dialog";
import { DataGridRow } from "@/registry/bases/base/components/data-grid/data-grid-row";
import { DataGridSearch } from "@/registry/bases/base/components/data-grid/data-grid-search";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";

const EMPTY_CELL_SELECTION_SET = new Set<string>();

interface DataGridProps<TData extends RowData>
  extends
    Omit<ReturnType<typeof useDataGrid<TData>>, "dir">,
    Omit<React.ComponentProps<"div">, "contextMenu"> {
  dir?: Direction;
  height?: number;
  stretchColumns?: boolean;
}

export function DataGrid<TData extends RowData>({
  dataGridRef,
  headerRef,
  rowMapRef,
  footerRef,
  dir = "ltr",
  table,
  tableMeta,
  virtualTotalSize,
  virtualItems,
  measureElement,
  columns,
  columnSizeVars,
  searchState,
  searchMatchesByRow,
  activeSearchMatch,
  cellSelectionMap,
  focusedCell,
  editingCell,
  readOnlyColumnIds,
  rowHeight,
  contextMenu,
  pasteDialog,
  onRowAdd: onRowAddProp,
  height = 600,
  stretchColumns = false,
  adjustLayout,
  className,
  ...props
}: DataGridProps<TData>) {
  const rows = table.getRowModel().rows;
  const readOnly = tableMeta?.readOnly ?? false;
  const columnVisibility = table.state.columnVisibility;
  const columnPinning = table.state.columnPinning;

  const onRowAddRef = useValueAsRef(onRowAddProp);

  const onRowAdd = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      void onRowAddRef.current?.(event);
    },
    [onRowAddRef],
  );

  const onDataGridContextMenu = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      event.preventDefault();
    },
    [],
  );

  const onFooterCellKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (!onRowAddRef.current) return;

      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        void onRowAddRef.current();
      }
    },
    [onRowAddRef],
  );

  return (
    <div
      data-slot="grid-wrapper"
      dir={dir}
      {...props}
      className={cn("relative flex w-full flex-col", className)}
    >
      {searchState && <DataGridSearch {...searchState} />}
      <DataGridContextMenu
        tableMeta={tableMeta}
        columns={columns}
        contextMenu={contextMenu}
      />
      <DataGridPasteDialog tableMeta={tableMeta} pasteDialog={pasteDialog} />
      <div
        role="grid"
        aria-label="Data grid"
        aria-rowcount={rows.length + (onRowAddProp ? 1 : 0)}
        aria-colcount={columns.length}
        data-slot="grid"
        tabIndex={0}
        ref={dataGridRef}
        className="relative isolate grid overflow-auto rounded-md border select-none focus:outline-none"
        style={{
          ...columnSizeVars,
          maxHeight: `${height}px`,
        }}
        onContextMenu={onDataGridContextMenu}
      >
        <div
          role="rowgroup"
          data-slot="grid-header"
          ref={headerRef}
          className="sticky top-0 z-30 grid border-b bg-background"
        >
          {table.getHeaderGroups().map((headerGroup, rowIndex) => (
            <div
              key={headerGroup.id}
              role="row"
              aria-rowindex={rowIndex + 1}
              data-slot="grid-header-row"
              tabIndex={-1}
              className="flex w-full"
            >
              {headerGroup.headers.map((header, colIndex) => {
                const sorting = table.state.sorting;
                const currentSort = sorting.find(
                  (sort) => sort.id === header.column.id,
                );
                const isSortable = header.column.getCanSort();

                const nextHeader = headerGroup.headers[colIndex + 1];
                const isLastColumn =
                  colIndex === headerGroup.headers.length - 1;

                const { showEndBorder, showStartBorder } =
                  getColumnBorderVisibility({
                    column: header.column,
                    nextColumn: nextHeader?.column,
                    isLastColumn,
                  });

                const cornerClassName = cn(
                  rowIndex === 0 && {
                    "rounded-ss-[calc(var(--radius-md)-1px)]": colIndex === 0,
                    "rounded-se-[calc(var(--radius-md)-1px)]": isLastColumn,
                  },
                );

                return (
                  <div
                    key={header.id}
                    role="columnheader"
                    aria-colindex={colIndex + 1}
                    aria-sort={
                      currentSort?.desc === false
                        ? "ascending"
                        : currentSort?.desc === true
                          ? "descending"
                          : isSortable
                            ? "none"
                            : undefined
                    }
                    data-slot="grid-header-cell"
                    data-column-id={header.column.id}
                    tabIndex={-1}
                    className={cn("relative", {
                      grow: stretchColumns && header.column.id !== "select",
                      "border-e":
                        showEndBorder && header.column.id !== "select",
                      "border-s":
                        showStartBorder && header.column.id !== "select",
                    })}
                    style={{
                      ...getColumnPinningStyle({ column: header.column, dir }),
                      width: `calc(var(--header-${header.id}-size) * 1px)`,
                    }}
                  >
                    {header.isPlaceholder ? null : typeof header.column
                        .columnDef.header === "function" ? (
                      <div
                        className={cn(
                          "size-full px-3 py-1.5 has-focus-visible:ring-1 has-focus-visible:ring-ring has-focus-visible:ring-inset",
                          cornerClassName,
                        )}
                      >
                        {flexRender(
                          header.column.columnDef.header,
                          header.getContext(),
                        )}
                      </div>
                    ) : (
                      <DataGridColumnHeader
                        header={header}
                        table={table}
                        className={cornerClassName}
                      />
                    )}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
        <div
          role="rowgroup"
          data-slot="grid-body"
          className="relative grid"
          style={{
            height: `${virtualTotalSize}px`,
            contain: adjustLayout ? "layout paint" : "strict",
          }}
        >
          {virtualItems.map((virtualItem) => {
            const row = rows[virtualItem.index];
            if (!row) return null;

            const cellSelectionKeys =
              cellSelectionMap?.get(row.id) ?? EMPTY_CELL_SELECTION_SET;

            const searchMatchColumns = searchMatchesByRow?.get(row.id) ?? null;
            const isActiveSearchRow = activeSearchMatch?.rowId === row.id;

            return (
              <DataGridRow
                key={row.id}
                row={row}
                tableMeta={tableMeta}
                rowMapRef={rowMapRef}
                virtualItem={virtualItem}
                measureElement={measureElement}
                rowHeight={rowHeight}
                columnVisibility={columnVisibility}
                columnPinning={columnPinning}
                focusedCell={focusedCell}
                editingCell={editingCell}
                cellSelectionKeys={cellSelectionKeys}
                searchMatchColumns={searchMatchColumns}
                activeSearchMatch={isActiveSearchRow ? activeSearchMatch : null}
                dir={dir}
                adjustLayout={adjustLayout}
                stretchColumns={stretchColumns}
                readOnlyColumnIds={readOnlyColumnIds}
              />
            );
          })}
        </div>
        {!readOnly && onRowAdd && (
          <div
            role="rowgroup"
            data-slot="grid-footer"
            ref={footerRef}
            className="sticky bottom-0 z-30 grid border-t bg-background"
          >
            <div
              role="row"
              aria-rowindex={rows.length + 2}
              data-slot="grid-add-row"
              tabIndex={-1}
              className="flex w-full"
            >
              <div
                role="gridcell"
                tabIndex={0}
                className="relative flex h-9 grow items-center bg-muted/30 transition-colors hover:bg-muted/50 focus:bg-muted/50 focus:outline-none"
                style={{
                  width: table.getTotalSize(),
                  minWidth: table.getTotalSize(),
                }}
                onClick={onRowAdd}
                onKeyDown={onFooterCellKeyDown}
              >
                <div className="sticky inset-s-0 flex items-center gap-2 px-3 text-muted-foreground">
                  <IconPlaceholder
                    lucide="Plus"
                    tabler="IconPlus"
                    hugeicons="PlusSignIcon"
                    phosphor="PlusIcon"
                    remixicon="RiAddLine"
                    className="size-3.5"
                  />
                  <span className="text-sm">Add row</span>
                </div>
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
src/registry/bases/base/components/data-grid/data-grid-presence.tsx
"use client";

import * as React from "react";

interface DataGridCellPresence {
  color: string;
  name: string;
}

const DataGridCellPresenceContext = React.createContext<Map<
  string,
  DataGridCellPresence
> | null>(null);

interface DataGridPresenceProviderProps {
  value: Map<string, DataGridCellPresence>;
  children: React.ReactNode;
}

function DataGridPresenceProvider({
  value,
  children,
}: DataGridPresenceProviderProps) {
  return (
    <DataGridCellPresenceContext.Provider value={value}>
      {children}
    </DataGridCellPresenceContext.Provider>
  );
}

function useDataGridPresence(cellKey: string): DataGridCellPresence | null {
  const map = React.useContext(DataGridCellPresenceContext);
  return map?.get(cellKey) ?? null;
}

export {
  type DataGridCellPresence,
  DataGridPresenceProvider,
  useDataGridPresence,
};
src/registry/bases/base/components/data-grid/data-grid-cell.tsx
"use client";

import type { RowData } from "@tanstack/react-table";

import * as React from "react";

import type { DataGridCellProps } from "@/lib/data-grid-types";

import {
  CheckboxCell,
  DateCell,
  FileCell,
  LongTextCell,
  MultiSelectCell,
  NumberCell,
  SelectCell,
  ShortTextCell,
  UrlCell,
} from "@/registry/bases/base/components/data-grid/data-grid-cell-variants";

export const DataGridCell = React.memo(DataGridCellImpl, (prev, next) => {
  // Fast path: check stable primitive props first
  if (prev.isFocused !== next.isFocused) return false;
  if (prev.isEditing !== next.isEditing) return false;
  if (prev.isSelected !== next.isSelected) return false;
  if (prev.isSearchMatch !== next.isSearchMatch) return false;
  if (prev.isActiveSearchMatch !== next.isActiveSearchMatch) return false;
  if (prev.readOnly !== next.readOnly) return false;
  if (prev.rowId !== next.rowId) return false;
  if (prev.columnId !== next.columnId) return false;
  if (prev.rowHeight !== next.rowHeight) return false;

  // Check cell value using row.original instead of getValue() for stability
  // getValue() is unstable and recreates on every render, breaking memoization
  const prevValue = (prev.cell.row.original as Record<string, unknown>)[
    prev.columnId
  ];
  const nextValue = (next.cell.row.original as Record<string, unknown>)[
    next.columnId
  ];
  if (prevValue !== nextValue) {
    return false;
  }

  // Check cell/row identity
  if (prev.cell.row.id !== next.cell.row.id) return false;

  return true;
}) as typeof DataGridCellImpl;

function DataGridCellImpl<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
  rowHeight,
}: DataGridCellProps<TData>) {
  const cellOpts = cell.column.columnDef.meta?.cell;
  const variant = cellOpts?.variant ?? "text";

  let Comp: React.ComponentType<DataGridCellProps<TData>>;

  switch (variant) {
    case "short-text":
      Comp = ShortTextCell;
      break;
    case "long-text":
      Comp = LongTextCell;
      break;
    case "number":
      Comp = NumberCell;
      break;
    case "url":
      Comp = UrlCell;
      break;
    case "checkbox":
      Comp = CheckboxCell;
      break;
    case "select":
      Comp = SelectCell;
      break;
    case "multi-select":
      Comp = MultiSelectCell;
      break;
    case "date":
      Comp = DateCell;
      break;
    case "file":
      Comp = FileCell;
      break;

    default:
      Comp = ShortTextCell;
      break;
  }

  return (
    <Comp
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
    />
  );
}
src/registry/bases/base/components/data-grid/data-grid-cell-wrapper.tsx
"use client";

import type { RowData } from "@tanstack/react-table";

import { useMergedRefs } from "@base-ui/utils/useMergedRefs";
import { cn } from "cn";
import * as React from "react";

import type { DataGridCellProps } from "@/lib/data-grid-types";

import { getCellKey } from "@/lib/data-grid-utils";
import { useDataGridPresence } from "@/registry/bases/base/components/data-grid/data-grid-presence";

interface DataGridCellWrapperProps<TData extends RowData>
  extends DataGridCellProps<TData>, React.ComponentProps<"div"> {}

export function DataGridCellWrapper<TData extends RowData>({
  tableMeta,
  rowId,
  columnId,
  isEditing,
  isFocused,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
  rowHeight,
  className,
  onClick: onClickProp,
  onKeyDown: onKeyDownProp,
  ref,
  ...props
}: DataGridCellWrapperProps<TData>) {
  const cellMapRef = tableMeta?.cellMapRef;
  const cellPresence = useDataGridPresence(getCellKey(rowId, columnId));

  const onCellChange = React.useCallback(
    (node: HTMLDivElement | null) => {
      if (!cellMapRef) return;

      const cellKey = getCellKey(rowId, columnId);

      if (node) {
        cellMapRef.current.set(cellKey, node);
      } else {
        cellMapRef.current.delete(cellKey);
      }
    },
    [rowId, columnId, cellMapRef],
  );

  const composedRef = useMergedRefs(ref, onCellChange);

  const onClick = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      if (!isEditing) {
        event.preventDefault();
        onClickProp?.(event);
        if (isFocused && !readOnly) {
          tableMeta?.onCellEditingStart?.(rowId, columnId);
        } else {
          tableMeta?.onCellClick?.(rowId, columnId, event);
        }
      }
    },
    [tableMeta, rowId, columnId, isEditing, isFocused, readOnly, onClickProp],
  );

  const onContextMenu = React.useCallback(
    (event: React.MouseEvent) => {
      if (!isEditing) {
        tableMeta?.onCellContextMenu?.(rowId, columnId, event);
      }
    },
    [tableMeta, rowId, columnId, isEditing],
  );

  const onDoubleClick = React.useCallback(
    (event: React.MouseEvent) => {
      if (!isEditing) {
        event.preventDefault();
        tableMeta?.onCellDoubleClick?.(rowId, columnId);
      }
    },
    [tableMeta, rowId, columnId, isEditing],
  );

  const onKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      onKeyDownProp?.(event);

      if (event.defaultPrevented) return;

      if (
        event.key === "ArrowUp" ||
        event.key === "ArrowDown" ||
        event.key === "ArrowLeft" ||
        event.key === "ArrowRight" ||
        event.key === "Home" ||
        event.key === "End" ||
        event.key === "PageUp" ||
        event.key === "PageDown" ||
        event.key === "Tab"
      ) {
        return;
      }

      if (isFocused && !isEditing && !readOnly) {
        if (event.key === "F2" || event.key === "Enter") {
          event.preventDefault();
          event.stopPropagation();
          tableMeta?.onCellEditingStart?.(rowId, columnId);
          return;
        }

        if (event.key === " ") {
          event.preventDefault();
          event.stopPropagation();
          tableMeta?.onCellEditingStart?.(rowId, columnId);
          return;
        }

        if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
          event.preventDefault();
          event.stopPropagation();
          tableMeta?.onCellEditingStart?.(rowId, columnId);
        }
      }
    },
    [onKeyDownProp, isFocused, isEditing, readOnly, tableMeta, rowId, columnId],
  );

  const onMouseDown = React.useCallback(
    (event: React.MouseEvent) => {
      if (!isEditing) {
        tableMeta?.onCellMouseDown?.(rowId, columnId, event);
      }
    },
    [tableMeta, rowId, columnId, isEditing],
  );

  const onMouseEnter = React.useCallback(() => {
    if (!isEditing) {
      tableMeta?.onCellMouseEnter?.(rowId, columnId);
    }
  }, [tableMeta, rowId, columnId, isEditing]);

  const onMouseUp = React.useCallback(() => {
    if (!isEditing) {
      tableMeta?.onCellMouseUp?.();
    }
  }, [tableMeta, isEditing]);

  return (
    <div
      role="button"
      data-slot="grid-cell-wrapper"
      data-editing={isEditing ? "" : undefined}
      data-focused={isFocused ? "" : undefined}
      data-selected={isSelected ? "" : undefined}
      tabIndex={isFocused && !isEditing ? 0 : -1}
      {...props}
      ref={composedRef}
      className={cn(
        "size-full px-2 py-1.5 text-start text-sm outline-none has-data-[slot=checkbox]:pt-2.5",
        {
          "ring-1 ring-inset": isFocused || !!cellPresence,
          "ring-ring": isFocused && !cellPresence,
          "bg-yellow-100 dark:bg-yellow-900/30":
            isSearchMatch && !isActiveSearchMatch,
          "bg-orange-200 dark:bg-orange-900/50": isActiveSearchMatch,
          "bg-primary/10": isSelected && !isEditing,
          "cursor-default": !isEditing,
          "**:data-[slot=grid-cell-content]:line-clamp-1":
            !isEditing && rowHeight === "short",
          "**:data-[slot=grid-cell-content]:line-clamp-2":
            !isEditing && rowHeight === "medium",
          "**:data-[slot=grid-cell-content]:line-clamp-3":
            !isEditing && rowHeight === "tall",
          "**:data-[slot=grid-cell-content]:line-clamp-4":
            !isEditing && rowHeight === "extra-tall",
        },
        className,
      )}
      style={
        cellPresence
          ? ({ "--tw-ring-color": cellPresence.color } as React.CSSProperties)
          : undefined
      }
      onClick={onClick}
      onContextMenu={onContextMenu}
      onDoubleClick={onDoubleClick}
      onMouseDown={onMouseDown}
      onMouseEnter={onMouseEnter}
      onMouseUp={onMouseUp}
      onKeyDown={onKeyDown}
    />
  );
}
src/registry/bases/base/components/data-grid/data-grid-cell-variants.tsx
"use client";

import type { RowData } from "@tanstack/react-table";

import { cn } from "cn";
import * as React from "react";
import { toast } from "sonner";

import type { DataGridCellProps, FileCellData } from "@/lib/data-grid-types";

import { useBadgeOverflow } from "@/hooks/use-badge-overflow";
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import {
  formatDateForDisplay,
  formatDateToString,
  formatFileSize,
  getCellKey,
  getFileIcon,
  getLineCount,
  getUrlHref,
  parseLocalDate,
} from "@/lib/data-grid-utils";
import { DataGridCellWrapper } from "@/registry/bases/base/components/data-grid/data-grid-cell-wrapper";
import { Badge } from "@/registry/bases/base/ui/badge";
import { Button } from "@/registry/bases/base/ui/button";
import { Checkbox } from "@/registry/bases/base/ui/checkbox";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/registry/bases/base/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/registry/bases/base/ui/popover";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/registry/bases/base/ui/select";
import { Skeleton } from "@/registry/bases/base/ui/skeleton";
import { Textarea } from "@/registry/bases/base/ui/textarea";
import { Calendar } from "@/registry/bases/radix/ui/calendar";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";

export function ShortTextCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isEditing,
  isFocused,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = cell.getValue() as string;
  const [value, setValue] = React.useState(initialValue);
  const cellRef = React.useRef<HTMLDivElement>(null);
  const containerRef = React.useRef<HTMLDivElement>(null);
  const prevIsEditingRef = React.useRef(false);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue);
    if (cellRef.current && !isEditing) {
      cellRef.current.textContent = initialValue;
    }
  }

  const onBlur = React.useCallback(() => {
    // Read the current value directly from the DOM to avoid stale state
    const currentValue = cellRef.current?.textContent ?? "";
    if (!readOnly && currentValue !== initialValue) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: currentValue });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, rowId, columnId, initialValue, readOnly]);

  const onInput = React.useCallback(
    (event: React.FormEvent<HTMLDivElement>) => {
      const currentValue = event.currentTarget.textContent ?? "";
      setValue(currentValue);
    },
    [],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing) {
        if (event.key === "Enter") {
          event.preventDefault();
          const currentValue = cellRef.current?.textContent ?? "";
          if (currentValue !== initialValue) {
            tableMeta?.onDataUpdate?.({
              rowId,
              columnId,
              value: currentValue,
            });
          }
          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
        } else if (event.key === "Tab") {
          event.preventDefault();
          const currentValue = cellRef.current?.textContent ?? "";
          if (currentValue !== initialValue) {
            tableMeta?.onDataUpdate?.({
              rowId,
              columnId,
              value: currentValue,
            });
          }
          tableMeta?.onCellEditingStop?.({
            direction: event.shiftKey ? "shift+tab" : "tab",
          });
        } else if (event.key === "Escape") {
          event.preventDefault();
          setValue(initialValue);
          cellRef.current?.blur();
        }
      } else if (
        isFocused &&
        event.key.length === 1 &&
        !event.ctrlKey &&
        !event.metaKey
      ) {
        // Handle typing to pre-fill the value when editing starts
        setValue(event.key);

        queueMicrotask(() => {
          if (cellRef.current && cellRef.current.contentEditable === "true") {
            cellRef.current.textContent = event.key;
            const range = document.createRange();
            const selection = window.getSelection();
            range.selectNodeContents(cellRef.current);
            range.collapse(false);
            selection?.removeAllRanges();
            selection?.addRange(range);
          }
        });
      }
    },
    [isEditing, isFocused, initialValue, tableMeta, rowId, columnId],
  );

  React.useEffect(() => {
    const wasEditing = prevIsEditingRef.current;
    prevIsEditingRef.current = isEditing;

    if (isEditing && !wasEditing && cellRef.current) {
      cellRef.current.focus();

      if (!cellRef.current.textContent && value) {
        cellRef.current.textContent = value;
      }

      if (cellRef.current.textContent) {
        const range = document.createRange();
        const selection = window.getSelection();
        range.selectNodeContents(cellRef.current);
        range.collapse(false);
        selection?.removeAllRanges();
        selection?.addRange(range);
      }
    }
  }, [isEditing, value]);

  const displayValue = !isEditing ? (value ?? "") : "";

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      <div
        role="textbox"
        data-slot="grid-cell-content"
        contentEditable={isEditing}
        tabIndex={-1}
        ref={cellRef}
        onBlur={onBlur}
        onInput={onInput}
        suppressContentEditableWarning
        className={cn("size-full overflow-hidden outline-none", {
          "whitespace-nowrap **:inline **:whitespace-nowrap [&_br]:hidden":
            isEditing,
        })}
      >
        {displayValue}
      </div>
    </DataGridCellWrapper>
  );
}

export function LongTextCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = cell.getValue() as string;
  const [value, setValue] = React.useState(initialValue ?? "");
  const textareaRef = React.useRef<HTMLTextAreaElement>(null);
  const containerRef = React.useRef<HTMLDivElement>(null);
  const pendingCharRef = React.useRef<string | null>(null);
  const sideOffset = -(containerRef.current?.clientHeight ?? 0);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue ?? "");
  }

  const debouncedSave = useDebouncedCallback((newValue: string) => {
    if (!readOnly) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: newValue });
    }
  }, 300);

  const onSave = React.useCallback(() => {
    // Immediately save any pending changes and close the popover
    if (!readOnly && value !== initialValue) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, value, initialValue, rowId, columnId, readOnly]);

  const onCancel = React.useCallback(() => {
    // Restore the original value
    setValue(initialValue ?? "");
    if (!readOnly) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: initialValue });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, initialValue, rowId, columnId, readOnly]);

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (open && !readOnly) {
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      } else {
        // Immediately save any pending changes when closing
        if (!readOnly && value !== initialValue) {
          tableMeta?.onDataUpdate?.({ rowId, columnId, value });
        }
        tableMeta?.onCellEditingStop?.();
      }
    },
    [tableMeta, value, initialValue, rowId, columnId, readOnly],
  );

  const onInitialFocus = React.useCallback(() => {
    const textarea = textareaRef.current;
    if (!textarea) return false;

    textarea.focus();
    const length = textarea.value.length;
    textarea.setSelectionRange(length, length);

    // Insert pending character using execCommand so it's part of undo history
    // Use requestAnimationFrame to ensure focus has fully settled
    if (pendingCharRef.current) {
      const char = pendingCharRef.current;
      pendingCharRef.current = null;
      requestAnimationFrame(() => {
        if (
          textareaRef.current &&
          document.activeElement === textareaRef.current
        ) {
          document.execCommand("insertText", false, char);
          textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
        }
      });
    } else {
      textarea.scrollTop = textarea.scrollHeight;
    }

    return false;
  }, []);

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (
        isFocused &&
        !isEditing &&
        !readOnly &&
        event.key.length === 1 &&
        !event.ctrlKey &&
        !event.metaKey
      ) {
        // Store the character to be inserted after textarea focuses
        // This ensures it's part of the textarea's undo history
        pendingCharRef.current = event.key;
      }
    },
    [isFocused, isEditing, readOnly],
  );

  const onBlur = React.useCallback(() => {
    // Immediately save any pending changes on blur
    if (!readOnly && value !== initialValue) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, value, initialValue, rowId, columnId, readOnly]);

  const onChange = React.useCallback(
    (event: React.ChangeEvent<HTMLTextAreaElement>) => {
      const newValue = event.target.value;
      setValue(newValue);
      debouncedSave(newValue);
    },
    [debouncedSave],
  );

  const onKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if (event.key === "Escape") {
        event.preventDefault();
        onCancel();
      } else if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
        event.preventDefault();
        onSave();
      } else if (event.key === "Tab") {
        event.preventDefault();
        // Save any pending changes
        if (value !== initialValue) {
          tableMeta?.onDataUpdate?.({ rowId, columnId, value });
        }
        tableMeta?.onCellEditingStop?.({
          direction: event.shiftKey ? "shift+tab" : "tab",
        });
        return;
      }
      // Stop propagation to prevent grid navigation
      event.stopPropagation();
    },
    [onSave, onCancel, value, initialValue, tableMeta, rowId, columnId],
  );

  return (
    <Popover open={isEditing} onOpenChange={onOpenChange}>
      <PopoverTrigger
        nativeButton={false}
        render={
          <DataGridCellWrapper<TData>
            ref={containerRef}
            cell={cell}
            tableMeta={tableMeta}
            rowId={rowId}
            columnId={columnId}
            rowHeight={rowHeight}
            isEditing={isEditing}
            isFocused={isFocused}
            isSelected={isSelected}
            isSearchMatch={isSearchMatch}
            isActiveSearchMatch={isActiveSearchMatch}
            readOnly={readOnly}
            onKeyDown={onWrapperKeyDown}
          />
        }
      >
        <span data-slot="grid-cell-content">{value}</span>
      </PopoverTrigger>
      <PopoverContent
        data-grid-cell-editor=""
        align="start"
        side="bottom"
        sideOffset={sideOffset}
        className="w-100 rounded-none p-0"
        initialFocus={onInitialFocus}
      >
        <Textarea
          placeholder="Enter text..."
          className="max-h-75 min-h-37.5 resize-none overflow-y-auto rounded-none border-0 shadow-none focus-visible:ring-1 focus-visible:ring-ring"
          ref={textareaRef}
          value={value}
          onBlur={onBlur}
          onChange={onChange}
          onKeyDown={onKeyDown}
        />
      </PopoverContent>
    </Popover>
  );
}

export function NumberCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = cell.getValue() as number;
  const [value, setValue] = React.useState(String(initialValue ?? ""));
  const inputRef = React.useRef<HTMLInputElement>(null);
  const containerRef = React.useRef<HTMLDivElement>(null);

  const cellOpts = cell.column.columnDef.meta?.cell;
  const numberCellOpts = cellOpts?.variant === "number" ? cellOpts : null;
  const min = numberCellOpts?.min;
  const max = numberCellOpts?.max;
  const step = numberCellOpts?.step;

  const prevIsEditingRef = React.useRef(false);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(String(initialValue ?? ""));
  }

  const onBlur = React.useCallback(() => {
    const numValue = value === "" ? null : Number(value);
    if (!readOnly && numValue !== initialValue) {
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: numValue });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, rowId, columnId, initialValue, value, readOnly]);

  const onChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setValue(event.target.value);
    },
    [],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing) {
        if (event.key === "Enter") {
          event.preventDefault();
          const numValue = value === "" ? null : Number(value);
          if (numValue !== initialValue) {
            tableMeta?.onDataUpdate?.({ rowId, columnId, value: numValue });
          }
          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
        } else if (event.key === "Tab") {
          event.preventDefault();
          const numValue = value === "" ? null : Number(value);
          if (numValue !== initialValue) {
            tableMeta?.onDataUpdate?.({ rowId, columnId, value: numValue });
          }
          tableMeta?.onCellEditingStop?.({
            direction: event.shiftKey ? "shift+tab" : "tab",
          });
        } else if (event.key === "Escape") {
          event.preventDefault();
          setValue(String(initialValue ?? ""));
          inputRef.current?.blur();
        }
      } else if (isFocused) {
        // Handle Backspace to start editing with empty value
        if (event.key === "Backspace") {
          setValue("");
        } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
          // Handle typing to pre-fill the value when editing starts
          setValue(event.key);
        }
      }
    },
    [isEditing, isFocused, initialValue, tableMeta, rowId, columnId, value],
  );

  React.useEffect(() => {
    const wasEditing = prevIsEditingRef.current;
    prevIsEditingRef.current = isEditing;

    // Only focus when we start editing (transition from false to true)
    if (isEditing && !wasEditing && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isEditing]);

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      {isEditing ? (
        <input
          type="number"
          ref={inputRef}
          value={value}
          min={min}
          max={max}
          step={step}
          className="w-full [appearance:textfield] border-none bg-transparent p-0 outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
          onBlur={onBlur}
          onChange={onChange}
        />
      ) : (
        <span data-slot="grid-cell-content">{value}</span>
      )}
    </DataGridCellWrapper>
  );
}

export function UrlCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isEditing,
  isFocused,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = cell.getValue() as string;
  const [value, setValue] = React.useState(initialValue ?? "");
  const cellRef = React.useRef<HTMLDivElement>(null);
  const containerRef = React.useRef<HTMLDivElement>(null);
  const prevIsEditingRef = React.useRef(false);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue ?? "");
    if (cellRef.current && !isEditing) {
      cellRef.current.textContent = initialValue ?? "";
    }
  }

  const onBlur = React.useCallback(() => {
    const currentValue = cellRef.current?.textContent?.trim() ?? "";

    if (!readOnly && currentValue !== initialValue) {
      tableMeta?.onDataUpdate?.({
        rowId,
        columnId,
        value: currentValue || null,
      });
    }
    tableMeta?.onCellEditingStop?.();
  }, [tableMeta, rowId, columnId, initialValue, readOnly]);

  const onInput = React.useCallback(
    (event: React.FormEvent<HTMLDivElement>) => {
      const currentValue = event.currentTarget.textContent ?? "";
      setValue(currentValue);
    },
    [],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing) {
        if (event.key === "Enter") {
          event.preventDefault();
          const currentValue = cellRef.current?.textContent?.trim() ?? "";
          if (!readOnly && currentValue !== initialValue) {
            tableMeta?.onDataUpdate?.({
              rowId,
              columnId,
              value: currentValue || null,
            });
          }
          tableMeta?.onCellEditingStop?.({ moveToNextRow: true });
        } else if (event.key === "Tab") {
          event.preventDefault();
          const currentValue = cellRef.current?.textContent?.trim() ?? "";
          if (!readOnly && currentValue !== initialValue) {
            tableMeta?.onDataUpdate?.({
              rowId,
              columnId,
              value: currentValue || null,
            });
          }
          tableMeta?.onCellEditingStop?.({
            direction: event.shiftKey ? "shift+tab" : "tab",
          });
        } else if (event.key === "Escape") {
          event.preventDefault();
          setValue(initialValue ?? "");
          cellRef.current?.blur();
        }
      } else if (
        isFocused &&
        event.key === "Enter" &&
        (event.ctrlKey || event.metaKey)
      ) {
        event.preventDefault();
        if (!value) return;
        const href = getUrlHref(value);
        if (href) {
          window.open(href, "_blank", "noopener,noreferrer");
        } else {
          toastDangerousUrl();
        }
      } else if (
        isFocused &&
        !readOnly &&
        event.key.length === 1 &&
        !event.ctrlKey &&
        !event.metaKey
      ) {
        // Handle typing to pre-fill the value when editing starts
        setValue(event.key);

        queueMicrotask(() => {
          if (cellRef.current && cellRef.current.contentEditable === "true") {
            cellRef.current.textContent = event.key;
            const range = document.createRange();
            const selection = window.getSelection();
            range.selectNodeContents(cellRef.current);
            range.collapse(false);
            selection?.removeAllRanges();
            selection?.addRange(range);
          }
        });
      }
    },
    [
      isEditing,
      isFocused,
      initialValue,
      value,
      tableMeta,
      rowId,
      columnId,
      readOnly,
    ],
  );

  const onLinkClick = React.useCallback(
    (event: React.MouseEvent<HTMLAnchorElement>) => {
      if (isEditing) {
        event.preventDefault();
        return;
      }

      // Check if URL was rejected due to dangerous protocol
      const href = getUrlHref(value);
      if (!href) {
        event.preventDefault();
        toastDangerousUrl();
        return;
      }

      // Stop propagation to prevent grid from interfering with link navigation
      event.stopPropagation();
    },
    [isEditing, value],
  );

  React.useEffect(() => {
    const wasEditing = prevIsEditingRef.current;
    prevIsEditingRef.current = isEditing;

    if (isEditing && !wasEditing && cellRef.current) {
      cellRef.current.focus();

      if (!cellRef.current.textContent && value) {
        cellRef.current.textContent = value;
      }

      if (cellRef.current.textContent) {
        const range = document.createRange();
        const selection = window.getSelection();
        range.selectNodeContents(cellRef.current);
        range.collapse(false);
        selection?.removeAllRanges();
        selection?.addRange(range);
      }
    }
  }, [isEditing, value]);

  const displayValue = !isEditing ? (value ?? "") : "";
  const urlHref = displayValue ? getUrlHref(displayValue) : "";
  const isDangerousUrl = displayValue && !urlHref;

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      {!isEditing && displayValue ? (
        <div
          data-slot="grid-cell-content"
          className="size-full overflow-hidden"
        >
          <a
            tabIndex={-1}
            data-focused={isFocused && !isDangerousUrl ? "" : undefined}
            data-invalid={isDangerousUrl ? "" : undefined}
            href={urlHref}
            target="_blank"
            rel="noopener noreferrer"
            className="truncate text-primary underline decoration-primary/30 underline-offset-2 hover:decoration-primary/60 data-focused:text-foreground data-focused:decoration-foreground/50 data-focused:hover:decoration-foreground/70 data-invalid:cursor-not-allowed data-invalid:text-destructive data-invalid:decoration-destructive/50 data-invalid:hover:decoration-destructive/70"
            onClick={onLinkClick}
          >
            {displayValue}
          </a>
        </div>
      ) : (
        <div
          role="textbox"
          data-slot="grid-cell-content"
          contentEditable={isEditing}
          tabIndex={-1}
          ref={cellRef}
          onBlur={onBlur}
          onInput={onInput}
          suppressContentEditableWarning
          className={cn("size-full overflow-hidden outline-none", {
            "whitespace-nowrap **:inline **:whitespace-nowrap [&_br]:hidden":
              isEditing,
          })}
        >
          {displayValue}
        </div>
      )}
    </DataGridCellWrapper>
  );
}

export function CheckboxCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: Omit<DataGridCellProps<TData>, "isEditing">) {
  const initialValue = cell.getValue() as boolean;
  const [value, setValue] = React.useState(initialValue);
  const containerRef = React.useRef<HTMLDivElement>(null);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue);
  }

  const onCheckedChange = React.useCallback(
    (checked: boolean) => {
      if (readOnly) return;
      setValue(checked);
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: checked });
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (
        isFocused &&
        !readOnly &&
        (event.key === " " || event.key === "Enter")
      ) {
        event.preventDefault();
        event.stopPropagation();
        onCheckedChange(!value);
      }
    },
    [isFocused, value, onCheckedChange, readOnly],
  );

  const onWrapperClick = React.useCallback(
    (event: React.MouseEvent) => {
      if (isFocused && !readOnly) {
        event.preventDefault();
        event.stopPropagation();
        onCheckedChange(!value);
      }
    },
    [isFocused, value, onCheckedChange, readOnly],
  );

  const onCheckboxClick = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
  }, []);

  const onCheckboxMouseDown = React.useCallback(
    (event: React.MouseEvent<HTMLElement>) => {
      event.stopPropagation();
    },
    [],
  );

  const onCheckboxDoubleClick = React.useCallback(
    (event: React.MouseEvent<HTMLElement>) => {
      event.stopPropagation();
    },
    [],
  );

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={false}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      className="flex size-full justify-center"
      onClick={onWrapperClick}
      onKeyDown={onWrapperKeyDown}
    >
      <Checkbox
        tabIndex={-1}
        checked={value}
        onCheckedChange={onCheckedChange}
        disabled={readOnly}
        className="border-primary"
        onClick={onCheckboxClick}
        onMouseDown={onCheckboxMouseDown}
        onDoubleClick={onCheckboxDoubleClick}
      />
    </DataGridCellWrapper>
  );
}

export function SelectCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = (cell.getValue() ?? undefined) as string | undefined;

  const [value, setValue] = React.useState(initialValue);
  const containerRef = React.useRef<HTMLDivElement>(null);
  const cellOpts = cell.column.columnDef.meta?.cell;
  const options = React.useMemo(
    () => (cellOpts?.variant === "select" ? cellOpts.options : []),
    [cellOpts],
  );
  const optionByValue = React.useMemo(
    () => new Map(options.map((option) => [option.value, option])),
    [options],
  );

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue);
  }

  const onValueChange = React.useCallback(
    (newValue: string | null) => {
      if (newValue == null || readOnly) return;
      setValue(newValue);
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: newValue });
      tableMeta?.onCellEditingStop?.();
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (open && !readOnly) {
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      } else {
        tableMeta?.onCellEditingStop?.();
      }
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing && event.key === "Escape") {
        event.preventDefault();
        setValue(initialValue);
        tableMeta?.onCellEditingStop?.();
      } else if (isEditing && event.key === "Tab") {
        event.preventDefault();
        tableMeta?.onCellEditingStop?.({
          direction: event.shiftKey ? "shift+tab" : "tab",
        });
      }
    },
    [isEditing, initialValue, tableMeta],
  );

  const displayLabel = value
    ? (optionByValue.get(value)?.label ?? value)
    : null;

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      {isEditing ? (
        <Select
          value={value}
          onValueChange={onValueChange}
          open={isEditing}
          onOpenChange={onOpenChange}
        >
          <SelectTrigger className="size-full items-start border-none p-0 shadow-none focus-visible:ring-0 dark:bg-transparent [&_svg]:hidden">
            {displayLabel ? (
              <Badge
                variant="secondary"
                className="px-1.5 py-px whitespace-pre-wrap"
              >
                <SelectValue />
              </Badge>
            ) : (
              <SelectValue />
            )}
          </SelectTrigger>
          <SelectContent
            data-grid-cell-editor=""
            // compensate for the wrapper padding
            align="start"
            alignOffset={-8}
            sideOffset={-8}
            className="min-w-[calc(var(--anchor-width)+16px)]"
          >
            <SelectGroup>
              {options.map((option) => (
                <SelectItem key={option.value} value={option.value}>
                  {option.label}
                </SelectItem>
              ))}
            </SelectGroup>
          </SelectContent>
        </Select>
      ) : displayLabel ? (
        <Badge
          data-slot="grid-cell-content"
          variant="secondary"
          className="px-1.5 py-px whitespace-pre-wrap"
        >
          {displayLabel}
        </Badge>
      ) : null}
    </DataGridCellWrapper>
  );
}

export function MultiSelectCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const cellValue = React.useMemo(() => {
    const value = cell.getValue() as string[];
    return value ?? [];
  }, [cell]);

  const cellKey = getCellKey(rowId, columnId);
  const prevCellKeyRef = React.useRef(cellKey);

  const [selectedValues, setSelectedValues] =
    React.useState<string[]>(cellValue);
  const [searchValue, setSearchValue] = React.useState("");
  const containerRef = React.useRef<HTMLDivElement>(null);
  const inputRef = React.useRef<HTMLInputElement>(null);
  const cellOpts = cell.column.columnDef.meta?.cell;
  const options = React.useMemo(
    () => (cellOpts?.variant === "multi-select" ? cellOpts.options : []),
    [cellOpts],
  );
  const optionByValue = React.useMemo(
    () => new Map(options.map((option) => [option.value, option])),
    [options],
  );
  const sideOffset = -(containerRef.current?.clientHeight ?? 0);

  const prevCellValueRef = React.useRef(cellValue);
  if (cellValue !== prevCellValueRef.current) {
    prevCellValueRef.current = cellValue;
    setSelectedValues(cellValue);
  }

  if (prevCellKeyRef.current !== cellKey) {
    prevCellKeyRef.current = cellKey;
    setSearchValue("");
  }

  const onValueChange = React.useCallback(
    (value: string) => {
      if (readOnly) return;
      let newValues: string[] = [];
      setSelectedValues((curr) => {
        newValues = curr.includes(value)
          ? curr.filter((v) => v !== value)
          : [...curr, value];
        return newValues;
      });
      queueMicrotask(() => {
        tableMeta?.onDataUpdate?.({ rowId, columnId, value: newValues });
        inputRef.current?.focus();
      });
      setSearchValue("");
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const removeValue = React.useCallback(
    (valueToRemove: string, event?: React.MouseEvent) => {
      if (readOnly) return;
      event?.stopPropagation();
      event?.preventDefault();
      let newValues: string[] = [];
      setSelectedValues((curr) => {
        newValues = curr.filter((v) => v !== valueToRemove);
        return newValues;
      });
      queueMicrotask(() => {
        tableMeta?.onDataUpdate?.({ rowId, columnId, value: newValues });
        inputRef.current?.focus();
      });
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const clearAll = React.useCallback(() => {
    if (readOnly) return;
    setSelectedValues([]);
    tableMeta?.onDataUpdate?.({ rowId, columnId, value: [] });
    queueMicrotask(() => inputRef.current?.focus());
  }, [tableMeta, rowId, columnId, readOnly]);

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (open && !readOnly) {
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      } else {
        setSearchValue("");
        tableMeta?.onCellEditingStop?.();
      }
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onInitialFocus = React.useCallback(() => {
    inputRef.current?.focus();
    return false;
  }, []);

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing && event.key === "Escape") {
        event.preventDefault();
        setSelectedValues(cellValue);
        setSearchValue("");
        tableMeta?.onCellEditingStop?.();
      } else if (isEditing && event.key === "Tab") {
        event.preventDefault();
        setSearchValue("");
        tableMeta?.onCellEditingStop?.({
          direction: event.shiftKey ? "shift+tab" : "tab",
        });
      }
    },
    [isEditing, cellValue, tableMeta],
  );

  const onInputKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLInputElement>) => {
      if (event.key === "Backspace" && searchValue === "") {
        event.preventDefault();
        let newValues: string[] | null = null;
        setSelectedValues((curr) => {
          if (curr.length === 0) return curr;
          newValues = curr.slice(0, -1);
          return newValues;
        });
        queueMicrotask(() => {
          if (newValues !== null) {
            tableMeta?.onDataUpdate?.({ rowId, columnId, value: newValues });
          }
          inputRef.current?.focus();
        });
      }
      if (event.key === "Escape") {
        event.stopPropagation();
      }
    },
    [searchValue, tableMeta, rowId, columnId],
  );

  const displayLabels = selectedValues
    .map((val) => optionByValue.get(val)?.label ?? val)
    .filter(Boolean);

  const selectedValuesSet = React.useMemo(
    () => new Set(selectedValues),
    [selectedValues],
  );

  const lineCount = getLineCount(rowHeight);

  const { visibleItems: visibleLabels, hiddenCount: hiddenBadgeCount } =
    useBadgeOverflow({
      items: displayLabels,
      getLabel: (label) => label,
      containerRef,
      lineCount,
    });

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      {isEditing ? (
        <Popover open={isEditing} onOpenChange={onOpenChange}>
          <PopoverTrigger
            nativeButton={false}
            render={<div className="pointer-events-none absolute inset-0" />}
          />
          <PopoverContent
            data-grid-cell-editor=""
            align="start"
            sideOffset={sideOffset}
            className="w-75 rounded-none p-0"
            initialFocus={onInitialFocus}
          >
            <Command className="**:data-[slot=command-input-wrapper]:min-w-16 **:data-[slot=command-input-wrapper]:flex-1 **:data-[slot=command-input-wrapper]:p-0 **:data-[slot=input-group]:h-auto! **:data-[slot=input-group]:rounded-none! **:data-[slot=input-group]:border-none **:data-[slot=input-group]:bg-transparent **:data-[slot=input-group-addon]:hidden **:data-[slot=input-group]:dark:bg-transparent">
              <div className="flex min-h-9 flex-wrap items-center gap-1 border-b px-3 py-1.5">
                {selectedValues.map((value) => {
                  const label = optionByValue.get(value)?.label ?? value;

                  return (
                    <Badge
                      key={value}
                      variant="secondary"
                      className="gap-1 px-1.5 py-px"
                    >
                      {label}
                      <button
                        type="button"
                        onClick={(event) => removeValue(value, event)}
                        onPointerDown={(event) => {
                          event.preventDefault();
                          event.stopPropagation();
                        }}
                      >
                        <IconPlaceholder
                          lucide="X"
                          tabler="IconX"
                          hugeicons="Cancel01Icon"
                          phosphor="XIcon"
                          remixicon="RiCloseLine"
                          className="size-3"
                        />
                      </button>
                    </Badge>
                  );
                })}
                <CommandInput
                  ref={inputRef}
                  value={searchValue}
                  onValueChange={setSearchValue}
                  onKeyDown={onInputKeyDown}
                  placeholder="Search..."
                  className="p-0! placeholder:text-muted-foreground"
                />
              </div>
              <CommandList className="max-h-full">
                <CommandEmpty>No options found.</CommandEmpty>
                <CommandGroup className="max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto">
                  {options.map((option) => {
                    const isSelected = selectedValuesSet.has(option.value);

                    return (
                      <CommandItem
                        key={option.value}
                        value={option.label}
                        onSelect={() => onValueChange(option.value)}
                        data-checked={isSelected}
                      >
                        <span>{option.label}</span>
                      </CommandItem>
                    );
                  })}
                </CommandGroup>
                {selectedValues.length > 0 && (
                  <>
                    <CommandSeparator />
                    <CommandGroup>
                      <CommandItem
                        onSelect={clearAll}
                        className="justify-center text-muted-foreground"
                      >
                        Clear all
                      </CommandItem>
                    </CommandGroup>
                  </>
                )}
              </CommandList>
            </Command>
          </PopoverContent>
        </Popover>
      ) : null}
      {displayLabels.length > 0 ? (
        <div className="flex flex-wrap items-center gap-1 overflow-hidden">
          {visibleLabels.map((label, index) => (
            <Badge
              key={selectedValues[index]}
              variant="secondary"
              className="px-1.5 py-px"
            >
              {label}
            </Badge>
          ))}
          {hiddenBadgeCount > 0 && (
            <Badge
              variant="outline"
              className="px-1.5 py-px text-muted-foreground"
            >
              +{hiddenBadgeCount}
            </Badge>
          )}
        </div>
      ) : null}
    </DataGridCellWrapper>
  );
}

export function DateCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const initialValue = cell.getValue() as string;
  const [value, setValue] = React.useState(initialValue ?? "");
  const containerRef = React.useRef<HTMLDivElement>(null);

  const prevInitialValueRef = React.useRef(initialValue);
  if (initialValue !== prevInitialValueRef.current) {
    prevInitialValueRef.current = initialValue;
    setValue(initialValue ?? "");
  }

  // Parse date as local time to avoid timezone shifts
  const selectedDate = value ? (parseLocalDate(value) ?? undefined) : undefined;

  const onDateSelect = React.useCallback(
    (date: Date | undefined) => {
      if (!date || readOnly) return;

      // Format using local date components to avoid timezone issues
      const formattedDate = formatDateToString(date);
      setValue(formattedDate);
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: formattedDate });
      tableMeta?.onCellEditingStop?.();
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (open && !readOnly) {
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      } else {
        tableMeta?.onCellEditingStop?.();
      }
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing && event.key === "Escape") {
        event.preventDefault();
        setValue(initialValue);
        tableMeta?.onCellEditingStop?.();
      } else if (isEditing && event.key === "Tab") {
        event.preventDefault();
        tableMeta?.onCellEditingStop?.({
          direction: event.shiftKey ? "shift+tab" : "tab",
        });
      }
    },
    [isEditing, initialValue, tableMeta],
  );

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      onKeyDown={onWrapperKeyDown}
    >
      <Popover open={isEditing} onOpenChange={onOpenChange}>
        <PopoverTrigger
          nativeButton={false}
          render={<span data-slot="grid-cell-content" />}
        >
          {formatDateForDisplay(value)}
        </PopoverTrigger>
        {isEditing && (
          <PopoverContent
            data-grid-cell-editor=""
            align="start"
            alignOffset={-8}
            className="w-auto p-0"
          >
            <Calendar
              autoFocus
              captionLayout="dropdown"
              mode="single"
              defaultMonth={selectedDate ?? new Date()}
              selected={selectedDate}
              onSelect={onDateSelect}
            />
          </PopoverContent>
        )}
      </Popover>
    </DataGridCellWrapper>
  );
}

export function FileCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  rowHeight,
  isFocused,
  isEditing,
  isSelected,
  isSearchMatch,
  isActiveSearchMatch,
  readOnly,
}: DataGridCellProps<TData>) {
  const cellValue = React.useMemo(
    () => (cell.getValue() as FileCellData[]) ?? [],
    [cell],
  );

  const cellKey = getCellKey(rowId, columnId);
  const prevCellKeyRef = React.useRef(cellKey);

  const labelId = React.useId();
  const descriptionId = React.useId();

  const [files, setFiles] = React.useState<FileCellData[]>(cellValue);
  const [uploadingFiles, setUploadingFiles] = React.useState<Set<string>>(
    new Set(),
  );
  const [deletingFiles, setDeletingFiles] = React.useState<Set<string>>(
    new Set(),
  );
  const [isDraggingOver, setIsDraggingOver] = React.useState(false);
  const [isDragging, setIsDragging] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);

  const isUploading = uploadingFiles.size > 0;
  const isDeleting = deletingFiles.size > 0;
  const isPending = isUploading || isDeleting;
  const containerRef = React.useRef<HTMLDivElement>(null);
  const fileInputRef = React.useRef<HTMLInputElement>(null);
  const dropzoneRef = React.useRef<HTMLDivElement>(null);
  const cellOpts = cell.column.columnDef.meta?.cell;
  const sideOffset = -(containerRef.current?.clientHeight ?? 0);

  const fileCellOpts = cellOpts?.variant === "file" ? cellOpts : null;
  const maxFileSize = fileCellOpts?.maxFileSize ?? 10 * 1024 * 1024;
  const maxFiles = fileCellOpts?.maxFiles ?? 10;
  const accept = fileCellOpts?.accept;
  const multiple = fileCellOpts?.multiple ?? false;

  const acceptedTypes = React.useMemo(
    () => (accept ? accept.split(",").map((t) => t.trim()) : null),
    [accept],
  );

  const prevCellValueRef = React.useRef(cellValue);
  if (cellValue !== prevCellValueRef.current) {
    prevCellValueRef.current = cellValue;
    for (const file of files) {
      if (file.url) {
        URL.revokeObjectURL(file.url);
      }
    }
    setFiles(cellValue);
    setError(null);
  }

  if (prevCellKeyRef.current !== cellKey) {
    prevCellKeyRef.current = cellKey;
    setError(null);
  }

  const validateFile = React.useCallback(
    (file: File): string | null => {
      if (maxFileSize && file.size > maxFileSize) {
        return `File size exceeds ${formatFileSize(maxFileSize)}`;
      }
      if (acceptedTypes) {
        const fileExtension = `.${file.name.split(".").pop()}`;
        const isAccepted = acceptedTypes.some((type) => {
          if (type.endsWith("/*")) {
            const baseType = type.slice(0, -2);
            return file.type.startsWith(`${baseType}/`);
          }
          if (type.startsWith(".")) {
            return fileExtension.toLowerCase() === type.toLowerCase();
          }
          return file.type === type;
        });
        if (!isAccepted) {
          return "File type not accepted";
        }
      }
      return null;
    },
    [maxFileSize, acceptedTypes],
  );

  const addFiles = React.useCallback(
    async (newFiles: File[], skipUpload = false) => {
      if (readOnly || isPending) return;
      setError(null);

      if (maxFiles && files.length + newFiles.length > maxFiles) {
        const errorMessage = `Maximum ${maxFiles} files allowed`;
        setError(errorMessage);
        toast(errorMessage);
        setTimeout(() => {
          setError(null);
        }, 2000);
        return;
      }

      const rejectedFiles: Array<{ name: string; reason: string }> = [];
      const filesToValidate: File[] = [];

      for (const file of newFiles) {
        const validationError = validateFile(file);
        if (validationError) {
          rejectedFiles.push({ name: file.name, reason: validationError });
          continue;
        }
        filesToValidate.push(file);
      }

      if (rejectedFiles.length > 0) {
        const firstError = rejectedFiles[0];
        if (firstError) {
          setError(firstError.reason);

          const truncatedName =
            firstError.name.length > 20
              ? `${firstError.name.slice(0, 20)}...`
              : firstError.name;

          if (rejectedFiles.length === 1) {
            toast(firstError.reason, {
              description: `"${truncatedName}" has been rejected`,
            });
          } else {
            toast(firstError.reason, {
              description: `"${truncatedName}" and ${rejectedFiles.length - 1} more rejected`,
            });
          }

          setTimeout(() => {
            setError(null);
          }, 2000);
        }
      }

      if (filesToValidate.length > 0) {
        if (!skipUpload) {
          const tempFiles = filesToValidate.map((f) => ({
            id: crypto.randomUUID(),
            name: f.name,
            size: f.size,
            type: f.type,
            url: undefined,
          }));
          const filesWithTemp = [...files, ...tempFiles];
          setFiles(filesWithTemp);

          const uploadingIds = new Set(tempFiles.map((f) => f.id));
          setUploadingFiles(uploadingIds);

          let uploadedFiles: FileCellData[] = [];

          if (tableMeta?.onFilesUpload) {
            try {
              uploadedFiles = await tableMeta.onFilesUpload({
                files: filesToValidate,
                rowId,
                columnId,
              });
            } catch (error) {
              toast.error(
                error instanceof Error
                  ? error.message
                  : `Failed to upload ${filesToValidate.length} file${filesToValidate.length !== 1 ? "s" : ""}`,
              );
              setFiles((prev) => prev.filter((f) => !uploadingIds.has(f.id)));
              setUploadingFiles(new Set());
              return;
            }
          } else {
            uploadedFiles = filesToValidate.map((f, i) => ({
              id: tempFiles[i]?.id ?? crypto.randomUUID(),
              name: f.name,
              size: f.size,
              type: f.type,
              url: URL.createObjectURL(f),
            }));
          }

          const finalFiles = filesWithTemp
            .map((f) => {
              if (uploadingIds.has(f.id)) {
                return uploadedFiles.find((uf) => uf.name === f.name) ?? f;
              }
              return f;
            })
            .filter((f) => f.url !== undefined);

          setFiles(finalFiles);
          setUploadingFiles(new Set());
          tableMeta?.onDataUpdate?.({ rowId, columnId, value: finalFiles });
        } else {
          const newFilesData: FileCellData[] = filesToValidate.map((f) => ({
            id: crypto.randomUUID(),
            name: f.name,
            size: f.size,
            type: f.type,
            url: URL.createObjectURL(f),
          }));
          const updatedFiles = [...files, ...newFilesData];
          setFiles(updatedFiles);
          tableMeta?.onDataUpdate?.({
            rowId,
            columnId,
            value: updatedFiles,
          });
        }
      }
    },
    [
      files,
      maxFiles,
      validateFile,
      tableMeta,
      rowId,
      columnId,
      readOnly,
      isPending,
    ],
  );

  const removeFile = React.useCallback(
    async (fileId: string) => {
      if (readOnly || isPending) return;
      setError(null);

      const fileToRemove = files.find((f) => f.id === fileId);
      if (!fileToRemove) return;

      setDeletingFiles((prev) => new Set(prev).add(fileId));

      if (tableMeta?.onFilesDelete) {
        try {
          await tableMeta.onFilesDelete({
            fileIds: [fileId],
            rowId,
            columnId,
          });
        } catch (error) {
          toast.error(
            error instanceof Error
              ? error.message
              : `Failed to delete ${fileToRemove.name}`,
          );
          setDeletingFiles((prev) => {
            const next = new Set(prev);
            next.delete(fileId);
            return next;
          });
          return;
        }
      }

      if (fileToRemove.url?.startsWith("blob:")) {
        URL.revokeObjectURL(fileToRemove.url);
      }

      const updatedFiles = files.filter((f) => f.id !== fileId);
      setFiles(updatedFiles);
      setDeletingFiles((prev) => {
        const next = new Set(prev);
        next.delete(fileId);
        return next;
      });
      tableMeta?.onDataUpdate?.({ rowId, columnId, value: updatedFiles });
    },
    [files, tableMeta, rowId, columnId, readOnly, isPending],
  );

  const clearAll = React.useCallback(async () => {
    if (readOnly || isPending) return;
    setError(null);

    const fileIds = files.map((f) => f.id);
    setDeletingFiles(new Set(fileIds));

    if (tableMeta?.onFilesDelete && files.length > 0) {
      try {
        await tableMeta.onFilesDelete({
          fileIds,
          rowId,
          columnId,
        });
      } catch (error) {
        toast.error(
          error instanceof Error ? error.message : "Failed to delete files",
        );
        setDeletingFiles(new Set());
        return;
      }
    }

    for (const file of files) {
      if (file.url?.startsWith("blob:")) {
        URL.revokeObjectURL(file.url);
      }
    }
    setFiles([]);
    setDeletingFiles(new Set());
    tableMeta?.onDataUpdate?.({ rowId, columnId, value: [] });
  }, [files, tableMeta, rowId, columnId, readOnly, isPending]);

  const onCellDragEnter = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
    if (event.dataTransfer.types.includes("Files")) {
      setIsDraggingOver(true);
    }
  }, []);

  const onCellDragLeave = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
    const rect = event.currentTarget.getBoundingClientRect();
    const x = event.clientX;
    const y = event.clientY;

    if (
      x <= rect.left ||
      x >= rect.right ||
      y <= rect.top ||
      y >= rect.bottom
    ) {
      setIsDraggingOver(false);
    }
  }, []);

  const onCellDragOver = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
  }, []);

  const onCellDrop = React.useCallback(
    (event: React.DragEvent) => {
      event.preventDefault();
      event.stopPropagation();
      setIsDraggingOver(false);

      const droppedFiles = Array.from(event.dataTransfer.files);
      if (droppedFiles.length > 0) {
        void addFiles(droppedFiles, false);
      }
    },
    [addFiles],
  );

  const onDropzoneDragEnter = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
    setIsDragging(true);
  }, []);

  const onDropzoneDragLeave = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
    const rect = event.currentTarget.getBoundingClientRect();
    const x = event.clientX;
    const y = event.clientY;

    if (
      x <= rect.left ||
      x >= rect.right ||
      y <= rect.top ||
      y >= rect.bottom
    ) {
      setIsDragging(false);
    }
  }, []);

  const onDropzoneDragOver = React.useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
  }, []);

  const onDropzoneDrop = React.useCallback(
    (event: React.DragEvent) => {
      event.preventDefault();
      event.stopPropagation();
      setIsDragging(false);

      const droppedFiles = Array.from(event.dataTransfer.files);
      void addFiles(droppedFiles, false);
    },
    [addFiles],
  );

  const onDropzoneClick = React.useCallback(() => {
    fileInputRef.current?.click();
  }, []);

  const onDropzoneKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        onDropzoneClick();
      }
    },
    [onDropzoneClick],
  );

  const onFileInputChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const selectedFiles = Array.from(event.target.files ?? []);
      void addFiles(selectedFiles, false);
      event.target.value = "";
    },
    [addFiles],
  );

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      if (open && !readOnly) {
        setError(null);
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      } else {
        setError(null);
        tableMeta?.onCellEditingStop?.();
      }
    },
    [tableMeta, rowId, columnId, readOnly],
  );

  const onEditorKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      // Prevent the escape key from propagating to the data grid's keyboard handler
      // which would call blurCell() and remove focus from the cell
      if (event.key === "Escape") event.stopPropagation();
    },
    [],
  );

  const onInitialFocus = React.useCallback(() => {
    queueMicrotask(() => {
      dropzoneRef.current?.focus();
    });
    return false;
  }, []);

  const onWrapperKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (isEditing) {
        if (event.key === "Escape") {
          event.preventDefault();
          setFiles(cellValue);
          setError(null);
          tableMeta?.onCellEditingStop?.();
        } else if (event.key === " ") {
          event.preventDefault();
          onDropzoneClick();
        } else if (event.key === "Tab") {
          event.preventDefault();
          tableMeta?.onCellEditingStop?.({
            direction: event.shiftKey ? "shift+tab" : "tab",
          });
        }
      } else if (isFocused && event.key === "Enter") {
        event.preventDefault();
        tableMeta?.onCellEditingStart?.(rowId, columnId);
      }
    },
    [
      isEditing,
      isFocused,
      cellValue,
      tableMeta,
      onDropzoneClick,
      rowId,
      columnId,
    ],
  );

  React.useEffect(() => {
    return () => {
      for (const file of files) {
        if (file.url) {
          URL.revokeObjectURL(file.url);
        }
      }
    };
  }, [files]);

  const lineCount = getLineCount(rowHeight);

  const { visibleItems: visibleFiles, hiddenCount: hiddenFileCount } =
    useBadgeOverflow({
      items: files,
      getLabel: (file) => file.name,
      containerRef,
      lineCount,
      cacheKeyPrefix: "file",
      iconSize: 12,
      maxWidth: 100,
    });

  return (
    <DataGridCellWrapper<TData>
      ref={containerRef}
      cell={cell}
      tableMeta={tableMeta}
      rowId={rowId}
      columnId={columnId}
      rowHeight={rowHeight}
      isEditing={isEditing}
      isFocused={isFocused}
      isSelected={isSelected}
      isSearchMatch={isSearchMatch}
      isActiveSearchMatch={isActiveSearchMatch}
      readOnly={readOnly}
      className={cn({
        "ring-1 ring-primary/80 ring-inset": isDraggingOver,
      })}
      onDragEnter={onCellDragEnter}
      onDragLeave={onCellDragLeave}
      onDragOver={onCellDragOver}
      onDrop={onCellDrop}
      onKeyDown={onWrapperKeyDown}
    >
      {isEditing ? (
        <Popover open={isEditing} onOpenChange={onOpenChange}>
          <PopoverTrigger
            nativeButton={false}
            render={<div className="pointer-events-none absolute inset-0" />}
          />
          <PopoverContent
            data-grid-cell-editor=""
            align="start"
            sideOffset={sideOffset}
            className="w-100 rounded-none p-0"
            initialFocus={onInitialFocus}
            onKeyDown={onEditorKeyDown}
          >
            <div className="flex flex-col gap-2 p-3">
              <span id={labelId} className="sr-only">
                File upload
              </span>
              <div
                role="region"
                aria-labelledby={labelId}
                aria-describedby={descriptionId}
                aria-invalid={!!error}
                aria-disabled={isPending}
                data-dragging={isDragging ? "" : undefined}
                data-invalid={error ? "" : undefined}
                data-disabled={isPending ? "" : undefined}
                tabIndex={isDragging || isPending ? -1 : 0}
                className="flex cursor-pointer flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed p-6 transition-colors outline-none hover:bg-accent/30 focus-visible:border-ring/50 data-dragging:border-primary/30 data-dragging:bg-accent/30 data-invalid:border-destructive data-invalid:ring-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50"
                ref={dropzoneRef}
                onClick={onDropzoneClick}
                onDragEnter={onDropzoneDragEnter}
                onDragLeave={onDropzoneDragLeave}
                onDragOver={onDropzoneDragOver}
                onDrop={onDropzoneDrop}
                onKeyDown={onDropzoneKeyDown}
              >
                <IconPlaceholder
                  lucide="Upload"
                  tabler="IconCloudUpload"
                  hugeicons="CloudUploadIcon"
                  phosphor="CloudArrowUpIcon"
                  remixicon="RiUploadCloudLine"
                  className="size-8 text-muted-foreground"
                />
                <div className="text-center text-sm">
                  <p className="font-medium">
                    {isDragging ? "Drop files here" : "Drag files here"}
                  </p>
                  <p className="text-xs text-muted-foreground">
                    or click to browse
                  </p>
                </div>
                <p id={descriptionId} className="text-xs text-muted-foreground">
                  {maxFileSize
                    ? `Max size: ${formatFileSize(maxFileSize)}${maxFiles ? ` • Max ${maxFiles} files` : ""}`
                    : maxFiles
                      ? `Max ${maxFiles} files`
                      : "Select files to upload"}
                </p>
              </div>
              <input
                type="file"
                aria-labelledby={labelId}
                aria-describedby={descriptionId}
                multiple={multiple}
                accept={accept}
                className="sr-only"
                ref={fileInputRef}
                onChange={onFileInputChange}
              />
              {files.length > 0 && (
                <div className="flex flex-col gap-2">
                  <div className="flex items-center justify-between">
                    <p className="text-xs font-medium text-muted-foreground">
                      {files.length} {files.length === 1 ? "file" : "files"}
                    </p>
                    <Button
                      type="button"
                      variant="ghost"
                      className="h-6 text-xs text-muted-foreground"
                      onClick={clearAll}
                      disabled={isPending}
                    >
                      Clear all
                    </Button>
                  </div>
                  <div className="max-h-50 space-y-1 overflow-y-auto">
                    {files.map((file) => {
                      const FileIcon = getFileIcon(file.type);
                      const isFileUploading = uploadingFiles.has(file.id);
                      const isFileDeleting = deletingFiles.has(file.id);
                      const isFilePending = isFileUploading || isFileDeleting;

                      return (
                        <div
                          key={file.id}
                          data-pending={isFilePending ? "" : undefined}
                          className="flex items-center gap-2 rounded-md border bg-muted/50 px-2 py-1.5 data-pending:opacity-60"
                        >
                          {FileIcon && (
                            <FileIcon className="size-4 shrink-0 text-muted-foreground" />
                          )}
                          <div className="flex-1 overflow-hidden">
                            <p className="truncate text-sm">{file.name}</p>
                            <p className="text-xs text-muted-foreground">
                              {isFileUploading
                                ? "Uploading..."
                                : isFileDeleting
                                  ? "Deleting..."
                                  : formatFileSize(file.size)}
                            </p>
                          </div>
                          <Button
                            type="button"
                            variant="ghost"
                            size="icon"
                            className="size-5 rounded-sm"
                            onClick={() => removeFile(file.id)}
                            disabled={isPending}
                          >
                            <IconPlaceholder
                              lucide="X"
                              tabler="IconX"
                              hugeicons="Cancel01Icon"
                              phosphor="XIcon"
                              remixicon="RiCloseLine"
                              className="size-3"
                            />
                          </Button>
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}
            </div>
          </PopoverContent>
        </Popover>
      ) : null}
      {isDraggingOver ? (
        <div className="flex items-center justify-center gap-2 text-sm text-primary">
          <IconPlaceholder
            lucide="Upload"
            tabler="IconCloudUpload"
            hugeicons="CloudUploadIcon"
            phosphor="CloudArrowUpIcon"
            remixicon="RiUploadCloudLine"
            className="size-4"
          />
          <span>Drop files here</span>
        </div>
      ) : files.length > 0 ? (
        <div className="flex flex-wrap items-center gap-1 overflow-hidden">
          {visibleFiles.map((file) => {
            const isUploading = uploadingFiles.has(file.id);

            if (isUploading) {
              return (
                <Skeleton
                  key={file.id}
                  className="h-5 shrink-0 px-1.5"
                  style={{
                    width: `${Math.min(file.name.length * 8 + 30, 100)}px`,
                  }}
                />
              );
            }

            const FileIcon = getFileIcon(file.type);

            return (
              <Badge
                key={file.id}
                variant="secondary"
                className="gap-1 px-1.5 py-px"
              >
                {FileIcon && <FileIcon className="size-3 shrink-0" />}
                <span className="max-w-25 truncate">{file.name}</span>
              </Badge>
            );
          })}
          {hiddenFileCount > 0 && (
            <Badge
              variant="outline"
              className="px-1.5 py-px text-muted-foreground"
            >
              +{hiddenFileCount}
            </Badge>
          )}
        </div>
      ) : null}
    </DataGridCellWrapper>
  );
}

function toastDangerousUrl() {
  toast.error("Invalid URL", {
    description:
      "URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)",
  });
}
src/registry/bases/base/components/data-grid/data-grid-column-header.tsx
"use client";

import type {
  Column,
  ColumnSort,
  Header,
  RowData,
  SortDirection,
  SortingState,
  Table,
} from "@tanstack/react-table";

import { cn } from "cn";
import * as React from "react";

import type { DataGridFeatures } from "@/lib/data-grid-features";

import { getBadgeListWidth } from "@/hooks/use-badge-overflow";
import {
  getColumnFitSize,
  getColumnVariant,
  getIsFileCellData,
} from "@/lib/data-grid-utils";
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";

interface DataGridColumnHeaderProps<
  TData extends RowData,
  TValue,
> extends React.ComponentProps<typeof DropdownMenuTrigger> {
  header: Header<DataGridFeatures, TData, TValue>;
  table: Table<DataGridFeatures, TData>;
}

export function DataGridColumnHeader<TData extends RowData, TValue>({
  header,
  table,
  className,
  onPointerDown,
  ...props
}: DataGridColumnHeaderProps<TData, TValue>) {
  const column = header.column;
  const label = column.columnDef.meta?.label
    ? column.columnDef.meta.label
    : typeof column.columnDef.header === "string"
      ? column.columnDef.header
      : column.id;

  const isAnyColumnResizing = table.store.state.columnResizing.isResizingColumn;

  const cellVariant = column.columnDef.meta?.cell;
  const columnVariant = getColumnVariant(cellVariant?.variant);

  const pinnedPosition = column.getIsPinned();
  const isPinnedLeft = pinnedPosition === "start";
  const isPinnedRight = pinnedPosition === "end";

  const onSortingChange = React.useCallback(
    (direction: SortDirection) => {
      table.setSorting((prev: SortingState) => {
        const existingSortIndex = prev.findIndex(
          (sort) => sort.id === column.id,
        );
        const newSort: ColumnSort = {
          id: column.id,
          desc: direction === "desc",
        };

        if (existingSortIndex >= 0) {
          const updated = [...prev];
          updated[existingSortIndex] = newSort;
          return updated;
        } else {
          return [...prev, newSort];
        }
      });
    },
    [column.id, table],
  );

  const onSortRemove = React.useCallback(() => {
    table.setSorting((prev: SortingState) =>
      prev.filter((sort) => sort.id !== column.id),
    );
  }, [column.id, table]);

  const onLeftPin = React.useCallback(() => {
    column.pin("start");
  }, [column]);

  const onRightPin = React.useCallback(() => {
    column.pin("end");
  }, [column]);

  const onUnpin = React.useCallback(() => {
    column.pin(false);
  }, [column]);

  const onTriggerPointerDown = React.useCallback<
    NonNullable<
      React.ComponentProps<typeof DropdownMenuTrigger>["onPointerDown"]
    >
  >(
    (event) => {
      onPointerDown?.(event);
      if (event.defaultPrevented) return;

      if (event.button !== 0) {
        return;
      }
      table.options.meta?.onColumnClick?.(column.id);
    },
    [table.options.meta, column.id, onPointerDown],
  );

  return (
    <>
      <DropdownMenu modal={false}>
        <DropdownMenuTrigger
          tabIndex={-1}
          className={cn(
            "flex size-full items-center justify-between gap-2 p-2 text-sm outline-none hover:bg-accent/40 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-inset data-open:bg-accent/40 [&_svg]:size-4",
            isAnyColumnResizing && "pointer-events-none",
            className,
          )}
          onPointerDown={onTriggerPointerDown}
          {...props}
        >
          <div className="flex min-w-0 flex-1 items-center gap-1.5">
            {columnVariant && (
              <TooltipProvider delay={100}>
                <Tooltip>
                  <TooltipTrigger
                    render={
                      <columnVariant.icon className="size-3.5 shrink-0 text-muted-foreground" />
                    }
                  />
                  <TooltipContent side="top">
                    <p>{columnVariant.label}</p>
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            )}
            <span className="truncate">{label}</span>
          </div>
          <IconPlaceholder
            lucide="ChevronDownIcon"
            tabler="IconChevronDown"
            hugeicons="ArrowDown01Icon"
            phosphor="CaretDownIcon"
            remixicon="RiArrowDownSLine"
            className="shrink-0 text-muted-foreground"
          />
        </DropdownMenuTrigger>
        <DropdownMenuContent align="start" sideOffset={0} className="w-60">
          {column.getCanSort() && (
            <>
              <DropdownMenuCheckboxItem
                className="relative ltr:pr-8 ltr:pl-2 rtl:pr-2 rtl:pl-8 [&_svg]:text-muted-foreground [&>span:first-child]:ltr:right-2 [&>span:first-child]:ltr:left-auto [&>span:first-child]:rtl:right-auto [&>span:first-child]:rtl:left-2"
                checked={column.getIsSorted() === "asc"}
                onClick={() => onSortingChange("asc")}
              >
                <IconPlaceholder
                  lucide="ChevronUpIcon"
                  tabler="IconChevronUp"
                  hugeicons="ArrowUp01Icon"
                  phosphor="CaretUpIcon"
                  remixicon="RiArrowUpSLine"
                />
                Sort asc
              </DropdownMenuCheckboxItem>
              <DropdownMenuCheckboxItem
                className="relative ltr:pr-8 ltr:pl-2 rtl:pr-2 rtl:pl-8 [&_svg]:text-muted-foreground [&>span:first-child]:ltr:right-2 [&>span:first-child]:ltr:left-auto [&>span:first-child]:rtl:right-auto [&>span:first-child]:rtl:left-2"
                checked={column.getIsSorted() === "desc"}
                onClick={() => onSortingChange("desc")}
              >
                <IconPlaceholder
                  lucide="ChevronDownIcon"
                  tabler="IconChevronDown"
                  hugeicons="ArrowDown01Icon"
                  phosphor="CaretDownIcon"
                  remixicon="RiArrowDownSLine"
                />
                Sort desc
              </DropdownMenuCheckboxItem>
              {column.getIsSorted() && (
                <DropdownMenuItem onClick={onSortRemove}>
                  <IconPlaceholder
                    lucide="XIcon"
                    tabler="IconX"
                    hugeicons="Cancel01Icon"
                    phosphor="XIcon"
                    remixicon="RiCloseLine"
                  />
                  Remove sort
                </DropdownMenuItem>
              )}
            </>
          )}
          {column.getCanPin() && (
            <>
              {column.getCanSort() && <DropdownMenuSeparator />}

              {isPinnedLeft ? (
                <DropdownMenuItem
                  className="[&_svg]:text-muted-foreground"
                  onClick={onUnpin}
                >
                  <IconPlaceholder
                    lucide="PinOffIcon"
                    tabler="IconPinnedOff"
                    hugeicons="BookmarkIcon"
                    phosphor="PushPinSlashIcon"
                    remixicon="RiUnpinLine"
                  />
                  Unpin from left
                </DropdownMenuItem>
              ) : (
                <DropdownMenuItem
                  className="[&_svg]:text-muted-foreground"
                  onClick={onLeftPin}
                >
                  <IconPlaceholder
                    lucide="PinIcon"
                    tabler="IconPin"
                    hugeicons="BookmarkIcon"
                    phosphor="PushPinIcon"
                    remixicon="RiPushpinLine"
                  />
                  Pin to left
                </DropdownMenuItem>
              )}
              {isPinnedRight ? (
                <DropdownMenuItem
                  className="[&_svg]:text-muted-foreground"
                  onClick={onUnpin}
                >
                  <IconPlaceholder
                    lucide="PinOffIcon"
                    tabler="IconPinnedOff"
                    hugeicons="BookmarkIcon"
                    phosphor="PushPinSlashIcon"
                    remixicon="RiUnpinLine"
                  />
                  Unpin from right
                </DropdownMenuItem>
              ) : (
                <DropdownMenuItem
                  className="[&_svg]:text-muted-foreground"
                  onClick={onRightPin}
                >
                  <IconPlaceholder
                    lucide="PinIcon"
                    tabler="IconPin"
                    hugeicons="BookmarkIcon"
                    phosphor="PushPinIcon"
                    remixicon="RiPushpinLine"
                  />
                  Pin to right
                </DropdownMenuItem>
              )}
            </>
          )}
          {column.getCanHide() && (
            <>
              <DropdownMenuSeparator />
              <DropdownMenuItem
                className="[&_svg]:text-muted-foreground"
                onClick={() => column.toggleVisibility(false)}
              >
                <IconPlaceholder
                  lucide="EyeOffIcon"
                  tabler="IconEyeClosed"
                  hugeicons="ViewOffIcon"
                  phosphor="EyeSlashIcon"
                  remixicon="RiEyeOffLine"
                />
                Hide column
              </DropdownMenuItem>
            </>
          )}
        </DropdownMenuContent>
      </DropdownMenu>
      {header.column.getCanResize() && (
        <DataGridColumnResizer header={header} table={table} label={label} />
      )}
    </>
  );
}

const DataGridColumnResizer = React.memo(
  DataGridColumnResizerImpl,
  (prev, next) => {
    const prevColumn = prev.header.column;
    const nextColumn = next.header.column;

    if (
      prevColumn.getIsResizing() !== nextColumn.getIsResizing() ||
      prevColumn.getSize() !== nextColumn.getSize()
    ) {
      return false;
    }

    if (prev.label !== next.label) return false;

    return true;
  },
) as typeof DataGridColumnResizerImpl;

interface DataGridColumnResizerProps<
  TData extends RowData,
  TValue,
> extends DataGridColumnHeaderProps<TData, TValue> {
  label: string;
}

function DataGridColumnResizerImpl<TData extends RowData, TValue>({
  header,
  table,
  label,
}: DataGridColumnResizerProps<TData, TValue>) {
  const defaultColumnDef = table.getDefaultColumnDef();

  const onDoubleClick = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      const column = header.column;
      const gridElement =
        event.currentTarget.closest<HTMLElement>('[role="grid"]');
      const fitSize = gridElement
        ? getColumnFitSize({
            gridElement,
            columnId: column.id,
            minSize: column.columnDef.minSize ?? defaultColumnDef.minSize ?? 0,
            maxSize:
              column.columnDef.maxSize ??
              defaultColumnDef.maxSize ??
              Number.POSITIVE_INFINITY,
            wrapperContentSize: getBadgeColumnContentSize(column, table),
          })
        : null;

      if (fitSize === null) {
        column.resetSize();
        return;
      }

      table.setColumnSizing((prev) => ({ ...prev, [column.id]: fitSize }));
    },
    [header.column, table, defaultColumnDef],
  );

  return (
    <div
      role="separator"
      aria-orientation="vertical"
      aria-label={`Resize ${label} column`}
      aria-valuenow={header.column.getSize()}
      aria-valuemin={defaultColumnDef.minSize}
      aria-valuemax={defaultColumnDef.maxSize}
      tabIndex={-1}
      className={cn(
        "absolute -inset-e-px top-0 z-10 h-full w-0.5 cursor-ew-resize touch-none bg-border transition-opacity select-none after:absolute after:inset-y-0 after:inset-s-1/2 after:h-full after:w-4.5 after:-translate-x-1/2 after:content-[''] hover:bg-primary focus:bg-primary focus:outline-none",
        header.column.getIsResizing()
          ? "bg-primary"
          : "opacity-0 hover:opacity-100",
      )}
      onDoubleClick={onDoubleClick}
      onMouseDown={header.getResizeHandler()}
      onTouchStart={header.getResizeHandler()}
    />
  );
}

function getBadgeColumnContentSize<TData extends RowData, TValue>(
  column: Column<DataGridFeatures, TData, TValue>,
  table: Table<DataGridFeatures, TData>,
): number {
  const cellOpts = column.columnDef.meta?.cell;
  if (cellOpts?.variant !== "multi-select" && cellOpts?.variant !== "file") {
    return 0;
  }

  const labelByValue = new Map(
    cellOpts.variant === "multi-select"
      ? cellOpts.options.map((option) => [option.value, option.label])
      : [],
  );

  let contentSize = 0;
  for (const row of table.getRowModel().rows) {
    const value = row.getValue(column.id);
    if (!Array.isArray(value)) continue;

    const rowContentSize =
      cellOpts.variant === "multi-select"
        ? getBadgeListWidth({
            items: value
              .filter((item): item is string => typeof item === "string")
              .map((item) => labelByValue.get(item) ?? item)
              .filter(Boolean),
            getLabel: (label) => label,
          })
        : getBadgeListWidth({
            items: value.filter(getIsFileCellData),
            getLabel: (file) => file.name,
            cacheKeyPrefix: "file",
            iconSize: 12,
            maxWidth: 100,
          });
    contentSize = Math.max(contentSize, rowContentSize);
  }

  return contentSize;
}
src/registry/bases/base/components/data-grid/data-grid-context-menu.tsx
"use client";

import type { ColumnDef, RowData } from "@tanstack/react-table";

import { useValueAsRef } from "@base-ui/utils/useValueAsRef";
import * as React from "react";
import { toast } from "sonner";

import type { DataGridFeatures } from "@/lib/data-grid-features";
import type {
  CellUpdate,
  ContextMenuState,
  DataGridTableMeta,
} from "@/lib/data-grid-types";

import { getEmptyCellValue, parseCellKey } from "@/lib/data-grid-utils";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";

interface DataGridContextMenuProps<TData extends RowData> {
  tableMeta: DataGridTableMeta;
  columns: ReadonlyArray<ColumnDef<DataGridFeatures, TData>>;
  contextMenu: ContextMenuState;
}

export function DataGridContextMenu<TData extends RowData>({
  tableMeta,
  columns,
  contextMenu,
}: DataGridContextMenuProps<TData>) {
  const onContextMenuOpenChange = tableMeta?.onContextMenuOpenChange;
  const selectionState = tableMeta?.selectionState;
  const dataGridRef = tableMeta?.dataGridRef;
  const onDataUpdate = tableMeta?.onDataUpdate;
  const onRowsDelete = tableMeta?.onRowsDelete;
  const onCellsCopy = tableMeta?.onCellsCopy;
  const onCellsCut = tableMeta?.onCellsCut;

  if (!contextMenu.open) return null;

  return (
    <ContextMenu
      tableMeta={tableMeta}
      columns={columns}
      dataGridRef={dataGridRef}
      contextMenu={contextMenu}
      onContextMenuOpenChange={onContextMenuOpenChange}
      selectionState={selectionState}
      onDataUpdate={onDataUpdate}
      onRowsDelete={onRowsDelete}
      onCellsCopy={onCellsCopy}
      onCellsCut={onCellsCut}
    />
  );
}

interface ContextMenuProps<TData extends RowData>
  extends
    Pick<
      DataGridTableMeta,
      | "dataGridRef"
      | "onContextMenuOpenChange"
      | "selectionState"
      | "onDataUpdate"
      | "onRowsDelete"
      | "onCellsCopy"
      | "onCellsCut"
      | "readOnly"
    >,
    Required<Pick<DataGridTableMeta, "contextMenu">> {
  tableMeta: DataGridTableMeta;
  columns: ReadonlyArray<ColumnDef<DataGridFeatures, TData>>;
}

const ContextMenu = React.memo(ContextMenuImpl, (prev, next) => {
  if (prev.contextMenu.open !== next.contextMenu.open) return false;
  if (!next.contextMenu.open) return true;
  if (prev.contextMenu.x !== next.contextMenu.x) return false;
  if (prev.contextMenu.y !== next.contextMenu.y) return false;

  const prevSize = prev.selectionState?.selectedCells?.size ?? 0;
  const nextSize = next.selectionState?.selectedCells?.size ?? 0;
  if (prevSize !== nextSize) return false;

  return true;
}) as typeof ContextMenuImpl;

function ContextMenuImpl<TData extends RowData>({
  tableMeta,
  columns,
  dataGridRef,
  contextMenu,
  onContextMenuOpenChange,
  selectionState,
  onDataUpdate,
  onRowsDelete,
  onCellsCopy,
  onCellsCut,
}: ContextMenuProps<TData>) {
  const propsRef = useValueAsRef({
    dataGridRef,
    selectionState,
    onDataUpdate,
    onRowsDelete,
    onCellsCopy,
    onCellsCut,
    columns,
  });

  const triggerStyle = React.useMemo<React.CSSProperties>(
    () => ({
      position: "fixed",
      left: `${contextMenu.x}px`,
      top: `${contextMenu.y}px`,
      width: "1px",
      height: "1px",
      padding: 0,
      margin: 0,
      border: "none",
      background: "transparent",
      pointerEvents: "none",
      opacity: 0,
    }),
    [contextMenu.x, contextMenu.y],
  );

  const onFinalFocus = React.useCallback(() => {
    propsRef.current.dataGridRef?.current?.focus();
    return false;
  }, [propsRef]);

  const onCopy = React.useCallback(() => {
    propsRef.current.onCellsCopy?.();
  }, [propsRef]);

  const onCut = React.useCallback(() => {
    propsRef.current.onCellsCut?.();
  }, [propsRef]);

  const onClear = React.useCallback(() => {
    const { selectionState, columns, onDataUpdate } = propsRef.current;

    if (
      !selectionState?.selectedCells ||
      selectionState.selectedCells.size === 0
    )
      return;

    const updates: Array<CellUpdate> = [];

    for (const cellKey of selectionState.selectedCells) {
      const { rowId, columnId } = parseCellKey(cellKey);

      // Get column from columns array
      const column = columns.find((col) => {
        if (col.id) return col.id === columnId;
        if ("accessorKey" in col) return col.accessorKey === columnId;
        return false;
      });
      const cellVariant = column?.meta?.cell?.variant;

      const emptyValue = getEmptyCellValue(cellVariant);

      updates.push({ rowId, columnId, value: emptyValue });
    }

    onDataUpdate?.(updates);

    toast.success(
      `${updates.length} cell${updates.length !== 1 ? "s" : ""} cleared`,
    );
  }, [propsRef]);

  const onDelete = React.useCallback(async () => {
    const { selectionState, onRowsDelete } = propsRef.current;

    if (
      !selectionState?.selectedCells ||
      selectionState.selectedCells.size === 0
    )
      return;

    const rowIds = new Set<string>();
    for (const cellKey of selectionState.selectedCells) {
      rowIds.add(parseCellKey(cellKey).rowId);
    }

    const rowCount = rowIds.size;

    await onRowsDelete?.(Array.from(rowIds));

    toast.success(`${rowCount} row${rowCount !== 1 ? "s" : ""} deleted`);
  }, [propsRef]);

  return (
    <DropdownMenu
      open={contextMenu.open}
      onOpenChange={onContextMenuOpenChange}
    >
      <DropdownMenuTrigger style={triggerStyle} />
      <DropdownMenuContent
        data-grid-popover=""
        align="start"
        className="w-48"
        finalFocus={onFinalFocus}
      >
        <DropdownMenuItem onClick={onCopy}>
          <IconPlaceholder
            lucide="CopyIcon"
            tabler="IconCopy"
            hugeicons="Copy01Icon"
            phosphor="CopyIcon"
            remixicon="RiFileCopyLine"
          />
          Copy
        </DropdownMenuItem>
        <DropdownMenuItem onClick={onCut} disabled={tableMeta?.readOnly}>
          <IconPlaceholder
            lucide="ScissorsIcon"
            tabler="IconCut"
            hugeicons="ScissorIcon"
            phosphor="ScissorsIcon"
            remixicon="RiScissorsLine"
          />
          Cut
        </DropdownMenuItem>
        <DropdownMenuItem onClick={onClear} disabled={tableMeta?.readOnly}>
          <IconPlaceholder
            lucide="EraserIcon"
            tabler="IconEraser"
            hugeicons="DeleteIcon"
            phosphor="EraserIcon"
            remixicon="RiEraserLine"
          />
          Clear
        </DropdownMenuItem>
        {onRowsDelete && (
          <>
            <DropdownMenuSeparator />
            <DropdownMenuItem variant="destructive" onClick={onDelete}>
              <IconPlaceholder
                lucide="Trash2Icon"
                tabler="IconTrash"
                hugeicons="Delete02Icon"
                phosphor="TrashIcon"
                remixicon="RiDeleteBinLine"
              />
              Delete rows
            </DropdownMenuItem>
          </>
        )}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
src/registry/bases/base/components/data-grid/data-grid-paste-dialog.tsx
"use client";

import { useValueAsRef } from "@base-ui/utils/useValueAsRef";
import { cn } from "cn";
import * as React from "react";

import type {
  DataGridTableMeta,
  PasteDialogState,
} from "@/lib/data-grid-types";

import { Button } from "@/registry/bases/base/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/registry/bases/base/ui/dialog";

interface DataGridPasteDialogProps {
  tableMeta: DataGridTableMeta;
  pasteDialog: PasteDialogState;
}

export function DataGridPasteDialog({
  tableMeta,
  pasteDialog,
}: DataGridPasteDialogProps) {
  const onPasteDialogOpenChange = tableMeta?.onPasteDialogOpenChange;
  const onCellsPaste = tableMeta?.onCellsPaste;

  if (!pasteDialog.open) return null;

  return (
    <PasteDialog
      pasteDialog={pasteDialog}
      onPasteDialogOpenChange={onPasteDialogOpenChange}
      onCellsPaste={onCellsPaste}
    />
  );
}

interface PasteDialogProps
  extends
    Pick<DataGridTableMeta, "onPasteDialogOpenChange" | "onCellsPaste">,
    Required<Pick<DataGridTableMeta, "pasteDialog">> {}

const PasteDialog = React.memo(PasteDialogImpl, (prev, next) => {
  if (prev.pasteDialog.open !== next.pasteDialog.open) return false;
  if (!next.pasteDialog.open) return true;
  if (prev.pasteDialog.rowsNeeded !== next.pasteDialog.rowsNeeded) return false;

  return true;
});

function PasteDialogImpl({
  pasteDialog,
  onPasteDialogOpenChange,
  onCellsPaste,
}: PasteDialogProps) {
  const propsRef = useValueAsRef({
    onPasteDialogOpenChange,
    onCellsPaste,
  });

  const expandRadioRef = React.useRef<HTMLInputElement | null>(null);

  const onOpenChange = React.useCallback(
    (open: boolean) => {
      propsRef.current.onPasteDialogOpenChange?.(open);
    },
    [propsRef],
  );

  const onCancel = React.useCallback(() => {
    propsRef.current.onPasteDialogOpenChange?.(false);
  }, [propsRef]);

  const onContinue = React.useCallback(() => {
    propsRef.current.onCellsPaste?.(expandRadioRef.current?.checked ?? false);
  }, [propsRef]);

  return (
    <Dialog open={pasteDialog.open} onOpenChange={onOpenChange}>
      <DialogContent data-grid-popover="">
        <DialogHeader>
          <DialogTitle>Do you want to add more rows?</DialogTitle>
          <DialogDescription>
            We need <strong>{pasteDialog.rowsNeeded}</strong> additional row
            {pasteDialog.rowsNeeded !== 1 ? "s" : ""} to paste everything from
            your clipboard.
          </DialogDescription>
        </DialogHeader>
        <div className="flex flex-col gap-3 py-1">
          <label className="flex cursor-pointer items-start gap-3">
            <RadioItem
              ref={expandRadioRef}
              name="expand-option"
              value="expand"
              defaultChecked
            />
            <div className="flex flex-col gap-1">
              <span className="text-sm leading-none font-medium">
                Create new rows
              </span>
              <span className="text-sm text-muted-foreground">
                Add {pasteDialog.rowsNeeded} new row
                {pasteDialog.rowsNeeded !== 1 ? "s" : ""} to the table and paste
                all data
              </span>
            </div>
          </label>
          <label className="flex cursor-pointer items-start gap-3">
            <RadioItem name="expand-option" value="no-expand" />
            <div className="flex flex-col gap-1">
              <span className="text-sm leading-none font-medium">
                Keep current rows
              </span>
              <span className="text-sm text-muted-foreground">
                Paste only what fits in the existing rows
              </span>
            </div>
          </label>
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={onCancel}>
            Cancel
          </Button>
          <Button onClick={onContinue}>Continue</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function RadioItem({ className, ...props }: React.ComponentProps<"input">) {
  return (
    <input
      type="radio"
      className={cn(
        "relative size-4 shrink-0 appearance-none rounded-full border border-input bg-background shadow-xs transition-[color,box-shadow] outline-none",
        "text-primary focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
        "disabled:cursor-not-allowed disabled:opacity-50",
        "checked:before:absolute checked:before:inset-s-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-['']",
        "dark:bg-input/30",
        className,
      )}
      {...props}
    />
  );
}
src/registry/bases/base/components/data-grid/data-grid-row.tsx
"use client";

import type {
  Cell,
  ColumnPinningState,
  ColumnVisibilityState,
  Row,
  RowData,
} from "@tanstack/react-table";
import type { VirtualItem } from "@tanstack/react-virtual";

import { useMergedRefs } from "@base-ui/utils/useMergedRefs";
import { cn } from "cn";
import * as React from "react";

import type { DataGridFeatures } from "@/lib/data-grid-features";
import type {
  CellPosition,
  DataGridTableMeta,
  Direction,
  RowHeightValue,
} from "@/lib/data-grid-types";

import {
  flexRender,
  getCellKey,
  getColumnBorderVisibility,
  getColumnPinningStyle,
  getRowHeightValue,
} from "@/lib/data-grid-utils";
import { DataGridCell } from "@/registry/bases/base/components/data-grid/data-grid-cell";

interface DataGridRowProps<
  TData extends RowData,
> extends React.ComponentProps<"div"> {
  row: Row<DataGridFeatures, TData>;
  tableMeta: DataGridTableMeta;
  virtualItem: VirtualItem;
  measureElement: (node: Element | null) => void;
  rowMapRef: React.RefObject<Map<number, HTMLDivElement>>;
  rowHeight: RowHeightValue;
  columnVisibility: ColumnVisibilityState;
  columnPinning: ColumnPinningState;
  focusedCell: CellPosition | null;
  editingCell: CellPosition | null;
  cellSelectionKeys: Set<string>;
  searchMatchColumns: Set<string> | null;
  activeSearchMatch: CellPosition | null;
  dir: Direction;
  readOnlyColumnIds: Set<string>;
  stretchColumns: boolean;
  adjustLayout: boolean;
}

export const DataGridRow = React.memo(DataGridRowImpl, (prev, next) => {
  const rowId = next.row.id;

  // Re-render if row identity changed
  if (prev.row.id !== rowId) {
    return false;
  }

  // Re-render if the row moved, since the row map and aria-rowindex are positional
  if (prev.virtualItem.index !== next.virtualItem.index) {
    return false;
  }

  // Re-render if row data (original) reference changed
  if (prev.row.original !== next.row.original) {
    return false;
  }

  // Re-render if virtual position changed (handles transform updates)
  if (prev.virtualItem.start !== next.virtualItem.start) {
    return false;
  }

  // Re-render if focus state changed for this row
  const prevHasFocus = prev.focusedCell?.rowId === rowId;
  const nextHasFocus = next.focusedCell?.rowId === rowId;

  if (prevHasFocus !== nextHasFocus) {
    return false;
  }

  // Re-render if focused column changed within this row
  if (nextHasFocus && prevHasFocus) {
    if (prev.focusedCell?.columnId !== next.focusedCell?.columnId) {
      return false;
    }
  }

  // Re-render if editing state changed for this row
  const prevHasEditing = prev.editingCell?.rowId === rowId;
  const nextHasEditing = next.editingCell?.rowId === rowId;

  if (prevHasEditing !== nextHasEditing) {
    return false;
  }

  // Re-render if editing column changed within this row
  if (nextHasEditing && prevHasEditing) {
    if (prev.editingCell?.columnId !== next.editingCell?.columnId) {
      return false;
    }
  }

  // Re-render if this row's selected cells changed
  // Using stable Set reference that only includes this row's cells
  if (prev.cellSelectionKeys !== next.cellSelectionKeys) {
    return false;
  }

  // Re-render if column visibility changed
  if (prev.columnVisibility !== next.columnVisibility) {
    return false;
  }

  // Re-render if row height changed
  if (prev.rowHeight !== next.rowHeight) {
    return false;
  }

  // Re-render if column pinning state changed
  if (prev.columnPinning !== next.columnPinning) {
    return false;
  }

  // Re-render if table or column editing permissions changed
  if (prev.readOnlyColumnIds !== next.readOnlyColumnIds) {
    return false;
  }

  // Re-render if search match columns changed for this row
  if (prev.searchMatchColumns !== next.searchMatchColumns) {
    return false;
  }

  // Re-render if active search match changed for this row
  if (prev.activeSearchMatch?.columnId !== next.activeSearchMatch?.columnId) {
    return false;
  }

  // Re-render if direction changed
  if (prev.dir !== next.dir) {
    return false;
  }

  // Re-render if adjustLayout state changed
  if (prev.adjustLayout !== next.adjustLayout) {
    return false;
  }

  // Re-render if stretchColumns changed
  if (prev.stretchColumns !== next.stretchColumns) {
    return false;
  }

  // Skip re-render - props are equal
  return true;
}) as typeof DataGridRowImpl;

function DataGridRowImpl<TData extends RowData>({
  row,
  tableMeta,
  virtualItem,
  measureElement,
  rowMapRef,
  rowHeight,
  columnVisibility,
  columnPinning,
  focusedCell,
  editingCell,
  cellSelectionKeys,
  searchMatchColumns,
  activeSearchMatch,
  dir,
  readOnlyColumnIds,
  stretchColumns,
  adjustLayout,
  className,
  style,
  ref,
  ...props
}: DataGridRowProps<TData>) {
  const virtualRowIndex = virtualItem.index;
  const rowId = row.id;

  const onRowChange = React.useCallback(
    (node: HTMLDivElement | null) => {
      if (typeof virtualRowIndex === "undefined") return;

      if (node) {
        measureElement(node);
        rowMapRef.current?.set(virtualRowIndex, node);
      } else {
        rowMapRef.current?.delete(virtualRowIndex);
      }
    },
    [virtualRowIndex, measureElement, rowMapRef],
  );

  const rowRef = useMergedRefs(ref, onRowChange);

  const isRowSelected = row.getIsSelected();

  // Memoize visible cells to avoid recreating cell array on every render
  // Though TanStack returns new Cell wrappers, memoizing the array helps React's reconciliation
  const visibleCells = React.useMemo(
    () => row.getVisibleCells(),
    [row, columnVisibility, columnPinning],
  );

  return (
    <div
      key={row.id}
      role="row"
      aria-rowindex={virtualRowIndex + 2}
      aria-selected={isRowSelected}
      data-index={virtualRowIndex}
      data-slot="grid-row"
      tabIndex={-1}
      {...props}
      ref={rowRef}
      className={cn(
        "absolute flex w-full border-b [content-visibility:auto]",
        !adjustLayout && "will-change-transform",
        className,
      )}
      style={{
        height: `${getRowHeightValue(rowHeight)}px`,
        ...(adjustLayout
          ? { top: `${virtualItem.start}px` }
          : { transform: `translateY(${virtualItem.start}px)` }),
        ...style,
      }}
    >
      {visibleCells.map((cell, colIndex) => {
        const columnId = cell.column.id;

        const isCellFocused =
          focusedCell?.rowId === rowId && focusedCell?.columnId === columnId;
        const isCellEditing =
          editingCell?.rowId === rowId && editingCell?.columnId === columnId;
        const isCellSelected =
          cellSelectionKeys?.has(getCellKey(rowId, columnId)) ?? false;

        const isSearchMatch = searchMatchColumns?.has(columnId) ?? false;
        const isActiveSearchMatch = activeSearchMatch?.columnId === columnId;

        const nextCell = visibleCells[colIndex + 1];
        const isLastColumn = colIndex === visibleCells.length - 1;
        const { showEndBorder, showStartBorder } = getColumnBorderVisibility({
          column: cell.column,
          nextColumn: nextCell?.column,
          isLastColumn,
        });

        return (
          <div
            key={cell.id}
            role="gridcell"
            aria-colindex={colIndex + 1}
            data-slot="grid-cell"
            data-column-id={columnId}
            data-highlighted={isCellFocused ? "" : undefined}
            tabIndex={-1}
            className={cn({
              grow: stretchColumns && columnId !== "select",
              "border-e": showEndBorder && columnId !== "select",
              "border-s": showStartBorder && columnId !== "select",
            })}
            style={{
              ...getColumnPinningStyle({ column: cell.column, dir }),
              width: `calc(var(--col-${columnId}-size) * 1px)`,
            }}
          >
            {typeof cell.column.columnDef.header === "function" ? (
              <DataGridUtilityCell
                cell={cell}
                tableMeta={tableMeta}
                rowId={rowId}
                columnId={columnId}
                isFocused={isCellFocused}
                isRowSelected={isRowSelected}
              />
            ) : (
              <DataGridCell
                cell={cell}
                tableMeta={tableMeta}
                rowId={rowId}
                columnId={columnId}
                rowHeight={rowHeight}
                isFocused={isCellFocused}
                isEditing={isCellEditing}
                isSelected={isCellSelected}
                isSearchMatch={isSearchMatch}
                isActiveSearchMatch={isActiveSearchMatch}
                readOnly={readOnlyColumnIds.has(columnId)}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}

interface DataGridUtilityCellProps<TData extends RowData> {
  cell: Cell<DataGridFeatures, TData>;
  tableMeta: DataGridTableMeta;
  rowId: string;
  columnId: string;
  isFocused: boolean;
  isRowSelected: boolean;
}

function DataGridUtilityCell<TData extends RowData>({
  cell,
  tableMeta,
  rowId,
  columnId,
  isFocused,
  isRowSelected,
}: DataGridUtilityCellProps<TData>) {
  const cellMapRef = tableMeta?.cellMapRef;

  const onCellChange = React.useCallback(
    (node: HTMLDivElement | null) => {
      if (!cellMapRef) return;

      const cellKey = getCellKey(rowId, columnId);

      if (node) {
        cellMapRef.current.set(cellKey, node);
      } else {
        cellMapRef.current.delete(cellKey);
      }
    },
    [rowId, columnId, cellMapRef],
  );

  return (
    <div
      data-slot="grid-utility-cell"
      data-focused={isFocused ? "" : undefined}
      tabIndex={-1}
      ref={onCellChange}
      className={cn("size-full px-3 py-1.5 outline-none", {
        "bg-primary/10": isRowSelected,
        "ring-1 ring-ring ring-inset": isFocused,
      })}
    >
      {flexRender(cell.column.columnDef.cell, cell.getContext())}
    </div>
  );
}
src/registry/bases/base/components/data-grid/data-grid-search.tsx
"use client";

import { useValueAsRef } from "@base-ui/utils/useValueAsRef";
import * as React from "react";

import type { SearchState } from "@/lib/data-grid-types";

import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import { Button } from "@/registry/bases/base/ui/button";
import { Input } from "@/registry/bases/base/ui/input";
import { IconPlaceholder } from "@/registry/icons/icon-placeholder";

function onTriggerPointerDown(event: React.PointerEvent<HTMLButtonElement>) {
  const target = event.target;
  if (!(target instanceof HTMLElement)) return;
  if (target.hasPointerCapture(event.pointerId)) {
    target.releasePointerCapture(event.pointerId);
  }

  // Prevent the trigger from stealing focus away from the input
  if (
    event.button === 0 &&
    event.ctrlKey === false &&
    event.pointerType === "mouse" &&
    !(event.target instanceof HTMLInputElement)
  ) {
    event.preventDefault();
  }
}

interface DataGridSearchProps extends SearchState {}

export const DataGridSearch = React.memo(DataGridSearchImpl, (prev, next) => {
  if (prev.searchOpen !== next.searchOpen) return false;

  if (!next.searchOpen) return true;

  // Exclude searchQuery because the input is uncontrolled, and hasQuery state handles the status text
  if (prev.matchIndex !== next.matchIndex) return false;

  if (prev.searchMatches.length !== next.searchMatches.length) return false;

  for (let i = 0; i < prev.searchMatches.length; i++) {
    const prevMatch = prev.searchMatches[i];
    const nextMatch = next.searchMatches[i];

    if (!prevMatch || !nextMatch) return false;

    if (
      prevMatch.rowId !== nextMatch.rowId ||
      prevMatch.columnId !== nextMatch.columnId
    ) {
      return false;
    }
  }

  return true;
});

function DataGridSearchImpl({
  searchMatches,
  matchIndex,
  searchOpen,
  onSearchOpenChange,
  searchQuery,
  onSearchQueryChange,
  onSearch,
  onNavigateToNextMatch,
  onNavigateToPrevMatch,
}: DataGridSearchProps) {
  const propsRef = useValueAsRef({
    onSearchOpenChange,
    onSearchQueryChange,
    onSearch,
    onNavigateToNextMatch,
    onNavigateToPrevMatch,
  });

  const inputRef = React.useRef<HTMLInputElement>(null);
  const isComposingRef = React.useRef(false);
  const [hasQuery, setHasQuery] = React.useState(searchQuery.length > 0);

  React.useEffect(() => {
    if (searchOpen) {
      requestAnimationFrame(() => {
        inputRef.current?.focus();
      });
      return;
    }

    isComposingRef.current = false;
    setHasQuery(false);
  }, [searchOpen]);

  React.useEffect(() => {
    if (!searchOpen) return;

    function onEscape(event: KeyboardEvent) {
      if (event.key === "Escape") {
        event.preventDefault();
        propsRef.current.onSearchOpenChange(false);
      }
    }

    document.addEventListener("keydown", onEscape);
    return () => document.removeEventListener("keydown", onEscape);
  }, [searchOpen, propsRef]);

  const debouncedSearch = useDebouncedCallback((query: string) => {
    propsRef.current.onSearch(query);
  }, 150);

  function onCompositionStart() {
    isComposingRef.current = true;
  }

  function onCompositionEnd(event: React.CompositionEvent<HTMLInputElement>) {
    isComposingRef.current = false;
    const value = event.currentTarget.value;
    setHasQuery(value.length > 0);
    propsRef.current.onSearchQueryChange(value);
    debouncedSearch(value);
  }

  function onKeyDown(event: React.KeyboardEvent) {
    event.stopPropagation();

    if (event.key === "Enter") {
      if (event.nativeEvent.isComposing) return;
      event.preventDefault();
      if (event.shiftKey) {
        propsRef.current.onNavigateToPrevMatch();
      } else {
        propsRef.current.onNavigateToNextMatch();
      }
    }
  }

  function onChange(event: React.ChangeEvent<HTMLInputElement>) {
    if (isComposingRef.current) return;
    const value = event.target.value;
    setHasQuery(value.length > 0);
    propsRef.current.onSearchQueryChange(value);
    debouncedSearch(value);
  }

  function onClose() {
    propsRef.current.onSearchOpenChange(false);
  }

  function onPrevMatch() {
    propsRef.current.onNavigateToPrevMatch();
  }

  function onNextMatch() {
    propsRef.current.onNavigateToNextMatch();
  }

  if (!searchOpen) return null;

  return (
    <div
      role="search"
      data-slot="grid-search"
      className="absolute inset-e-4 top-4 z-50 flex animate-in flex-col gap-2 rounded-lg border bg-background p-2 shadow-lg fade-in-0 slide-in-from-top-2"
    >
      <div className="flex items-center gap-2">
        <Input
          autoComplete="off"
          autoCorrect="off"
          autoCapitalize="off"
          spellCheck={false}
          placeholder="Find in table..."
          className="w-64"
          ref={inputRef}
          defaultValue={searchQuery}
          onChange={onChange}
          onKeyDown={onKeyDown}
          onCompositionStart={onCompositionStart}
          onCompositionEnd={onCompositionEnd}
        />
        <div className="flex items-center gap-1">
          <Button
            aria-label="Previous match"
            variant="ghost"
            size="icon"
            className="size-7"
            onClick={onPrevMatch}
            onPointerDown={onTriggerPointerDown}
            disabled={searchMatches.length === 0}
          >
            <IconPlaceholder
              lucide="ChevronUp"
              tabler="IconChevronUp"
              hugeicons="ArrowUp01Icon"
              phosphor="CaretUpIcon"
              remixicon="RiArrowUpSLine"
            />
          </Button>
          <Button
            aria-label="Next match"
            variant="ghost"
            size="icon"
            className="size-7"
            onClick={onNextMatch}
            onPointerDown={onTriggerPointerDown}
            disabled={searchMatches.length === 0}
          >
            <IconPlaceholder
              lucide="ChevronDown"
              tabler="IconChevronDown"
              hugeicons="ArrowDown01Icon"
              phosphor="CaretDownIcon"
              remixicon="RiArrowDownSLine"
            />
          </Button>
          <Button
            aria-label="Close search"
            variant="ghost"
            size="icon"
            className="size-7"
            onClick={onClose}
          >
            <IconPlaceholder
              lucide="X"
              tabler="IconX"
              hugeicons="Cancel01Icon"
              phosphor="XIcon"
              remixicon="RiCloseLine"
            />
          </Button>
        </div>
      </div>
      <div className="flex items-center gap-1 text-xs whitespace-nowrap text-muted-foreground">
        {searchMatches.length > 0 ? (
          <span>
            {matchIndex + 1} of {searchMatches.length}
          </span>
        ) : hasQuery ? (
          <span>No results</span>
        ) : (
          <span>Type to search</span>
        )}
      </div>
    </div>
  );
}
src/registry/bases/base/ui/direction.tsx
"use client";

export {
  DirectionProvider,
  useDirection,
} from "@base-ui/react/direction-provider";
src/hooks/use-value-as-ref.ts
import * as React from "react";

import { useIsomorphicLayoutEffect } from "@/hooks/use-isomorphic-layout-effect";

function useValueAsRef<T>(value: T) {
  const ref = React.useRef<T>(value);

  useIsomorphicLayoutEffect(() => {
    ref.current = value;
  });

  return ref;
}

export { useValueAsRef };
src/hooks/use-badge-overflow.ts
import * as React from "react";

const badgeWidthCache = new Map<string, number>();

const DEFAULT_CONTAINER_PADDING = 16; // px-2 = 8px * 2
const DEFAULT_BADGE_GAP = 4; // gap-1 = 4px
const DEFAULT_OVERFLOW_BADGE_WIDTH = 40; // Approximate width of "+N" badge

interface MeasureBadgeWidthProps {
  label: string;
  cacheKey: string;
  iconSize?: number;
  maxWidth?: number;
  className?: string;
}

function measureBadgeWidth({
  label,
  cacheKey,
  iconSize,
  maxWidth,
  className,
}: MeasureBadgeWidthProps): number {
  const cached = badgeWidthCache.get(cacheKey);
  if (cached !== undefined) {
    return cached;
  }

  const measureEl = document.createElement("div");
  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 ${
    className ?? ""
  }`;
  measureEl.style.whiteSpace = "nowrap";

  if (iconSize) {
    const icon = document.createElement("span");
    icon.className = "shrink-0";
    icon.style.width = `${iconSize}px`;
    icon.style.height = `${iconSize}px`;
    measureEl.appendChild(icon);
  }

  if (maxWidth) {
    const text = document.createElement("span");
    text.className = "truncate";
    text.style.maxWidth = `${maxWidth}px`;
    text.textContent = label;
    measureEl.appendChild(text);
  } else {
    measureEl.textContent = label;
  }

  document.body.appendChild(measureEl);
  const width = measureEl.offsetWidth;
  document.body.removeChild(measureEl);

  badgeWidthCache.set(cacheKey, width);
  return width;
}

interface GetBadgeListWidthProps<T> {
  items: T[];
  getLabel: (item: T) => string;
  cacheKeyPrefix?: string;
  iconSize?: number;
  maxWidth?: number;
  className?: string;
  containerPadding?: number;
  badgeGap?: number;
}

export function getBadgeListWidth<T>({
  items,
  getLabel,
  cacheKeyPrefix = "",
  iconSize,
  maxWidth,
  className,
  containerPadding = DEFAULT_CONTAINER_PADDING,
  badgeGap = DEFAULT_BADGE_GAP,
}: GetBadgeListWidthProps<T>): number {
  if (items.length === 0) return 0;

  let width = containerPadding;
  for (const item of items) {
    const label = getLabel(item);
    width +=
      measureBadgeWidth({
        label,
        cacheKey: cacheKeyPrefix ? `${cacheKeyPrefix}:${label}` : label,
        iconSize,
        maxWidth,
        className,
      }) + badgeGap;
  }

  return width;
}

interface UseBadgeOverflowProps<T> extends GetBadgeListWidthProps<T> {
  containerRef: React.RefObject<HTMLElement | null>;
  lineCount: number;
  overflowBadgeWidth?: number;
}

interface UseBadgeOverflowReturn<T> {
  visibleItems: T[];
  hiddenCount: number;
  containerWidth: number;
}

export function useBadgeOverflow<T>({
  items,
  getLabel,
  containerRef,
  lineCount,
  cacheKeyPrefix = "",
  containerPadding = DEFAULT_CONTAINER_PADDING,
  badgeGap = DEFAULT_BADGE_GAP,
  overflowBadgeWidth = DEFAULT_OVERFLOW_BADGE_WIDTH,
  iconSize,
  maxWidth,
  className,
}: UseBadgeOverflowProps<T>): UseBadgeOverflowReturn<T> {
  const [containerWidth, setContainerWidth] = React.useState(0);

  React.useEffect(() => {
    if (!containerRef.current) return;

    function measureWidth() {
      if (containerRef.current) {
        const width = containerRef.current.clientWidth - containerPadding;
        setContainerWidth(width);
      }
    }

    measureWidth();

    const resizeObserver = new ResizeObserver(measureWidth);
    resizeObserver.observe(containerRef.current);

    return () => {
      resizeObserver.disconnect();
    };
  }, [containerRef, containerPadding]);

  const result = React.useMemo(() => {
    if (!containerWidth || items.length === 0) {
      return { visibleItems: items, hiddenCount: 0, containerWidth };
    }

    let currentLineWidth = 0;
    let currentLine = 1;
    const visible: T[] = [];

    for (const item of items) {
      const label = getLabel(item);
      const cacheKey = cacheKeyPrefix ? `${cacheKeyPrefix}:${label}` : label;
      const badgeWidth = measureBadgeWidth({
        label,
        cacheKey,
        iconSize,
        maxWidth,
        className,
      });
      const widthWithGap = badgeWidth + badgeGap;

      if (currentLineWidth + widthWithGap <= containerWidth) {
        currentLineWidth += widthWithGap;
        visible.push(item);
      } else if (currentLine < lineCount) {
        currentLine++;
        currentLineWidth = widthWithGap;
        visible.push(item);
      } else {
        if (
          currentLineWidth + overflowBadgeWidth > containerWidth &&
          visible.length > 0
        ) {
          visible.pop();
        }

        break;
      }
    }

    return {
      visibleItems: visible,
      hiddenCount: Math.max(0, items.length - visible.length),
      containerWidth,
    };
  }, [
    items,
    getLabel,
    containerWidth,
    lineCount,
    cacheKeyPrefix,
    iconSize,
    maxWidth,
    className,
    badgeGap,
    overflowBadgeWidth,
  ]);

  return result;
}

export function clearBadgeWidthCache(): void {
  badgeWidthCache.clear();
}
src/registry/bases/base/hooks/use-data-grid.ts
import { useIsoLayoutEffect } from "@base-ui/utils/useIsoLayoutEffect";
import { useRefWithInit } from "@base-ui/utils/useRefWithInit";
import { useValueAsRef } from "@base-ui/utils/useValueAsRef";
import {
  type ColumnDef,
  type ColumnFiltersState,
  type ColumnOrderState,
  type ColumnPinningState,
  type ColumnVisibilityState,
  type Row,
  type RowData,
  type RowSelectionState,
  type SortingState,
  type Table,
  type TableOptions,
  type TableState,
  type Updater,
  useTable,
} from "@tanstack/react-table";
import { useVirtualizer, type Virtualizer } from "@tanstack/react-virtual";
import * as React from "react";
import { toast } from "sonner";

import type {
  CellPosition,
  CellUpdate,
  ContextMenuState,
  DataGridTableMeta,
  Direction,
  FileCellData,
  NavigationDirection,
  PasteDialogState,
  SearchState,
  SelectionState,
} from "@/lib/data-grid-types";

import {
  type DataGridFeatures,
  dataGridFeatures,
} from "@/lib/data-grid-features";
import {
  getCellFocusTarget,
  getCellKey,
  getEmptyCellValue,
  getIsFileCellData,
  getIsInPopover,
  getRowIndexById,
  getScrollDirection,
  getTabTargetCell,
  getVisibleColumnIds,
  matchSelectOption,
  parseCellKey,
  parseTsv,
  scrollCellIntoView,
  stringifyUnknown,
} from "@/lib/data-grid-utils";
import { useDirection } from "@/registry/bases/base/ui/direction";

const OVERSCAN = 6;
const VIEWPORT_OFFSET = 1;
const HORIZONTAL_PAGE_SIZE = 5;
const SCROLL_SYNC_RETRY_COUNT = 16;
const MIN_COLUMN_SIZE = 60;
const MAX_COLUMN_SIZE = 800;
const SEARCH_SHORTCUT_KEY = "f";
const NON_NAVIGABLE_COLUMN_IDS = new Set(["select", "actions"]);
const AUTO_SCROLL_EDGE_ZONE = 50;
const AUTO_SCROLL_SPEED_RAMP_ZONE = AUTO_SCROLL_EDGE_ZONE * 3;
const AUTO_SCROLL_MIN_SPEED = 8;
const AUTO_SCROLL_MAX_SPEED = 40;
const AUTO_SCROLL_SELECTION_THROTTLE_MS = 32;

const DOMAIN_REGEX = /^[\w.-]+\.[a-z]{2,}(\/\S*)?$/i;
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}.*)?$/;
const TRUTHY_BOOLEANS = new Set(["true", "1", "yes", "checked"]);
const VALID_BOOLEANS = new Set([
  "true",
  "false",
  "1",
  "0",
  "yes",
  "no",
  "checked",
  "unchecked",
]);

function getIsDataColumn(columnId: string) {
  return !NON_NAVIGABLE_COLUMN_IDS.has(columnId);
}

function getCanEditColumnId<TData extends RowData>(
  table: Table<DataGridFeatures, TData>,
  columnId: string,
) {
  const column = table.getAllFlatColumnsById()[columnId];
  return column
    ? column.getCanEdit()
    : table.options.enableCellEditing !== false;
}

function setRowSelected(
  rowSelection: RowSelectionState,
  rowId: string,
  selected: boolean,
) {
  if (selected) {
    rowSelection[rowId] = true;
  } else {
    delete rowSelection[rowId];
  }
}

interface DataGridState {
  sorting: SortingState;
  columnFilters: ColumnFiltersState;
  columnVisibility: ColumnVisibilityState;
  columnPinning: ColumnPinningState;
  columnOrder: ColumnOrderState;
  rowSelection: RowSelectionState;
  selectionState: SelectionState;
  focusedCell: CellPosition | null;
  /** Cut source keys laid out in the same rows and columns as the clipboard text. */
  cutCells: Array<Array<string | null>>;
  contextMenu: ContextMenuState;
  searchQuery: string;
  searchMatches: CellPosition[];
  matchIndex: number;
  searchOpen: boolean;
  lastClickedRowId: string | null;
  pasteDialog: PasteDialogState;
}

interface DataGridStore {
  subscribe: (callback: () => void) => () => void;
  getState: () => DataGridState;
  setState: <K extends keyof DataGridState>(
    key: K,
    value: DataGridState[K],
  ) => void;
  notify: () => void;
  batch: (fn: () => void) => void;
}

function useStore<T>(
  store: DataGridStore,
  selector: (state: DataGridState) => T,
): T {
  const getSnapshot = React.useCallback(
    () => selector(store.getState()),
    [store, selector],
  );

  return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
}

interface UseDataGridProps<TData extends RowData> extends Omit<
  TableOptions<DataGridFeatures, TData>,
  "features"
> {
  onDataChange?: (data: TData[]) => void;
  onRowAdd?: (
    event?: React.MouseEvent<HTMLDivElement>,
  ) => Partial<CellPosition> | Promise<Partial<CellPosition> | null> | null;
  onRowsAdd?: (count: number) => void | Promise<void>;
  onRowsDelete?: (rows: TData[], rowIds: string[]) => void | Promise<void>;
  onPaste?: (updates: Array<CellUpdate>) => void | Promise<void>;
  onFilesUpload?: (params: {
    files: File[];
    rowId: string;
    columnId: string;
  }) => Promise<FileCellData[]>;
  onFilesDelete?: (params: {
    fileIds: string[];
    rowId: string;
    columnId: string;
  }) => void | Promise<void>;
  overscan?: number;
  dir?: Direction;
  autoFocus?: boolean | Partial<CellPosition>;
  enableSingleCellSelection?: boolean;
  enableColumnSelection?: boolean;
  enableSearch?: boolean;
  enablePaste?: boolean;
  readOnly?: boolean;
}

function useDataGrid<TData extends RowData>({
  data,
  columns,
  overscan = OVERSCAN,
  dir: dirProp,
  initialState,
  ...props
}: UseDataGridProps<TData>) {
  const contextDir = useDirection();
  const dir = dirProp ?? contextDir;
  const dataGridRef = React.useRef<HTMLDivElement>(null);
  const tableRef =
    React.useRef<ReturnType<typeof useTable<DataGridFeatures, TData>>>(null);
  const rowVirtualizerRef =
    React.useRef<Virtualizer<HTMLDivElement, Element>>(null);
  const headerRef = React.useRef<HTMLDivElement>(null);
  const rowMapRef = React.useRef<Map<number, HTMLDivElement>>(new Map());
  const cellMapRef = React.useRef<Map<string, HTMLDivElement>>(new Map());
  const footerRef = React.useRef<HTMLDivElement>(null);
  const focusGuardRef = React.useRef(false);

  const propsRef = useValueAsRef({
    ...props,
    data,
    columns,
    initialState,
  });

  const listenersRef = useRefWithInit(() => new Set<() => void>());

  const stateRef = useRefWithInit<DataGridState>(() => {
    return {
      sorting: initialState?.sorting ?? [],
      columnFilters: initialState?.columnFilters ?? [],
      columnVisibility: initialState?.columnVisibility ?? {},
      columnPinning: {
        start: initialState?.columnPinning?.start ?? [],
        end: initialState?.columnPinning?.end ?? [],
      },
      columnOrder: initialState?.columnOrder ?? [],
      rowSelection: initialState?.rowSelection ?? {},
      selectionState: {
        selectedCells: new Set(),
        selectionRange: null,
        isSelecting: false,
      },
      focusedCell: null,
      cutCells: [],
      contextMenu: {
        open: false,
        x: 0,
        y: 0,
      },
      searchQuery: "",
      searchMatches: [],
      matchIndex: -1,
      searchOpen: false,
      lastClickedRowId: null,
      pasteDialog: {
        open: false,
        rowsNeeded: 0,
        clipboardText: "",
      },
    };
  });

  const store = React.useMemo<DataGridStore>(() => {
    let isBatching = false;
    let pendingNotification = false;

    return {
      subscribe: (callback) => {
        listenersRef.current.add(callback);
        return () => listenersRef.current.delete(callback);
      },
      getState: () => stateRef.current,
      setState: (key, value) => {
        if (Object.is(stateRef.current[key], value)) return;
        stateRef.current[key] = value;

        if (isBatching) {
          pendingNotification = true;
        } else {
          if (!pendingNotification) {
            pendingNotification = true;
            queueMicrotask(() => {
              pendingNotification = false;
              store.notify();
            });
          }
        }
      },
      notify: () => {
        for (const listener of listenersRef.current) {
          listener();
        }
      },
      batch: (fn) => {
        if (isBatching) {
          fn();
          return;
        }

        isBatching = true;
        const wasPending = pendingNotification;
        pendingNotification = false;

        try {
          fn();
        } finally {
          isBatching = false;
          if (pendingNotification || wasPending) {
            pendingNotification = false;
            store.notify();
          }
        }
      },
    };
  }, [listenersRef, stateRef]);

  const focusedCell = useStore(store, (state) => state.focusedCell);
  const selectionState = useStore(store, (state) => state.selectionState);
  const searchQuery = useStore(store, (state) => state.searchQuery);
  const searchMatches = useStore(store, (state) => state.searchMatches);
  const matchIndex = useStore(store, (state) => state.matchIndex);
  const searchOpen = useStore(store, (state) => state.searchOpen);
  const sorting = useStore(store, (state) => state.sorting);
  const columnFilters = useStore(store, (state) => state.columnFilters);
  const storeColumnVisibility = useStore(
    store,
    (state) => state.columnVisibility,
  );
  const storeColumnPinning = useStore(store, (state) => state.columnPinning);
  const storeColumnOrder = useStore(store, (state) => state.columnOrder);
  const columnVisibility =
    props.state?.columnVisibility ?? storeColumnVisibility;
  const columnPinning = props.state?.columnPinning ?? storeColumnPinning;
  const columnOrder = props.state?.columnOrder ?? storeColumnOrder;
  const rowSelection = useStore(store, (state) => state.rowSelection);
  const contextMenu = useStore(store, (state) => state.contextMenu);
  const pasteDialog = useStore(store, (state) => state.pasteDialog);

  const prevCellSelectionMapRef = useRefWithInit(
    () => new Map<string, Set<string>>(),
  );

  // Memoize per-row selection sets to prevent unnecessary row re-renders
  // Each row gets a stable Set reference that only changes when its cells' selection changes
  const cellSelectionMap = React.useMemo(() => {
    const selectedCells = selectionState.selectedCells;

    if (selectedCells.size === 0) {
      prevCellSelectionMapRef.current.clear();
      return null;
    }

    const newRowCells = new Map<string, Set<string>>();
    for (const cellKey of selectedCells) {
      const { rowId } = parseCellKey(cellKey);
      let rowSet = newRowCells.get(rowId);
      if (!rowSet) {
        rowSet = new Set<string>();
        newRowCells.set(rowId, rowSet);
      }
      rowSet.add(cellKey);
    }

    const stableMap = new Map<string, Set<string>>();
    for (const [rowId, newSet] of newRowCells) {
      const prevSet = prevCellSelectionMapRef.current.get(rowId);
      if (
        prevSet &&
        prevSet.size === newSet.size &&
        [...newSet].every((key) => prevSet.has(key))
      ) {
        stableMap.set(rowId, prevSet);
      } else {
        stableMap.set(rowId, newSet);
      }
    }

    prevCellSelectionMapRef.current = stableMap;
    return stableMap;
  }, [selectionState.selectedCells, prevCellSelectionMapRef]);

  const getRowIndex = React.useCallback((rowId: string) => {
    const currentTable = tableRef.current;
    return currentTable ? getRowIndexById(currentTable, rowId) : -1;
  }, []);

  const getRowIdAt = React.useCallback(
    (rowIndex: number) => tableRef.current?.getRowModel().rows[rowIndex]?.id,
    [],
  );

  const columnIds = React.useMemo(() => {
    return getVisibleColumnIds({
      columnIds: columns
        .map((c) => {
          if (c.id) return c.id;
          if ("accessorKey" in c) return c.accessorKey as string;
          return undefined;
        })
        .filter((id): id is string => Boolean(id)),
      columnVisibility,
      columnPinning,
      columnOrder,
    });
  }, [columns, columnVisibility, columnPinning, columnOrder]);

  const navigableColumnIds = React.useMemo(() => {
    return columnIds.filter((c) => !NON_NAVIGABLE_COLUMN_IDS.has(c));
  }, [columnIds]);

  const onDataUpdate = React.useCallback(
    (updates: CellUpdate | Array<CellUpdate>) => {
      if (propsRef.current.readOnly) return;

      const updateArray = Array.isArray(updates) ? updates : [updates];

      if (updateArray.length === 0) return;

      const currentTable = tableRef.current;
      if (!currentTable) return;

      const rowsById = currentTable.getCoreRowModel().rowsById;
      const currentData = propsRef.current.data;
      const rowUpdatesMap = new Map<
        number,
        { original: TData; updates: Array<Omit<CellUpdate, "rowId">> }
      >();

      for (const update of updateArray) {
        const row = rowsById[update.rowId];
        if (!row || !getCanEditColumnId(currentTable, update.columnId)) {
          continue;
        }

        let rowUpdates = rowUpdatesMap.get(row.index);
        if (!rowUpdates) {
          rowUpdates = { original: row.original, updates: [] };
          rowUpdatesMap.set(row.index, rowUpdates);
        }
        rowUpdates.updates.push({
          columnId: update.columnId,
          value: update.value,
        });
      }

      if (rowUpdatesMap.size === 0) return;

      const newData = [...currentData];

      for (const [dataIndex, { original, updates }] of rowUpdatesMap) {
        const updatedRow = Object.assign(
          {},
          (currentData[dataIndex] ?? original) as Record<string, unknown>,
        );
        for (const { columnId, value } of updates) {
          updatedRow[columnId] = value;
        }
        newData[dataIndex] = updatedRow as TData;
      }

      propsRef.current.onDataChange?.(newData);
    },
    [propsRef],
  );

  const getIsCellSelected = React.useCallback(
    (rowId: string, columnId: string) => {
      const currentSelectionState = store.getState().selectionState;
      return currentSelectionState.selectedCells.has(
        getCellKey(rowId, columnId),
      );
    },
    [store],
  );

  const onSelectionClear = React.useCallback(() => {
    store.batch(() => {
      store.setState("selectionState", {
        selectedCells: new Set(),
        selectionRange: null,
        isSelecting: false,
      });
      store.setState("rowSelection", {});
    });
  }, [store]);

  const selectAll = React.useCallback(() => {
    const allCells = new Set<string>();
    const rows = tableRef.current?.getRowModel().rows ?? [];

    for (const row of rows) {
      for (const columnId of navigableColumnIds) {
        allCells.add(getCellKey(row.id, columnId));
      }
    }

    const firstRowId = rows[0]?.id;
    const lastRowId = rows[rows.length - 1]?.id;
    const firstColumnId = navigableColumnIds[0];
    const lastColumnId = navigableColumnIds[navigableColumnIds.length - 1];

    store.setState("selectionState", {
      selectedCells: allCells,
      selectionRange:
        firstRowId && lastRowId && firstColumnId && lastColumnId
          ? {
              start: { rowId: firstRowId, columnId: firstColumnId },
              end: { rowId: lastRowId, columnId: lastColumnId },
            }
          : null,
      isSelecting: false,
    });
  }, [navigableColumnIds, store]);

  const selectColumn = React.useCallback(
    (columnId: string) => {
      const rows = tableRef.current?.getRowModel().rows ?? [];
      const firstRowId = rows[0]?.id;
      const lastRowId = rows[rows.length - 1]?.id;

      if (!firstRowId || !lastRowId) return;

      const selectedCells = new Set<string>();

      for (const row of rows) {
        selectedCells.add(getCellKey(row.id, columnId));
      }

      store.setState("selectionState", {
        selectedCells,
        selectionRange: {
          start: { rowId: firstRowId, columnId },
          end: { rowId: lastRowId, columnId },
        },
        isSelecting: false,
      });
    },
    [store],
  );

  const selectRange = React.useCallback(
    (start: CellPosition, end: CellPosition, isSelecting = false) => {
      const rows = tableRef.current?.getRowModel().rows ?? [];
      const startRowIndex = getRowIndex(start.rowId);
      const endRowIndex = getRowIndex(end.rowId);
      const startColIndex = columnIds.indexOf(start.columnId);
      const endColIndex = columnIds.indexOf(end.columnId);

      const selectedCells = new Set<string>();

      if (startRowIndex !== -1 && endRowIndex !== -1) {
        const minRow = Math.min(startRowIndex, endRowIndex);
        const maxRow = Math.max(startRowIndex, endRowIndex);
        const minCol = Math.min(startColIndex, endColIndex);
        const maxCol = Math.max(startColIndex, endColIndex);

        for (let rowIndex = minRow; rowIndex <= maxRow; rowIndex++) {
          const rowId = rows[rowIndex]?.id;
          if (!rowId) continue;

          for (let colIndex = minCol; colIndex <= maxCol; colIndex++) {
            const columnId = columnIds[colIndex];
            if (columnId) {
              selectedCells.add(getCellKey(rowId, columnId));
            }
          }
        }
      }

      store.setState("selectionState", {
        selectedCells,
        selectionRange: { start, end },
        isSelecting,
      });
    },
    [columnIds, store, getRowIndex],
  );

  const serializeCellsToTsv = React.useCallback(() => {
    const currentState = store.getState();

    let selectedCellsArray: string[];
    if (!currentState.selectionState.selectedCells.size) {
      if (!currentState.focusedCell) return null;
      const focusedCellKey = getCellKey(
        currentState.focusedCell.rowId,
        currentState.focusedCell.columnId,
      );
      selectedCellsArray = [focusedCellKey];
    } else {
      selectedCellsArray = Array.from(
        currentState.selectionState.selectedCells,
      );
    }

    const currentTable = tableRef.current;
    const rowsById = currentTable?.getRowModel().rowsById;
    if (!rowsById) return null;

    const selectedColumnIds: string[] = [];
    const seenColumnIds = new Set<string>();
    const cellData = new Map<string, string>();
    const selectedRows = new Map<string, Row<DataGridFeatures, TData>>();
    const rowCellMaps = new Map<
      string,
      Map<
        string,
        ReturnType<Row<DataGridFeatures, TData>["getVisibleCells"]>[number]
      >
    >();
    const navigableCells: string[] = [];

    for (const cellKey of selectedCellsArray) {
      const { rowId, columnId } = parseCellKey(cellKey);

      if (columnId && NON_NAVIGABLE_COLUMN_IDS.has(columnId)) {
        continue;
      }

      navigableCells.push(cellKey);

      if (columnId && !seenColumnIds.has(columnId)) {
        seenColumnIds.add(columnId);
        selectedColumnIds.push(columnId);
      }

      const row = rowsById[rowId];
      if (row) {
        selectedRows.set(rowId, row);
        let cellMap = rowCellMaps.get(rowId);
        if (!cellMap) {
          cellMap = new Map(row.getVisibleCells().map((c) => [c.column.id, c]));
          rowCellMaps.set(rowId, cellMap);
        }
        const cell = cellMap.get(columnId);
        if (cell) {
          const value = cell.getValue();
          const cellVariant = cell.column.columnDef?.meta?.cell?.variant;

          let serializedValue = "";
          if (cellVariant === "file" || cellVariant === "multi-select") {
            serializedValue = value ? JSON.stringify(value) : "";
          } else if (value instanceof Date) {
            serializedValue = value.toISOString();
          } else {
            serializedValue = stringifyUnknown(value);
          }

          cellData.set(cellKey, serializedValue);
        }
      }
    }

    const colIndices = new Set<number>();
    for (const cellKey of navigableCells) {
      const { columnId } = parseCellKey(cellKey);
      const colIndex = selectedColumnIds.indexOf(columnId);
      if (colIndex >= 0) {
        colIndices.add(colIndex);
      }
    }

    const sortedRows = Array.from(selectedRows.values()).sort(
      (a, b) => a.getDisplayIndex() - b.getDisplayIndex(),
    );
    const sortedColIndices = Array.from(colIndices).sort((a, b) => a - b);
    const sortedColumnIds = sortedColIndices.map((i) => selectedColumnIds[i]);

    const navigableCellSet = new Set(navigableCells);
    const cellGrid = sortedRows.map((row) =>
      sortedColumnIds.map((columnId) => {
        if (!columnId) return null;
        const cellKey = getCellKey(row.id, columnId);
        return navigableCellSet.has(cellKey) ? cellKey : null;
      }),
    );

    const tsvData = sortedRows
      .map((row) =>
        sortedColumnIds
          .map((columnId) =>
            columnId ? (cellData.get(getCellKey(row.id, columnId)) ?? "") : "",
          )
          .join("\t"),
      )
      .join("\n");

    return { tsvData, cellGrid, selectedCellsArray: navigableCells };
  }, [store]);

  const onCellsCopy = React.useCallback(async () => {
    const result = serializeCellsToTsv();
    if (!result) return;

    const { tsvData, selectedCellsArray } = result;

    try {
      await navigator.clipboard.writeText(tsvData);

      const currentState = store.getState();
      if (currentState.cutCells.length > 0) {
        store.setState("cutCells", []);
      }

      toast.success(
        `${selectedCellsArray.length} cell${
          selectedCellsArray.length !== 1 ? "s" : ""
        } copied`,
      );
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Failed to copy to clipboard",
      );
    }
  }, [store, serializeCellsToTsv]);

  const onCellsCut = React.useCallback(async () => {
    if (propsRef.current.readOnly) return;

    const result = serializeCellsToTsv();
    if (!result) return;

    const { tsvData, cellGrid, selectedCellsArray } = result;

    try {
      await navigator.clipboard.writeText(tsvData);

      store.setState("cutCells", cellGrid);

      toast.success(
        `${selectedCellsArray.length} cell${
          selectedCellsArray.length !== 1 ? "s" : ""
        } cut`,
      );
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "Failed to cut to clipboard",
      );
    }
  }, [store, propsRef, serializeCellsToTsv]);

  const restoreFocus = React.useCallback((element: HTMLDivElement | null) => {
    if (element && document.activeElement !== element) {
      requestAnimationFrame(() => {
        element.focus();
      });
    }
  }, []);

  const onCellsPaste = React.useCallback(
    async (expandRows = false) => {
      if (propsRef.current.readOnly) return;

      const currentState = store.getState();
      if (!currentState.focusedCell) return;

      const currentTable = tableRef.current;
      const rows = currentTable?.getRowModel().rows;
      if (!rows) return;

      try {
        let clipboardText = currentState.pasteDialog.clipboardText;

        if (!clipboardText) {
          clipboardText = await navigator.clipboard.readText();
          if (!clipboardText) return;
        }

        const rawPastedData = parseTsv(
          clipboardText,
          navigableColumnIds.length,
        );

        // Fill entire selection when clipboard has a single value and multiple cells are selected
        const selectionCells = currentState.selectionState.selectedCells;
        const isSingleCellClipboard =
          rawPastedData.length === 1 && (rawPastedData[0]?.length ?? 0) === 1;

        let pastedData = rawPastedData;
        let isFillPaste = false;
        let startRowIndex = getRowIndex(currentState.focusedCell.rowId);
        let startColIndex = navigableColumnIds.indexOf(
          currentState.focusedCell.columnId,
        );

        if (isSingleCellClipboard && selectionCells.size > 1) {
          const singleValue = rawPastedData[0]?.[0] ?? "";
          let minRow = Infinity;
          let maxRow = -Infinity;
          let minColIdx = Infinity;
          let maxColIdx = -Infinity;

          for (const cellKey of selectionCells) {
            const { rowId, columnId } = parseCellKey(cellKey);
            const rowIndex = getRowIndex(rowId);
            const colIdx = navigableColumnIds.indexOf(columnId);
            if (rowIndex === -1 || colIdx === -1) continue;
            minRow = Math.min(minRow, rowIndex);
            maxRow = Math.max(maxRow, rowIndex);
            minColIdx = Math.min(minColIdx, colIdx);
            maxColIdx = Math.max(maxColIdx, colIdx);
          }

          if (minRow !== Infinity) {
            startRowIndex = minRow;
            startColIndex = minColIdx;
            const numRows = maxRow - minRow + 1;
            const numCols = maxColIdx - minColIdx + 1;
            pastedData = Array.from({ length: numRows }, () =>
              Array.from({ length: numCols }, () => singleValue),
            );
            isFillPaste = true;
          }
        }

        if (startRowIndex === -1 || startColIndex === -1) return;

        const rowCount = rows.length;
        const rowsNeeded = startRowIndex + pastedData.length - rowCount;

        if (
          rowsNeeded > 0 &&
          !expandRows &&
          propsRef.current.onRowAdd &&
          !currentState.pasteDialog.clipboardText
        ) {
          store.setState("pasteDialog", {
            open: true,
            rowsNeeded,
            clipboardText,
          });
          return;
        }

        if (expandRows && rowsNeeded > 0) {
          const expectedRowCount = rowCount + rowsNeeded;

          if (propsRef.current.onRowsAdd) {
            await propsRef.current.onRowsAdd(rowsNeeded);
          } else if (propsRef.current.onRowAdd) {
            for (let i = 0; i < rowsNeeded; i++) {
              await propsRef.current.onRowAdd();
            }
          }

          let attempts = 0;
          const maxAttempts = 50;
          let currentTableRowCount =
            tableRef.current?.getRowModel().rows.length ?? 0;

          while (
            currentTableRowCount < expectedRowCount &&
            attempts < maxAttempts
          ) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            currentTableRowCount =
              tableRef.current?.getRowModel().rows.length ?? 0;
            attempts++;
          }
        }

        const updates: Array<CellUpdate> = [];
        const tableColumns = currentTable?.getAllColumns() ?? [];
        let cellsUpdated = 0;
        let endRowIndex = startRowIndex;
        let endColIndex = startColIndex;

        const updatedTable = tableRef.current;
        const updatedRows = updatedTable?.getRowModel().rows;

        let cellsSkipped = 0;
        const writtenCellKeys = new Set<string>();
        const movedSourceCellKeys = new Set<string>();

        const columnMap = new Map(tableColumns.map((c) => [c.id, c]));

        for (
          let pasteRowIdx = 0;
          pasteRowIdx < pastedData.length;
          pasteRowIdx++
        ) {
          const pasteRow = pastedData[pasteRowIdx];
          if (!pasteRow) continue;

          const targetRowIndex = startRowIndex + pasteRowIdx;
          const targetRowId = updatedRows?.[targetRowIndex]?.id;
          if (!targetRowId) break;

          for (
            let pasteColIdx = 0;
            pasteColIdx < pasteRow.length;
            pasteColIdx++
          ) {
            const targetColIndex = startColIndex + pasteColIdx;
            if (targetColIndex >= navigableColumnIds.length) break;

            const targetColumnId = navigableColumnIds[targetColIndex];
            if (!targetColumnId) continue;

            if (
              currentTable &&
              !getCanEditColumnId(currentTable, targetColumnId)
            ) {
              cellsSkipped++;
              endRowIndex = Math.max(endRowIndex, targetRowIndex);
              endColIndex = Math.max(endColIndex, targetColIndex);
              continue;
            }

            const pastedValue = pasteRow[pasteColIdx] ?? "";
            const column = columnMap.get(targetColumnId);
            const cellOpts = column?.columnDef?.meta?.cell;
            const cellVariant = cellOpts?.variant;

            let processedValue: unknown = pastedValue;
            let shouldSkip = false;

            switch (cellVariant) {
              case "number": {
                if (!pastedValue) {
                  processedValue = null;
                } else {
                  const num = Number.parseFloat(pastedValue);
                  if (Number.isNaN(num)) shouldSkip = true;
                  else processedValue = num;
                }
                break;
              }

              case "checkbox": {
                if (!pastedValue) {
                  processedValue = false;
                } else {
                  const lower = pastedValue.toLowerCase();
                  if (VALID_BOOLEANS.has(lower)) {
                    processedValue = TRUTHY_BOOLEANS.has(lower);
                  } else {
                    shouldSkip = true;
                  }
                }
                break;
              }

              case "date": {
                if (!pastedValue) {
                  processedValue = null;
                } else {
                  const date = new Date(pastedValue);
                  if (Number.isNaN(date.getTime())) shouldSkip = true;
                  else processedValue = date;
                }
                break;
              }

              case "select": {
                const options = cellOpts?.options ?? [];
                if (!pastedValue) {
                  processedValue = null;
                } else {
                  const matched = matchSelectOption(pastedValue, options);
                  if (matched) processedValue = matched;
                  else shouldSkip = true;
                }
                break;
              }

              case "multi-select": {
                const options = cellOpts?.options ?? [];
                let values: string[] = [];
                try {
                  const parsed = JSON.parse(pastedValue);
                  if (Array.isArray(parsed)) {
                    values = parsed.filter(
                      (v): v is string => typeof v === "string",
                    );
                  }
                } catch {
                  values = pastedValue
                    ? pastedValue.split(",").map((v) => v.trim())
                    : [];
                }

                const validated = values
                  .map((v) => matchSelectOption(v, options))
                  .filter(Boolean) as string[];

                if (values.length > 0 && validated.length === 0) {
                  shouldSkip = true;
                } else {
                  processedValue = validated;
                }
                break;
              }

              case "file": {
                if (!pastedValue) {
                  processedValue = [];
                } else {
                  try {
                    const parsed = JSON.parse(pastedValue);
                    if (!Array.isArray(parsed)) {
                      shouldSkip = true;
                    } else {
                      const validFiles = parsed.filter(getIsFileCellData);
                      if (parsed.length > 0 && validFiles.length === 0) {
                        shouldSkip = true;
                      } else {
                        processedValue = validFiles;
                      }
                    }
                  } catch {
                    shouldSkip = true;
                  }
                }
                break;
              }

              case "url": {
                if (!pastedValue) {
                  processedValue = "";
                } else {
                  const firstChar = pastedValue[0];
                  if (firstChar === "[" || firstChar === "{") {
                    shouldSkip = true;
                  } else {
                    try {
                      new URL(pastedValue);
                      processedValue = pastedValue;
                    } catch {
                      if (DOMAIN_REGEX.test(pastedValue)) {
                        processedValue = pastedValue;
                      } else {
                        shouldSkip = true;
                      }
                    }
                  }
                }
                break;
              }

              default: {
                if (!pastedValue) {
                  processedValue = "";
                  break;
                }

                if (ISO_DATE_REGEX.test(pastedValue)) {
                  const date = new Date(pastedValue);
                  if (!Number.isNaN(date.getTime())) {
                    processedValue = date.toLocaleDateString();
                    break;
                  }
                }

                const firstChar = pastedValue[0];
                if (
                  firstChar === "[" ||
                  firstChar === "{" ||
                  firstChar === "t" ||
                  firstChar === "f"
                ) {
                  try {
                    const parsed = JSON.parse(pastedValue);

                    if (Array.isArray(parsed)) {
                      if (
                        parsed.length > 0 &&
                        parsed.every(getIsFileCellData)
                      ) {
                        processedValue = parsed.map((f) => f.name).join(", ");
                      } else if (parsed.every((v) => typeof v === "string")) {
                        processedValue = (parsed as string[]).join(", ");
                      }
                    } else if (typeof parsed === "boolean") {
                      processedValue = parsed ? "Checked" : "Unchecked";
                    }
                  } catch {
                    const lower = pastedValue.toLowerCase();
                    if (lower === "true" || lower === "false") {
                      processedValue =
                        lower === "true" ? "Checked" : "Unchecked";
                    }
                  }
                }
              }
            }

            if (shouldSkip) {
              cellsSkipped++;
              endRowIndex = Math.max(endRowIndex, targetRowIndex);
              endColIndex = Math.max(endColIndex, targetColIndex);
              continue;
            }

            updates.push({
              rowId: targetRowId,
              columnId: targetColumnId,
              value: processedValue,
            });
            cellsUpdated++;
            writtenCellKeys.add(getCellKey(targetRowId, targetColumnId));

            const sourceCellKey = isFillPaste
              ? currentState.cutCells[0]?.[0]
              : currentState.cutCells[pasteRowIdx]?.[pasteColIdx];
            if (sourceCellKey) movedSourceCellKeys.add(sourceCellKey);

            endRowIndex = Math.max(endRowIndex, targetRowIndex);
            endColIndex = Math.max(endColIndex, targetColIndex);
          }
        }

        if (updates.length > 0) {
          if (propsRef.current.onPaste) {
            await propsRef.current.onPaste(updates);
          }

          const allUpdates = [...updates];

          if (currentState.cutCells.length > 0) {
            const columnById = new Map(tableColumns.map((c) => [c.id, c]));

            for (const cellKey of movedSourceCellKeys) {
              if (writtenCellKeys.has(cellKey)) continue;

              const { rowId, columnId } = parseCellKey(cellKey);
              const column = columnById.get(columnId);
              const cellVariant = column?.columnDef?.meta?.cell?.variant;
              const emptyValue = getEmptyCellValue(cellVariant);
              allUpdates.push({ rowId, columnId, value: emptyValue });
            }

            store.setState("cutCells", []);
          }

          onDataUpdate(allUpdates);

          if (cellsSkipped > 0) {
            toast.success(
              `${cellsUpdated} cell${
                cellsUpdated !== 1 ? "s" : ""
              } pasted, ${cellsSkipped} skipped`,
            );
          } else {
            toast.success(
              `${cellsUpdated} cell${cellsUpdated !== 1 ? "s" : ""} pasted`,
            );
          }

          const startRowId = updatedRows?.[startRowIndex]?.id;
          const endRowId = updatedRows?.[endRowIndex]?.id;
          const endColumnId = navigableColumnIds[endColIndex];
          if (startRowId && endRowId && endColumnId) {
            selectRange(
              {
                rowId: startRowId,
                columnId: currentState.focusedCell.columnId,
              },
              { rowId: endRowId, columnId: endColumnId },
            );
          }

          restoreFocus(dataGridRef.current);
        } else if (cellsSkipped > 0) {
          toast.error(
            `${cellsSkipped} cell${
              cellsSkipped !== 1 ? "s" : ""
            } skipped pasting for invalid data`,
          );
        }

        if (currentState.pasteDialog.open) {
          store.setState("pasteDialog", {
            open: false,
            rowsNeeded: 0,
            clipboardText: "",
          });
        }
      } catch (error) {
        toast.error(
          error instanceof Error
            ? error.message
            : "Failed to paste. Please try again.",
        );
      }
    },
    [
      store,
      navigableColumnIds,
      propsRef,
      onDataUpdate,
      selectRange,
      restoreFocus,
      getRowIndex,
    ],
  );

  // Release focus guard after delay to allow async data re-renders to settle.
  // 300ms accounts for db sync and virtualized cell mounting
  const releaseFocusGuard = React.useCallback((immediate = false) => {
    if (immediate) {
      focusGuardRef.current = false;
      return;
    }

    setTimeout(() => {
      focusGuardRef.current = false;
    }, 300);
  }, []);

  const focusCellWrapper = React.useCallback(
    (rowId: string, columnId: string) => {
      focusGuardRef.current = true;

      requestAnimationFrame(() => {
        const cellKey = getCellKey(rowId, columnId);
        const cellWrapperElement = cellMapRef.current.get(cellKey);

        if (!cellWrapperElement) {
          const container = dataGridRef.current;
          if (container) {
            container.focus();
          }
          releaseFocusGuard();
          return;
        }

        getCellFocusTarget(cellWrapperElement).focus();
        releaseFocusGuard();
      });
    },
    [releaseFocusGuard],
  );

  const focusCell = React.useCallback(
    (rowId: string, columnId: string) => {
      store.batch(() => {
        store.setState("focusedCell", { rowId, columnId });
        tableRef.current?.setEditingCell(null);
      });

      const currentState = store.getState();

      if (currentState.searchOpen) return;

      focusCellWrapper(rowId, columnId);
    },
    [store, focusCellWrapper],
  );

  const onRowsDelete = React.useCallback(
    async (rowIds: string[]) => {
      if (
        propsRef.current.readOnly ||
        !propsRef.current.onRowsDelete ||
        rowIds.length === 0
      )
        return;

      const rowsById = tableRef.current?.getRowModel().rowsById;
      if (!rowsById) return;

      const currentState = store.getState();
      const currentFocusedColumn =
        currentState.focusedCell?.columnId ?? navigableColumnIds[0];

      const rowsToDelete: TData[] = [];
      const deletedRowIds: string[] = [];
      let minDeletedRowIndex = Infinity;
      for (const rowId of rowIds) {
        const row = rowsById[rowId];
        if (!row) continue;

        rowsToDelete.push(row.original);
        deletedRowIds.push(rowId);
        minDeletedRowIndex = Math.min(
          minDeletedRowIndex,
          row.getDisplayIndex(),
        );
      }

      if (rowsToDelete.length === 0) return;

      await propsRef.current.onRowsDelete(rowsToDelete, deletedRowIds);

      store.batch(() => {
        store.setState("selectionState", {
          selectedCells: new Set(),
          selectionRange: null,
          isSelecting: false,
        });
        store.setState("rowSelection", {});
        tableRef.current?.setEditingCell(null);
      });

      requestAnimationFrame(() => {
        const currentRows = tableRef.current?.getRowModel().rows ?? [];
        const targetRow =
          currentRows[Math.min(minDeletedRowIndex, currentRows.length - 1)];

        if (targetRow && currentFocusedColumn) {
          focusCell(targetRow.id, currentFocusedColumn);
        }
      });
    },
    [propsRef, store, navigableColumnIds, focusCell],
  );

  const navigateCell = React.useCallback(
    (direction: NavigationDirection) => {
      const currentState = store.getState();
      if (!currentState.focusedCell) return;

      const { rowId, columnId } = currentState.focusedCell;
      const rowIndex = getRowIndex(rowId);
      if (rowIndex === -1) return;

      const currentColIndex = navigableColumnIds.indexOf(columnId);
      const rowVirtualizer = rowVirtualizerRef.current;
      const rows = tableRef.current?.getRowModel().rows ?? [];
      const rowCount = rows.length;

      let newRowIndex = rowIndex;
      let newColumnId = columnId;

      const isRtl = dir === "rtl";

      switch (direction) {
        case "up":
          newRowIndex = Math.max(0, rowIndex - 1);
          break;
        case "down":
          newRowIndex = Math.min(rowCount - 1, rowIndex + 1);
          break;
        case "left":
        case "right": {
          const isNext = (direction === "right") !== isRtl;
          const adjacentColumnId =
            columnIds[columnIds.indexOf(columnId) + (isNext ? 1 : -1)];
          if (adjacentColumnId) newColumnId = adjacentColumnId;
          break;
        }
        case "home":
          if (navigableColumnIds.length > 0) {
            newColumnId = navigableColumnIds[0] ?? columnId;
          }
          break;
        case "end":
          if (navigableColumnIds.length > 0) {
            newColumnId =
              navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;
          }
          break;
        case "ctrl+home":
          newRowIndex = 0;
          if (navigableColumnIds.length > 0) {
            newColumnId = navigableColumnIds[0] ?? columnId;
          }
          break;
        case "ctrl+end":
          newRowIndex = Math.max(0, rowCount - 1);
          if (navigableColumnIds.length > 0) {
            newColumnId =
              navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;
          }
          break;
        case "ctrl+up":
          newRowIndex = 0;
          break;
        case "ctrl+down":
          newRowIndex = Math.max(0, rowCount - 1);
          break;
        case "pageup":
          if (rowVirtualizer) {
            const visibleRange = rowVirtualizer.getVirtualItems();
            const pageSize = visibleRange.length ?? 10;
            newRowIndex = Math.max(0, rowIndex - pageSize);
          } else {
            newRowIndex = Math.max(0, rowIndex - 10);
          }
          break;
        case "pagedown":
          if (rowVirtualizer) {
            const visibleRange = rowVirtualizer.getVirtualItems();
            const pageSize = visibleRange.length ?? 10;
            newRowIndex = Math.min(rowCount - 1, rowIndex + pageSize);
          } else {
            newRowIndex = Math.min(rowCount - 1, rowIndex + 10);
          }
          break;
        case "pageleft":
          if (currentColIndex > 0) {
            const targetIndex = Math.max(
              0,
              currentColIndex - HORIZONTAL_PAGE_SIZE,
            );
            const targetColumnId = navigableColumnIds[targetIndex];
            if (targetColumnId) newColumnId = targetColumnId;
          }
          break;
        case "pageright":
          if (currentColIndex < navigableColumnIds.length - 1) {
            const targetIndex = Math.min(
              navigableColumnIds.length - 1,
              currentColIndex + HORIZONTAL_PAGE_SIZE,
            );
            const targetColumnId = navigableColumnIds[targetIndex];
            if (targetColumnId) newColumnId = targetColumnId;
          }
          break;
        case "tab":
        case "shift+tab": {
          const target = getTabTargetCell({
            rowIndex,
            columnId,
            columnIds,
            rowCount,
            isBackward: direction === "shift+tab",
            getIsColumnTabbable: getIsDataColumn,
          });
          if (target) {
            newRowIndex = target.rowIndex;
            newColumnId = target.columnId;
          }
          break;
        }
      }

      const newRowId = rows[newRowIndex]?.id;
      if (!newRowId) return;

      if (newRowIndex !== rowIndex || newColumnId !== columnId) {
        focusCell(newRowId, newColumnId);

        // Calculate and apply scrolls synchronously to avoid flashing
        const container = dataGridRef.current;
        if (!container) return;

        const targetRow = rowMapRef.current.get(newRowIndex);
        const cellKey = getCellKey(newRowId, newColumnId);
        const targetCell = cellMapRef.current.get(cellKey);

        // If target row is not rendered, scroll it into view first
        if (!targetRow) {
          if (rowVirtualizer) {
            const align =
              direction === "up" ||
              direction === "pageup" ||
              direction === "ctrl+up" ||
              direction === "ctrl+home" ||
              direction === "shift+tab"
                ? "start"
                : direction === "down" ||
                    direction === "pagedown" ||
                    direction === "ctrl+down" ||
                    direction === "ctrl+end" ||
                    direction === "tab"
                  ? "end"
                  : "center";

            rowVirtualizer.scrollToIndex(newRowIndex, { align });

            // Wait for row to render before horizontal scroll
            if (newColumnId !== columnId) {
              requestAnimationFrame(() => {
                const cellKeyRetry = getCellKey(newRowId, newColumnId);
                const targetCellRetry = cellMapRef.current.get(cellKeyRetry);

                if (targetCellRetry) {
                  const scrollDirection = getScrollDirection(direction);

                  scrollCellIntoView({
                    container,
                    targetCell: targetCellRetry,
                    tableRef,
                    viewportOffset: VIEWPORT_OFFSET,
                    direction: scrollDirection,
                    isRtl: dir === "rtl",
                  });
                }
              });
            }
          } else {
            // Use direct scroll calculation when virtualizer is not available
            const rowSize = tableRef.current?.getRowSize() ?? 0;
            container.scrollTop = newRowIndex * rowSize;
          }

          return;
        }

        // Vertical scrolling for rendered rows that changed
        if (newRowIndex !== rowIndex && targetRow) {
          requestAnimationFrame(() => {
            const containerRect = container.getBoundingClientRect();
            const headerHeight =
              headerRef.current?.getBoundingClientRect().height ?? 0;
            const footerHeight =
              footerRef.current?.getBoundingClientRect().height ?? 0;
            const viewportTop =
              containerRect.top + headerHeight + VIEWPORT_OFFSET;
            const viewportBottom =
              containerRect.bottom - footerHeight - VIEWPORT_OFFSET;

            const rowRect = targetRow.getBoundingClientRect();
            const isFullyVisible =
              rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;

            if (!isFullyVisible) {
              // Only apply vertical scroll for vertical navigation
              const isVerticalNavigation =
                direction === "up" ||
                direction === "down" ||
                direction === "pageup" ||
                direction === "pagedown" ||
                direction === "ctrl+up" ||
                direction === "ctrl+down" ||
                direction === "ctrl+home" ||
                direction === "ctrl+end" ||
                direction === "tab" ||
                direction === "shift+tab";

              if (isVerticalNavigation) {
                if (
                  direction === "down" ||
                  direction === "pagedown" ||
                  direction === "ctrl+down" ||
                  direction === "ctrl+end" ||
                  direction === "tab"
                ) {
                  container.scrollTop += rowRect.bottom - viewportBottom;
                } else {
                  container.scrollTop -= viewportTop - rowRect.top;
                }
              }
            }
          });
        }

        // Horizontal scrolling for rendered cells
        if (newColumnId !== columnId && targetCell) {
          requestAnimationFrame(() => {
            const scrollDirection = getScrollDirection(direction);

            scrollCellIntoView({
              container,
              targetCell,
              tableRef,
              viewportOffset: VIEWPORT_OFFSET,
              direction: scrollDirection,
              isRtl: dir === "rtl",
            });
          });
        }
      }
    },
    [dir, store, columnIds, navigableColumnIds, focusCell, getRowIndex],
  );

  const onCellEditingStart = React.useCallback(
    (rowId: string, columnId: string) => {
      const currentTable = tableRef.current;
      if (!currentTable || !getCanEditColumnId(currentTable, columnId)) return;

      store.batch(() => {
        store.setState("focusedCell", { rowId, columnId });
        tableRef.current?.setEditingCell({ rowId, columnId });
      });
    },
    [store],
  );

  const onCellEditingStop = React.useCallback(
    (opts?: { moveToNextRow?: boolean; direction?: NavigationDirection }) => {
      const currentEditing = tableRef.current?.getEditingCell();

      tableRef.current?.setEditingCell(null);

      if (opts?.moveToNextRow && currentEditing) {
        const { rowId, columnId } = currentEditing;
        const nextRowId = getRowIdAt(getRowIndex(rowId) + 1);

        if (nextRowId) {
          requestAnimationFrame(() => {
            focusCell(nextRowId, columnId);
          });
        }
      } else if (opts?.direction && currentEditing) {
        const { rowId, columnId } = currentEditing;
        focusCell(rowId, columnId);
        requestAnimationFrame(() => {
          navigateCell(opts.direction ?? "right");
        });
      } else if (currentEditing) {
        const { rowId, columnId } = currentEditing;
        focusCellWrapper(rowId, columnId);
      }
    },
    [store, focusCell, navigateCell, focusCellWrapper, getRowIndex, getRowIdAt],
  );

  const onSearchOpenChange = React.useCallback(
    (open: boolean) => {
      if (open) {
        store.setState("searchOpen", true);
        return;
      }

      const currentState = store.getState();
      const currentMatch =
        currentState.matchIndex >= 0 &&
        currentState.searchMatches[currentState.matchIndex];

      store.batch(() => {
        store.setState("searchOpen", false);
        store.setState("searchQuery", "");
        store.setState("searchMatches", []);
        store.setState("matchIndex", -1);

        if (currentMatch) {
          store.setState("focusedCell", {
            rowId: currentMatch.rowId,
            columnId: currentMatch.columnId,
          });
        }
      });

      if (
        dataGridRef.current &&
        document.activeElement !== dataGridRef.current
      ) {
        dataGridRef.current.focus();
      }
    },
    [store],
  );

  const onSearch = React.useCallback(
    (query: string) => {
      if (!query.trim()) {
        store.batch(() => {
          store.setState("searchMatches", []);
          store.setState("matchIndex", -1);
        });
        return;
      }

      const matches: CellPosition[] = [];
      let firstMatchRowIndex = -1;
      const currentTable = tableRef.current;
      const rows = currentTable?.getRowModel().rows ?? [];

      const lowerQuery = query.toLowerCase();

      for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
        const row = rows[rowIndex];
        if (!row) continue;

        const cellById = new Map(
          row.getVisibleCells().map((c) => [c.column.id, c]),
        );

        for (const columnId of columnIds) {
          const cell = cellById.get(columnId);
          if (!cell) continue;

          const value = cell.getValue();
          const stringValue = stringifyUnknown(value).toLowerCase();

          if (stringValue.includes(lowerQuery)) {
            if (firstMatchRowIndex === -1) firstMatchRowIndex = rowIndex;
            matches.push({ rowId: row.id, columnId });
          }
        }
      }

      store.batch(() => {
        store.setState("searchMatches", matches);
        store.setState("matchIndex", matches.length > 0 ? 0 : -1);
      });

      if (firstMatchRowIndex !== -1) {
        rowVirtualizerRef.current?.scrollToIndex(firstMatchRowIndex, {
          align: "center",
        });
      }
    },
    [columnIds, store],
  );

  const onSearchQueryChange = React.useCallback(
    (query: string) => store.setState("searchQuery", query),
    [store],
  );

  const onNavigateToPrevMatch = React.useCallback(() => {
    const currentState = store.getState();
    if (currentState.searchMatches.length === 0) return;

    const prevIndex =
      currentState.matchIndex - 1 < 0
        ? currentState.searchMatches.length - 1
        : currentState.matchIndex - 1;
    const match = currentState.searchMatches[prevIndex];
    const matchRowIndex = match ? getRowIndex(match.rowId) : -1;

    if (match && matchRowIndex !== -1) {
      rowVirtualizerRef.current?.scrollToIndex(matchRowIndex, {
        align: "center",
      });

      requestAnimationFrame(() => {
        store.setState("matchIndex", prevIndex);
        requestAnimationFrame(() => {
          focusCell(match.rowId, match.columnId);
        });
      });
    }
  }, [store, focusCell, getRowIndex]);

  const onNavigateToNextMatch = React.useCallback(() => {
    const currentState = store.getState();
    if (currentState.searchMatches.length === 0) return;

    const nextIndex =
      (currentState.matchIndex + 1) % currentState.searchMatches.length;
    const match = currentState.searchMatches[nextIndex];
    const matchRowIndex = match ? getRowIndex(match.rowId) : -1;

    if (match && matchRowIndex !== -1) {
      rowVirtualizerRef.current?.scrollToIndex(matchRowIndex, {
        align: "center",
      });

      requestAnimationFrame(() => {
        store.setState("matchIndex", nextIndex);
        requestAnimationFrame(() => {
          focusCell(match.rowId, match.columnId);
        });
      });
    }
  }, [store, focusCell, getRowIndex]);

  const searchMatchSet = React.useMemo(() => {
    return new Set(searchMatches.map((m) => getCellKey(m.rowId, m.columnId)));
  }, [searchMatches]);

  const getIsSearchMatch = React.useCallback(
    (rowId: string, columnId: string) => {
      return searchMatchSet.has(getCellKey(rowId, columnId));
    },
    [searchMatchSet],
  );

  const getIsActiveSearchMatch = React.useCallback(
    (rowId: string, columnId: string) => {
      const currentState = store.getState();
      if (currentState.matchIndex < 0) return false;
      const currentMatch = currentState.searchMatches[currentState.matchIndex];
      return (
        currentMatch?.rowId === rowId && currentMatch?.columnId === columnId
      );
    },
    [store],
  );

  // Compute search match data for targeted row re-renders
  const searchMatchesByRow = React.useMemo(() => {
    if (searchMatches.length === 0) return null;
    const rowMap = new Map<string, Set<string>>();
    for (const match of searchMatches) {
      let columnSet = rowMap.get(match.rowId);
      if (!columnSet) {
        columnSet = new Set<string>();
        rowMap.set(match.rowId, columnSet);
      }
      columnSet.add(match.columnId);
    }
    return rowMap;
  }, [searchMatches]);

  const activeSearchMatch = React.useMemo<CellPosition | null>(() => {
    if (matchIndex < 0 || searchMatches.length === 0) return null;
    return searchMatches[matchIndex] ?? null;
  }, [searchMatches, matchIndex]);

  const blurCell = React.useCallback(() => {
    if (
      tableRef.current?.getEditingCell() &&
      document.activeElement instanceof HTMLElement
    ) {
      document.activeElement.blur();
    }

    store.batch(() => {
      store.setState("focusedCell", null);
      tableRef.current?.setEditingCell(null);
    });
  }, [store]);

  const scrollToCell = React.useCallback(
    (rowId: string, columnId: string) => {
      requestAnimationFrame(() => {
        const container = dataGridRef.current;
        const cellKey = getCellKey(rowId, columnId);
        const targetCell = cellMapRef.current.get(cellKey);

        if (container && targetCell) {
          scrollCellIntoView({
            container,
            targetCell,
            tableRef,
            viewportOffset: VIEWPORT_OFFSET,
            isRtl: dir === "rtl",
          });
        }
      });
    },
    [dir],
  );

  const onCellClick = React.useCallback(
    (rowId: string, columnId: string, event?: React.MouseEvent) => {
      if (event?.button === 2) return;

      const currentState = store.getState();
      const currentFocused = currentState.focusedCell;

      if (event) {
        if (event.ctrlKey || event.metaKey) {
          event.preventDefault();
          const cellKey = getCellKey(rowId, columnId);
          const newSelectedCells = new Set(
            currentState.selectionState.selectedCells,
          );

          if (newSelectedCells.has(cellKey)) {
            newSelectedCells.delete(cellKey);
          } else {
            newSelectedCells.add(cellKey);
          }

          store.setState("selectionState", {
            selectedCells: newSelectedCells,
            selectionRange: null,
            isSelecting: false,
          });
          focusCell(rowId, columnId);
          scrollToCell(rowId, columnId);
          return;
        }

        if (event.shiftKey && currentState.focusedCell) {
          event.preventDefault();
          selectRange(currentState.focusedCell, { rowId, columnId });
          scrollToCell(rowId, columnId);
          return;
        }
      }

      const hasSelectedCells =
        currentState.selectionState.selectedCells.size > 0;
      const hasSelectedRows = Object.keys(currentState.rowSelection).length > 0;

      if (hasSelectedCells && !currentState.selectionState.isSelecting) {
        const cellKey = getCellKey(rowId, columnId);
        const isClickingSelectedCell =
          currentState.selectionState.selectedCells.has(cellKey);

        if (!isClickingSelectedCell) {
          onSelectionClear();
        } else {
          focusCell(rowId, columnId);
          scrollToCell(rowId, columnId);
          return;
        }
      } else if (hasSelectedRows && columnId !== "select") {
        onSelectionClear();
      }

      if (
        currentFocused?.rowId === rowId &&
        currentFocused?.columnId === columnId
      ) {
        onCellEditingStart(rowId, columnId);
      } else {
        focusCell(rowId, columnId);
        scrollToCell(rowId, columnId);
      }
    },
    [
      store,
      focusCell,
      scrollToCell,
      onCellEditingStart,
      selectRange,
      onSelectionClear,
    ],
  );

  const onCellDoubleClick = React.useCallback(
    (rowId: string, columnId: string, event?: React.MouseEvent) => {
      if (event?.defaultPrevented) return;

      onCellEditingStart(rowId, columnId);
    },
    [onCellEditingStart],
  );

  const onCellMouseDown = React.useCallback(
    (rowId: string, columnId: string, event: React.MouseEvent) => {
      if (event.button === 2) {
        return;
      }

      event.preventDefault();

      if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
        const cellKey = getCellKey(rowId, columnId);
        store.batch(() => {
          store.setState("selectionState", {
            selectedCells: propsRef.current.enableSingleCellSelection
              ? new Set([cellKey])
              : new Set(),
            selectionRange: {
              start: { rowId, columnId },
              end: { rowId, columnId },
            },
            isSelecting: true,
          });
          store.setState("rowSelection", {});
        });
      }
    },
    [store, propsRef],
  );

  const onCellMouseEnter = React.useCallback(
    (rowId: string, columnId: string) => {
      const currentState = store.getState();
      if (
        currentState.selectionState.isSelecting &&
        currentState.selectionState.selectionRange
      ) {
        const start = currentState.selectionState.selectionRange.start;
        const end = { rowId, columnId };

        if (
          currentState.focusedCell?.rowId !== start.rowId ||
          currentState.focusedCell?.columnId !== start.columnId
        ) {
          focusCell(start.rowId, start.columnId);
        }

        selectRange(start, end, true);
      }
    },
    [store, selectRange, focusCell],
  );

  const onCellMouseUp = React.useCallback(() => {
    const currentState = store.getState();
    store.setState("selectionState", {
      ...currentState.selectionState,
      isSelecting: false,
    });
  }, [store]);

  const onCellContextMenu = React.useCallback(
    (rowId: string, columnId: string, event: React.MouseEvent) => {
      event.preventDefault();
      event.stopPropagation();

      const currentState = store.getState();
      const cellKey = getCellKey(rowId, columnId);
      const isTargetCellSelected =
        currentState.selectionState.selectedCells.has(cellKey);

      if (!isTargetCellSelected) {
        store.batch(() => {
          store.setState("selectionState", {
            selectedCells: new Set([cellKey]),
            selectionRange: {
              start: { rowId, columnId },
              end: { rowId, columnId },
            },
            isSelecting: false,
          });
          store.setState("focusedCell", { rowId, columnId });
        });
      }

      store.setState("contextMenu", {
        open: true,
        x: event.clientX,
        y: event.clientY,
      });
    },
    [store],
  );

  const onContextMenuOpenChange = React.useCallback(
    (open: boolean) => {
      if (!open) {
        const currentMenu = store.getState().contextMenu;
        store.setState("contextMenu", {
          open: false,
          x: currentMenu.x,
          y: currentMenu.y,
        });
      }
    },
    [store],
  );

  const onSortingChange = React.useCallback(
    (updater: Updater<SortingState>) => {
      const currentState = store.getState();
      const newSorting =
        typeof updater === "function" ? updater(currentState.sorting) : updater;

      store.batch(() => {
        store.setState("sorting", newSorting);
      });

      propsRef.current.onSortingChange?.(newSorting);
    },
    [store, propsRef],
  );

  const onColumnVisibilityChange = React.useCallback(
    (updater: Updater<ColumnVisibilityState>) => {
      const currentState = store.getState();
      const newColumnVisibility =
        typeof updater === "function"
          ? updater(
              propsRef.current.state?.columnVisibility ??
                currentState.columnVisibility,
            )
          : updater;

      store.batch(() => {
        store.setState("columnVisibility", newColumnVisibility);

        const focusedColumnId = currentState.focusedCell?.columnId;
        if (focusedColumnId && newColumnVisibility[focusedColumnId] === false) {
          store.setState("focusedCell", null);
          tableRef.current?.setEditingCell(null);
        }
      });

      propsRef.current.onColumnVisibilityChange?.(newColumnVisibility);
    },
    [store, propsRef],
  );

  const onColumnPinningChange = React.useCallback(
    (updater: Updater<ColumnPinningState>) => {
      const currentState = store.getState();
      const newColumnPinning =
        typeof updater === "function"
          ? updater(
              propsRef.current.state?.columnPinning ??
                currentState.columnPinning,
            )
          : updater;

      store.setState("columnPinning", newColumnPinning);
      propsRef.current.onColumnPinningChange?.(newColumnPinning);
    },
    [store, propsRef],
  );

  const onColumnOrderChange = React.useCallback(
    (updater: Updater<ColumnOrderState>) => {
      const currentState = store.getState();
      const newColumnOrder =
        typeof updater === "function"
          ? updater(
              propsRef.current.state?.columnOrder ?? currentState.columnOrder,
            )
          : updater;

      store.setState("columnOrder", newColumnOrder);
      propsRef.current.onColumnOrderChange?.(newColumnOrder);
    },
    [store, propsRef],
  );

  const onColumnFiltersChange = React.useCallback(
    (updater: Updater<ColumnFiltersState>) => {
      const currentState = store.getState();
      const newColumnFilters =
        typeof updater === "function"
          ? updater(currentState.columnFilters)
          : updater;

      store.batch(() => {
        store.setState("columnFilters", newColumnFilters);
      });

      propsRef.current.onColumnFiltersChange?.(newColumnFilters);
    },
    [store, propsRef],
  );

  const onRowSelectionChange = React.useCallback(
    (updater: Updater<RowSelectionState>) => {
      const currentState = store.getState();
      const newRowSelection =
        typeof updater === "function"
          ? updater(currentState.rowSelection)
          : updater;

      const selectedRows = Object.keys(newRowSelection).filter(
        (key) => newRowSelection[key],
      );

      const selectedCells = new Set<string>();
      const rowsById = tableRef.current?.getRowModel().rowsById ?? {};

      for (const rowId of selectedRows) {
        if (!rowsById[rowId]) continue;

        for (const columnId of navigableColumnIds) {
          selectedCells.add(getCellKey(rowId, columnId));
        }
      }

      const isUtilityCellFocused =
        currentState.focusedCell !== null &&
        !getIsDataColumn(currentState.focusedCell.columnId);

      store.batch(() => {
        store.setState("rowSelection", newRowSelection);
        store.setState("selectionState", {
          selectedCells,
          selectionRange: null,
          isSelecting: false,
        });
        if (!isUtilityCellFocused) {
          store.setState("focusedCell", null);
          tableRef.current?.setEditingCell(null);
        }
      });

      propsRef.current.onRowSelectionChange?.(updater);
    },
    [store, navigableColumnIds, propsRef],
  );

  const onRowSelect = React.useCallback(
    (rowId: string, selected: boolean, shiftKey: boolean) => {
      const currentState = store.getState();
      const rows = tableRef.current?.getRowModel().rows ?? [];
      const currentRowIndex = rows.findIndex((r) => r.id === rowId);
      const currentRow = currentRowIndex >= 0 ? rows[currentRowIndex] : null;
      if (!currentRow) return;

      if (shiftKey && currentState.lastClickedRowId !== null) {
        const lastClickedRowIndex = rows.findIndex(
          (r) => r.id === currentState.lastClickedRowId,
        );
        if (lastClickedRowIndex >= 0) {
          const startIndex = Math.min(lastClickedRowIndex, currentRowIndex);
          const endIndex = Math.max(lastClickedRowIndex, currentRowIndex);

          const newRowSelection: RowSelectionState = {
            ...currentState.rowSelection,
          };

          for (let i = startIndex; i <= endIndex; i++) {
            const row = rows[i];
            if (row) {
              setRowSelected(newRowSelection, row.id, selected);
            }
          }

          onRowSelectionChange(newRowSelection);
        } else {
          const newRowSelection: RowSelectionState = {
            ...currentState.rowSelection,
          };
          setRowSelected(newRowSelection, currentRow.id, selected);
          onRowSelectionChange(newRowSelection);
        }
      } else {
        const newRowSelection: RowSelectionState = {
          ...currentState.rowSelection,
        };
        setRowSelected(newRowSelection, currentRow.id, selected);
        onRowSelectionChange(newRowSelection);
      }

      store.setState("lastClickedRowId", rowId);

      const focusedCell = store.getState().focusedCell;
      if (focusedCell && !getIsDataColumn(focusedCell.columnId)) {
        store.setState("focusedCell", { rowId, columnId: "select" });
      }
    },
    [store, onRowSelectionChange],
  );

  const onColumnClick = React.useCallback(
    (columnId: string) => {
      if (!propsRef.current.enableColumnSelection) {
        onSelectionClear();
        return;
      }

      selectColumn(columnId);
    },
    [propsRef, selectColumn, onSelectionClear],
  );

  const onPasteDialogOpenChange = React.useCallback(
    (open: boolean) => {
      if (!open) {
        store.setState("pasteDialog", {
          open: false,
          rowsNeeded: 0,
          clipboardText: "",
        });
      }
    },
    [store],
  );

  const defaultColumn: Partial<ColumnDef<DataGridFeatures, TData>> =
    React.useMemo(
      () => ({
        // Cell is rendered directly in DataGridRow to bypass flexRender's
        // unstable cell.getContext() (https://github.com/TanStack/table/issues/4794)
        minSize: MIN_COLUMN_SIZE,
        maxSize: MAX_COLUMN_SIZE,
      }),
      [],
    );

  const tableMeta = React.useMemo<DataGridTableMeta>(() => {
    return {
      ...propsRef.current.meta,
      dataGridRef,
      cellMapRef,
      get focusedCell() {
        return store.getState().focusedCell;
      },
      get editingCell() {
        return tableRef.current?.getEditingCell() ?? null;
      },
      get selectionState() {
        return store.getState().selectionState;
      },
      get searchOpen() {
        return store.getState().searchOpen;
      },
      get contextMenu() {
        return store.getState().contextMenu;
      },
      get pasteDialog() {
        return store.getState().pasteDialog;
      },
      get readOnly() {
        return propsRef.current.readOnly;
      },
      getIsCellSelected,
      getIsSearchMatch,
      getIsActiveSearchMatch,
      scrollToCell: (rowId, columnId, align = "auto") => {
        const container = dataGridRef.current;
        const rowIndex = getRowIndex(rowId);
        if (!container || rowIndex === -1) return;

        rowVirtualizerRef.current?.scrollToIndex(rowIndex, { align });

        const scrollRowIntoView = (retries = 1) => {
          requestAnimationFrame(() => {
            const targetRow = rowMapRef.current.get(rowIndex);
            if (!targetRow) {
              if (retries > 0) scrollRowIntoView(retries - 1);
              return;
            }

            const headerBottom =
              headerRef.current?.getBoundingClientRect().bottom ??
              container.getBoundingClientRect().top;

            const viewportTop = headerBottom + VIEWPORT_OFFSET;

            const rowRect = targetRow.getBoundingClientRect();

            if (rowRect.top < viewportTop) {
              container.scrollTop -= viewportTop - rowRect.top;
            }

            const cellKey = getCellKey(rowId, columnId);
            const targetCell = cellMapRef.current.get(cellKey);
            if (targetCell) {
              scrollCellIntoView({
                container,
                targetCell,
                tableRef,
                viewportOffset: VIEWPORT_OFFSET,
                isRtl: dir === "rtl",
              });
            }
          });
        };

        scrollRowIntoView();
      },
      onRowSelect,
      onDataUpdate,
      onRowsDelete: propsRef.current.onRowsDelete ? onRowsDelete : undefined,
      onColumnClick,
      onCellClick,
      onCellDoubleClick,
      onCellMouseDown,
      onCellMouseEnter,
      onCellMouseUp,
      onCellContextMenu,
      onCellEditingStart,
      onCellEditingStop,
      onCellsCopy,
      onCellsCut,
      onCellsPaste,
      onSelectionClear,
      onFilesUpload: propsRef.current.onFilesUpload
        ? propsRef.current.onFilesUpload
        : undefined,
      onFilesDelete: propsRef.current.onFilesDelete
        ? propsRef.current.onFilesDelete
        : undefined,
      onContextMenuOpenChange,
      onPasteDialogOpenChange,
    };
  }, [
    propsRef,
    store,
    dir,
    getIsCellSelected,
    getIsSearchMatch,
    getIsActiveSearchMatch,
    getRowIndex,
    onRowSelect,
    onDataUpdate,
    onRowsDelete,
    onColumnClick,
    onCellClick,
    onCellDoubleClick,
    onCellMouseDown,
    onCellMouseEnter,
    onCellMouseUp,
    onCellContextMenu,
    onCellEditingStart,
    onCellEditingStop,
    onCellsCopy,
    onCellsCut,
    onCellsPaste,
    onSelectionClear,
    onContextMenuOpenChange,
    onPasteDialogOpenChange,
  ]);

  // Memoize state object to reduce shallow equality checks
  const tableState = React.useMemo<Partial<TableState<DataGridFeatures>>>(
    () => ({
      ...props.state,
      sorting,
      columnFilters,
      columnVisibility,
      columnPinning,
      columnOrder,
      rowSelection,
    }),
    [
      sorting,
      columnFilters,
      columnVisibility,
      columnPinning,
      columnOrder,
      rowSelection,
      props.state?.rowHeight,
      props.state?.editingCell,
    ],
  );

  const enableCellEditing =
    !props.readOnly && props.enableCellEditing !== false;
  const hasEditingCellChange = !!props.onEditingCellChange;
  const hasRowHeightChange = !!props.onRowHeightChange;

  const tableOptions = React.useMemo<
    TableOptions<DataGridFeatures, TData>
  >(() => {
    const {
      onEditingCellChange: _onEditingCellChange,
      onRowHeightChange: _onRowHeightChange,
      ...tableProps
    } = propsRef.current;

    return {
      ...tableProps,
      ...(hasEditingCellChange && {
        onEditingCellChange: (updater) =>
          propsRef.current.onEditingCellChange?.(updater),
      }),
      ...(hasRowHeightChange && {
        onRowHeightChange: (updater) =>
          propsRef.current.onRowHeightChange?.(updater),
      }),
      features: dataGridFeatures,
      data,
      columns,
      defaultColumn,
      initialState: propsRef.current.initialState,
      state: tableState,
      enableCellEditing,
      onRowSelectionChange,
      onSortingChange,
      onColumnFiltersChange,
      onColumnVisibilityChange,
      onColumnPinningChange,
      onColumnOrderChange,
      columnResizeMode: "onChange",
      columnResizeDirection: dir,
      meta: tableMeta,
    };
  }, [
    propsRef,
    data,
    columns,
    defaultColumn,
    tableState,
    enableCellEditing,
    hasEditingCellChange,
    hasRowHeightChange,
    dir,
    onRowSelectionChange,
    onSortingChange,
    onColumnFiltersChange,
    onColumnVisibilityChange,
    onColumnPinningChange,
    onColumnOrderChange,
    tableMeta,
  ]);

  const table = useTable(tableOptions);

  if (!tableRef.current) {
    tableRef.current = table;
  }

  const rowHeight = table.state.rowHeight;
  const editingCell = table.state.editingCell;
  const readOnlyColumnIds = React.useMemo(
    () =>
      new Set(
        table
          .getAllLeafColumns()
          .filter((column) => !column.getCanEdit())
          .map((column) => column.id),
      ),
    [table, columns, enableCellEditing],
  );
  const rowSize = table.getRowSize();

  const dragDepsRef = useValueAsRef({
    selectRange,
    dir,
    rowSize,
    columnIds,
  });

  const columnSizeVars = React.useMemo(() => {
    const headers = table.getFlatHeaders();
    const colSizes: { [key: string]: number } = {};
    for (const header of headers) {
      colSizes[`--header-${header.id}-size`] = header.getSize();
      colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
    }
    return colSizes;
  }, [table.state.columnResizing, table.state.columnSizing]);

  const isFirefox = React.useSyncExternalStore(
    React.useCallback(() => () => {}, []),
    React.useCallback(() => {
      if (typeof window === "undefined" || typeof navigator === "undefined") {
        return false;
      }
      return navigator.userAgent.indexOf("Firefox") !== -1;
    }, []),
    React.useCallback(() => false, []),
  );

  const adjustLayout = React.useMemo(() => {
    const columnPinning = table.state.columnPinning;
    return (
      isFirefox &&
      ((columnPinning.start?.length ?? 0) > 0 ||
        (columnPinning.end?.length ?? 0) > 0)
    );
  }, [isFirefox, table.state.columnPinning]);

  const rowVirtualizer = useVirtualizer({
    count: table.getRowModel().rows.length,
    getScrollElement: () => dataGridRef.current,
    estimateSize: () => rowSize,
    overscan,
    measureElement: !isFirefox
      ? (element) => element?.getBoundingClientRect().height
      : undefined,
    scrollPaddingStart:
      (headerRef.current?.getBoundingClientRect().bottom ?? 0) -
      (dataGridRef.current?.getBoundingClientRect().top ?? 0) +
      VIEWPORT_OFFSET,
    // Add extra row buffer to absorb virtual position drift after render measurements
    scrollPaddingEnd:
      (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) -
      (footerRef.current?.getBoundingClientRect().top ??
        dataGridRef.current?.getBoundingClientRect().bottom ??
        0) +
      rowSize +
      VIEWPORT_OFFSET,
  });

  if (!rowVirtualizerRef.current) {
    rowVirtualizerRef.current = rowVirtualizer;
  }

  const onScrollToRow = React.useCallback(
    async (opts: Partial<CellPosition> & { rowIndex?: number }) => {
      const { rowId, columnId } = opts;

      focusGuardRef.current = true;

      const targetColumnId = columnId ?? navigableColumnIds[0];

      if (!targetColumnId) {
        releaseFocusGuard(true);
        return;
      }

      async function onScrollAndFocus(retryCount: number) {
        if (!targetColumnId) return;
        const rows = tableRef.current?.getRowModel().rows ?? [];
        const rowIndex =
          rowId !== undefined ? getRowIndex(rowId) : (opts.rowIndex ?? 0);

        // If the requested row doesn't exist yet, wait for data to update
        if ((rowIndex === -1 || rowIndex >= rows.length) && retryCount > 0) {
          await new Promise((resolve) => setTimeout(resolve, 50));
          await onScrollAndFocus(retryCount - 1);
          return;
        }

        const safeRowIndex =
          rowIndex === -1
            ? rows.length - 1
            : Math.min(rowIndex, rows.length - 1);
        const safeRowId = rows[safeRowIndex]?.id;

        if (!safeRowId) {
          releaseFocusGuard();
          return;
        }

        const currentRowCount = rows.length;

        const isBottomHalf = safeRowIndex > currentRowCount / 2;
        rowVirtualizer.scrollToIndex(safeRowIndex, {
          align: isBottomHalf ? "end" : "start",
        });

        await new Promise((resolve) => requestAnimationFrame(resolve));

        // Adjust scroll position to account for sticky header/footer
        const container = dataGridRef.current;
        const targetRow = rowMapRef.current.get(safeRowIndex);

        if (container && targetRow) {
          const containerRect = container.getBoundingClientRect();
          const headerHeight =
            headerRef.current?.getBoundingClientRect().height ?? 0;
          const footerHeight =
            footerRef.current?.getBoundingClientRect().height ?? 0;

          const viewportTop =
            containerRect.top + headerHeight + VIEWPORT_OFFSET;
          const viewportBottom =
            containerRect.bottom - footerHeight - VIEWPORT_OFFSET;

          const rowRect = targetRow.getBoundingClientRect();
          const isFullyVisible =
            rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;

          if (!isFullyVisible) {
            if (rowRect.top < viewportTop) {
              // Scroll up as row is partially hidden by header
              container.scrollTop -= viewportTop - rowRect.top;
            } else if (rowRect.bottom > viewportBottom) {
              // Scroll down as row is partially hidden by footer
              container.scrollTop += rowRect.bottom - viewportBottom;
            }
          }
        }

        store.batch(() => {
          store.setState("focusedCell", {
            rowId: safeRowId,
            columnId: targetColumnId,
          });
          tableRef.current?.setEditingCell(null);
        });

        const cellKey = getCellKey(safeRowId, targetColumnId);
        const cellElement = cellMapRef.current.get(cellKey);

        if (cellElement) {
          getCellFocusTarget(cellElement).focus();
          releaseFocusGuard();
        } else if (retryCount > 0) {
          await new Promise((resolve) => requestAnimationFrame(resolve));
          await onScrollAndFocus(retryCount - 1);
        } else {
          dataGridRef.current?.focus();
          releaseFocusGuard();
        }
      }

      await onScrollAndFocus(SCROLL_SYNC_RETRY_COUNT);
    },
    [rowVirtualizer, store, releaseFocusGuard, navigableColumnIds, getRowIndex],
  );

  const onRowAdd = React.useCallback(
    async (event?: React.MouseEvent<HTMLDivElement>) => {
      if (propsRef.current.readOnly || !propsRef.current.onRowAdd) return;

      const initialRowCount = tableRef.current?.getRowModel().rows.length ?? 0;

      let result: Partial<CellPosition> | null;
      try {
        result = await propsRef.current.onRowAdd(event);
      } catch {
        // Callback threw an error, don't proceed with scroll/focus
        return;
      }

      if (result === null || event?.defaultPrevented) return;

      onSelectionClear();

      // onScrollToRow will handle retries if the row isn't rendered yet
      void onScrollToRow({
        rowId: result.rowId,
        rowIndex: initialRowCount,
        columnId: result.columnId,
      });
    },
    [propsRef, onScrollToRow, onSelectionClear],
  );

  const getColumnHeaderTrigger = React.useCallback((columnId: string) => {
    return (
      headerRef.current?.querySelector<HTMLElement>(
        `[data-slot="grid-header-cell"][data-column-id="${CSS.escape(columnId)}"] :is(button, [role="checkbox"])`,
      ) ?? null
    );
  }, []);

  const focusColumnHeader = React.useCallback(
    (columnId: string) => {
      const trigger = getColumnHeaderTrigger(columnId);
      if (!trigger) return false;

      const currentState = store.getState();
      if (
        currentState.selectionState.selectedCells.size > 0 &&
        Object.keys(currentState.rowSelection).length === 0
      ) {
        onSelectionClear();
      }
      store.batch(() => {
        store.setState("focusedCell", null);
        tableRef.current?.setEditingCell(null);
      });
      trigger.focus();
      return true;
    },
    [store, getColumnHeaderTrigger, onSelectionClear],
  );

  const onGridTabBackOut = React.useCallback(() => {
    const container = dataGridRef.current;
    if (!container) return;

    // The container precedes every cell in the tab order, so it has to drop out
    // until the browser finishes moving focus or Shift+Tab would land on it.
    container.tabIndex = -1;
    requestAnimationFrame(() => {
      container.tabIndex = 0;
    });
  }, []);

  const onColumnHeaderKeyDown = React.useCallback(
    (event: KeyboardEvent, columnId: string) => {
      const { key, shiftKey, ctrlKey, metaKey, altKey } = event;
      if (ctrlKey || metaKey || altKey) return false;

      const headerColumnIds = columnIds.filter((id) =>
        getColumnHeaderTrigger(id),
      );
      const headerIndex = headerColumnIds.indexOf(columnId);
      if (headerIndex === -1) return false;

      const rowCount = tableRef.current?.getRowModel().rows.length ?? 0;
      const isRtl = dir === "rtl";

      function focusHeaderAt(index: number) {
        const targetColumnId = headerColumnIds[index];
        if (targetColumnId) focusColumnHeader(targetColumnId);
      }

      switch (key) {
        case "ArrowLeft":
        case "ArrowRight": {
          const isNext = (key === "ArrowRight") !== isRtl;
          focusHeaderAt(headerIndex + (isNext ? 1 : -1));
          break;
        }
        case "Home":
          focusHeaderAt(0);
          break;
        case "End":
          focusHeaderAt(headerColumnIds.length - 1);
          break;
        case "ArrowUp":
          break;
        case "ArrowDown":
          // Stops the menu trigger from opening its menu on ArrowDown
          event.stopPropagation();
          if (rowCount > 0) void onScrollToRow({ rowIndex: 0, columnId });
          break;
        case "Escape":
          dataGridRef.current?.focus();
          break;
        case "Tab":
          if (shiftKey) {
            if (headerIndex === 0) {
              onGridTabBackOut();
              return true;
            }
            focusHeaderAt(headerIndex - 1);
          } else if (headerIndex < headerColumnIds.length - 1) {
            focusHeaderAt(headerIndex + 1);
          } else {
            const firstColumnId = navigableColumnIds[0];
            if (rowCount === 0 || !firstColumnId) return true;
            void onScrollToRow({ rowIndex: 0, columnId: firstColumnId });
          }
          break;
        default:
          return false;
      }

      event.preventDefault();
      return true;
    },
    [
      dir,
      columnIds,
      navigableColumnIds,
      getColumnHeaderTrigger,
      focusColumnHeader,
      onGridTabBackOut,
      onScrollToRow,
    ],
  );

  const onDataGridKeyDown = React.useCallback(
    (event: KeyboardEvent) => {
      const currentState = store.getState();
      const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
      const isCtrlPressed = ctrlKey || metaKey;

      if (
        propsRef.current.enableSearch &&
        isCtrlPressed &&
        !shiftKey &&
        key === SEARCH_SHORTCUT_KEY
      ) {
        event.preventDefault();
        onSearchOpenChange(true);
        return;
      }

      if (
        propsRef.current.enableSearch &&
        currentState.searchOpen &&
        !tableRef.current?.getEditingCell()
      ) {
        if (key === "Enter") {
          event.preventDefault();
          if (shiftKey) {
            onNavigateToPrevMatch();
          } else {
            onNavigateToNextMatch();
          }
          return;
        }
        if (key === "Escape") {
          event.preventDefault();
          onSearchOpenChange(false);
          return;
        }
        return;
      }

      // Cell editing keyboard events (Enter, Tab, Escape) are handled by the cell variants
      // to ensure proper value commitment before navigation
      if (tableRef.current?.getEditingCell()) return;

      const headerCell =
        event.target instanceof Element
          ? event.target.closest<HTMLElement>('[data-slot="grid-header-cell"]')
          : null;
      const headerColumnId = headerCell?.dataset.columnId;
      if (headerColumnId && onColumnHeaderKeyDown(event, headerColumnId)) {
        return;
      }

      if (
        isCtrlPressed &&
        (key === "Backspace" || key === "Delete") &&
        !propsRef.current.readOnly &&
        propsRef.current.onRowsDelete
      ) {
        const rowIds = new Set<string>();

        if (Object.keys(currentState.rowSelection).length > 0) {
          const rows = tableRef.current?.getRowModel().rows ?? [];
          for (const row of rows) {
            if (currentState.rowSelection[row.id]) {
              rowIds.add(row.id);
            }
          }
        } else if (currentState.selectionState.selectedCells.size > 0) {
          for (const cellKey of currentState.selectionState.selectedCells) {
            rowIds.add(parseCellKey(cellKey).rowId);
          }
        } else if (currentState.focusedCell) {
          rowIds.add(currentState.focusedCell.rowId);
        }

        if (rowIds.size > 0) {
          event.preventDefault();
          void onRowsDelete(Array.from(rowIds));
        }
        return;
      }

      if (!currentState.focusedCell) return;

      let direction: NavigationDirection | null = null;

      const isUtilityCellFocused = !getIsDataColumn(
        currentState.focusedCell.columnId,
      );

      if (
        isUtilityCellFocused &&
        shiftKey &&
        (key.startsWith("Arrow") ||
          key === "Home" ||
          key === "End" ||
          key === "PageUp" ||
          key === "PageDown")
      ) {
        event.preventDefault();
        return;
      }

      if (isCtrlPressed && !shiftKey && key === "a") {
        event.preventDefault();
        selectAll();
        return;
      }

      if (isCtrlPressed && !shiftKey && key === "c" && !isUtilityCellFocused) {
        event.preventDefault();
        void onCellsCopy();
        return;
      }

      if (
        isCtrlPressed &&
        !shiftKey &&
        key === "x" &&
        !isUtilityCellFocused &&
        !propsRef.current.readOnly
      ) {
        event.preventDefault();
        void onCellsCut();
        return;
      }

      if (
        propsRef.current.enablePaste &&
        isCtrlPressed &&
        !shiftKey &&
        key === "v" &&
        !isUtilityCellFocused &&
        !propsRef.current.readOnly
      ) {
        event.preventDefault();
        void onCellsPaste();
        return;
      }

      if (
        (key === "Delete" || key === "Backspace") &&
        !isCtrlPressed &&
        !isUtilityCellFocused &&
        !propsRef.current.readOnly
      ) {
        const cellsToClear =
          currentState.selectionState.selectedCells.size > 0
            ? Array.from(currentState.selectionState.selectedCells)
            : currentState.focusedCell
              ? [
                  getCellKey(
                    currentState.focusedCell.rowId,
                    currentState.focusedCell.columnId,
                  ),
                ]
              : [];

        if (cellsToClear.length > 0) {
          event.preventDefault();

          const updates: Array<CellUpdate> = [];

          const currentTable = tableRef.current;
          const tableColumns = currentTable?.getAllColumns() ?? [];
          const columnById = new Map(tableColumns.map((c) => [c.id, c]));

          for (const cellKey of cellsToClear) {
            const { rowId, columnId } = parseCellKey(cellKey);
            const column = columnById.get(columnId);
            const cellVariant = column?.columnDef?.meta?.cell?.variant;
            const emptyValue = getEmptyCellValue(cellVariant);
            updates.push({ rowId, columnId, value: emptyValue });
          }

          onDataUpdate(updates);

          if (currentState.selectionState.selectedCells.size > 0) {
            onSelectionClear();
          }

          if (currentState.cutCells.length > 0) {
            store.setState("cutCells", []);
          }
        }
        return;
      }

      if (
        key === "Enter" &&
        shiftKey &&
        !propsRef.current.readOnly &&
        propsRef.current.onRowAdd
      ) {
        event.preventDefault();
        const initialRowCount =
          tableRef.current?.getRowModel().rows.length ?? 0;
        const currentColumnId = isUtilityCellFocused
          ? navigableColumnIds[0]
          : currentState.focusedCell.columnId;

        Promise.resolve(propsRef.current.onRowAdd())
          .then(async (result) => {
            if (result === null) return;

            onSelectionClear();

            void onScrollToRow({
              rowId: result.rowId,
              rowIndex: initialRowCount,
              columnId: result.columnId ?? currentColumnId,
            });
          })
          .catch(() => {
            // Callback threw an error, don't proceed with scroll/focus
          });
        return;
      }

      switch (key) {
        case "ArrowUp":
          if (altKey && !isCtrlPressed && !shiftKey) {
            direction = "pageup";
          } else if (isCtrlPressed && shiftKey) {
            const selectionEdge =
              currentState.selectionState.selectionRange?.end ||
              currentState.focusedCell;
            const currentColIndex = navigableColumnIds.indexOf(
              selectionEdge.columnId,
            );
            const selectionStart =
              currentState.selectionState.selectionRange?.start ||
              currentState.focusedCell;
            const firstRowId = getRowIdAt(0);

            if (firstRowId) {
              selectRange(selectionStart, {
                rowId: firstRowId,
                columnId:
                  navigableColumnIds[currentColIndex] ?? selectionEdge.columnId,
              });
            }

            const rowVirtualizer = rowVirtualizerRef.current;
            if (rowVirtualizer) {
              rowVirtualizer.scrollToIndex(0, { align: "start" });
            }

            restoreFocus(dataGridRef.current);

            event.preventDefault();
            return;
          } else if (isCtrlPressed && !shiftKey) {
            direction = "ctrl+up";
          } else if (
            !shiftKey &&
            getRowIndex(currentState.focusedCell.rowId) === 0 &&
            focusColumnHeader(currentState.focusedCell.columnId)
          ) {
            event.preventDefault();
            return;
          } else {
            direction = "up";
          }
          break;
        case "ArrowDown":
          if (altKey && !isCtrlPressed && !shiftKey) {
            direction = "pagedown";
          } else if (isCtrlPressed && shiftKey) {
            const rowCount = tableRef.current?.getRowModel().rows.length ?? 0;
            const selectionEdge =
              currentState.selectionState.selectionRange?.end ||
              currentState.focusedCell;
            const currentColIndex = navigableColumnIds.indexOf(
              selectionEdge.columnId,
            );
            const selectionStart =
              currentState.selectionState.selectionRange?.start ||
              currentState.focusedCell;
            const lastRowId = getRowIdAt(rowCount - 1);

            if (lastRowId) {
              selectRange(selectionStart, {
                rowId: lastRowId,
                columnId:
                  navigableColumnIds[currentColIndex] ?? selectionEdge.columnId,
              });
            }

            const rowVirtualizer = rowVirtualizerRef.current;
            if (rowVirtualizer) {
              rowVirtualizer.scrollToIndex(Math.max(0, rowCount - 1), {
                align: "end",
              });
            }

            restoreFocus(dataGridRef.current);

            event.preventDefault();
            return;
          } else if (isCtrlPressed && !shiftKey) {
            direction = "ctrl+down";
          } else {
            direction = "down";
          }
          break;
        case "ArrowLeft":
          if (isCtrlPressed && shiftKey) {
            const selectionEdge =
              currentState.selectionState.selectionRange?.end ||
              currentState.focusedCell;
            const selectionStart =
              currentState.selectionState.selectionRange?.start ||
              currentState.focusedCell;
            const targetColumnId =
              dir === "rtl"
                ? navigableColumnIds[navigableColumnIds.length - 1]
                : navigableColumnIds[0];

            if (targetColumnId) {
              selectRange(selectionStart, {
                rowId: selectionEdge.rowId,
                columnId: targetColumnId,
              });

              const container = dataGridRef.current;
              const cellKey = getCellKey(selectionEdge.rowId, targetColumnId);
              const targetCell = cellMapRef.current.get(cellKey);
              if (container && targetCell) {
                scrollCellIntoView({
                  container,
                  targetCell,
                  tableRef,
                  viewportOffset: VIEWPORT_OFFSET,
                  direction: "home",
                  isRtl: dir === "rtl",
                });
              }

              restoreFocus(container);
            }
            event.preventDefault();
            return;
          } else if (isCtrlPressed && !shiftKey) {
            direction = "home";
          } else {
            direction = "left";
          }
          break;
        case "ArrowRight":
          if (isCtrlPressed && shiftKey) {
            const selectionEdge =
              currentState.selectionState.selectionRange?.end ||
              currentState.focusedCell;
            const selectionStart =
              currentState.selectionState.selectionRange?.start ||
              currentState.focusedCell;
            const targetColumnId =
              dir === "rtl"
                ? navigableColumnIds[0]
                : navigableColumnIds[navigableColumnIds.length - 1];

            if (targetColumnId) {
              selectRange(selectionStart, {
                rowId: selectionEdge.rowId,
                columnId: targetColumnId,
              });

              const container = dataGridRef.current;
              const cellKey = getCellKey(selectionEdge.rowId, targetColumnId);
              const targetCell = cellMapRef.current.get(cellKey);
              if (container && targetCell) {
                scrollCellIntoView({
                  container,
                  targetCell,
                  tableRef,
                  viewportOffset: VIEWPORT_OFFSET,
                  direction: "end",
                  isRtl: dir === "rtl",
                });
              }

              restoreFocus(container);
            }
            event.preventDefault();
            return;
          } else if (isCtrlPressed && !shiftKey) {
            direction = "end";
          } else {
            direction = "right";
          }
          break;
        case "Home":
          direction = isCtrlPressed ? "ctrl+home" : "home";
          break;
        case "End":
          direction = isCtrlPressed ? "ctrl+end" : "end";
          break;
        case "PageUp":
          direction = altKey ? "pageleft" : "pageup";
          break;
        case "PageDown":
          direction = altKey ? "pageright" : "pagedown";
          break;
        case "Escape":
          event.preventDefault();
          if (
            currentState.selectionState.selectedCells.size > 0 ||
            Object.keys(currentState.rowSelection).length > 0
          ) {
            onSelectionClear();
          } else {
            blurCell();
            dataGridRef.current?.focus();
          }
          return;
        case "Tab": {
          if (isCtrlPressed || altKey) return;

          const tabTarget = getTabTargetCell({
            rowIndex: getRowIndex(currentState.focusedCell.rowId),
            columnId: currentState.focusedCell.columnId,
            columnIds,
            rowCount: tableRef.current?.getRowModel().rows.length ?? 0,
            isBackward: shiftKey,
            getIsColumnTabbable: getIsDataColumn,
          });

          if (!tabTarget) {
            if (shiftKey) onGridTabBackOut();
            return;
          }

          direction = shiftKey ? "shift+tab" : "tab";
          break;
        }
      }

      if (direction) {
        event.preventDefault();

        if (shiftKey && key !== "Tab" && currentState.focusedCell) {
          const selectionEdge =
            currentState.selectionState.selectionRange?.end ||
            currentState.focusedCell;

          const currentColIndex = navigableColumnIds.indexOf(
            selectionEdge.columnId,
          );
          const edgeRowIndex = getRowIndex(selectionEdge.rowId);
          if (edgeRowIndex === -1) return;

          let newRowIndex = edgeRowIndex;
          let newColumnId = selectionEdge.columnId;

          const isRtl = dir === "rtl";

          const rowCount = tableRef.current?.getRowModel().rows.length ?? 0;

          switch (direction) {
            case "up":
              newRowIndex = Math.max(0, edgeRowIndex - 1);
              break;
            case "down":
              newRowIndex = Math.min(rowCount - 1, edgeRowIndex + 1);
              break;
            case "left":
     
[truncated — showing 100000 of 124658 characters]
src/hooks/use-debounced-callback.ts
import * as React from "react";

export function useDebouncedCallback<T extends (...args: never[]) => unknown>(
  callback: T,
  delay: number,
) {
  const callbackRef = React.useRef(callback);
  callbackRef.current = callback;

  const debounceTimerRef = React.useRef(0);

  React.useEffect(
    () => () => window.clearTimeout(debounceTimerRef.current),
    [],
  );

  return React.useCallback(
    (...args: Parameters<T>) => {
      window.clearTimeout(debounceTimerRef.current);
      debounceTimerRef.current = window.setTimeout(
        () => callbackRef.current(...args),
        delay,
      );
    },
    [delay],
  );
}
src/hooks/use-isomorphic-layout-effect.ts
import * as React from "react";

const useIsomorphicLayoutEffect =
  typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;

export { useIsomorphicLayoutEffect };
src/hooks/use-ref-with-init.ts
import * as React from "react";

const UNINITIALIZED = {};

function useRefWithInit<T>(init: () => T): React.RefObject<T> {
  const ref = React.useRef<T | typeof UNINITIALIZED>(UNINITIALIZED);

  if (ref.current === UNINITIALIZED) {
    ref.current = init();
  }

  return ref as React.RefObject<T>;
}

export { useRefWithInit };
src/lib/data-grid-utils.ts
import type {
  Column,
  ColumnOrderState,
  ColumnPinningState,
  ColumnVisibilityState,
  RowData,
  Table,
} from "@tanstack/react-table";
import type * as React from "react";

import {
  BaselineIcon,
  CalendarIcon,
  CheckSquareIcon,
  File,
  FileArchive,
  FileAudio,
  FileIcon,
  FileImage,
  FileSpreadsheet,
  FileText,
  FileVideo,
  HashIcon,
  LinkIcon,
  ListChecksIcon,
  ListIcon,
  Presentation,
  TextInitialIcon,
} from "lucide-react";

import type { DataGridFeatures } from "@/lib/data-grid-features";
import type {
  CellOpts,
  CellPosition,
  Direction,
  FileCellData,
  RowHeightValue,
} from "@/lib/data-grid-types";

export function stringifyUnknown(value: unknown): string {
  if (value == null) return "";
  if (typeof value === "string") return value;
  if (typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }
  if (value instanceof Date) return value.toISOString();
  try {
    return JSON.stringify(value) ?? "";
  } catch {
    return "";
  }
}

export function flexRender<TProps extends object>(
  Comp: ((props: TProps) => React.ReactNode) | string | undefined,
  props: TProps,
): React.ReactNode {
  if (typeof Comp === "string") {
    return Comp;
  }
  return Comp?.(props);
}

export function getIsFileCellData(item: unknown): item is FileCellData {
  return (
    !!item &&
    typeof item === "object" &&
    "id" in item &&
    "name" in item &&
    "size" in item &&
    "type" in item
  );
}

export function matchSelectOption(
  value: string,
  options: { value: string; label: string }[],
): string | undefined {
  return options.find(
    (o) =>
      o.value === value ||
      o.value.toLowerCase() === value.toLowerCase() ||
      o.label.toLowerCase() === value.toLowerCase(),
  )?.value;
}

// Unit separator, so row ids and column ids may contain any printable character
const CELL_KEY_SEPARATOR = "\u001f";

export function getCellKey(rowId: string, columnId: string) {
  return `${rowId}${CELL_KEY_SEPARATOR}${columnId}`;
}

export function parseCellKey(cellKey: string): CellPosition {
  const separatorIndex = cellKey.indexOf(CELL_KEY_SEPARATOR);
  if (separatorIndex === -1) return { rowId: "", columnId: "" };
  return {
    rowId: cellKey.slice(0, separatorIndex),
    columnId: cellKey.slice(separatorIndex + CELL_KEY_SEPARATOR.length),
  };
}

/** Position of a row in the rendered row model, or `-1` if it isn't rendered. */
export function getRowIndexById<TData extends RowData>(
  table: Table<DataGridFeatures, TData>,
  rowId: string,
) {
  return table.getRowModel().rowsById[rowId]?.getDisplayIndex() ?? -1;
}

export function getRowHeightValue(rowHeight: RowHeightValue): number {
  const rowHeightMap: Record<RowHeightValue, number> = {
    short: 36,
    medium: 56,
    tall: 76,
    "extra-tall": 96,
  };

  return rowHeightMap[rowHeight];
}

export function getLineCount(rowHeight: RowHeightValue): number {
  const lineCountMap: Record<RowHeightValue, number> = {
    short: 1,
    medium: 2,
    tall: 3,
    "extra-tall": 4,
  };

  return lineCountMap[rowHeight];
}

export function getColumnBorderVisibility<TData extends RowData>(params: {
  column: Column<DataGridFeatures, TData>;
  nextColumn?: Column<DataGridFeatures, TData>;
  isLastColumn: boolean;
}): {
  showEndBorder: boolean;
  showStartBorder: boolean;
} {
  const { column, nextColumn, isLastColumn } = params;

  const isPinned = column.getIsPinned();
  const isFirstRightPinnedColumn =
    isPinned === "end" && column.getIsFirstColumn("end");
  const isLastRightPinnedColumn =
    isPinned === "end" && column.getIsLastColumn("end");

  const nextIsPinned = nextColumn?.getIsPinned();
  const isBeforeRightPinned =
    nextIsPinned === "end" && nextColumn?.getIsFirstColumn("end");

  const showEndBorder =
    !isBeforeRightPinned && (isLastColumn || !isLastRightPinnedColumn);

  const showStartBorder = isFirstRightPinnedColumn;

  return {
    showEndBorder,
    showStartBorder,
  };
}

export function getColumnPinningStyle<TData extends RowData>(params: {
  column: Column<DataGridFeatures, TData>;
  withBorder?: boolean;
  dir?: Direction;
}): React.CSSProperties {
  const { column, dir = "ltr", withBorder = false } = params;

  const isPinned = column.getIsPinned();
  const isLastLeftPinnedColumn =
    isPinned === "start" && column.getIsLastColumn("start");
  const isFirstRightPinnedColumn =
    isPinned === "end" && column.getIsFirstColumn("end");

  const isRtl = dir === "rtl";

  const leftPosition =
    isPinned === "start" ? `${column.getStart("start")}px` : undefined;
  const rightPosition =
    isPinned === "end" ? `${column.getAfter("end")}px` : undefined;

  return {
    boxShadow: withBorder
      ? isLastLeftPinnedColumn
        ? isRtl
          ? "4px 0 4px -4px var(--border) inset"
          : "-4px 0 4px -4px var(--border) inset"
        : isFirstRightPinnedColumn
          ? isRtl
            ? "-4px 0 4px -4px var(--border) inset"
            : "4px 0 4px -4px var(--border) inset"
          : undefined
      : undefined,
    left: isRtl ? rightPosition : leftPosition,
    right: isRtl ? leftPosition : rightPosition,
    opacity: isPinned ? 0.97 : 1,
    position: isPinned ? "sticky" : "relative",
    background: isPinned ? "var(--background)" : "var(--background)",
    width: column.getSize(),
    zIndex: isPinned ? 20 : undefined,
  };
}

export function getVisibleColumnIds(params: {
  columnIds: string[];
  columnVisibility?: ColumnVisibilityState;
  columnPinning?: Partial<ColumnPinningState>;
  columnOrder?: ColumnOrderState;
}): string[] {
  const {
    columnIds,
    columnVisibility = {},
    columnPinning = {},
    columnOrder = [],
  } = params;

  const knownIds = new Set(columnIds);
  const orderedIds = new Set(columnOrder.filter((id) => knownIds.has(id)));
  for (const id of columnIds) orderedIds.add(id);

  const visibleIds = [...orderedIds].filter(
    (id) => columnVisibility[id] !== false,
  );
  const visibleIdSet = new Set(visibleIds);
  const startIds = (columnPinning.start ?? []).filter((id) =>
    visibleIdSet.has(id),
  );
  const endIds = (columnPinning.end ?? []).filter((id) => visibleIdSet.has(id));
  const pinnedIds = new Set([...startIds, ...endIds]);

  return [
    ...startIds,
    ...visibleIds.filter((id) => !pinnedIds.has(id)),
    ...endIds,
  ];
}

export function getTabTargetCell(params: {
  rowIndex: number;
  columnId: string;
  columnIds: string[];
  rowCount: number;
  isBackward: boolean;
  getIsColumnTabbable?: (columnId: string) => boolean;
}): { rowIndex: number; columnId: string } | null {
  const {
    rowIndex,
    columnId,
    columnIds,
    rowCount,
    isBackward,
    getIsColumnTabbable = () => true,
  } = params;
  const colIndex = columnIds.indexOf(columnId);
  if (colIndex === -1 || !columnIds.some(getIsColumnTabbable)) return null;

  const step = isBackward ? -1 : 1;
  let nextRowIndex = rowIndex;
  let nextColIndex = colIndex;

  while (true) {
    nextColIndex += step;
    if (nextColIndex >= columnIds.length) {
      nextColIndex = 0;
      nextRowIndex++;
    } else if (nextColIndex < 0) {
      nextColIndex = columnIds.length - 1;
      nextRowIndex--;
    }

    if (nextRowIndex < 0 || nextRowIndex >= rowCount) return null;

    const nextColumnId = columnIds[nextColIndex];
    if (nextColumnId && getIsColumnTabbable(nextColumnId)) {
      return { rowIndex: nextRowIndex, columnId: nextColumnId };
    }
  }
}

export function getCellFocusTarget(cellElement: HTMLElement): HTMLElement {
  if (cellElement.dataset.slot === "grid-cell-wrapper") return cellElement;
  return (
    cellElement.querySelector<HTMLElement>(
      'button, a[href], [role="checkbox"]',
    ) ?? cellElement
  );
}

export function getColumnFitSize(params: {
  gridElement: HTMLElement;
  columnId: string;
  minSize: number;
  maxSize: number;
  wrapperContentSize?: number;
}): number | null {
  const {
    gridElement,
    columnId,
    minSize,
    maxSize,
    wrapperContentSize = 0,
  } = params;
  const cellElements = gridElement.querySelectorAll<HTMLElement>(
    `:is([data-slot="grid-header-cell"], [data-slot="grid-cell"])[data-column-id="${CSS.escape(columnId)}"]`,
  );
  if (cellElements.length === 0) return null;

  let cellChromeSize = 0;
  for (const cellElement of cellElements) {
    const wrapperElement = cellElement.querySelector<HTMLElement>(
      '[data-slot="grid-cell-wrapper"]',
    );
    if (!wrapperElement) continue;
    cellChromeSize =
      cellElement.getBoundingClientRect().width - wrapperElement.clientWidth;
    break;
  }

  const measurer = document.createElement("div");
  measurer.setAttribute("aria-hidden", "true");
  Object.assign(measurer.style, {
    position: "absolute",
    top: "0",
    insetInlineStart: "0",
    display: "flex",
    flexDirection: "column",
    alignItems: "flex-start",
    visibility: "hidden",
    pointerEvents: "none",
    contain: "layout style",
  });

  for (const cellElement of cellElements) {
    const clone = cellElement.cloneNode(true) as HTMLElement;
    clone.removeAttribute("id");
    Object.assign(clone.style, {
      position: "static",
      width: "max-content",
      minWidth: "0",
      maxWidth: "none",
    });
    for (const element of clone.querySelectorAll<HTMLElement>("*")) {
      element.style.whiteSpace = "nowrap";
      element.style.flexWrap = "nowrap";
      element.style.webkitLineClamp = "unset";
    }
    measurer.append(clone);
  }

  gridElement.append(measurer);
  let contentSize =
    wrapperContentSize > 0 ? wrapperContentSize + cellChromeSize : 0;
  for (const clone of measurer.children) {
    contentSize = Math.max(contentSize, clone.getBoundingClientRect().width);
  }
  measurer.remove();

  return Math.min(maxSize, Math.max(minSize, Math.ceil(contentSize)));
}

export function getScrollDirection(
  direction: string,
): "left" | "right" | "home" | "end" | undefined {
  if (
    direction === "left" ||
    direction === "right" ||
    direction === "home" ||
    direction === "end"
  ) {
    return direction as "left" | "right" | "home" | "end";
  }
  if (direction === "pageleft") return "left";
  if (direction === "pageright") return "right";
  return undefined;
}

export function scrollCellIntoView<TData extends RowData>(params: {
  container: HTMLDivElement;
  targetCell: HTMLDivElement;
  tableRef: React.RefObject<Table<DataGridFeatures, TData> | null>;
  viewportOffset: number;
  direction?: "left" | "right" | "home" | "end";
  isRtl: boolean;
}): void {
  const { container, targetCell, tableRef, direction, viewportOffset, isRtl } =
    params;

  const containerRect = container.getBoundingClientRect();
  const cellRect = targetCell.getBoundingClientRect();

  const hasNegativeScroll = container.scrollLeft < 0;
  const isActuallyRtl = isRtl || hasNegativeScroll;

  const currentTable = tableRef.current;
  const leftPinnedColumns = currentTable?.getStartVisibleLeafColumns() ?? [];
  const rightPinnedColumns = currentTable?.getEndVisibleLeafColumns() ?? [];

  const leftPinnedWidth = leftPinnedColumns.reduce(
    (sum, c) => sum + c.getSize(),
    0,
  );
  const rightPinnedWidth = rightPinnedColumns.reduce(
    (sum, c) => sum + c.getSize(),
    0,
  );

  const viewportLeft = isActuallyRtl
    ? containerRect.left + rightPinnedWidth + viewportOffset
    : containerRect.left + leftPinnedWidth + viewportOffset;
  const viewportRight = isActuallyRtl
    ? containerRect.right - leftPinnedWidth - viewportOffset
    : containerRect.right - rightPinnedWidth - viewportOffset;

  const isFullyVisible =
    cellRect.left >= viewportLeft && cellRect.right <= viewportRight;

  if (isFullyVisible) return;

  const isClippedLeft = cellRect.left < viewportLeft;
  const isClippedRight = cellRect.right > viewportRight;

  let scrollDelta = 0;

  if (!direction) {
    if (isClippedRight) {
      scrollDelta = cellRect.right - viewportRight;
    } else if (isClippedLeft) {
      scrollDelta = -(viewportLeft - cellRect.left);
    }
  } else {
    const shouldScrollRight = isActuallyRtl
      ? direction === "right" || direction === "home"
      : direction === "right" || direction === "end";

    if (shouldScrollRight) {
      scrollDelta = cellRect.right - viewportRight;
    } else {
      scrollDelta = -(viewportLeft - cellRect.left);
    }
  }

  container.scrollLeft += scrollDelta;
}

function countTabs(s: string): number {
  let n = 0;
  for (let i = 0; i < s.length; i++) if (s[i] === "\t") n++;
  return n;
}

export function parseTsv(
  text: string,
  fallbackColumnCount: number,
): string[][] {
  if (text.startsWith('"') || text.includes('\t"')) {
    const rows: string[][] = [];
    let currentRow: string[] = [];
    let currentField = "";
    let inQuotes = false;
    let i = 0;

    while (i < text.length) {
      const char = text[i];
      const nextChar = text[i + 1];

      if (inQuotes) {
        if (char === '"' && nextChar === '"') {
          currentField += '"';
          i += 2;
        } else if (char === '"') {
          inQuotes = false;
          i++;
        } else {
          currentField += char;
          i++;
        }
      } else {
        if (char === '"' && currentField === "") {
          inQuotes = true;
          i++;
        } else if (char === "\t") {
          currentRow.push(currentField);
          currentField = "";
          i++;
        } else if (char === "\n") {
          currentRow.push(currentField);
          if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {
            rows.push(currentRow);
          }
          currentRow = [];
          currentField = "";
          i++;
        } else if (char === "\r" && nextChar === "\n") {
          currentRow.push(currentField);
          if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {
            rows.push(currentRow);
          }
          currentRow = [];
          currentField = "";
          i += 2;
        } else {
          currentField += char;
          i++;
        }
      }
    }

    currentRow.push(currentField);
    if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {
      rows.push(currentRow);
    }

    return rows;
  }

  const lines = text.split("\n").map((l) => l.replace(/\r$/, ""));
  let maxTabCount = 0;
  for (const line of lines) {
    const n = countTabs(line);
    if (n > maxTabCount) maxTabCount = n;
  }
  const columnCount = maxTabCount > 0 ? maxTabCount + 1 : fallbackColumnCount;
  if (columnCount <= 0) return [];

  const expectedTabCount = columnCount - 1;
  const rows: string[][] = [];
  let buf = "";
  let bufTabCount = 0;

  for (const line of lines) {
    const tc = countTabs(line);

    if (tc === expectedTabCount) {
      if (buf && bufTabCount === expectedTabCount) rows.push(buf.split("\t"));
      buf = "";
      bufTabCount = 0;
      rows.push(line.split("\t"));
    } else {
      buf = buf ? `${buf}\n${line}` : line;
      bufTabCount += tc;
      if (bufTabCount === expectedTabCount) {
        rows.push(buf.split("\t"));
        buf = "";
        bufTabCount = 0;
      }
    }
  }

  if (buf && bufTabCount === expectedTabCount) rows.push(buf.split("\t"));

  return rows.length > 0
    ? rows
    : lines.filter((l) => l.length > 0).map((l) => l.split("\t"));
}

export function getIsInPopover(element: unknown): boolean {
  if (!(element instanceof Element)) return false;

  return (
    element.closest("[data-grid-cell-editor]") !== null ||
    element.closest("[data-grid-popover]") !== null ||
    element.closest("[data-slot='dropdown-menu-content']") !== null ||
    element.closest("[data-slot='popover-content']") !== null ||
    element.closest("[data-slot='select-content']") !== null ||
    element.closest("[data-slot='faceted-content']") !== null
  );
}

export function getColumnVariant(variant?: CellOpts["variant"]): {
  icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
  label: string;
} | null {
  switch (variant) {
    case "short-text":
      return { label: "Short text", icon: BaselineIcon };
    case "long-text":
      return { label: "Long text", icon: TextInitialIcon };
    case "number":
      return { label: "Number", icon: HashIcon };
    case "url":
      return { label: "URL", icon: LinkIcon };
    case "checkbox":
      return { label: "Checkbox", icon: CheckSquareIcon };
    case "select":
      return { label: "Select", icon: ListIcon };
    case "multi-select":
      return { label: "Multi-select", icon: ListChecksIcon };
    case "date":
      return { label: "Date", icon: CalendarIcon };
    case "file":
      return { label: "File", icon: FileIcon };
    default:
      return null;
  }
}

export function getEmptyCellValue(
  variant: CellOpts["variant"] | undefined,
): unknown {
  if (variant === "multi-select" || variant === "file") return [];
  if (variant === "number" || variant === "date" || variant === "select")
    return null;
  if (variant === "checkbox") return false;
  return "";
}

export function getUrlHref(urlString: string): string {
  if (!urlString || urlString.trim() === "") return "";

  const trimmed = urlString.trim();

  // Reject dangerous protocols (extra safety, though our http:// prefix would neutralize them)
  if (/^(javascript|data|vbscript|file):/i.test(trimmed)) {
    return "";
  }

  if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
    return trimmed;
  }

  return `http://${trimmed}`;
}

export function parseLocalDate(dateStr: unknown): Date | null {
  if (!dateStr) return null;
  if (dateStr instanceof Date) return dateStr;
  if (typeof dateStr !== "string") return null;
  const [year, month, day] = dateStr.split("-").map(Number);
  if (!year || !month || !day) return null;
  const date = new Date(year, month - 1, day);
  // Verify date wasn't auto-corrected (e.g. Feb 30 -> Mar 1)
  if (
    date.getFullYear() !== year ||
    date.getMonth() !== month - 1 ||
    date.getDate() !== day
  ) {
    return null;
  }
  return date;
}

export function formatDateToString(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

export function formatDateForDisplay(dateStr: unknown): string {
  if (!dateStr) return "";
  const date = parseLocalDate(dateStr);
  if (!date) return typeof dateStr === "string" ? dateStr : "";
  return date.toLocaleDateString();
}

export function formatFileSize(bytes: number): string {
  if (bytes <= 0 || !Number.isFinite(bytes)) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB"];
  const i = Math.min(
    sizes.length - 1,
    Math.floor(Math.log(bytes) / Math.log(k)),
  );
  return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
}

export function getFileIcon(
  type: string,
): React.ComponentType<React.SVGProps<SVGSVGElement>> {
  if (type.startsWith("image/")) return FileImage;
  if (type.startsWith("video/")) return FileVideo;
  if (type.startsWith("audio/")) return FileAudio;
  if (type.includes("pdf")) return FileText;
  if (type.includes("zip") || type.includes("rar")) return FileArchive;
  if (
    type.includes("word") ||
    type.includes("document") ||
    type.includes("doc")
  )
    return FileText;
  if (type.includes("sheet") || type.includes("excel") || type.includes("xls"))
    return FileSpreadsheet;
  if (
    type.includes("presentation") ||
    type.includes("powerpoint") ||
    type.includes("ppt")
  )
    return Presentation;
  return File;
}
src/lib/data-grid-types.ts
import type { Cell, RowData } from "@tanstack/react-table";

import type { DataGridFeatures } from "@/lib/data-grid-features";

export type Direction = "ltr" | "rtl";

export type RowHeightValue = "short" | "medium" | "tall" | "extra-tall";

export interface CellSelectOption {
  label: string;
  value: string;
  icon?: React.ComponentType<React.ComponentProps<"svg">>;
  count?: number;
}

export type CellOpts =
  | {
      variant: "short-text";
    }
  | {
      variant: "long-text";
    }
  | {
      variant: "number";
      min?: number;
      max?: number;
      step?: number;
    }
  | {
      variant: "select";
      options: CellSelectOption[];
    }
  | {
      variant: "multi-select";
      options: CellSelectOption[];
    }
  | {
      variant: "checkbox";
    }
  | {
      variant: "date";
    }
  | {
      variant: "url";
    }
  | {
      variant: "file";
      maxFileSize?: number;
      maxFiles?: number;
      accept?: string;
      multiple?: boolean;
    };

export interface CellUpdate {
  rowId: string;
  columnId: string;
  value: unknown;
}

export interface DataGridColumnMeta {
  label?: string;
  cell?: CellOpts;
}

export interface DataGridTableMeta {
  dataGridRef?: React.RefObject<HTMLElement | null>;
  cellMapRef?: React.RefObject<Map<string, HTMLDivElement>>;
  focusedCell?: CellPosition | null;
  editingCell?: CellPosition | null;
  selectionState?: SelectionState;
  searchOpen?: boolean;
  getIsCellSelected?: (rowId: string, columnId: string) => boolean;
  getIsSearchMatch?: (rowId: string, columnId: string) => boolean;
  getIsActiveSearchMatch?: (rowId: string, columnId: string) => boolean;
  scrollToCell?: (
    rowId: string,
    columnId: string,
    align?: "auto" | "start" | "center" | "end",
  ) => void;
  onRowSelect?: (rowId: string, checked: boolean, shiftKey: boolean) => void;
  onDataUpdate?: (params: CellUpdate | Array<CellUpdate>) => void;
  onRowsDelete?: (rowIds: string[]) => void | Promise<void>;
  onColumnClick?: (columnId: string) => void;
  onCellClick?: (
    rowId: string,
    columnId: string,
    event?: React.MouseEvent,
  ) => void;
  onCellDoubleClick?: (rowId: string, columnId: string) => void;
  onCellMouseDown?: (
    rowId: string,
    columnId: string,
    event: React.MouseEvent,
  ) => void;
  onCellMouseEnter?: (rowId: string, columnId: string) => void;
  onCellMouseUp?: () => void;
  onCellContextMenu?: (
    rowId: string,
    columnId: string,
    event: React.MouseEvent,
  ) => void;
  onCellEditingStart?: (rowId: string, columnId: string) => void;
  onCellEditingStop?: (opts?: {
    direction?: NavigationDirection;
    moveToNextRow?: boolean;
  }) => void;
  onCellsCopy?: () => void;
  onCellsCut?: () => void;
  onCellsPaste?: (expand?: boolean) => void;
  onSelectionClear?: () => void;
  onFilesUpload?: (params: {
    files: File[];
    rowId: string;
    columnId: string;
  }) => Promise<FileCellData[]>;
  onFilesDelete?: (params: {
    fileIds: string[];
    rowId: string;
    columnId: string;
  }) => void | Promise<void>;
  contextMenu?: ContextMenuState;
  onContextMenuOpenChange?: (open: boolean) => void;
  pasteDialog?: PasteDialogState;
  onPasteDialogOpenChange?: (open: boolean) => void;
  readOnly?: boolean;
}

export interface CellPosition {
  rowId: string;
  columnId: string;
}

export interface CellRange {
  start: CellPosition;
  end: CellPosition;
}

export interface SelectionState {
  selectedCells: Set<string>;
  selectionRange: CellRange | null;
  isSelecting: boolean;
}

export interface ContextMenuState {
  open: boolean;
  x: number;
  y: number;
}

export interface PasteDialogState {
  open: boolean;
  rowsNeeded: number;
  clipboardText: string;
}

export type NavigationDirection =
  | "up"
  | "down"
  | "left"
  | "right"
  | "home"
  | "end"
  | "ctrl+up"
  | "ctrl+down"
  | "ctrl+home"
  | "ctrl+end"
  | "pageup"
  | "pagedown"
  | "pageleft"
  | "pageright"
  | "tab"
  | "shift+tab";

export interface SearchState {
  searchMatches: CellPosition[];
  matchIndex: number;
  searchOpen: boolean;
  onSearchOpenChange: (open: boolean) => void;
  searchQuery: string;
  onSearchQueryChange: (query: string) => void;
  onSearch: (query: string) => void;
  onNavigateToNextMatch: () => void;
  onNavigateToPrevMatch: () => void;
}

export interface DataGridCellProps<TData extends RowData> {
  cell: Cell<DataGridFeatures, TData>;
  tableMeta: DataGridTableMeta;
  rowId: string;
  columnId: string;
  rowHeight: RowHeightValue;
  isEditing: boolean;
  isFocused: boolean;
  isSelected: boolean;
  isSearchMatch: boolean;
  isActiveSearchMatch: boolean;
  readOnly: boolean;
}

export interface FileCellData {
  id: string;
  name: string;
  size: number;
  type: string;
  url?: string;
}

export type TextFilterOperator =
  | "contains"
  | "notContains"
  | "equals"
  | "notEquals"
  | "startsWith"
  | "endsWith"
  | "isEmpty"
  | "isNotEmpty";

export type NumberFilterOperator =
  | "equals"
  | "notEquals"
  | "lessThan"
  | "lessThanOrEqual"
  | "greaterThan"
  | "greaterThanOrEqual"
  | "isBetween"
  | "isEmpty"
  | "isNotEmpty";

export type DateFilterOperator =
  | "equals"
  | "notEquals"
  | "before"
  | "after"
  | "onOrBefore"
  | "onOrAfter"
  | "isBetween"
  | "isEmpty"
  | "isNotEmpty";

export type SelectFilterOperator =
  | "is"
  | "isNot"
  | "isAnyOf"
  | "isNoneOf"
  | "isEmpty"
  | "isNotEmpty";

export type BooleanFilterOperator = "isTrue" | "isFalse";

export type FilterOperator =
  | TextFilterOperator
  | NumberFilterOperator
  | DateFilterOperator
  | SelectFilterOperator
  | BooleanFilterOperator;

export interface FilterValue {
  operator: FilterOperator;
  value?: string | number | string[];
  endValue?: string | number;
}
src/lib/data-grid-features.ts
import {
  assignPrototypeAPIs,
  assignTableAPIs,
  type CellData,
  columnFilteringFeature,
  columnOrderingFeature,
  columnPinningFeature,
  columnResizingFeature,
  columnSizingFeature,
  columnVisibilityFeature,
  createFilteredRowModel,
  createSortedRowModel,
  functionalUpdate,
  makeStateUpdater,
  metaHelper,
  type OnChangeFn,
  type RowData,
  rowSelectionFeature,
  rowSortingFeature,
  type Table,
  type TableFeature,
  type TableFeatures,
  tableFeatures,
  type Updater,
} from "@tanstack/react-table";

import type {
  CellPosition,
  DataGridColumnMeta,
  DataGridTableMeta,
  RowHeightValue,
} from "@/lib/data-grid-types";

import { getLineCount, getRowHeightValue } from "@/lib/data-grid-utils";

const DEFAULT_ROW_HEIGHT: RowHeightValue = "short";

interface TableState_DataGridRowHeight {
  rowHeight: RowHeightValue;
}

interface TableOptions_DataGridRowHeight {
  onRowHeightChange?: OnChangeFn<RowHeightValue>;
}

interface Table_DataGridRowHeight {
  getRowHeight: () => RowHeightValue;
  getRowSize: () => number;
  getRowLineCount: () => number;
  setRowHeight: (updater: Updater<RowHeightValue>) => void;
  resetRowHeight: (defaultState?: boolean) => void;
}

interface TableState_DataGridCellEditing {
  editingCell: CellPosition | null;
}

interface TableOptions_DataGridCellEditing {
  enableCellEditing?: boolean;
  onEditingCellChange?: OnChangeFn<CellPosition | null>;
}

interface ColumnDef_DataGridCellEditing {
  enableCellEditing?: boolean;
}

interface Table_DataGridCellEditing {
  getEditingCell: () => CellPosition | null;
  setEditingCell: (updater: Updater<CellPosition | null>) => void;
  resetEditingCell: (defaultState?: boolean) => void;
}

interface Column_DataGridCellEditing {
  getCanEdit: () => boolean;
}

interface Cell_DataGridCellEditing {
  getCanEdit: () => boolean;
  getIsEditing: () => boolean;
}

declare module "@tanstack/react-table" {
  interface Plugins {
    dataGridRowHeightFeature: TableFeature;
    dataGridCellEditingFeature: TableFeature;
  }

  interface TableState_FeatureMap {
    dataGridRowHeightFeature: TableState_DataGridRowHeight;
    dataGridCellEditingFeature: TableState_DataGridCellEditing;
  }

  interface TableOptions_FeatureMap<
    in out TFeatures extends TableFeatures,
    in out TData extends RowData,
  > {
    dataGridRowHeightFeature: TableOptions_DataGridRowHeight;
    dataGridCellEditingFeature: TableOptions_DataGridCellEditing;
  }

  interface ColumnDef_FeatureMap<
    in out TFeatures extends TableFeatures,
    in out TData extends RowData,
    TValue extends CellData,
  > {
    dataGridCellEditingFeature: ColumnDef_DataGridCellEditing;
  }

  interface Table_FeatureMap<
    in out TFeatures extends TableFeatures,
    in out TData extends RowData,
  > {
    dataGridRowHeightFeature: Table_DataGridRowHeight;
    dataGridCellEditingFeature: Table_DataGridCellEditing;
  }

  interface Column_FeatureMap<
    in out TFeatures extends TableFeatures,
    in out TData extends RowData,
  > {
    dataGridCellEditingFeature: Column_DataGridCellEditing;
  }

  interface Cell_FeatureMap {
    dataGridCellEditingFeature: Cell_DataGridCellEditing;
  }
}

type DataGridInstance = Table<DataGridFeatures, RowData>;

function asDataGrid(table: object) {
  return table as DataGridInstance;
}

const dataGridRowHeightFeature: TableFeature = {
  getInitialState: (initialState) => ({
    rowHeight: DEFAULT_ROW_HEIGHT,
    ...initialState,
  }),
  getDefaultTableOptions: (table) => {
    const options: TableOptions_DataGridRowHeight = {
      onRowHeightChange: makeStateUpdater("rowHeight", table),
    };
    return options;
  },
  constructTableAPIs: (table) => {
    const instance = asDataGrid(table);

    const setRowHeight = (updater: Updater<RowHeightValue>) =>
      instance.options.onRowHeightChange?.((old) =>
        functionalUpdate(updater, old),
      );

    assignTableAPIs("dataGridRowHeightFeature", table, {
      table_getRowHeight: {
        fn: () => instance.atoms.rowHeight.get(),
      },
      table_getRowSize: {
        fn: () => getRowHeightValue(instance.atoms.rowHeight.get()),
      },
      table_getRowLineCount: {
        fn: () => getLineCount(instance.atoms.rowHeight.get()),
      },
      table_setRowHeight: { fn: setRowHeight },
      table_resetRowHeight: {
        fn: (defaultState?: boolean) =>
          setRowHeight(
            defaultState
              ? DEFAULT_ROW_HEIGHT
              : (instance.initialState.rowHeight ?? DEFAULT_ROW_HEIGHT),
          ),
      },
    });
  },
};

function getCanEditColumn(column: {
  table: object;
  columnDef: ColumnDef_DataGridCellEditing;
}) {
  return (
    asDataGrid(column.table).options.enableCellEditing !== false &&
    column.columnDef.enableCellEditing !== false
  );
}

const dataGridCellEditingFeature: TableFeature = {
  getInitialState: (initialState) => ({
    editingCell: null,
    ...initialState,
  }),
  getDefaultTableOptions: (table) => {
    const options: TableOptions_DataGridCellEditing = {
      onEditingCellChange: makeStateUpdater("editingCell", table),
    };
    return options;
  },
  constructTableAPIs: (table) => {
    const instance = asDataGrid(table);

    const setEditingCell = (updater: Updater<CellPosition | null>) =>
      instance.options.onEditingCellChange?.((old) =>
        functionalUpdate(updater, old),
      );

    assignTableAPIs("dataGridCellEditingFeature", table, {
      table_getEditingCell: {
        fn: () => instance.atoms.editingCell.get(),
      },
      table_setEditingCell: { fn: setEditingCell },
      table_resetEditingCell: {
        fn: (defaultState?: boolean) =>
          setEditingCell(
            defaultState ? null : (instance.initialState.editingCell ?? null),
          ),
      },
    });
  },
  assignColumnPrototype: (prototype, table) => {
    assignPrototypeAPIs("dataGridCellEditingFeature", prototype, table, {
      column_getCanEdit: { fn: getCanEditColumn },
    });
  },
  assignCellPrototype: (prototype, table) => {
    assignPrototypeAPIs("dataGridCellEditingFeature", prototype, table, {
      cell_getCanEdit: {
        fn: (cell: { column: Parameters<typeof getCanEditColumn>[0] }) =>
          getCanEditColumn(cell.column),
      },
      cell_getIsEditing: {
        fn: (cell: {
          table: object;
          row: { id: string };
          column: { id: string };
        }) => {
          const editingCell = asDataGrid(cell.table).atoms.editingCell.get();
          return (
            editingCell?.rowId === cell.row.id &&
            editingCell.columnId === cell.column.id
          );
        },
      },
    });
  },
};

export const dataGridFeatures = tableFeatures({
  columnFilteringFeature,
  columnOrderingFeature,
  columnPinningFeature,
  columnResizingFeature,
  columnSizingFeature,
  columnVisibilityFeature,
  rowSelectionFeature,
  rowSortingFeature,
  dataGridRowHeightFeature,
  dataGridCellEditingFeature,
  filteredRowModel: createFilteredRowModel(),
  sortedRowModel: createSortedRowModel(),
  tableMeta: metaHelper<DataGridTableMeta>(),
  columnMeta: metaHelper<DataGridColumnMeta>(),
});

export type DataGridFeatures = typeof dataGridFeatures;

More from Shadcn Table

Browse all components in this library →