shadimage
retab
blocksources-viewer-blocksource: ok · hit

Sources Viewer

Every source-backed format in one viewer — PDF/image bbox sources use segmented-document anchors; text, CSV, Excel, and Word use segmented source links with format-specific target adapters.

npx shadcn add "https://ui.retab.com/r/sources-viewer-block.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
formsreact-hook-form
ui-primitives@radix-ui/react-checkbox@^1.1.3@radix-ui/react-label@^2.1.1@radix-ui/react-slot@^1.1.1

Registry dependencies

@retab/file-viewer@retab/pdf-viewer@retab/image-viewer@retab/image-source@retab/text-viewer@retab/text-source@retab/csv-viewer@retab/csv-source@retab/xlsx-viewer@retab/xlsx-source@retab/docx-viewer@retab/docx-source@retab/document-source@retab/scroll-area@retab/segmented-document@retab/source-evidence@retab/source-segmented-document@retab/source-field-link@retab/source-indicator@retab/utils@retab/data-cell@retab/inputtooltip

Files42 · source cached 10/15/2026, 9:53:35 AM

registry/new-york-v4/blocks/sources-viewer-block.tsx
"use client";

import * as React from "react";
import type { JSONSchema7 } from "json-schema";
import { useForm } from "react-hook-form";

import {
  extractionSourcesToSourceMap,
  type Source,
  type SourceMap,
} from "@/lib/document-source";
import { cn } from "@/lib/utils";
import {
  sourceToCsvCell,
  useCsvSourceTarget,
} from "@/components/ui/csv-source";
import {
  CsvViewerGrid,
  CsvViewerProvider,
  type CsvViewerHandle,
} from "@/components/ui/csv-viewer";
import {
  sourceToDocxHighlight,
  useDocxSourceTarget,
} from "@/components/ui/docx-source";
import {
  DocxViewerDocument,
  DocxViewerProvider,
  type DocxViewerHandle,
} from "@/components/ui/docx-viewer";
import {
  FileViewerContent,
  FileViewerHeader,
  FileViewerTitle,
  FileViewer,
  FileViewerProvider,
  FileViewerSidebar,
  FileViewerSidebarContent,
  FileViewerSidebarSection,
  FileViewerSidebarSectionContent,
  FileViewerSidebarSectionHeader,
  FileViewerSidebarSectionTitle,
  FileViewerSidebarTrigger,
  FileViewerInset,
  FileViewerControls,
  FileViewerViewport,
  type ViewerSource,
  useFileViewerResource,
} from "@/components/ui/file-viewer";
import {
  ImageViewerFrames,
  ImageViewerProvider,
  type ImageViewerHandle,
} from "@/components/ui/image-viewer";
import { PdfViewerPages, PdfViewerProvider } from "@/components/ui/pdf-viewer";
import {
  SegmentedDocumentProvider,
  useSegmentedDocumentViewport,
} from "@/components/ui/segmented-document-provider";
import { sourceMapToEvidenceModel } from "@/components/ui/source-evidence";
import {
  useSegmentedSourceFieldLink,
  type SegmentedSourceFieldLink,
  type SourceFieldLink,
} from "@/components/ui/source-field-link";
import { SourceIndicator } from "@/components/ui/source-indicator";
import { createSourcesSegmentedDocumentModel } from "@/components/ui/source-segmented-document-model";
import {
  useSegmentedImageSourceOverlay,
  useSegmentedImageViewerHandle,
  useSegmentedPdfSourceOverlay,
  useSegmentedPdfViewerHandle,
} from "@/components/ui/source-segmented-document-overlays";
import {
  sourceToTextHighlight,
  useTextSourceTarget,
} from "@/components/ui/text-source";
import {
  TextViewerDocument,
  TextViewerProvider,
  type TextViewerHandle,
} from "@/components/ui/text-viewer";
import {
  sourceToXlsxCell,
  useXlsxSourceTarget,
} from "@/components/ui/xlsx-source";
import {
  XlsxViewerProvider,
  XlsxViewerWorkbook,
  type XlsxViewerHandle,
} from "@/components/ui/xlsx-viewer";
import { JsonForm } from "@/components/json-form/json-form";
import csvSample from "@/components/viewers/sample-data/csv-sources.json";
import docxSample from "@/components/viewers/sample-data/docx-sources.json";
import imageSample from "@/components/viewers/sample-data/image-sources.json";
import jsonFormSample from "@/components/viewers/sample-data/json-form-sources.json";
import textSample from "@/components/viewers/sample-data/text-sources.json";
import xlsxSample from "@/components/viewers/sample-data/xlsx-sources.json";

// ── Sample sources, one per file format ───────────────────────────────────────

const PDF_URL = "/samples/jane-doe-bank-statement-5-pages.pdf";
const PDF_SOURCE = {
  kind: "url" as const,
  url: PDF_URL,
  fileName: "jane-doe-bank-statement-5-pages.pdf",
};
const IMAGE_URL = "/samples/an-image-is-worth-16x16-words-page-1.png";
const XLSX_URL = "/samples/nvidia-financials-fy2024.xlsx";
const DOCX_URL = "/samples/quarterly-business-review.docx";

const TEXT_TEXT = [
  "[retab] extraction run",
  "run_id=run_3xK9b2QvT7",
  "tenant=acme-corp environment=prod",
  "received file bank-statement.pdf (80.1 KB)",
  "sha256=4f1a...c0de",
  "mime=application/pdf",
  "detected document_type=bank_statement",
  "confidence=0.984",
  "page_count=4",
  "preprocessing started",
  "rasterizing pages at 192 dpi",
  "ocr blocks=324",
  "table candidates=7",
  "classification completed",
  "extraction started",
  "model=retab-large n_consensus=3",
  "schema=bank_statement_v2 fields=18",
  "run 1 completed",
  "run 2 completed",
  "run 3 completed",
  "merged 3 runs, mean_likelihood=0.91",
  "field transactions.7.amount low agreement (0.58)",
  "source grounding started",
  "source grounding completed",
  "review hints generated",
  "16/18 fields grounded",
  "low confidence fields queued",
  "human review required=false",
  "assigned to queue=finance-ops",
  "prompt_tokens=14820 completion_tokens=2110",
  "total_tokens=16930",
  "run status=completed",
  "duration_ms=11842",
].join("\n");
const CSV_TEXT = `region,quarter,revenue,customers,nrr
North America,Q1,1240000,48,1.12
North America,Q2,1510000,61,1.21
EMEA,Q1,820000,33,1.08
EMEA,Q2,910000,39,1.15
APAC,Q1,430000,18,1.04
APAC,Q2,560000,24,1.11`;
const IMAGE_SOURCE = {
  kind: "url" as const,
  url: IMAGE_URL,
  fileName: "an-image-is-worth-16x16-words-page-1.png",
};
const TEXT_SOURCE = {
  kind: "text" as const,
  text: TEXT_TEXT,
  fileName: "extraction-run.log",
};
const CSV_SOURCE = {
  kind: "text" as const,
  text: CSV_TEXT,
  fileName: "sales.csv",
};
const XLSX_SOURCE = {
  kind: "url" as const,
  url: XLSX_URL,
  fileName: "nvidia-financials-fy2024.xlsx",
};
const DOCX_SOURCE = {
  kind: "url" as const,
  url: DOCX_URL,
  fileName: "quarterly-business-review.docx",
};

// A flat extraction sample: each field is a scalar with the source it came from.
type FlatField = { key: string; label: string; value: string; source: Source };

type SourceExtraction = {
  schema: JSONSchema7;
  sources: SourceMap;
  values: Record<string, unknown>;
};

type SourceLinkedViewerSource = ViewerSource;

// Build a JSON form's inputs from a flat field array. The schema property names
// match the source-map keys used for json-form hover and click interactions.
function flatSourceExtraction(fields: FlatField[]): SourceExtraction {
  const sources = Object.fromEntries(
    fields.map((field) => [field.key, field.source]),
  );

  return {
    schema: {
      type: "object",
      properties: Object.fromEntries(
        fields.map((field) => [
          field.key,
          { type: "string", title: field.label },
        ]),
      ),
    },
    values: Object.fromEntries(fields.map((field) => [field.key, field.value])),
    sources,
  };
}

// The PDF tab uses the richer, nested extraction sample (a real /sources
// response); the rest derive a flat form from their per-format field arrays.
const PDF_SOURCE_MAP = extractionSourcesToSourceMap(jsonFormSample.sources);
const PDF_EVIDENCE = sourceMapToEvidenceModel({
  sourceMap: PDF_SOURCE_MAP,
  schema: jsonFormSample.schema as JSONSchema7,
  values: jsonFormSample.extraction as Record<string, unknown>,
});
const PDF_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel({
  labels: Object.fromEntries(
    PDF_EVIDENCE.evidenceItems.map((item) => [item.id, item.payload.label]),
  ),
  sourceMap: PDF_SOURCE_MAP,
});
const PDF_INITIAL_SOURCE_PATH =
  PDF_EVIDENCE.evidenceItems.find((item) => item.anchor.status === "resolved")
    ?.id ?? null;
const PDF_EXTRACTION: SourceExtraction = {
  schema: jsonFormSample.schema as JSONSchema7,
  sources: PDF_SOURCE_MAP,
  values: jsonFormSample.extraction as Record<string, unknown>,
};
const IMAGE_FIELDS = imageSample as FlatField[];
const IMAGE_EXTRACTION = flatSourceExtraction(IMAGE_FIELDS);
const IMAGE_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel(
  fieldsToSegmentedFields(IMAGE_FIELDS),
);
const TEXT_FIELDS = textSample as FlatField[];
const TEXT_EXTRACTION = flatSourceExtraction(TEXT_FIELDS);
const TEXT_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel(
  fieldsToSegmentedFields(TEXT_FIELDS),
);
const CSV_FIELDS = csvSample as FlatField[];
const CSV_EXTRACTION = flatSourceExtraction(CSV_FIELDS);
const CSV_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel(
  fieldsToSegmentedFields(CSV_FIELDS),
);
const XLSX_FIELDS = xlsxSample as FlatField[];
const XLSX_EXTRACTION = flatSourceExtraction(XLSX_FIELDS);
const XLSX_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel(
  fieldsToSegmentedFields(XLSX_FIELDS),
);
const DOCX_FIELDS = docxSample as FlatField[];
const DOCX_EXTRACTION = flatSourceExtraction(DOCX_FIELDS);
const DOCX_SEGMENTED_DOCUMENT = createSourcesSegmentedDocumentModel(
  fieldsToSegmentedFields(DOCX_FIELDS),
);

function fieldsToSegmentedFields(fields: readonly FlatField[]) {
  return fields.map((field) => ({
    id: field.key,
    label: field.label,
    source: field.source,
  }));
}

// ── Shared layout: viewer + json-form source panel ────────────────────────────

function SourceLinkedFileHeader() {
  return (
    <FileViewerHeader>
        <FileViewerSidebarTrigger className="-ms-1" />
        <FileViewerTitle />
        <FileViewerControls />
    </FileViewerHeader>
  );
}

function SourceLinkedViewer({
  children,
  extraction,
  link,
  source,
}: {
  children: React.ReactNode;
  extraction: SourceExtraction;
  link: SegmentedSourceFieldLink;
  source: SourceLinkedViewerSource;
}) {
  return (
    <FileViewerProvider source={source} headerMode="outlets" defaultSidebarOpen>
      <FileViewer className="bg-background">
        <SourceLinkedFileHeader />
        <FileViewerContent>
          <FileViewerInset>
            <FileViewerViewport>{children}</FileViewerViewport>
          </FileViewerInset>
          <FileViewerSidebar
            aria-label="Source-linked fields"
            side="right"
            width="420px"
            className="bg-background flex flex-shrink-0 flex-col border-l"
          >
            <SourcesFormHeader link={link} />
            <FileViewerSidebarContent>
              <SourcesForm extraction={extraction} link={link} />
            </FileViewerSidebarContent>
          </FileViewerSidebar>
        </FileViewerContent>
      </FileViewer>
    </FileViewerProvider>
  );
}

function SourcesFormHeader({ link }: { link: SourceFieldLink }) {
  return (
    <FileViewerSidebarSectionHeader className="bg-background sticky top-0 z-20 flex h-10 flex-shrink-0 items-center gap-2 border-b px-4">
      <FileViewerSidebarSectionTitle className="sr-only">
        Source fields
      </FileViewerSidebarSectionTitle>
      <SourceIndicator
        path={link.activeSourcePath}
        className="min-h-0 flex-1 px-0 py-0"
      />
    </FileViewerSidebarSectionHeader>
  );
}

function SourcesForm({
  extraction,
  link,
}: {
  extraction: SourceExtraction;
  link: SourceFieldLink;
}) {
  const form = useForm<Record<string, unknown>>({
    defaultValues: extraction.values,
  });

  // `json-form` is source-aware: pass the link and every field becomes a
  // hoverable card that reports its path. No per-field wiring needed.
  return (
    <FileViewerSidebarSection className="p-4">
      <FileViewerSidebarSectionTitle className="sr-only">
        Source fields
      </FileViewerSidebarSectionTitle>
      <FileViewerSidebarSectionContent>
        <JsonForm form={form} schema={extraction.schema} sourceLink={link} />
      </FileViewerSidebarSectionContent>
    </FileViewerSidebarSection>
  );
}

type SourceTarget = {
  scrollTo?: (source: Source, options: { behavior: ScrollBehavior }) => void;
};

function useSourceTargetedSegmentedFieldLink({
  initialSourcePath,
  sources,
  target,
}: {
  initialSourcePath?: string | null;
  sources: SourceMap;
  target: SourceTarget;
}): SegmentedSourceFieldLink {
  const link = useSegmentedSourceFieldLink({ initialSourcePath });
  const scrollToPath = React.useCallback(
    (path: string, behavior: ScrollBehavior) => {
      const source = sources[path];
      if (source) target.scrollTo?.(source, { behavior });
    },
    [sources, target],
  );
  const onSourceHover = React.useCallback(
    (path: string | null) => {
      link.onSourceHover(path);
      if (path) scrollToPath(path, "auto");
    },
    [link, scrollToPath],
  );
  const selectSourcePath = React.useCallback(
    (path: string) => {
      link.selectSourcePath?.(path);
      scrollToPath(path, "smooth");
    },
    [link, scrollToPath],
  );

  return React.useMemo(
    () => ({
      ...link,
      onSourceHover,
      selectSourcePath,
    }),
    [link, onSourceHover, selectSourcePath],
  );
}

function sourceForPath(
  sources: SourceMap,
  path: string | null,
): Source | undefined {
  return path ? sources[path] : undefined;
}

// ── Per-format tabs ───────────────────────────────────────────────────────────

function PdfTab() {
  return (
    <SegmentedDocumentProvider model={PDF_SEGMENTED_DOCUMENT}>
      <PdfTabContent />
    </SegmentedDocumentProvider>
  );
}

function PdfTabContent() {
  const link = useSegmentedSourceFieldLink({
    initialSourcePath: PDF_INITIAL_SOURCE_PATH,
  });
  const { documentHandlers } = useSegmentedDocumentViewport();
  const renderPageOverlay = useSegmentedPdfSourceOverlay(link);
  const setPdfViewerHandle = useSegmentedPdfViewerHandle();

  return (
    <SourceLinkedViewer
      extraction={PDF_EXTRACTION}
      link={link}
      source={PDF_SOURCE}
    >
      <PdfViewerProvider>
        <PdfViewerPages
          ref={setPdfViewerHandle}
          bare
          className="h-full"
          onScrollProgressChange={documentHandlers.onScrollProgressChange}
          onVisiblePageChange={documentHandlers.onCurrentPageChange}
          renderPageOverlay={renderPageOverlay}
        />
      </PdfViewerProvider>
    </SourceLinkedViewer>
  );
}

function ImageTab() {
  return (
    <SegmentedDocumentProvider model={IMAGE_SEGMENTED_DOCUMENT}>
      <ImageTabContent />
    </SegmentedDocumentProvider>
  );
}

function ImageTabContent() {
  const link = useSegmentedSourceFieldLink({
    initialSourcePath: IMAGE_FIELDS[0]?.key,
  });
  const { documentHandlers } = useSegmentedDocumentViewport();
  const renderFrameOverlay = useSegmentedImageSourceOverlay(link);
  const setImageViewerHandle = useSegmentedImageViewerHandle();

  return (
    <SourceLinkedViewer
      extraction={IMAGE_EXTRACTION}
      link={link}
      source={IMAGE_SOURCE}
    >
      <FileResourceImageViewer
        ref={setImageViewerHandle}
        bare
        className="h-full"
        controls={false}
        fallbackFrameSize={{ width: 1224, height: 1584 }}
        onScrollProgressChange={documentHandlers.onScrollProgressChange}
        onVisibleFrameChange={documentHandlers.onCurrentPageChange}
        renderFrameOverlay={renderFrameOverlay}
      />
    </SourceLinkedViewer>
  );
}

function TextTab() {
  return (
    <SegmentedDocumentProvider model={TEXT_SEGMENTED_DOCUMENT}>
      <TextTabContent />
    </SegmentedDocumentProvider>
  );
}

function TextTabContent() {
  const viewerRef = React.useRef<TextViewerHandle>(null);
  const target = useTextSourceTarget(viewerRef);
  const link = useSourceTargetedSegmentedFieldLink({
    initialSourcePath: TEXT_FIELDS[0]?.key,
    sources: TEXT_EXTRACTION.sources,
    target,
  });
  const highlight = sourceToTextHighlight(
    sourceForPath(TEXT_EXTRACTION.sources, link.activeSourcePath),
  );

  return (
    <SourceLinkedViewer
      extraction={TEXT_EXTRACTION}
      link={link}
      source={TEXT_SOURCE}
    >
      <FileResourceTextViewer
        ref={viewerRef}
        bare
        className="h-full"
        controls={false}
        highlight={highlight}
        mode="text"
      />
    </SourceLinkedViewer>
  );
}

function CsvTab() {
  return (
    <SegmentedDocumentProvider model={CSV_SEGMENTED_DOCUMENT}>
      <CsvTabContent />
    </SegmentedDocumentProvider>
  );
}

function CsvTabContent() {
  const viewerRef = React.useRef<CsvViewerHandle>(null);
  const target = useCsvSourceTarget(viewerRef);
  const link = useSourceTargetedSegmentedFieldLink({
    initialSourcePath: CSV_FIELDS[0]?.key,
    sources: CSV_EXTRACTION.sources,
    target,
  });
  const activeCell = sourceToCsvCell(
    sourceForPath(CSV_EXTRACTION.sources, link.activeSourcePath),
  );

  return (
    <SourceLinkedViewer
      extraction={CSV_EXTRACTION}
      link={link}
      source={CSV_SOURCE}
    >
      <FileResourceCsvViewer
        ref={viewerRef}
        fillHeight
        className="h-full"
        controls={false}
        activeCell={activeCell}
      />
    </SourceLinkedViewer>
  );
}

function ExcelTab() {
  return (
    <SegmentedDocumentProvider model={XLSX_SEGMENTED_DOCUMENT}>
      <ExcelTabContent />
    </SegmentedDocumentProvider>
  );
}

function ExcelTabContent() {
  const viewerRef = React.useRef<XlsxViewerHandle>(null);
  const target = useXlsxSourceTarget(viewerRef);
  const link = useSourceTargetedSegmentedFieldLink({
    initialSourcePath: XLSX_FIELDS[0]?.key,
    sources: XLSX_EXTRACTION.sources,
    target,
  });
  const activeCell = sourceToXlsxCell(
    sourceForPath(XLSX_EXTRACTION.sources, link.activeSourcePath),
  );

  return (
    <SourceLinkedViewer
      extraction={XLSX_EXTRACTION}
      link={link}
      source={XLSX_SOURCE}
    >
      <FileResourceXlsxViewer
        ref={viewerRef}
        bare
        className="h-full"
        controls={false}
        activeCell={activeCell}
      />
    </SourceLinkedViewer>
  );
}

function DocxTab() {
  return (
    <SegmentedDocumentProvider model={DOCX_SEGMENTED_DOCUMENT}>
      <DocxTabContent />
    </SegmentedDocumentProvider>
  );
}

function DocxTabContent() {
  const viewerRef = React.useRef<DocxViewerHandle>(null);
  const target = useDocxSourceTarget(viewerRef);
  const link = useSourceTargetedSegmentedFieldLink({
    initialSourcePath: DOCX_FIELDS[0]?.key,
    sources: DOCX_EXTRACTION.sources,
    target,
  });
  const highlight = sourceToDocxHighlight(
    sourceForPath(DOCX_EXTRACTION.sources, link.activeSourcePath),
  );

  return (
    <SourceLinkedViewer
      extraction={DOCX_EXTRACTION}
      link={link}
      source={DOCX_SOURCE}
    >
      <FileResourceDocxViewer
        ref={viewerRef}
        bare
        className="h-full"
        controls={false}
        highlight={highlight}
      />
    </SourceLinkedViewer>
  );
}

const FileResourceImageViewer = React.forwardRef<
  ImageViewerHandle,
  React.ComponentProps<typeof ImageViewerFrames>
>(function FileResourceImageViewer(props, ref) {
  const resource = useFileViewerResource();
  return (
    <ImageViewerProvider resource={resource}>
      <ImageViewerFrames {...props} ref={ref} />
    </ImageViewerProvider>
  );
});

const FileResourceTextViewer = React.forwardRef<
  TextViewerHandle,
  React.ComponentProps<typeof TextViewerDocument>
>(function FileResourceTextViewer(props, ref) {
  const resource = useFileViewerResource();
  return (
    <TextViewerProvider resource={resource}>
      <TextViewerDocument {...props} ref={ref} />
    </TextViewerProvider>
  );
});

const FileResourceCsvViewer = React.forwardRef<
  CsvViewerHandle,
  React.ComponentProps<typeof CsvViewerGrid>
>(function FileResourceCsvViewer(props, ref) {
  const resource = useFileViewerResource();
  return (
    <CsvViewerProvider resource={resource}>
      <CsvViewerGrid {...props} ref={ref} />
    </CsvViewerProvider>
  );
});

const FileResourceXlsxViewer = React.forwardRef<
  XlsxViewerHandle,
  React.ComponentProps<typeof XlsxViewerWorkbook>
>(function FileResourceXlsxViewer(props, ref) {
  const resource = useFileViewerResource();
  return (
    <XlsxViewerProvider resource={resource}>
      <XlsxViewerWorkbook {...props} ref={ref} />
    </XlsxViewerProvider>
  );
});

const FileResourceDocxViewer = React.forwardRef<
  DocxViewerHandle,
  React.ComponentProps<typeof DocxViewerDocument>
>(function FileResourceDocxViewer(props, ref) {
  const resource = useFileViewerResource();
  return (
    <DocxViewerProvider resource={resource}>
      <DocxViewerDocument {...props} ref={ref} />
    </DocxViewerProvider>
  );
});

const TABS = [
  { id: "pdf", label: "PDF", Tab: PdfTab },
  { id: "image", label: "Image", Tab: ImageTab },
  { id: "text", label: "Text", Tab: TextTab },
  { id: "csv", label: "CSV", Tab: CsvTab },
  { id: "excel", label: "Excel", Tab: ExcelTab },
  { id: "docx", label: "DOCX", Tab: DocxTab },
] as const;

type TabId = (typeof TABS)[number]["id"];

/**
 * Sources viewer — every source-backed format in one component. A tab bar switches
 * the file format (PDF, image, text, CSV, Excel, Word); each tab is the same
 * source shell: the source document beside a JSON form of its extracted values,
 * linked by their sources. Segmented source interaction drives every tab; only
 * the viewer and its source target adapter differ per format.
 *
 * Tabs mount lazily on first visit and stay mounted (hidden) afterwards, so each
 * format's viewer keeps its scroll position and avoids re-fetching its document.
 */
export function SourcesViewerBlock() {
  const [active, setActive] = React.useState<TabId>("pdf");
  const [mounted, setMounted] = React.useState<Set<TabId>>(
    () => new Set(["pdf"]),
  );

  function selectTab(id: TabId) {
    setActive(id);
    setMounted((prev) => {
      if (prev.has(id)) return prev;
      const next = new Set(prev);
      next.add(id);
      return next;
    });
  }

  return (
    <div className="bg-background flex h-full min-h-[680px] flex-col">
      <div
        role="tablist"
        aria-label="Source format"
        className="flex h-11 flex-shrink-0 items-center gap-1 border-b px-2"
      >
        {TABS.map((tab) => {
          const isActive = tab.id === active;
          return (
            <button
              key={tab.id}
              type="button"
              role="tab"
              aria-selected={isActive}
              onClick={() => selectTab(tab.id)}
              className={cn(
                "focus-visible:ring-ring relative h-full px-3 text-sm font-medium transition-colors outline-none focus-visible:ring-2",
                isActive
                  ? "text-foreground"
                  : "text-muted-foreground hover:text-foreground",
              )}
            >
              {tab.label}
              {isActive ? (
                <span className="bg-foreground absolute inset-x-2 -bottom-px h-0.5 rounded-full" />
              ) : null}
            </button>
          );
        })}
      </div>
      <div className="relative min-h-0 flex-1">
        {TABS.map((tab) =>
          mounted.has(tab.id) ? (
            <div
              key={tab.id}
              role="tabpanel"
              hidden={tab.id !== active}
              className={cn(
                "absolute inset-0",
                tab.id === active ? "block" : "hidden",
              )}
            >
              <tab.Tab />
            </div>
          ) : null,
        )}
      </div>
    </div>
  );
}
components/viewers/sample-data/json-form-sources.json
{
  "object": "extraction.sources",
  "document_type": "pdf",
  "file": {
    "id": "file_jane_doe_bank_statement_jul_2003",
    "filename": "jane-doe-bank-statement-5-pages.pdf",
    "mime_type": "application/pdf"
  },
  "schema": {
    "type": "object",
    "title": "Bank statement",
    "properties": {
      "account_number": {
        "type": "string",
        "description": "Account number"
      },
      "statement_date": {
        "type": "string",
        "description": "Statement date",
        "format": "date"
      },
      "transactions": {
        "type": "array",
        "title": "Transactions",
        "items": {
          "type": "object",
          "properties": {
            "date": {
              "type": "string",
              "format": "date"
            },
            "description": {
              "type": "string"
            },
            "amount": {
              "type": "number"
            }
          }
        }
      }
    },
    "required": [
      "account_number",
      "statement_date"
    ]
  },
  "extraction": {
    "account_number": "000009752",
    "statement_date": "2003-07-08",
    "transactions": [
      {
        "date": "2003-06-06",
        "description": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
        "amount": -85.34
      },
      {
        "date": "2003-06-06",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -81.23
      },
      {
        "date": "2003-06-06",
        "description": "DEPOSIT - PAYROLL DIRECT DEP",
        "amount": 1875.24
      },
      {
        "date": "2003-06-06",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -69.01
      },
      {
        "date": "2003-06-07",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -30.79
      },
      {
        "date": "2003-06-07",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -108.3
      },
      {
        "date": "2003-06-07",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -41.2
      },
      {
        "date": "2003-06-07",
        "description": "ONLINE TRANSFER TO SAVINGS",
        "amount": -213.49
      },
      {
        "date": "2003-06-08",
        "description": "CHECKCARD PURCHASE - GARDEN CENTER",
        "amount": -35.52
      },
      {
        "date": "2003-06-08",
        "description": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
        "amount": -7.95
      },
      {
        "date": "2003-06-08",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -19.24
      },
      {
        "date": "2003-06-08",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -41.72
      },
      {
        "date": "2003-06-09",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -174.12
      },
      {
        "date": "2003-06-09",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -27.77
      },
      {
        "date": "2003-06-09",
        "description": "BILL PAYMENT - MOBILE SERVICE",
        "amount": -48.75
      },
      {
        "date": "2003-06-09",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -58.01
      },
      {
        "date": "2003-06-10",
        "description": "CHECK 1007",
        "amount": -123.1
      },
      {
        "date": "2003-06-10",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -26.65
      },
      {
        "date": "2003-06-10",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -74.08
      },
      {
        "date": "2003-06-10",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -87.17
      },
      {
        "date": "2003-06-11",
        "description": "MONTHLY SERVICE CHARGE",
        "amount": -5
      },
      {
        "date": "2003-06-11",
        "description": "CHECKCARD PURCHASE - DINER",
        "amount": -38.07
      },
      {
        "date": "2003-06-11",
        "description": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
        "amount": -73.25
      },
      {
        "date": "2003-06-11",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -77.9
      },
      {
        "date": "2003-06-12",
        "description": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
        "amount": -31.14
      },
      {
        "date": "2003-06-12",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -58.72
      },
      {
        "date": "2003-06-12",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -41.2
      },
      {
        "date": "2003-06-12",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -129.74
      },
      {
        "date": "2003-06-13",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -34.84
      },
      {
        "date": "2003-06-13",
        "description": "DEPOSIT - MOBILE DEPOSIT",
        "amount": 420
      },
      {
        "date": "2003-06-13",
        "description": "CHECKCARD PURCHASE - GARDEN CENTER",
        "amount": -71.14
      },
      {
        "date": "2003-06-13",
        "description": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
        "amount": -8.67
      },
      {
        "date": "2003-06-14",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -41.37
      },
      {
        "date": "2003-06-14",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -41.86
      },
      {
        "date": "2003-06-14",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -132.11
      },
      {
        "date": "2003-06-14",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -31.5
      },
      {
        "date": "2003-06-15",
        "description": "BILL PAYMENT - MOBILE SERVICE",
        "amount": -72.18
      },
      {
        "date": "2003-06-15",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -52.41
      },
      {
        "date": "2003-06-15",
        "description": "CHECKCARD PURCHASE - POSTAL CENTER",
        "amount": -15.84
      },
      {
        "date": "2003-06-15",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -31.59
      },
      {
        "date": "2003-06-16",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -47.27
      },
      {
        "date": "2003-06-16",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -93.98
      },
      {
        "date": "2003-06-16",
        "description": "MONTHLY SERVICE CHARGE",
        "amount": -5
      },
      {
        "date": "2003-06-16",
        "description": "CHECK 1008",
        "amount": -89
      },
      {
        "date": "2003-06-17",
        "description": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
        "amount": -71.51
      },
      {
        "date": "2003-06-17",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -73.43
      },
      {
        "date": "2003-06-17",
        "description": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
        "amount": -41.87
      },
      {
        "date": "2003-06-17",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -77.2
      },
      {
        "date": "2003-06-18",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -46.3
      },
      {
        "date": "2003-06-18",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -120.45
      },
      {
        "date": "2003-06-18",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -35
      },
      {
        "date": "2003-06-18",
        "description": "ONLINE TRANSFER TO SAVINGS",
        "amount": -106.83
      },
      {
        "date": "2003-06-19",
        "description": "CHECKCARD PURCHASE - GARDEN CENTER",
        "amount": -52.55
      },
      {
        "date": "2003-06-19",
        "description": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
        "amount": -15.42
      },
      {
        "date": "2003-06-19",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -31.46
      },
      {
        "date": "2003-06-19",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -47.3
      },
      {
        "date": "2003-06-20",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -155.05
      },
      {
        "date": "2003-06-20",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -35.57
      },
      {
        "date": "2003-06-20",
        "description": "ACH CREDIT - CUSTOMER REFUND",
        "amount": 54.19
      },
      {
        "date": "2003-06-20",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -55.86
      },
      {
        "date": "2003-06-21",
        "description": "CHECKCARD PURCHASE - POSTAL CENTER",
        "amount": -23.97
      },
      {
        "date": "2003-06-21",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -35.78
      },
      {
        "date": "2003-06-21",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -64.3
      },
      {
        "date": "2003-06-21",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -73.81
      },
      {
        "date": "2003-06-22",
        "description": "MONTHLY SERVICE CHARGE",
        "amount": -5
      },
      {
        "date": "2003-06-22",
        "description": "CHECKCARD PURCHASE - DINER",
        "amount": -44.13
      },
      {
        "date": "2003-06-22",
        "description": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
        "amount": -48.6
      },
      {
        "date": "2003-06-22",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -77.82
      },
      {
        "date": "2003-06-23",
        "description": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
        "amount": -58.48
      },
      {
        "date": "2003-06-23",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -28.91
      },
      {
        "date": "2003-06-23",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -27.12
      },
      {
        "date": "2003-06-23",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -125.07
      },
      {
        "date": "2003-06-24",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -42.1
      },
      {
        "date": "2003-06-24",
        "description": "ONLINE TRANSFER TO SAVINGS",
        "amount": -222.37
      },
      {
        "date": "2003-06-24",
        "description": "CHECKCARD PURCHASE - GARDEN CENTER",
        "amount": -31.59
      },
      {
        "date": "2003-06-24",
        "description": "CHECK 1009",
        "amount": -69.65
      },
      {
        "date": "2003-06-25",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -54.55
      },
      {
        "date": "2003-06-25",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -48.03
      },
      {
        "date": "2003-06-25",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -147.09
      },
      {
        "date": "2003-06-25",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -25.3
      },
      {
        "date": "2003-06-26",
        "description": "BILL PAYMENT - MOBILE SERVICE",
        "amount": -62.79
      },
      {
        "date": "2003-06-26",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -66.49
      },
      {
        "date": "2003-06-26",
        "description": "CHECKCARD PURCHASE - POSTAL CENTER",
        "amount": -26.59
      },
      {
        "date": "2003-06-26",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -28.02
      },
      {
        "date": "2003-06-27",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -30.23
      },
      {
        "date": "2003-06-27",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -70.26
      },
      {
        "date": "2003-06-27",
        "description": "MONTHLY SERVICE CHARGE",
        "amount": -5
      },
      {
        "date": "2003-06-27",
        "description": "CHECKCARD PURCHASE - DINER",
        "amount": -22.66
      },
      {
        "date": "2003-06-28",
        "description": "ACH CREDIT - EXPENSE REIMBURSEMENT",
        "amount": 96.5
      },
      {
        "date": "2003-06-28",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -88.25
      },
      {
        "date": "2003-06-28",
        "description": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
        "amount": -38.13
      },
      {
        "date": "2003-06-28",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -49.3
      },
      {
        "date": "2003-06-29",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -32.73
      },
      {
        "date": "2003-06-29",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -87.98
      },
      {
        "date": "2003-06-29",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -27.12
      },
      {
        "date": "2003-06-29",
        "description": "ONLINE TRANSFER TO SAVINGS",
        "amount": -173.65
      },
      {
        "date": "2003-06-30",
        "description": "CHECKCARD PURCHASE - GARDEN CENTER",
        "amount": -51.97
      },
      {
        "date": "2003-06-30",
        "description": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
        "amount": -17.91
      },
      {
        "date": "2003-06-30",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -74.91
      },
      {
        "date": "2003-06-30",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -33.34
      },
      {
        "date": "2003-07-01",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -167.63
      },
      {
        "date": "2003-07-01",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -18.94
      },
      {
        "date": "2003-07-01",
        "description": "BILL PAYMENT - MOBILE SERVICE",
        "amount": -59.37
      },
      {
        "date": "2003-07-01",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -43.76
      },
      {
        "date": "2003-07-02",
        "description": "CHECK 1010",
        "amount": -115.17
      },
      {
        "date": "2003-07-02",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -20.21
      },
      {
        "date": "2003-07-02",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -75.17
      },
      {
        "date": "2003-07-02",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -93.9
      },
      {
        "date": "2003-07-03",
        "description": "MONTHLY SERVICE CHARGE",
        "amount": -5
      },
      {
        "date": "2003-07-03",
        "description": "CHECKCARD PURCHASE - DINER",
        "amount": -21.27
      },
      {
        "date": "2003-07-03",
        "description": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
        "amount": -52.88
      },
      {
        "date": "2003-07-03",
        "description": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
        "amount": -88.93
      },
      {
        "date": "2003-07-04",
        "description": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
        "amount": -45.92
      },
      {
        "date": "2003-07-04",
        "description": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
        "amount": -42.9
      },
      {
        "date": "2003-07-04",
        "description": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
        "amount": -35.54
      },
      {
        "date": "2003-07-04",
        "description": "BILL PAYMENT - CITY UTILITIES",
        "amount": -81.24
      },
      {
        "date": "2003-07-05",
        "description": "CHECKCARD PURCHASE - BOOKS AND NEWS",
        "amount": -39.69
      },
      {
        "date": "2003-07-05",
        "description": "ONLINE TRANSFER TO SAVINGS",
        "amount": -219.21
      },
      {
        "date": "2003-07-05",
        "description": "ATM DEPOSIT - SMALL TOWN BRANCH",
        "amount": 225
      },
      {
        "date": "2003-07-05",
        "description": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
        "amount": -6.58
      },
      {
        "date": "2003-07-06",
        "description": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
        "amount": -36.07
      },
      {
        "date": "2003-07-06",
        "description": "CHECKCARD PURCHASE - HARDWARE STORE",
        "amount": -51.25
      },
      {
        "date": "2003-07-06",
        "description": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
        "amount": -127.13
      },
      {
        "date": "2003-07-06",
        "description": "CHECKCARD PURCHASE - DRY CLEANERS",
        "amount": -25.2
      },
      {
        "date": "2003-07-07",
        "description": "BILL PAYMENT - MOBILE SERVICE",
        "amount": -76.42
      },
      {
        "date": "2003-07-07",
        "description": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
        "amount": -33.72
      },
      {
        "date": "2003-07-07",
        "description": "CHECKCARD PURCHASE - POSTAL CENTER",
        "amount": -24.21
      },
      {
        "date": "2003-07-07",
        "description": "CHECKCARD PURCHASE - MOVIE THEATER",
        "amount": -45.25
      },
      {
        "date": "2003-07-08",
        "description": "CHECKCARD PURCHASE - AUTO PARTS",
        "amount": -70.6
      },
      {
        "date": "2003-07-08",
        "description": "AUTOMATIC PAYMENT - CABLE SERVICE",
        "amount": -76.67
      }
    ]
  },
  "sources": {
    "account_number": {
      "value": "000009752",
      "source": {
        "content": "000009752",
        "anchor": {
          "kind": "pdf_bbox",
          "page": 1,
          "left": 0.8111,
          "top": 0.1772,
          "width": 0.0736,
          "height": 0.0127
        }
      }
    },
    "statement_date": {
      "value": "2003-07-08",
      "source": {
        "content": "July 8, 2003",
        "anchor": {
          "kind": "pdf_bbox",
          "page": 1,
          "left": 0.8021,
          "top": 0.1933,
          "width": 0.0826,
          "height": 0.0127
        }
      }
    },
    "transactions": [
      {
        "date": {
          "value": "2003-06-06",
          "source": {
            "content": "06-06",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.4195,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
          "source": {
            "content": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.4195,
              "width": 0.3067,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -85.34,
          "source": {
            "content": "$85.34",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.4195,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-06",
          "source": {
            "content": "06-06",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.4489,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.4489,
              "width": 0.3009,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -81.23,
          "source": {
            "content": "$81.23",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.4489,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-06",
          "source": {
            "content": "06-06",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.4669,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "DEPOSIT - PAYROLL DIRECT DEP",
          "source": {
            "content": "DEPOSIT - PAYROLL DIRECT DEP",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.4669,
              "width": 0.2157,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": 1875.24,
          "source": {
            "content": "$1,875.24",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.8024,
              "top": 0.4669,
              "width": 0.06,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-06",
          "source": {
            "content": "06-06",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.4839,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
          "source": {
            "content": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.4839,
              "width": 0.2809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -69.01,
          "source": {
            "content": "$69.01",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.4839,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-07",
          "source": {
            "content": "06-07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.5019,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
          "source": {
            "content": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.5019,
              "width": 0.2666,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -30.79,
          "source": {
            "content": "$30.79",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.5019,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-07",
          "source": {
            "content": "06-07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.5313,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - CITY UTILITIES",
          "source": {
            "content": "BILL PAYMENT - CITY UTILITIES",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.5313,
              "width": 0.203,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -108.3,
          "source": {
            "content": "$108.30",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.7075,
              "top": 0.5313,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-07",
          "source": {
            "content": "06-07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.5493,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - BOOKS AND NEWS",
          "source": {
            "content": "CHECKCARD PURCHASE - BOOKS AND NEWS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.5493,
              "width": 0.2996,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -41.2,
          "source": {
            "content": "$41.20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.5493,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-07",
          "source": {
            "content": "06-07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.5673,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ONLINE TRANSFER TO SAVINGS",
          "source": {
            "content": "ONLINE TRANSFER TO SAVINGS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.5673,
              "width": 0.211,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -213.49,
          "source": {
            "content": "$213.49",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.7075,
              "top": 0.5673,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-08",
          "source": {
            "content": "06-08",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.5853,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GARDEN CENTER",
          "source": {
            "content": "CHECKCARD PURCHASE - GARDEN CENTER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.5853,
              "width": 0.2929,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -35.52,
          "source": {
            "content": "$35.52",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.5853,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-08",
          "source": {
            "content": "06-08",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6033,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
          "source": {
            "content": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6033,
              "width": 0.3086,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -7.95,
          "source": {
            "content": "$7.95",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.7225,
              "top": 0.6033,
              "width": 0.0337,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-08",
          "source": {
            "content": "06-08",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6203,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
          "source": {
            "content": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6203,
              "width": 0.3149,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -19.24,
          "source": {
            "content": "$19.24",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.6203,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-08",
          "source": {
            "content": "06-08",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6383,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - HARDWARE STORE",
          "source": {
            "content": "CHECKCARD PURCHASE - HARDWARE STORE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6383,
              "width": 0.3041,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -41.72,
          "source": {
            "content": "$41.72",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.6383,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-09",
          "source": {
            "content": "06-09",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6563,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
          "source": {
            "content": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6563,
              "width": 0.3036,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -174.12,
          "source": {
            "content": "$174.12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.7075,
              "top": 0.6563,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-09",
          "source": {
            "content": "06-09",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6743,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - DRY CLEANERS",
          "source": {
            "content": "CHECKCARD PURCHASE - DRY CLEANERS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6743,
              "width": 0.2804,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -27.77,
          "source": {
            "content": "$27.77",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.6743,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-09",
          "source": {
            "content": "06-09",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.6923,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - MOBILE SERVICE",
          "source": {
            "content": "BILL PAYMENT - MOBILE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.6923,
              "width": 0.2195,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -48.75,
          "source": {
            "content": "$48.75",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.6923,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-09",
          "source": {
            "content": "06-09",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.7103,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
          "source": {
            "content": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.7103,
              "width": 0.3168,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -58.01,
          "source": {
            "content": "$58.01",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.7103,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-10",
          "source": {
            "content": "06-10",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.7283,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECK 1007",
          "source": {
            "content": "CHECK 1007",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.7283,
              "width": 0.0809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -123.1,
          "source": {
            "content": "$123.10",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.7075,
              "top": 0.7283,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-10",
          "source": {
            "content": "06-10",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1307,
              "top": 0.7463,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MOVIE THEATER",
          "source": {
            "content": "CHECKCARD PURCHASE - MOVIE THEATER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.1992,
              "top": 0.7463,
              "width": 0.2849,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -26.65,
          "source": {
            "content": "$26.65",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 1,
              "left": 0.715,
              "top": 0.7463,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-10",
          "source": {
            "content": "06-10",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.3267,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - AUTO PARTS",
          "source": {
            "content": "CHECKCARD PURCHASE - AUTO PARTS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.3267,
              "width": 0.2599,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -74.08,
          "source": {
            "content": "$74.08",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.3267,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-10",
          "source": {
            "content": "06-10",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.3447,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - CABLE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - CABLE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.3447,
              "width": 0.2637,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -87.17,
          "source": {
            "content": "$87.17",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.3447,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-11",
          "source": {
            "content": "06-11",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.3627,
              "width": 0.0335,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "MONTHLY SERVICE CHARGE",
          "source": {
            "content": "MONTHLY SERVICE CHARGE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.3627,
              "width": 0.1888,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -5,
          "source": {
            "content": "$5.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.7225,
              "top": 0.3627,
              "width": 0.0337,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-11",
          "source": {
            "content": "06-11",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.3807,
              "width": 0.0335,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - DINER",
          "source": {
            "content": "CHECKCARD PURCHASE - DINER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.3807,
              "width": 0.218,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -38.07,
          "source": {
            "content": "$38.07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.3807,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-11",
          "source": {
            "content": "06-11",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.3987,
              "width": 0.0335,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
          "source": {
            "content": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.3987,
              "width": 0.3067,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -73.25,
          "source": {
            "content": "$73.25",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.3987,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-11",
          "source": {
            "content": "06-11",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.4271,
              "width": 0.0335,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.4271,
              "width": 0.3009,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -77.9,
          "source": {
            "content": "$77.90",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.4271,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-12",
          "source": {
            "content": "06-12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.4451,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
          "source": {
            "content": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.4451,
              "width": 0.2864,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -31.14,
          "source": {
            "content": "$31.14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.4451,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-12",
          "source": {
            "content": "06-12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.4745,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
          "source": {
            "content": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.4745,
              "width": 0.2809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -58.72,
          "source": {
            "content": "$58.72",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.4745,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-12",
          "source": {
            "content": "06-12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.4925,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
          "source": {
            "content": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.4925,
              "width": 0.2666,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -41.2,
          "source": {
            "content": "$41.20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.4925,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-12",
          "source": {
            "content": "06-12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.5218,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - CITY UTILITIES",
          "source": {
            "content": "BILL PAYMENT - CITY UTILITIES",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.5218,
              "width": 0.203,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -129.74,
          "source": {
            "content": "$129.74",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.7075,
              "top": 0.5218,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-13",
          "source": {
            "content": "06-13",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.5398,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - BOOKS AND NEWS",
          "source": {
            "content": "CHECKCARD PURCHASE - BOOKS AND NEWS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.5398,
              "width": 0.2996,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -34.84,
          "source": {
            "content": "$34.84",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.5398,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-13",
          "source": {
            "content": "06-13",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.5569,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "DEPOSIT - MOBILE DEPOSIT",
          "source": {
            "content": "DEPOSIT - MOBILE DEPOSIT",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.5569,
              "width": 0.1848,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": 420,
          "source": {
            "content": "$420.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.8137,
              "top": 0.5569,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-13",
          "source": {
            "content": "06-13",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.5749,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GARDEN CENTER",
          "source": {
            "content": "CHECKCARD PURCHASE - GARDEN CENTER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.5749,
              "width": 0.2929,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -71.14,
          "source": {
            "content": "$71.14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.5749,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-13",
          "source": {
            "content": "06-13",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.5928,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
          "source": {
            "content": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.5928,
              "width": 0.3086,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -8.67,
          "source": {
            "content": "$8.67",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.7225,
              "top": 0.5928,
              "width": 0.0337,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-14",
          "source": {
            "content": "06-14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6108,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
          "source": {
            "content": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6108,
              "width": 0.3149,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -41.37,
          "source": {
            "content": "$41.37",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.6108,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-14",
          "source": {
            "content": "06-14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6288,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - HARDWARE STORE",
          "source": {
            "content": "CHECKCARD PURCHASE - HARDWARE STORE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6288,
              "width": 0.3041,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -41.86,
          "source": {
            "content": "$41.86",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.6288,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-14",
          "source": {
            "content": "06-14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6468,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
          "source": {
            "content": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6468,
              "width": 0.3036,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -132.11,
          "source": {
            "content": "$132.11",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.7085,
              "top": 0.6468,
              "width": 0.0477,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-14",
          "source": {
            "content": "06-14",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6648,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - DRY CLEANERS",
          "source": {
            "content": "CHECKCARD PURCHASE - DRY CLEANERS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6648,
              "width": 0.2804,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -31.5,
          "source": {
            "content": "$31.50",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.6648,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-15",
          "source": {
            "content": "06-15",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6828,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - MOBILE SERVICE",
          "source": {
            "content": "BILL PAYMENT - MOBILE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6828,
              "width": 0.2195,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -72.18,
          "source": {
            "content": "$72.18",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.6828,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-15",
          "source": {
            "content": "06-15",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.6999,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
          "source": {
            "content": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.6999,
              "width": 0.3168,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -52.41,
          "source": {
            "content": "$52.41",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.6999,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-15",
          "source": {
            "content": "06-15",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.7178,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - POSTAL CENTER",
          "source": {
            "content": "CHECKCARD PURCHASE - POSTAL CENTER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.7178,
              "width": 0.2869,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -15.84,
          "source": {
            "content": "$15.84",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.7178,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-15",
          "source": {
            "content": "06-15",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.7358,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MOVIE THEATER",
          "source": {
            "content": "CHECKCARD PURCHASE - MOVIE THEATER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.7358,
              "width": 0.2849,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -31.59,
          "source": {
            "content": "$31.59",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.7358,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-16",
          "source": {
            "content": "06-16",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.7538,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - AUTO PARTS",
          "source": {
            "content": "CHECKCARD PURCHASE - AUTO PARTS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.7538,
              "width": 0.2599,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -47.27,
          "source": {
            "content": "$47.27",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.7538,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-16",
          "source": {
            "content": "06-16",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.7718,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - CABLE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - CABLE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.7718,
              "width": 0.2637,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -93.98,
          "source": {
            "content": "$93.98",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.7718,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-16",
          "source": {
            "content": "06-16",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.7898,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "MONTHLY SERVICE CHARGE",
          "source": {
            "content": "MONTHLY SERVICE CHARGE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.7898,
              "width": 0.1888,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -5,
          "source": {
            "content": "$5.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.7225,
              "top": 0.7898,
              "width": 0.0337,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-16",
          "source": {
            "content": "06-16",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.8078,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECK 1008",
          "source": {
            "content": "CHECK 1008",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.8078,
              "width": 0.0809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -89,
          "source": {
            "content": "$89.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.8078,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-17",
          "source": {
            "content": "06-17",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.8258,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
          "source": {
            "content": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.8258,
              "width": 0.3067,
              "height": 0.0221
            }
          }
        },
        "amount": {
          "value": -71.51,
          "source": {
            "content": "$71.51",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.8258,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-17",
          "source": {
            "content": "06-17",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1307,
              "top": 0.8542,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.1992,
              "top": 0.8542,
              "width": 0.3009,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -73.43,
          "source": {
            "content": "$73.43",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 2,
              "left": 0.715,
              "top": 0.8542,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-17",
          "source": {
            "content": "06-17",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.3267,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
          "source": {
            "content": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.3267,
              "width": 0.2864,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -41.87,
          "source": {
            "content": "$41.87",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.3267,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-17",
          "source": {
            "content": "06-17",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.3561,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
          "source": {
            "content": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.3561,
              "width": 0.2809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -77.2,
          "source": {
            "content": "$77.20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.3561,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-18",
          "source": {
            "content": "06-18",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.3741,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
          "source": {
            "content": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.3741,
              "width": 0.2666,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -46.3,
          "source": {
            "content": "$46.30",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.3741,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-18",
          "source": {
            "content": "06-18",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4034,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - CITY UTILITIES",
          "source": {
            "content": "BILL PAYMENT - CITY UTILITIES",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4034,
              "width": 0.203,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -120.45,
          "source": {
            "content": "$120.45",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.7075,
              "top": 0.4034,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-18",
          "source": {
            "content": "06-18",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4205,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - BOOKS AND NEWS",
          "source": {
            "content": "CHECKCARD PURCHASE - BOOKS AND NEWS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4205,
              "width": 0.2996,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -35,
          "source": {
            "content": "$35.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.4205,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-18",
          "source": {
            "content": "06-18",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4385,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ONLINE TRANSFER TO SAVINGS",
          "source": {
            "content": "ONLINE TRANSFER TO SAVINGS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4385,
              "width": 0.211,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -106.83,
          "source": {
            "content": "$106.83",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.7075,
              "top": 0.4385,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-19",
          "source": {
            "content": "06-19",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4565,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GARDEN CENTER",
          "source": {
            "content": "CHECKCARD PURCHASE - GARDEN CENTER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4565,
              "width": 0.2929,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -52.55,
          "source": {
            "content": "$52.55",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.4565,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-19",
          "source": {
            "content": "06-19",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4745,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
          "source": {
            "content": "CHECKCARD PURCHASE - COFFEE HOUSE 013",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4745,
              "width": 0.3086,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -15.42,
          "source": {
            "content": "$15.42",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.4745,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-19",
          "source": {
            "content": "06-19",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.4925,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
          "source": {
            "content": "CHECKCARD PURCHASE - OFFICE SUPPLY 3301",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.4925,
              "width": 0.3149,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -31.46,
          "source": {
            "content": "$31.46",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.4925,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-19",
          "source": {
            "content": "06-19",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5105,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - HARDWARE STORE",
          "source": {
            "content": "CHECKCARD PURCHASE - HARDWARE STORE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5105,
              "width": 0.3041,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -47.3,
          "source": {
            "content": "$47.30",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.5105,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-20",
          "source": {
            "content": "06-20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5284,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
          "source": {
            "content": "AUTOMATIC PAYMENT - INSURANCE PREMIUM",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5284,
              "width": 0.3036,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -155.05,
          "source": {
            "content": "$155.05",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.7075,
              "top": 0.5284,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-20",
          "source": {
            "content": "06-20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5464,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - DRY CLEANERS",
          "source": {
            "content": "CHECKCARD PURCHASE - DRY CLEANERS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5464,
              "width": 0.2804,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -35.57,
          "source": {
            "content": "$35.57",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.5464,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-20",
          "source": {
            "content": "06-20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5635,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ACH CREDIT - CUSTOMER REFUND",
          "source": {
            "content": "ACH CREDIT - CUSTOMER REFUND",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5635,
              "width": 0.2309,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": 54.19,
          "source": {
            "content": "$54.19",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.8212,
              "top": 0.5635,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-20",
          "source": {
            "content": "06-20",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5815,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
          "source": {
            "content": "CHECKCARD PURCHASE - FAMILY RESTAURANT",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5815,
              "width": 0.3168,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -55.86,
          "source": {
            "content": "$55.86",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.5815,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-21",
          "source": {
            "content": "06-21",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.5995,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - POSTAL CENTER",
          "source": {
            "content": "CHECKCARD PURCHASE - POSTAL CENTER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.5995,
              "width": 0.2869,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -23.97,
          "source": {
            "content": "$23.97",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.5995,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-21",
          "source": {
            "content": "06-21",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.6175,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MOVIE THEATER",
          "source": {
            "content": "CHECKCARD PURCHASE - MOVIE THEATER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.6175,
              "width": 0.2849,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -35.78,
          "source": {
            "content": "$35.78",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.6175,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-21",
          "source": {
            "content": "06-21",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.6355,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - AUTO PARTS",
          "source": {
            "content": "CHECKCARD PURCHASE - AUTO PARTS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.6355,
              "width": 0.2599,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -64.3,
          "source": {
            "content": "$64.30",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.6355,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-21",
          "source": {
            "content": "06-21",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.6534,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - CABLE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - CABLE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.6534,
              "width": 0.2637,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -73.81,
          "source": {
            "content": "$73.81",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.6534,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-22",
          "source": {
            "content": "06-22",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.6714,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "MONTHLY SERVICE CHARGE",
          "source": {
            "content": "MONTHLY SERVICE CHARGE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.6714,
              "width": 0.1888,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -5,
          "source": {
            "content": "$5.00",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.7225,
              "top": 0.6714,
              "width": 0.0337,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-22",
          "source": {
            "content": "06-22",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.6894,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - DINER",
          "source": {
            "content": "CHECKCARD PURCHASE - DINER",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.6894,
              "width": 0.218,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -44.13,
          "source": {
            "content": "$44.13",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.6894,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-22",
          "source": {
            "content": "06-22",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.7065,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
          "source": {
            "content": "CHECKCARD PURCHASE - GROCER'S MARKET 0042, SMALL TOWN MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.7065,
              "width": 0.3067,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -48.6,
          "source": {
            "content": "$48.60",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.7065,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-22",
          "source": {
            "content": "06-22",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.7358,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
          "source": {
            "content": "AUTOMATIC PAYMENT - TELEPHONE SERVICE",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.7358,
              "width": 0.3009,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -77.82,
          "source": {
            "content": "$77.82",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.7358,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-23",
          "source": {
            "content": "06-23",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.7538,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
          "source": {
            "content": "CHECKCARD PURCHASE - FUEL STOP 1187, KANSAS CITY MO",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.7538,
              "width": 0.2864,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -58.48,
          "source": {
            "content": "$58.48",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.7538,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-23",
          "source": {
            "content": "06-23",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.7832,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
          "source": {
            "content": "ATM WITHDRAWAL - 1000 WALNUT ST M119",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.7832,
              "width": 0.2809,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -28.91,
          "source": {
            "content": "$28.91",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.7832,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-23",
          "source": {
            "content": "06-23",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.8012,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
          "source": {
            "content": "CHECKCARD PURCHASE - MAIN STREET PHARMACY",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.8012,
              "width": 0.2666,
              "height": 0.023
            }
          }
        },
        "amount": {
          "value": -27.12,
          "source": {
            "content": "$27.12",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.715,
              "top": 0.8012,
              "width": 0.0412,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-23",
          "source": {
            "content": "06-23",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.8296,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "BILL PAYMENT - CITY UTILITIES",
          "source": {
            "content": "BILL PAYMENT - CITY UTILITIES",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.8296,
              "width": 0.203,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -125.07,
          "source": {
            "content": "$125.07",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.7075,
              "top": 0.8296,
              "width": 0.0487,
              "height": 0.0116
            }
          }
        }
      },
      {
        "date": {
          "value": "2003-06-24",
          "source": {
            "content": "06-24",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1307,
              "top": 0.8476,
              "width": 0.0345,
              "height": 0.0116
            }
          }
        },
        "description": {
          "value": "CHECKCARD PURCHASE - BOOKS AND NEWS",
          "source": {
            "content": "CHECKCARD PURCHASE - BOOKS AND NEWS",
            "anchor": {
              "kind": "pdf_bbox",
              "page": 3,
              "left": 0.1992,
              "top": 0.8476,
              "width": 0.2996,
              "height": 0.0116
            }
          }
        },
        "amount": {
          "value": -42.1,
          "source": {
            "content": "$42.10",
            "anchor": {
     
[truncated — showing 100000 of 163436 characters]
components/viewers/sample-data/image-sources.json
[
  {
    "key": "title",
    "label": "Title",
    "value": "An Image is Worth 16x16 Words",
    "source": {
      "content": "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.1772,
        "top": 0.1034,
        "width": 0.6457,
        "height": 0.0446
      }
    }
  },
  {
    "key": "firstAuthor",
    "label": "First author",
    "value": "Alexey Dosovitskiy",
    "source": {
      "content": "Alexey Dosovitskiy",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.2313,
        "top": 0.1739,
        "width": 0.1314,
        "height": 0.0113
      }
    }
  },
  {
    "key": "affiliation",
    "label": "Affiliation",
    "value": "Google Research, Brain Team",
    "source": {
      "content": "Google Research, Brain Team",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.4118,
        "top": 0.2417,
        "width": 0.196,
        "height": 0.0112
      }
    }
  },
  {
    "key": "firstAuthorEmail",
    "label": "First author email",
    "value": "adosovitskiy@google.com",
    "source": {
      "content": "{adosovitskiy, neilhoulsby}@google.com",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.3258,
        "top": 0.2575,
        "width": 0.3679,
        "height": 0.0119
      }
    }
  },
  {
    "key": "secondAuthor",
    "label": "Second author",
    "value": "Lucas Beyer",
    "source": {
      "content": "Lucas Beyer",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.3882,
        "top": 0.1739,
        "width": 0.0862,
        "height": 0.0113
      }
    }
  },
  {
    "key": "model",
    "label": "Proposed model",
    "value": "Vision Transformer (ViT)",
    "source": {
      "content": "Vision Transformer (ViT)",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.4868,
        "top": 0.4533,
        "width": 0.1682,
        "height": 0.0112
      }
    }
  },
  {
    "key": "benchmark",
    "label": "Benchmarks",
    "value": "ImageNet, CIFAR-100, VTAB",
    "source": {
      "content": "ImageNet, CIFAR-100, VTAB",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.2349,
        "top": 0.4533,
        "width": 0.2481,
        "height": 0.0112
      }
    }
  },
  {
    "key": "venue",
    "label": "Venue",
    "value": "ICLR 2021",
    "source": {
      "content": "Published as a conference paper at ICLR 2021",
      "anchor": {
        "kind": "image_bbox",
        "left": 0.1765,
        "top": 0.0361,
        "width": 0.3024,
        "height": 0.0112
      }
    }
  }
]
components/viewers/sample-data/text-sources.json
[
  {
    "key": "runId",
    "label": "Run ID",
    "value": "run_3xK9b2QvT7",
    "source": {
      "content": "run_id=run_3xK9b2QvT7",
      "anchor": { "kind": "text_span", "line_start": 2, "line_end": 2 }
    }
  },
  {
    "key": "tenant",
    "label": "Tenant",
    "value": "acme-corp",
    "source": {
      "content": "tenant=acme-corp environment=prod",
      "anchor": { "kind": "text_span", "line_start": 3, "line_end": 3 }
    }
  },
  {
    "key": "environment",
    "label": "Environment",
    "value": "prod",
    "source": {
      "content": "tenant=acme-corp environment=prod",
      "anchor": { "kind": "text_span", "line_start": 3, "line_end": 3 }
    }
  },
  {
    "key": "fileName",
    "label": "File name",
    "value": "bank-statement.pdf",
    "source": {
      "content": "received file bank-statement.pdf (80.1 KB)",
      "anchor": { "kind": "text_span", "line_start": 4, "line_end": 4 }
    }
  },
  {
    "key": "fileSize",
    "label": "File size",
    "value": "80.1 KB",
    "source": {
      "content": "received file bank-statement.pdf (80.1 KB)",
      "anchor": { "kind": "text_span", "line_start": 4, "line_end": 4 }
    }
  },
  {
    "key": "sha256",
    "label": "SHA-256",
    "value": "4f1a…c0de",
    "source": {
      "content": "sha256=4f1a...c0de",
      "anchor": { "kind": "text_span", "line_start": 5, "line_end": 5 }
    }
  },
  {
    "key": "mimeType",
    "label": "MIME type",
    "value": "application/pdf",
    "source": {
      "content": "mime=application/pdf",
      "anchor": { "kind": "text_span", "line_start": 6, "line_end": 6 }
    }
  },
  {
    "key": "documentType",
    "label": "Document type",
    "value": "bank_statement",
    "source": {
      "content": "detected document_type=bank_statement",
      "anchor": { "kind": "text_span", "line_start": 7, "line_end": 7 }
    }
  },
  {
    "key": "classifierConfidence",
    "label": "Classifier confidence",
    "value": "0.984",
    "source": {
      "content": "confidence=0.984",
      "anchor": { "kind": "text_span", "line_start": 8, "line_end": 8 }
    }
  },
  {
    "key": "pageCount",
    "label": "Page count",
    "value": "4",
    "source": {
      "content": "page_count=4",
      "anchor": { "kind": "text_span", "line_start": 9, "line_end": 9 }
    }
  },
  {
    "key": "ocrDpi",
    "label": "OCR resolution",
    "value": "192 dpi",
    "source": {
      "content": "rasterizing pages at 192 dpi",
      "anchor": { "kind": "text_span", "line_start": 11, "line_end": 11 }
    }
  },
  {
    "key": "model",
    "label": "Extraction model",
    "value": "retab-large",
    "source": {
      "content": "model=retab-large n_consensus=3",
      "anchor": { "kind": "text_span", "line_start": 16, "line_end": 16 }
    }
  },
  {
    "key": "consensusRuns",
    "label": "Consensus runs",
    "value": "3",
    "source": {
      "content": "model=retab-large n_consensus=3",
      "anchor": { "kind": "text_span", "line_start": 16, "line_end": 16 }
    }
  },
  {
    "key": "schema",
    "label": "Schema",
    "value": "bank_statement_v2",
    "source": {
      "content": "schema=bank_statement_v2 fields=18",
      "anchor": { "kind": "text_span", "line_start": 17, "line_end": 17 }
    }
  },
  {
    "key": "schemaFields",
    "label": "Schema fields",
    "value": "18",
    "source": {
      "content": "schema=bank_statement_v2 fields=18",
      "anchor": { "kind": "text_span", "line_start": 17, "line_end": 17 }
    }
  },
  {
    "key": "meanLikelihood",
    "label": "Mean likelihood",
    "value": "0.91",
    "source": {
      "content": "merged 3 runs, mean_likelihood=0.91",
      "anchor": { "kind": "text_span", "line_start": 21, "line_end": 21 }
    }
  },
  {
    "key": "lowAgreementField",
    "label": "Low-agreement field",
    "value": "transactions.7.amount",
    "source": {
      "content": "field transactions.7.amount low agreement (0.58)",
      "anchor": { "kind": "text_span", "line_start": 22, "line_end": 22 }
    }
  },
  {
    "key": "fieldsGrounded",
    "label": "Fields grounded",
    "value": "16 / 18",
    "source": {
      "content": "16/18 fields grounded",
      "anchor": { "kind": "text_span", "line_start": 26, "line_end": 26 }
    }
  },
  {
    "key": "reviewQueue",
    "label": "Review queue",
    "value": "finance-ops",
    "source": {
      "content": "assigned to queue=finance-ops",
      "anchor": { "kind": "text_span", "line_start": 29, "line_end": 29 }
    }
  },
  {
    "key": "promptTokens",
    "label": "Prompt tokens",
    "value": "14,820",
    "source": {
      "content": "prompt_tokens=14820 completion_tokens=2110",
      "anchor": { "kind": "text_span", "line_start": 30, "line_end": 30 }
    }
  },
  {
    "key": "completionTokens",
    "label": "Completion tokens",
    "value": "2,110",
    "source": {
      "content": "prompt_tokens=14820 completion_tokens=2110",
      "anchor": { "kind": "text_span", "line_start": 30, "line_end": 30 }
    }
  },
  {
    "key": "totalTokens",
    "label": "Total tokens",
    "value": "16,930",
    "source": {
      "content": "total_tokens=16930",
      "anchor": { "kind": "text_span", "line_start": 31, "line_end": 31 }
    }
  },
  {
    "key": "status",
    "label": "Run status",
    "value": "completed",
    "source": {
      "content": "run status=completed",
      "anchor": { "kind": "text_span", "line_start": 32, "line_end": 32 }
    }
  },
  {
    "key": "durationMs",
    "label": "Duration",
    "value": "11,842 ms",
    "source": {
      "content": "duration_ms=11842",
      "anchor": { "kind": "text_span", "line_start": 33, "line_end": 33 }
    }
  }
]
components/viewers/sample-data/csv-sources.json
[
  {
    "key": "naQ1Revenue",
    "label": "North America Q1 revenue",
    "value": "$1,240,000",
    "source": {
      "content": "1240000",
      "anchor": { "kind": "csv_cell", "row": 1, "column": "C", "coordinate": "C2" }
    }
  },
  {
    "key": "naQ1Customers",
    "label": "North America Q1 customers",
    "value": "48",
    "source": {
      "content": "48",
      "anchor": { "kind": "csv_cell", "row": 1, "column": "D", "coordinate": "D2" }
    }
  },
  {
    "key": "naQ1Nrr",
    "label": "North America Q1 net revenue retention",
    "value": "1.12",
    "source": {
      "content": "1.12",
      "anchor": { "kind": "csv_cell", "row": 1, "column": "E", "coordinate": "E2" }
    }
  },
  {
    "key": "naQ2Revenue",
    "label": "North America Q2 revenue",
    "value": "$1,510,000",
    "source": {
      "content": "1510000",
      "anchor": { "kind": "csv_cell", "row": 2, "column": "C", "coordinate": "C3" }
    }
  },
  {
    "key": "naQ2Customers",
    "label": "North America Q2 customers",
    "value": "61",
    "source": {
      "content": "61",
      "anchor": { "kind": "csv_cell", "row": 2, "column": "D", "coordinate": "D3" }
    }
  },
  {
    "key": "naQ2Nrr",
    "label": "North America Q2 net revenue retention",
    "value": "1.21",
    "source": {
      "content": "1.21",
      "anchor": { "kind": "csv_cell", "row": 2, "column": "E", "coordinate": "E3" }
    }
  },
  {
    "key": "emeaQ1Revenue",
    "label": "EMEA Q1 revenue",
    "value": "$820,000",
    "source": {
      "content": "820000",
      "anchor": { "kind": "csv_cell", "row": 3, "column": "C", "coordinate": "C4" }
    }
  },
  {
    "key": "emeaQ1Customers",
    "label": "EMEA Q1 customers",
    "value": "33",
    "source": {
      "content": "33",
      "anchor": { "kind": "csv_cell", "row": 3, "column": "D", "coordinate": "D4" }
    }
  },
  {
    "key": "emeaQ2Revenue",
    "label": "EMEA Q2 revenue",
    "value": "$910,000",
    "source": {
      "content": "910000",
      "anchor": { "kind": "csv_cell", "row": 4, "column": "C", "coordinate": "C5" }
    }
  },
  {
    "key": "emeaQ2Customers",
    "label": "EMEA Q2 customers",
    "value": "39",
    "source": {
      "content": "39",
      "anchor": { "kind": "csv_cell", "row": 4, "column": "D", "coordinate": "D5" }
    }
  },
  {
    "key": "emeaQ2Nrr",
    "label": "EMEA Q2 net revenue retention",
    "value": "1.15",
    "source": {
      "content": "1.15",
      "anchor": { "kind": "csv_cell", "row": 4, "column": "E", "coordinate": "E5" }
    }
  },
  {
    "key": "apacQ1Revenue",
    "label": "APAC Q1 revenue",
    "value": "$430,000",
    "source": {
      "content": "430000",
      "anchor": { "kind": "csv_cell", "row": 5, "column": "C", "coordinate": "C6" }
    }
  },
  {
    "key": "apacQ1Nrr",
    "label": "APAC Q1 net revenue retention",
    "value": "1.04",
    "source": {
      "content": "1.04",
      "anchor": { "kind": "csv_cell", "row": 5, "column": "E", "coordinate": "E6" }
    }
  },
  {
    "key": "apacQ2Revenue",
    "label": "APAC Q2 revenue",
    "value": "$560,000",
    "source": {
      "content": "560000",
      "anchor": { "kind": "csv_cell", "row": 6, "column": "C", "coordinate": "C7" }
    }
  },
  {
    "key": "apacQ2Customers",
    "label": "APAC Q2 customers",
    "value": "24",
    "source": {
      "content": "24",
      "anchor": { "kind": "csv_cell", "row": 6, "column": "D", "coordinate": "D7" }
    }
  },
  {
    "key": "apacQ2Nrr",
    "label": "APAC Q2 net revenue retention",
    "value": "1.11",
    "source": {
      "content": "1.11",
      "anchor": { "kind": "csv_cell", "row": 6, "column": "E", "coordinate": "E7" }
    }
  }
]
components/viewers/sample-data/xlsx-sources.json
[
  {
    "key": "registrantName",
    "label": "Registrant name",
    "value": "NVIDIA CORP",
    "source": {
      "content": "NVIDIA CORP",
      "anchor": { "kind": "spreadsheet_cell", "row": 10, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B10" }
    }
  },
  {
    "key": "documentType",
    "label": "Document type",
    "value": "10-K",
    "source": {
      "content": "10-K",
      "anchor": { "kind": "spreadsheet_cell", "row": 4, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B4" }
    }
  },
  {
    "key": "periodEnd",
    "label": "Period end date",
    "value": "Jan. 28, 2024",
    "source": {
      "content": "Jan. 28,  2024",
      "anchor": { "kind": "spreadsheet_cell", "row": 6, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B6" }
    }
  },
  {
    "key": "fiscalYearEnd",
    "label": "Fiscal year end",
    "value": "--01-28",
    "source": {
      "content": "--01-28",
      "anchor": { "kind": "spreadsheet_cell", "row": 7, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B7" }
    }
  },
  {
    "key": "fileNumber",
    "label": "Entity file number",
    "value": "0-23985",
    "source": {
      "content": "0-23985",
      "anchor": { "kind": "spreadsheet_cell", "row": 9, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B9" }
    }
  },
  {
    "key": "incorporationState",
    "label": "Incorporation state",
    "value": "DE",
    "source": {
      "content": "DE",
      "anchor": { "kind": "spreadsheet_cell", "row": 11, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B11" }
    }
  },
  {
    "key": "taxId",
    "label": "Tax ID",
    "value": "94-3177549",
    "source": {
      "content": "94-3177549",
      "anchor": { "kind": "spreadsheet_cell", "row": 12, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B12" }
    }
  },
  {
    "key": "address",
    "label": "Address",
    "value": "2788 San Tomas Expressway",
    "source": {
      "content": "2788 San Tomas Expressway",
      "anchor": { "kind": "spreadsheet_cell", "row": 13, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B13" }
    }
  },
  {
    "key": "city",
    "label": "City",
    "value": "Santa Clara",
    "source": {
      "content": "Santa Clara",
      "anchor": { "kind": "spreadsheet_cell", "row": 14, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B14" }
    }
  },
  {
    "key": "state",
    "label": "State",
    "value": "CA",
    "source": {
      "content": "CA",
      "anchor": { "kind": "spreadsheet_cell", "row": 15, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B15" }
    }
  },
  {
    "key": "zip",
    "label": "ZIP code",
    "value": "95051",
    "source": {
      "content": "95051",
      "anchor": { "kind": "spreadsheet_cell", "row": 16, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B16" }
    }
  },
  {
    "key": "phone",
    "label": "Phone",
    "value": "(408) 486-2000",
    "source": {
      "content": "486-2000",
      "anchor": { "kind": "spreadsheet_cell", "row": 18, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B18" }
    }
  },
  {
    "key": "securityTitle",
    "label": "Security title",
    "value": "Common Stock, $0.001 par value",
    "source": {
      "content": "Common Stock, $0.001 par value per share",
      "anchor": { "kind": "spreadsheet_cell", "row": 19, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B19" }
    }
  },
  {
    "key": "tradingSymbol",
    "label": "Trading symbol",
    "value": "NVDA",
    "source": {
      "content": "NVDA",
      "anchor": { "kind": "spreadsheet_cell", "row": 20, "column": "B", "sheet_index": 0, "sheet_name": "Cover Page", "coordinate": "B20" }
    }
  },
  {
    "key": "auditorName",
    "label": "Auditor",
    "value": "PricewaterhouseCoopers LLP",
    "source": {
      "content": "PricewaterhouseCoopers LLP",
      "anchor": { "kind": "spreadsheet_cell", "row": 5, "column": "B", "sheet_index": 1, "sheet_name": "Audit Information", "coordinate": "B5" }
    }
  },
  {
    "key": "auditorLocation",
    "label": "Auditor location",
    "value": "San Jose, California",
    "source": {
      "content": "San Jose, California",
      "anchor": { "kind": "spreadsheet_cell", "row": 6, "column": "B", "sheet_index": 1, "sheet_name": "Audit Information", "coordinate": "B6" }
    }
  },
  {
    "key": "auditorFirmId",
    "label": "Auditor firm ID",
    "value": "238",
    "source": {
      "content": "238",
      "anchor": { "kind": "spreadsheet_cell", "row": 4, "column": "B", "sheet_index": 1, "sheet_name": "Audit Information", "coordinate": "B4" }
    }
  }
]
components/viewers/sample-data/docx-sources.json
[
  {
    "key": "title",
    "label": "Report title",
    "value": "Quarterly Business Review",
    "source": {
      "content": "Quarterly Business Review",
      "anchor": { "kind": "docx_text_span", "paragraph": 0 }
    }
  },
  {
    "key": "fiscalYear",
    "label": "Fiscal year",
    "value": "FY2026",
    "source": {
      "content": "Fiscal Year 2026 — Consolidated Report",
      "anchor": { "kind": "docx_text_span", "paragraph": 1 }
    }
  },
  {
    "key": "totalPages",
    "label": "Total pages",
    "value": "25",
    "source": {
      "content": "Total pages: 25",
      "anchor": { "kind": "docx_text_span", "paragraph": 3 }
    }
  },
  {
    "key": "execSummarySection",
    "label": "Executive summary section",
    "value": "Executive Summary",
    "source": {
      "content": "1. Executive Summary",
      "anchor": { "kind": "docx_text_span", "paragraph": 5 }
    }
  },
  {
    "key": "marketOverviewSection",
    "label": "Market overview section",
    "value": "Market Overview",
    "source": {
      "content": "2. Market Overview",
      "anchor": { "kind": "docx_text_span", "paragraph": 12 }
    }
  },
  {
    "key": "revenueSection",
    "label": "Revenue section",
    "value": "Revenue Analysis",
    "source": {
      "content": "3. Revenue Analysis",
      "anchor": { "kind": "docx_text_span", "paragraph": 19 }
    }
  },
  {
    "key": "costStructureSection",
    "label": "Cost structure section",
    "value": "Cost Structure",
    "source": {
      "content": "4. Cost Structure",
      "anchor": { "kind": "docx_text_span", "paragraph": 26 }
    }
  },
  {
    "key": "operatingMarginsSection",
    "label": "Operating margins section",
    "value": "Operating Margins",
    "source": {
      "content": "5. Operating Margins",
      "anchor": { "kind": "docx_text_span", "paragraph": 33 }
    }
  },
  {
    "key": "segmentPerformanceSection",
    "label": "Segment performance section",
    "value": "Segment Performance",
    "source": {
      "content": "6. Segment Performance",
      "anchor": { "kind": "docx_text_span", "paragraph": 40 }
    }
  },
  {
    "key": "cashFlowSection",
    "label": "Cash flow section",
    "value": "Cash Flow",
    "source": {
      "content": "11. Cash Flow",
      "anchor": { "kind": "docx_text_span", "paragraph": 75 }
    }
  },
  {
    "key": "balanceSheetSection",
    "label": "Balance sheet section",
    "value": "Balance Sheet Review",
    "source": {
      "content": "12. Balance Sheet Review",
      "anchor": { "kind": "docx_text_span", "paragraph": 82 }
    }
  },
  {
    "key": "riskSection",
    "label": "Risk section",
    "value": "Risk Factors",
    "source": {
      "content": "14. Risk Factors",
      "anchor": { "kind": "docx_text_span", "paragraph": 96 }
    }
  },
  {
    "key": "governanceSection",
    "label": "Governance section",
    "value": "Governance",
    "source": {
      "content": "19. Governance",
      "anchor": { "kind": "docx_text_span", "paragraph": 131 }
    }
  },
  {
    "key": "outlookSection",
    "label": "Outlook section",
    "value": "Outlook & Guidance",
    "source": {
      "content": "20. Outlook & Guidance",
      "anchor": { "kind": "docx_text_span", "paragraph": 138 }
    }
  },
  {
    "key": "methodologyAppendix",
    "label": "Methodology appendix",
    "value": "Appendix A: Methodology",
    "source": {
      "content": "23. Appendix A: Methodology",
      "anchor": { "kind": "docx_text_span", "paragraph": 159 }
    }
  }
]
components/json-form/array-fields.tsx
"use client";

import * as React from "react";
import { Plus, X } from "lucide-react";
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { DisclosureHeader } from "@/components/json-form/disclosure";
import type { RenderJsonFormField } from "@/components/json-form/field-renderer";
import {
  AUTO_COLLAPSE_DEPTH,
  CARD_VIRTUALIZE_THRESHOLD,
  LONG_ARRAY_THRESHOLD,
} from "@/components/json-form/json-form-constants";
import { useJsonFormStartsOpen } from "@/components/json-form/open-paths";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  emptyArrayItemFormValue,
  joinJsonFormPath,
  joinJsonSourcePath,
} from "@/components/json-form/path-codec";
import type { JsonFormTextInput } from "@/components/json-form/scalar-control";
import {
  arrayItemSchemaAt,
  canAppendArrayItem,
  canRemoveArrayItem,
  hasDynamicObjectProperties,
  scalarObjectColumns,
  type Schema,
} from "@/components/json-form/schema-model";
import { ArrayTable } from "@/components/json-form/table/array-table";
import { VirtualList } from "@/components/json-form/virtual-list";

export function JsonFormArray({
  name,
  sourcePath,
  schema,
  label,
  textInput,
  className,
  depth,
  renderField,
}: {
  name: string;
  sourcePath: string;
  schema: Schema;
  label: string;
  textInput?: JsonFormTextInput;
  className?: string;
  depth: number;
  renderField: RenderJsonFormField;
}) {
  const readOnly = useJsonFormReadOnly();
  const { control, getValues, setValue, unregister } = useFormContext();
  const { fields, append, remove } = useFieldArray({ control, name });
  const arrayValue = useWatch({ control, name });
  const renderedFields = React.useMemo(() => {
    // react-hook-form's useFieldArray compacts falsy primitive items (false,
    // 0, "") out of `fields`, so derive the rendered item count from the
    // watched array value instead of the compacted field list.
    if (!Array.isArray(arrayValue)) {
      return fields.map((field, index) => ({
        id: field.id ?? `${name}.${index}`,
      }));
    }
    return arrayValue.map((_, index) => ({
      id: fields[index]?.id ?? `${name}.${index}`,
    }));
  }, [arrayValue, fields, name]);
  const itemSchema = React.useMemo(
    () => arrayItemSchemaAt(schema, 0),
    [schema],
  );
  const isTupleArray = Array.isArray(schema.items);
  const hasDynamicItemProperties = React.useMemo(
    () => hasDynamicObjectProperties(itemSchema),
    [itemSchema],
  );
  const itemSchemaForIndex = React.useCallback(
    (index: number) => arrayItemSchemaAt(schema, index),
    [schema],
  );

  const columns = React.useMemo(
    () =>
      isTupleArray || hasDynamicItemProperties
        ? null
        : scalarObjectColumns(itemSchema),
    [hasDynamicItemProperties, isTupleArray, itemSchema],
  );

  const startsOpen = useJsonFormStartsOpen(
    sourcePath,
    depth < AUTO_COLLAPSE_DEPTH &&
      renderedFields.length <= LONG_ARRAY_THRESHOLD,
  );
  const [open, setOpen] = React.useState(startsOpen);
  const canAddItem =
    !readOnly && canAppendArrayItem(schema, renderedFields.length);
  const canRemoveItem =
    !readOnly && canRemoveArrayItem(schema, renderedFields.length);

  const add = React.useCallback(() => {
    const current = getValues(name);
    const nextIndex = Array.isArray(current)
      ? current.length
      : renderedFields.length;
    if (!canAppendArrayItem(schema, nextIndex)) return;
    const nextSchema = arrayItemSchemaAt(schema, nextIndex);
    const nextItem = emptyArrayItemFormValue(nextSchema);
    append(nextItem as never);
    if (Array.isArray(current)) {
      setValue(name, [...current, nextItem], { shouldDirty: true });
    }
    setOpen(true);
  }, [append, getValues, name, renderedFields.length, schema, setValue]);
  const removeAt = React.useCallback(
    (index: number) => {
      const current = getValues(name);
      if (Array.isArray(current)) {
        if (!canRemoveArrayItem(schema, current.length)) return;
        const next = current.slice();
        next.splice(index, 1);
        remove(index);
        setValue(name, next, { shouldDirty: true });
        unregister(`${name}.${next.length}`);
        return;
      }
      if (!canRemoveArrayItem(schema, renderedFields.length)) return;
      remove(index);
    },
    [
      getValues,
      name,
      remove,
      renderedFields.length,
      schema,
      setValue,
      unregister,
    ],
  );

  return (
    <div
      className={cn(
        "bg-background overflow-hidden rounded-lg border shadow-sm",
        className,
      )}
    >
      <DisclosureHeader
        open={open}
        onToggle={() => setOpen((o) => !o)}
        title={label}
        summary={`${renderedFields.length} item${renderedFields.length === 1 ? "" : "s"}`}
        description={schema.description}
        actions={
          readOnly ? null : (
            <Button
              type="button"
              size="sm"
              variant="outline"
              onClick={add}
              disabled={!canAddItem}
            >
              <Plus className="size-4" />
              Add
            </Button>
          )
        }
      />
      {open ? (
        <div className={cn("border-t", columns ? "" : "p-3")}>
          {renderedFields.length === 0 ? (
            <p className="text-muted-foreground text-sm">No items.</p>
          ) : columns ? (
            <ArrayTable
              name={name}
              sourcePath={sourcePath}
              fields={renderedFields}
              remove={removeAt}
              canRemove={canRemoveItem}
              columns={columns}
            />
          ) : (
            <ArrayCards
              name={name}
              sourcePath={sourcePath}
              fields={renderedFields}
              remove={removeAt}
              canRemove={canRemoveItem}
              itemSchemaForIndex={itemSchemaForIndex}
              label={label}
              textInput={textInput}
              depth={depth}
              renderField={renderField}
            />
          )}
        </div>
      ) : null}
    </div>
  );
}

interface ArrayCardsProps {
  name: string;
  sourcePath: string;
  fields: { id: string }[];
  remove: (index: number) => void;
  canRemove: boolean;
  itemSchemaForIndex: (index: number) => Schema;
  label: string;
  textInput?: JsonFormTextInput;
  depth: number;
  renderField: RenderJsonFormField;
}

function ArrayCards({
  name,
  sourcePath,
  fields,
  remove,
  canRemove,
  itemSchemaForIndex,
  label,
  textInput,
  depth,
  renderField,
}: ArrayCardsProps) {
  const renderCard = React.useCallback(
    (index: number) => (
      <ArrayCard
        name={name}
        sourcePath={sourcePath}
        index={index}
        remove={remove}
        canRemove={canRemove}
        itemSchema={itemSchemaForIndex(index)}
        label={label}
        textInput={textInput}
        depth={depth}
        renderField={renderField}
      />
    ),
    [
      name,
      sourcePath,
      remove,
      canRemove,
      itemSchemaForIndex,
      label,
      textInput,
      depth,
      renderField,
    ],
  );

  if (fields.length > CARD_VIRTUALIZE_THRESHOLD) {
    return (
      <VirtualList
        fields={fields}
        estimateSize={64}
        renderItem={renderCard}
        gap={8}
      />
    );
  }

  return (
    <div className="space-y-2">
      {fields.map((entry, index) => (
        <React.Fragment key={entry.id}>{renderCard(index)}</React.Fragment>
      ))}
    </div>
  );
}

const ArrayCard = React.memo(function ArrayCard({
  name,
  sourcePath,
  index,
  remove,
  canRemove,
  itemSchema,
  label,
  textInput,
  depth,
  renderField,
}: {
  name: string;
  sourcePath: string;
  index: number;
  remove: (index: number) => void;
  canRemove: boolean;
  itemSchema: Schema;
  label: string;
  textInput?: JsonFormTextInput;
  depth: number;
  renderField: RenderJsonFormField;
}) {
  const readOnly = useJsonFormReadOnly();

  return (
    <div className="flex items-start gap-2">
      <div className="min-w-0 flex-1">
        {renderField({
          name: joinJsonFormPath(name, index),
          sourcePath: joinJsonSourcePath(sourcePath, index),
          schema: itemSchema,
          label: `${label} ${index + 1}`,
          textInput,
          depth: depth + 1,
        })}
      </div>
      {readOnly ? null : (
        <Button
          type="button"
          size="icon"
          variant="ghost"
          className="text-muted-foreground hover:border-border hover:text-destructive mt-1 border-transparent hover:bg-transparent"
          onClick={() => remove(index)}
          aria-label="Remove item"
          disabled={!canRemove}
        >
          <X className="size-4" />
        </Button>
      )}
    </div>
  );
});
components/json-form/disclosure.tsx
"use client";

import * as React from "react";
import { ChevronRight } from "lucide-react";

import { cn } from "@/lib/utils";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";

/**
 * Wraps a label element so a field description shows as a hover tooltip on the
 * label itself rather than a body-text block that changes row height.
 */
export function WithDescription({
  text,
  children,
}: {
  text?: string;
  children: React.ReactElement;
}) {
  if (!text) return children;
  return (
    <Tooltip>
      <TooltipTrigger asChild>{children}</TooltipTrigger>
      <TooltipContent className="max-w-xs text-left whitespace-pre-line">
        {text}
      </TooltipContent>
    </Tooltip>
  );
}

export function DisclosureHeader({
  open,
  onToggle,
  title,
  summary,
  description,
  actions,
}: {
  open: boolean;
  onToggle: () => void;
  title: string;
  summary?: string;
  description?: string;
  actions?: React.ReactNode;
}) {
  return (
    <div className="flex items-center gap-1 px-2 py-1.5">
      <button
        type="button"
        onClick={onToggle}
        aria-expanded={open}
        aria-label={summary ? `${title} ${summary}` : title}
        className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
      >
        <ChevronRight
          className={cn(
            "text-muted-foreground size-4 shrink-0 transition-transform",
            open && "rotate-90",
          )}
        />
        <WithDescription text={description}>
          <span className="truncate text-sm font-medium">{title}</span>
        </WithDescription>
        {summary ? (
          <span className="text-muted-foreground shrink-0 text-xs">
            {summary}
          </span>
        ) : null}
      </button>
      {actions}
    </div>
  );
}
components/json-form/field-renderer.ts
import type * as React from "react";

import type { JsonFormTextInput } from "@/components/json-form/scalar-control";
import type { Schema } from "@/components/json-form/schema-model";

export interface JsonFormFieldRenderProps {
  name: string;
  sourcePath?: string;
  schema: Schema;
  required?: boolean;
  label?: string;
  textInput?: JsonFormTextInput;
  className?: string;
  depth?: number;
}

export type RenderJsonFormField = (
  props: JsonFormFieldRenderProps,
) => React.ReactNode;
components/json-form/form-primitives.tsx
"use client";

import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { CheckIcon } from "lucide-react";
import {
  Controller,
  FormProvider,
  useFormContext,
  useFormState,
  type ControllerProps,
  type FieldPath,
  type FieldValues,
} from "react-hook-form";

import { cn } from "@/lib/utils";

export const Form = FormProvider;

type FormFieldContextValue<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
  name: TName;
};

const FormFieldContext = React.createContext<FormFieldContextValue | null>(
  null,
);

export function FormField<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(props: ControllerProps<TFieldValues, TName>) {
  return (
    <FormFieldContext.Provider value={{ name: props.name }}>
      <Controller {...props} />
    </FormFieldContext.Provider>
  );
}

type FormItemContextValue = {
  id: string;
};

const FormItemContext = React.createContext<FormItemContextValue | null>(null);

function useFormField() {
  const fieldContext = React.useContext(FormFieldContext);
  const itemContext = React.useContext(FormItemContext);
  const { getFieldState } = useFormContext();

  if (!fieldContext) {
    throw new Error("useFormField should be used within <FormField>");
  }
  if (!itemContext) {
    throw new Error("useFormField should be used within <FormItem>");
  }

  const formState = useFormState({ name: fieldContext.name });
  const fieldState = getFieldState(fieldContext.name, formState);
  const { id } = itemContext;

  return {
    id,
    name: fieldContext.name,
    formItemId: `${id}-form-item`,
    formDescriptionId: `${id}-form-item-description`,
    formMessageId: `${id}-form-item-message`,
    ...fieldState,
  };
}

export function FormItem({ className, ...props }: React.ComponentProps<"div">) {
  const id = React.useId();

  return (
    <FormItemContext.Provider value={{ id }}>
      <div
        data-slot="form-item"
        className={cn("grid gap-2", className)}
        {...props}
      />
    </FormItemContext.Provider>
  );
}

export function FormLabel({
  className,
  ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
  const { error, formItemId } = useFormField();

  return (
    <LabelPrimitive.Root
      data-slot="form-label"
      data-error={!!error}
      className={cn("data-[error=true]:text-destructive", className)}
      htmlFor={formItemId}
      {...props}
    />
  );
}

export function FormControl(props: React.ComponentProps<typeof Slot>) {
  const { error, formItemId, formDescriptionId, formMessageId } =
    useFormField();
  const { children, ...controlProps } = props;
  const fieldProps = {
    "data-slot": "form-control",
    id: formItemId,
    "aria-describedby": error
      ? `${formDescriptionId} ${formMessageId}`
      : `${formDescriptionId}`,
    "aria-invalid": !!error,
    ...controlProps,
  };

  if (React.isValidElement(children)) {
    return React.cloneElement(
      children as React.ReactElement<Record<string, unknown>>,
      fieldProps,
    );
  }

  return <Slot {...fieldProps} />;
}

export function FormMessage({
  className,
  ...props
}: React.ComponentProps<"p">) {
  const { error, formMessageId } = useFormField();
  const body = error ? String(error.message ?? "") : props.children;

  if (!body) return null;

  return (
    <p
      data-slot="form-message"
      id={formMessageId}
      className={cn("text-destructive text-sm", className)}
      {...props}
    >
      {body}
    </p>
  );
}

export function Checkbox({
  className,
  ...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
  return (
    <CheckboxPrimitive.Root
      data-slot="checkbox"
      className={cn(
        "peer border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
        className,
      )}
      {...props}
    >
      <CheckboxPrimitive.Indicator
        data-slot="checkbox-indicator"
        className="flex items-center justify-center text-current transition-none"
      >
        <CheckIcon className="size-3.5" />
      </CheckboxPrimitive.Indicator>
    </CheckboxPrimitive.Root>
  );
}

export function Textarea({
  className,
  ...props
}: React.ComponentProps<"textarea">) {
  return (
    <textarea
      data-slot="textarea"
      className={cn(
        "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40 flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-none transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        className,
      )}
      {...props}
    />
  );
}
components/json-form/json-form-constants.ts
export const AUTO_COLLAPSE_DEPTH = 1;
export const CARD_VIRTUALIZE_THRESHOLD = 30;
export const LONG_ARRAY_THRESHOLD = 8;
components/json-form/json-form.tsx
"use client";

import * as React from "react";
import { type SubmitHandler, type UseFormReturn } from "react-hook-form";

import { cn } from "@/lib/utils";
import { JsonFormArray } from "@/components/json-form/array-fields";
import { WithDescription } from "@/components/json-form/disclosure";
import type { JsonFormFieldRenderProps } from "@/components/json-form/field-renderer";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/json-form/form-primitives";
import {
  JsonFormObject,
  JsonFormRootFields,
} from "@/components/json-form/object-fields";
import { JsonFormOpenPathsContext } from "@/components/json-form/open-paths";
import {
  decodeJsonFormValue,
  encodeJsonFormValue,
  schemaNeedsJsonFormPathEncoding,
} from "@/components/json-form/path-codec";
import { JsonFormReadOnlyProvider } from "@/components/json-form/read-only";
import {
  BooleanControl,
  NullableBooleanControl,
  ScalarControl,
  type JsonFormTextInput,
} from "@/components/json-form/scalar-control";
import {
  expandRefs,
  fieldKind,
  labelFor,
  unwrapNullable,
  type Schema,
} from "@/components/json-form/schema-model";
import {
  JsonFormSourceLinkProvider,
  SourceLinkShell,
  type JsonFormSourceLink,
} from "@/components/json-form/source-link";
import { useKeyedMountEffect } from "@/hooks/use-keyed-mount-effect";
import { joinEffectKey } from "@/lib/effect-key";

export type { JsonFormTextInput } from "@/components/json-form/scalar-control";

/**
 * A JSON-Schema-driven form built entirely on shadcn's `FormField` abstraction.
 *
 * Each schema property is rendered through `<FormField>` →
 * `<FormItem>/<FormLabel>/<FormControl>/<FormDescription>/<FormMessage>`, so it
 * inherits shadcn's react-hook-form wiring, accessibility, and error display
 * with zero bespoke styling. Objects nest, arrays repeat, and scalars map to the
 * matching control. Drop it inside your own `useForm()` instance.
 *
 * Built to scale to deep, repetitive documents (e.g. an extraction with
 * `properties[] → production[] → line_items[]`):
 *
 *  - **Lazy mount.** Nested objects and arrays are collapsible; their children
 *    are only mounted in the DOM while expanded, so a 5,000-field tree boots as
 *    a handful of summary rows.
 *  - **Table mode.** An array whose items are flat objects of scalars renders as
 *    a dense editable table (one row per item, one column per field) instead of
 *    a stack of bordered cards.
 *  - **Virtualization.** Long arrays (card *or* table mode) window their rows
 *    through local row virtualizers, so only the visible items are in the DOM.
 *  - **Isolated re-renders.** Each row subscribes to its own field state, so a
 *    keystroke in one item never re-renders its siblings.
 */

// ---------------------------------------------------------------------------
// JsonFormField — the unit of composition
// ---------------------------------------------------------------------------

export type JsonFormFieldProps = JsonFormFieldRenderProps;

export function JsonFormField({
  name,
  sourcePath,
  schema: rawSchema,
  required = false,
  label,
  textInput,
  className,
  depth = 0,
}: JsonFormFieldProps) {
  const expandedSchema = React.useMemo(
    () => expandRefs(rawSchema),
    [rawSchema],
  );
  const { schema, nullable } = unwrapNullable(expandedSchema);
  const kind = fieldKind(schema);
  const heading = labelFor(name, schema, label);
  const resolvedSourcePath = sourcePath ?? name;

  if (kind === "object") {
    return (
      <JsonFormObject
        name={name}
        sourcePath={resolvedSourcePath}
        schema={schema}
        label={heading}
        textInput={textInput}
        className={className}
        depth={depth}
        renderField={renderJsonFormField}
      />
    );
  }

  if (kind === "array") {
    return (
      <JsonFormArray
        name={name}
        sourcePath={resolvedSourcePath}
        schema={schema}
        label={heading}
        textInput={textInput}
        className={className}
        depth={depth}
        renderField={renderJsonFormField}
      />
    );
  }

  if (kind === "boolean") {
    if (nullable) {
      return (
        <SourceLinkShell sourcePath={resolvedSourcePath}>
          <FormField
            name={name}
            render={({ field }) => (
              <FormItem className={className}>
                <WithDescription text={schema.description}>
                  <FormLabel>
                    {heading}
                    {required ? (
                      <span className="text-destructive"> *</span>
                    ) : null}
                  </FormLabel>
                </WithDescription>
                <FormControl>
                  <NullableBooleanControl
                    field={field}
                    label={`${heading}${required ? " *" : ""}`}
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
        </SourceLinkShell>
      );
    }

    return (
      <SourceLinkShell sourcePath={resolvedSourcePath}>
        <FormField
          name={name}
          render={({ field }) => (
            <FormItem className={className}>
              <WithDescription text={schema.description}>
                <FormLabel>
                  {heading}
                  {required ? (
                    <span className="text-destructive"> *</span>
                  ) : null}
                </FormLabel>
              </WithDescription>
              <FormControl>
                <BooleanControl
                  field={field}
                  label={`${heading}${required ? " *" : ""}`}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
      </SourceLinkShell>
    );
  }

  return (
    <SourceLinkShell sourcePath={resolvedSourcePath}>
      <FormField
        name={name}
        render={({ field }) => (
          <FormItem className={className}>
            <WithDescription text={schema.description}>
              <FormLabel>
                {heading}
                {required ? <span className="text-destructive"> *</span> : null}
              </FormLabel>
            </WithDescription>
            <FormControl>
              <ScalarControl
                kind={kind}
                schema={schema}
                field={field}
                textInput={textInput}
                nullable={nullable}
              />
            </FormControl>
            <FormMessage />
          </FormItem>
        )}
      />
    </SourceLinkShell>
  );
}

function renderJsonFormField(props: JsonFormFieldRenderProps) {
  return <JsonFormField {...props} />;
}

// ---------------------------------------------------------------------------
// JsonForm — convenience wrapper over a whole schema
// ---------------------------------------------------------------------------

export interface JsonFormProps {
  form: UseFormReturn<Record<string, unknown>>;
  schema: Schema;
  onSubmit?: SubmitHandler<Record<string, unknown>>;
  className?: string;
  /** Force plain string fields to render as single-line inputs or textareas. */
  textInput?: JsonFormTextInput;
  /**
   * Opt into field-level source linking. When set, every scalar field becomes a
   * hoverable card that reports its path and highlights when active — wire it
   * straight from a source field link.
   */
  sourceLink?: JsonFormSourceLink;
  /**
   * Source/logical paths that should start expanded. Intended for controlled
   * demos and benchmarks that need a deep virtualized body mounted immediately.
   */
  defaultOpenPaths?: readonly string[];
  /**
   * Render every control inert. Values stay visible (and selectable) but
   * cannot be changed, and array add/remove affordances are dropped. Use it on
   * viewer surfaces that never submit the form.
   */
  readOnly?: boolean;
  /** Rendered after the fields, e.g. a submit button. */
  children?: React.ReactNode;
}

export function JsonForm({
  form,
  schema,
  onSubmit,
  className,
  textInput,
  sourceLink,
  defaultOpenPaths,
  readOnly = false,
  children,
}: JsonFormProps) {
  const expandedSchema = React.useMemo(() => expandRefs(schema), [schema]);
  const usesEncodedPaths = React.useMemo(
    () => schemaNeedsJsonFormPathEncoding(expandedSchema),
    [expandedSchema],
  );
  const defaultOpenPathSet = React.useMemo(
    () =>
      defaultOpenPaths && defaultOpenPaths.length > 0
        ? new Set(defaultOpenPaths)
        : null,
    [defaultOpenPaths],
  );
  const hasEncodedInitialValuesRef = React.useRef(false);

  useKeyedMountEffect(
    joinEffectKey([expandedSchema, form, usesEncodedPaths]),
    () => {
      if (!usesEncodedPaths || hasEncodedInitialValuesRef.current) {
        return;
      }
      hasEncodedInitialValuesRef.current = true;
      form.reset(
        encodeJsonFormValue(expandedSchema, form.getValues()) as Record<
          string,
          unknown
        >,
      );
    },
  );

  const handleSubmit = React.useCallback(
    (event: React.FormEvent) => {
      if (!onSubmit) {
        event.preventDefault();
        return;
      }
      const activeElement = event.currentTarget.ownerDocument.activeElement;
      if (
        activeElement instanceof HTMLElement &&
        event.currentTarget.contains(activeElement)
      ) {
        activeElement.blur();
      }
      return form.handleSubmit((data, submitEvent) => {
        const decoded = usesEncodedPaths
          ? (decodeJsonFormValue(expandedSchema, data) as Record<
              string,
              unknown
            >)
          : data;
        return onSubmit(decoded, submitEvent);
      })(event);
    },
    [expandedSchema, form, onSubmit, usesEncodedPaths],
  );

  return (
    <JsonFormSourceLinkProvider sourceLink={sourceLink}>
      <JsonFormReadOnlyProvider readOnly={readOnly}>
        <JsonFormOpenPathsContext.Provider value={defaultOpenPathSet}>
          <Form {...form}>
            <form
              onSubmit={handleSubmit}
              className={cn("space-y-4", className)}
            >
              <JsonFormRootFields
                schema={expandedSchema}
                textInput={textInput}
                renderField={renderJsonFormField}
              />
              {children}
            </form>
          </Form>
        </JsonFormOpenPathsContext.Provider>
      </JsonFormReadOnlyProvider>
    </JsonFormSourceLinkProvider>
  );
}
components/json-form/object-fields.tsx
"use client";

import * as React from "react";
import { useFormContext, useWatch } from "react-hook-form";

import { cn } from "@/lib/utils";
import { DisclosureHeader } from "@/components/json-form/disclosure";
import type { RenderJsonFormField } from "@/components/json-form/field-renderer";
import { AUTO_COLLAPSE_DEPTH } from "@/components/json-form/json-form-constants";
import { useJsonFormStartsOpen } from "@/components/json-form/open-paths";
import {
  dynamicPropertyEntries,
  joinJsonFormPath,
  joinJsonSourcePath,
  staticPropertyKeys,
} from "@/components/json-form/path-codec";
import type { JsonFormTextInput } from "@/components/json-form/scalar-control";
import {
  labelFor,
  schemaProperties,
  type Schema,
} from "@/components/json-form/schema-model";

export function JsonFormObject({
  name,
  sourcePath,
  schema,
  label,
  textInput,
  className,
  depth,
  renderField,
}: {
  name: string;
  sourcePath: string;
  schema: Schema;
  label: string;
  textInput?: JsonFormTextInput;
  className?: string;
  depth: number;
  renderField: RenderJsonFormField;
}) {
  const { control, getValues } = useFormContext();
  const properties = React.useMemo(() => schemaProperties(schema), [schema]);
  const required = React.useMemo(
    () => new Set(schema.required ?? []),
    [schema],
  );
  const entries = React.useMemo(() => Object.entries(properties), [properties]);
  const currentValue = useWatch({
    control,
    name,
    defaultValue: getValues(name),
  }) as unknown;
  const staticKeys = React.useMemo(() => staticPropertyKeys(schema), [schema]);
  const dynamicEntries = React.useMemo(
    () => dynamicPropertyEntries(schema, currentValue, staticKeys),
    [currentValue, schema, staticKeys],
  );
  const fieldCount = entries.length + dynamicEntries.length;
  const startsOpen = useJsonFormStartsOpen(
    sourcePath,
    depth < AUTO_COLLAPSE_DEPTH,
  );
  const [open, setOpen] = React.useState(startsOpen);

  return (
    <div className={cn("rounded-lg border", className)}>
      <DisclosureHeader
        open={open}
        onToggle={() => setOpen((o) => !o)}
        title={label}
        summary={`${fieldCount} field${fieldCount === 1 ? "" : "s"}`}
        description={schema.description}
      />
      {open ? (
        <div className="space-y-3 border-t p-3">
          {entries.map(([key, child]) =>
            typeof child === "object" ? (
              <React.Fragment key={key}>
                {renderField({
                  name: joinJsonFormPath(name, key),
                  sourcePath: joinJsonSourcePath(sourcePath, key),
                  schema: child,
                  required: required.has(key),
                  label: labelFor(key, child),
                  textInput,
                  depth: depth + 1,
                })}
              </React.Fragment>
            ) : null,
          )}
          {dynamicEntries.map(({ key, schema: child }) => (
            <React.Fragment key={key}>
              {renderField({
                name: joinJsonFormPath(name, key),
                sourcePath: joinJsonSourcePath(sourcePath, key),
                schema: child,
                label: key,
                textInput,
                depth: depth + 1,
              })}
            </React.Fragment>
          ))}
        </div>
      ) : null}
    </div>
  );
}

export function JsonFormRootFields({
  schema,
  textInput,
  renderField,
}: {
  schema: Schema;
  textInput?: JsonFormTextInput;
  renderField: RenderJsonFormField;
}) {
  const { control, getValues } = useFormContext();
  const properties = schemaProperties(schema);
  const required = new Set(schema.required ?? []);
  const entries = Object.entries(properties);
  const currentValue = useWatch({
    control,
    defaultValue: getValues(),
  }) as unknown;
  const staticKeys = React.useMemo(() => staticPropertyKeys(schema), [schema]);
  const dynamicEntries = React.useMemo(
    () => dynamicPropertyEntries(schema, currentValue, staticKeys),
    [currentValue, schema, staticKeys],
  );

  return (
    <>
      {entries.map(([key, child]) =>
        typeof child === "object" ? (
          <React.Fragment key={key}>
            {renderField({
              name: joinJsonFormPath("", key),
              sourcePath: key,
              schema: child,
              required: required.has(key),
              label: labelFor(key, child),
              textInput,
              depth: 0,
            })}
          </React.Fragment>
        ) : null,
      )}
      {dynamicEntries.map(({ key, schema: child }) => (
        <React.Fragment key={key}>
          {renderField({
            name: joinJsonFormPath("", key),
            sourcePath: key,
            schema: child,
            label: key,
            textInput,
            depth: 0,
          })}
        </React.Fragment>
      ))}
    </>
  );
}
components/json-form/open-paths.tsx
"use client";

import * as React from "react";

export const JsonFormOpenPathsContext =
  React.createContext<ReadonlySet<string> | null>(null);

export function useJsonFormStartsOpen(sourcePath: string, fallback: boolean) {
  return (
    React.useContext(JsonFormOpenPathsContext)?.has(sourcePath) ?? fallback
  );
}
components/json-form/path-codec.ts
import {
  arrayItemSchemaAt,
  dynamicPropertySchemaFor,
  emptyValueFor,
  fieldKind,
  isRecordValue,
  schemaPatternProperties,
  schemaProperties,
  unwrapNullable,
  type Schema,
} from "@/components/json-form/schema-model";

export function encodeJsonFormKey(segment: string): string {
  return encodeURIComponent(segment)
    .replace(
      /[!'()*]/g,
      (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
    )
    .replace(
      /[.[\]'"]/g,
      (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
    );
}

export function decodeJsonFormKey(segment: string): string {
  try {
    return decodeURIComponent(segment);
  } catch {
    return segment;
  }
}

export function joinJsonFormPath(parent: string, key: string | number): string {
  const segment =
    typeof key === "number" ? String(key) : encodeJsonFormKey(key);
  return parent ? `${parent}.${segment}` : segment;
}

export function joinJsonSourcePath(
  parent: string,
  key: string | number,
): string {
  const segment = String(key);
  return parent ? `${parent}.${segment}` : segment;
}

export function staticPropertyKeys(schema: Schema): Set<string> {
  return new Set(
    Object.keys(schemaProperties(schema)).flatMap((key) => [
      key,
      encodeJsonFormKey(key),
    ]),
  );
}

export function dynamicPropertyEntries(
  schema: Schema,
  currentValue: unknown,
  staticKeys: Set<string>,
): Array<{ key: string; schema: Schema }> {
  if (!isRecordValue(currentValue)) return [];
  return Object.keys(currentValue).flatMap((key) => {
    if (staticKeys.has(key)) return [];
    const decodedKey = decodeJsonFormKey(key);
    if (staticKeys.has(decodedKey)) return [];
    const childSchema = dynamicPropertySchemaFor(schema, decodedKey);
    return childSchema ? [{ key: decodedKey, schema: childSchema }] : [];
  });
}

export function schemaNeedsJsonFormPathEncoding(schema: Schema): boolean {
  const { schema: inner } = unwrapNullable(schema);
  const kind = fieldKind(inner);
  if (kind === "object") {
    const properties = schemaProperties(inner);
    const patternProperties = schemaPatternProperties(inner);
    return (
      isRecordValue(inner.additionalProperties) ||
      Object.values(patternProperties).some(isRecordValue) ||
      Object.entries(properties).some(([key, child]) => {
        return (
          encodeJsonFormKey(key) !== key ||
          (typeof child === "object" &&
            child !== null &&
            schemaNeedsJsonFormPathEncoding(child))
        );
      })
    );
  }
  if (kind === "array" && typeof inner.items === "object" && inner.items) {
    if (Array.isArray(inner.items)) {
      return inner.items.some((item) =>
        isRecordValue(item)
          ? schemaNeedsJsonFormPathEncoding(item as Schema)
          : false,
      );
    }
    return schemaNeedsJsonFormPathEncoding(inner.items as Schema);
  }
  return false;
}

export function encodeJsonFormValue(schema: Schema, value: unknown): unknown {
  const { schema: inner } = unwrapNullable(schema);
  const kind = fieldKind(inner);

  if (kind === "array") {
    if (!Array.isArray(value)) return value;
    return value.map((item, index) =>
      encodeJsonFormValue(arrayItemSchemaAt(inner, index), item),
    );
  }

  if (kind !== "object" || !isRecordValue(value)) return value;

  const properties = schemaProperties(inner);
  const encoded: Record<string, unknown> = {};
  for (const [key, child] of Object.entries(properties)) {
    if (typeof child !== "object" || child === null) continue;
    const encodedKey = encodeJsonFormKey(key);
    const rawValue = Object.prototype.hasOwnProperty.call(value, key)
      ? value[key]
      : value[encodedKey];
    if (
      rawValue !== undefined ||
      Object.prototype.hasOwnProperty.call(value, key)
    ) {
      encoded[encodedKey] = encodeJsonFormValue(child, rawValue);
    }
  }
  const propertyKeys = new Set(Object.keys(properties));
  for (const [key, rawValue] of Object.entries(value)) {
    const decodedKey = decodeJsonFormKey(key);
    if (propertyKeys.has(key) || propertyKeys.has(decodedKey)) continue;
    const childSchema = dynamicPropertySchemaFor(inner, decodedKey);
    if (!childSchema) continue;
    encoded[encodeJsonFormKey(decodedKey)] = encodeJsonFormValue(
      childSchema,
      rawValue,
    );
  }
  return encoded;
}

export function decodeJsonFormValue(schema: Schema, value: unknown): unknown {
  const { schema: inner } = unwrapNullable(schema);
  const kind = fieldKind(inner);

  if (kind === "array") {
    if (!Array.isArray(value)) return value;
    return value.map((item, index) =>
      decodeJsonFormValue(arrayItemSchemaAt(inner, index), item),
    );
  }

  if (kind !== "object" || !isRecordValue(value)) return value;

  const properties = schemaProperties(inner);
  const decoded: Record<string, unknown> = {};
  const handledKeys = new Set<string>();
  for (const [key, child] of Object.entries(properties)) {
    if (typeof child !== "object" || child === null) continue;
    const encodedKey = encodeJsonFormKey(key);
    const hasEncoded = Object.prototype.hasOwnProperty.call(value, encodedKey);
    const rawValue = hasEncoded ? value[encodedKey] : value[key];
    handledKeys.add(encodedKey);
    handledKeys.add(key);
    if (
      rawValue !== undefined ||
      hasEncoded ||
      Object.prototype.hasOwnProperty.call(value, key)
    ) {
      decoded[key] = decodeJsonFormValue(child, rawValue);
    }
  }
  for (const [key, rawValue] of Object.entries(value)) {
    const decodedKey = decodeJsonFormKey(key);
    if (handledKeys.has(key) || handledKeys.has(decodedKey)) continue;
    const childSchema = dynamicPropertySchemaFor(inner, decodedKey);
    if (!childSchema) continue;
    decoded[decodedKey] = decodeJsonFormValue(childSchema, rawValue);
  }
  return decoded;
}

export function emptyArrayItemFormValue(schema: Schema): unknown {
  const { schema: inner, nullable } = unwrapNullable(schema);
  if (nullable) return null;
  if (fieldKind(inner) !== "object") return emptyValueFor(inner);

  const value: Record<string, unknown> = {};
  const shouldEncodeKeys = schemaNeedsJsonFormPathEncoding(inner);
  for (const [key, child] of Object.entries(schemaProperties(inner))) {
    if (typeof child === "object" && child !== null) {
      value[shouldEncodeKeys ? encodeJsonFormKey(key) : key] =
        emptyValueFor(child);
    }
  }
  return value;
}
components/json-form/scalar-control.tsx
"use client";

import {
  BooleanControl,
  NullableBooleanControl,
} from "@/components/json-form/scalar/boolean-control";
import {
  datetimeLocalInputValue,
  DateTimeScalarControl,
} from "@/components/json-form/scalar/date-time-control";
import {
  EnumControl,
  enumLabel,
  enumValueEquals,
} from "@/components/json-form/scalar/enum-control";
import {
  dataCellNumberValue,
  NumberControl,
} from "@/components/json-form/scalar/number-control";
import {
  dataCellTextValue,
  TextControl,
} from "@/components/json-form/scalar/text-control";
import type {
  ControlFieldApi,
  DateTimeControlKind,
  JsonFormTextInput,
  ScalarControlDomProps,
} from "@/components/json-form/scalar/types";
import type { FieldKind, Schema } from "@/components/json-form/schema-model";

export type {
  ControlFieldApi,
  JsonFormTextInput,
  ScalarControlDomProps,
} from "@/components/json-form/scalar/types";
export {
  BooleanControl,
  dataCellNumberValue,
  dataCellTextValue,
  datetimeLocalInputValue,
  enumLabel,
  enumValueEquals,
  NullableBooleanControl,
};

export function ScalarControl({
  kind,
  schema,
  field,
  textInput,
  compact = false,
  nullable = false,
  ...controlProps
}: {
  kind: FieldKind;
  schema: Schema;
  field: ControlFieldApi;
  textInput?: JsonFormTextInput;
  /** Dense, single-line variant for table cells. */
  compact?: boolean;
  nullable?: boolean;
} & ScalarControlDomProps) {
  if (kind === "enum") {
    return (
      <EnumControl
        {...controlProps}
        schema={schema}
        field={field}
        compact={compact}
        nullable={nullable}
      />
    );
  }

  if (kind === "number" || kind === "integer") {
    return (
      <NumberControl
        {...controlProps}
        kind={kind}
        field={field}
        compact={compact}
        nullable={nullable}
      />
    );
  }

  if (
    schema.format === "date" ||
    schema.format === "time" ||
    schema.format === "date-time"
  ) {
    return (
      <DateTimeScalarControl
        {...controlProps}
        kind={schema.format as DateTimeControlKind}
        field={field}
        compact={compact}
        nullable={nullable}
      />
    );
  }

  return (
    <TextControl
      {...controlProps}
      schema={schema}
      field={field}
      textInput={textInput}
      compact={compact}
      nullable={nullable}
    />
  );
}
components/json-form/scalar/boolean-control.tsx
"use client";

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/json-form/form-primitives";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import { NULL_SELECT_VALUE } from "@/components/json-form/scalar/enum-control";
import {
  type ControlFieldApi,
  type ScalarControlDomProps,
} from "@/components/json-form/scalar/types";

export function BooleanControl({
  field,
  label,
}: {
  field: ControlFieldApi;
  label: string;
}) {
  const readOnly = useJsonFormReadOnly();

  return (
    <Checkbox
      disabled={readOnly}
      checked={Boolean(field.value)}
      aria-label={label}
      onCheckedChange={(value) => field.onChange(value === true)}
      onBlur={field.onBlur}
    />
  );
}

export function NullableBooleanControl({
  field,
  label,
  ...controlProps
}: {
  field: ControlFieldApi;
  label: string;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();
  const selectValue =
    field.value === true
      ? "true"
      : field.value === false
        ? "false"
        : NULL_SELECT_VALUE;
  const displayValue =
    field.value === true
      ? "True"
      : field.value === false
        ? "False"
        : "No value";

  return (
    <Select
      disabled={readOnly}
      value={selectValue}
      onValueChange={(value) => {
        if (value === "true") {
          field.onChange(true);
          return;
        }
        if (value === "false") {
          field.onChange(false);
          return;
        }
        field.onChange(null);
      }}
    >
      <SelectTrigger {...controlProps} aria-label={label}>
        <SelectValue placeholder="Select...">{displayValue}</SelectValue>
      </SelectTrigger>
      <SelectContent>
        <SelectItem value={NULL_SELECT_VALUE}>No value</SelectItem>
        <SelectItem value="true">True</SelectItem>
        <SelectItem value="false">False</SelectItem>
      </SelectContent>
    </Select>
  );
}
components/json-form/scalar/date-time-control.tsx
"use client";

import * as React from "react";
import { CalendarIcon, ClockIcon } from "lucide-react";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
  DataCell,
  formatDataCellDisplayValue,
} from "@/components/ui/data-cell";
import { Input } from "@/components/ui/input";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  compactJsonFormDataCellClass,
  type ControlFieldApi,
  type DateTimeControlKind,
  type ScalarControlDomProps,
} from "@/components/json-form/scalar/types";

export function datetimeLocalInputValue(value: string): string {
  const withoutTimezone = value.trim().replace(/(?:Z|[+-]\d{2}:\d{2})$/, "");
  return withoutTimezone.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/)?.[0] ?? value;
}

export function DateTimeScalarControl({
  kind,
  field,
  compact,
  nullable,
  ...controlProps
}: {
  kind: DateTimeControlKind;
  field: ControlFieldApi;
  compact: boolean;
  nullable: boolean;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();
  const value = field.value == null ? "" : String(field.value);
  if (!compact) {
    return (
      <DateTimePickerControl
        {...controlProps}
        kind={kind}
        field={field}
        nullable={nullable}
      />
    );
  }

  return (
    <DataCell
      {...controlProps}
      kind={kind}
      active={!readOnly}
      value={field.value == null ? null : value}
      dateTimeZone={kind === "date-time" ? "preserve" : undefined}
      draftValue={kind === "date-time" ? datetimeLocalInputValue(value) : value}
      className={compactJsonFormDataCellClass}
      onDraftValueChange={(nextValue) =>
        field.onChange(nextValue === "" && nullable ? null : nextValue)
      }
      onCommit={(nextValue) =>
        field.onChange(nextValue === "" && nullable ? null : nextValue)
      }
      onBlur={field.onBlur}
      name={field.name}
    />
  );
}

function pickerPlaceholder(kind: DateTimeControlKind): string {
  if (kind === "time") return "Pick a time";
  if (kind === "date-time") return "Pick a date and time";
  return "Pick a date";
}

function pickerEditValue(kind: DateTimeControlKind, value: string): string {
  if (kind === "date-time") return datetimeLocalInputValue(value);
  if (kind === "date") return value.match(/^\d{4}-\d{2}-\d{2}/)?.[0] ?? value;
  if (kind === "time")
    return value.match(/^\d{2}:\d{2}(?::\d{2})?/)?.[0] ?? value;
  return value;
}

function pickerDate(
  kind: DateTimeControlKind,
  value: string,
): Date | undefined {
  if (kind === "time" || value === "") return undefined;
  const dateValue =
    kind === "date-time" ? value.match(/^\d{4}-\d{2}-\d{2}/)?.[0] : value;
  const match = dateValue?.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!match) return undefined;
  return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
}

function pickerTime(kind: DateTimeControlKind, value: string): string {
  if (kind === "date") return "";
  return value.match(/\d{2}:\d{2}(?::\d{2})?/)?.[0] ?? "";
}

function pickerValueWithDate(
  kind: DateTimeControlKind,
  value: string,
  date: Date,
): string {
  const dateValue = formatPickerDate(date);
  if (kind === "date") return dateValue;
  if (kind === "time") return value;
  return `${dateValue}T${pickerTime("date-time", value) || "00:00"}`;
}

function pickerValueWithTime(
  kind: DateTimeControlKind,
  value: string,
  time: string,
): string {
  if (kind === "time") return time;
  if (kind === "date") return value;
  const dateValue =
    value.match(/^\d{4}-\d{2}-\d{2}/)?.[0] ?? formatPickerDate(new Date());
  return `${dateValue}T${time || "00:00"}`;
}

function formatPickerDate(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}`;
}

function DateTimePickerControl({
  kind,
  field,
  nullable,
  ...controlProps
}: {
  kind: DateTimeControlKind;
  field: ControlFieldApi;
  nullable: boolean;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();
  const [open, setOpen] = React.useState(false);
  const value = field.value == null ? "" : String(field.value);
  const pickerValue = pickerEditValue(kind, value);
  const selectedDate = pickerDate(kind, pickerValue);
  const timeValue = pickerTime(kind, pickerValue);
  const displayValue = formatDataCellDisplayValue(kind, value);
  const isEmpty = displayValue === "";

  const commitPickerValue = (nextValue: string) => {
    field.onChange(nextValue === "" && nullable ? null : nextValue);
  };

  const setDate = (date: Date) => {
    const nextValue = pickerValueWithDate(kind, pickerValue, date);
    commitPickerValue(nextValue);
    if (kind === "date") setOpen(false);
  };

  const setTime = (time: string) => {
    commitPickerValue(pickerValueWithTime(kind, pickerValue, time));
  };

  const setToday = () => {
    commitPickerValue(pickerValueWithDate(kind, pickerValue, new Date()));
    if (kind === "date") setOpen(false);
  };

  const setNow = () => {
    const now = new Date();
    const time = `${String(now.getHours()).padStart(2, "0")}:${String(
      now.getMinutes(),
    ).padStart(2, "0")}`;
    commitPickerValue(
      kind === "time"
        ? time
        : pickerValueWithTime(
            kind,
            pickerValueWithDate(kind, pickerValue, now),
            time,
          ),
    );
  };

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button
          {...controlProps}
          type="button"
          disabled={readOnly}
          data-empty={isEmpty || undefined}
          className="border-input bg-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 dark:bg-input/32 inline-flex h-8.5 w-full min-w-0 items-center justify-between gap-2 rounded-lg border px-[calc(--spacing(3)-1px)] text-left text-base font-normal shadow-xs/5 transition-shadow outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-64 sm:h-7.5 sm:text-sm"
          onBlur={field.onBlur}
        >
          <span className={cn("truncate", isEmpty && "text-muted-foreground")}>
            {isEmpty ? pickerPlaceholder(kind) : displayValue}
          </span>
          {kind === "time" ? (
            <ClockIcon className="size-4.5 shrink-0 opacity-80 sm:size-4" />
          ) : (
            <CalendarIcon className="size-4.5 shrink-0 opacity-80 sm:size-4" />
          )}
        </button>
      </PopoverTrigger>
      <PopoverContent
        align="start"
        className="w-auto rounded-xl p-2 before:rounded-[calc(var(--radius-xl)-1px)]"
      >
        {(kind === "date" || kind === "date-time") && (
          <Calendar
            mode="single"
            selected={selectedDate}
            defaultMonth={selectedDate}
            onSelect={(date) => {
              if (date) setDate(date);
            }}
          />
        )}
        {(kind === "time" || kind === "date-time") && (
          <div className="border-t p-3 first:border-t-0">
            <Input
              nativeInput
              type="time"
              step={1}
              value={timeValue}
              onChange={(event) => setTime(event.currentTarget.value)}
            />
          </div>
        )}
        <div className="flex items-center justify-between gap-2 border-t p-2">
          <Button
            type="button"
            variant="ghost"
            size="sm"
            onClick={() => commitPickerValue("")}
          >
            Clear
          </Button>
          <Button
            type="button"
            variant="ghost"
            size="sm"
            onClick={kind === "time" ? setNow : setToday}
          >
            {kind === "time" ? "Now" : "Today"}
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  );
}
components/json-form/scalar/enum-control.tsx
"use client";

import * as React from "react";

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  compactJsonFormSelectDataCellClass,
  type ControlFieldApi,
  type ScalarControlDomProps,
} from "@/components/json-form/scalar/types";
import {
  isRecordValue,
  type Schema,
} from "@/components/json-form/schema-model";

export const NULL_SELECT_VALUE = "__json-form-null__";

function enumOptionValue(index: number): string {
  return `enum:${index}`;
}

export function enumLabel(value: unknown): string {
  if (value === null) return "No value";
  if (typeof value === "string") return value;
  return JSON.stringify(value);
}

function hasOwnRecordValue(
  value: Record<string, unknown>,
  key: string,
): boolean {
  return Object.prototype.hasOwnProperty.call(value, key);
}

export function enumValueEquals(a: unknown, b: unknown): boolean {
  if (Object.is(a, b)) return true;
  if (Array.isArray(a) || Array.isArray(b)) {
    if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
      return false;
    }
    return a.every((item, index) => enumValueEquals(item, b[index]));
  }
  if (!isRecordValue(a) || !isRecordValue(b)) {
    return false;
  }

  const aKeys = Object.keys(a);
  const bKeys = Object.keys(b);
  if (aKeys.length !== bKeys.length) return false;
  return aKeys.every(
    (key) => hasOwnRecordValue(b, key) && enumValueEquals(a[key], b[key]),
  );
}

export function EnumControl({
  schema,
  field,
  compact,
  nullable,
  ...controlProps
}: {
  schema: Schema;
  field: ControlFieldApi;
  compact: boolean;
  nullable: boolean;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();
  const enumValues = schema.enum ?? [];
  const hasNullEnumValue = enumValues.some((value) => value === null);
  const currentIndex = enumValues.findIndex((value) =>
    enumValueEquals(value, field.value),
  );
  const selectValue =
    currentIndex >= 0
      ? enumOptionValue(currentIndex)
      : field.value === null && nullable
        ? NULL_SELECT_VALUE
        : undefined;
  const displayValue =
    currentIndex >= 0
      ? enumLabel(enumValues[currentIndex])
      : field.value === null && nullable
        ? "No value"
        : undefined;

  return (
    <Select
      disabled={readOnly}
      value={selectValue}
      onValueChange={(value) => {
        if (typeof value !== "string") return;
        if (value === NULL_SELECT_VALUE) {
          field.onChange(null);
          return;
        }
        const index = Number(value.replace("enum:", ""));
        field.onChange(enumValues[index]);
      }}
    >
      <SelectTrigger
        {...controlProps}
        {...(compact
          ? {
              "data-slot": "data-cell",
              "data-kind": "text",
              "data-mode": "edit",
            }
          : {})}
        className={compact ? compactJsonFormSelectDataCellClass : undefined}
      >
        <SelectValue placeholder="Select...">{displayValue}</SelectValue>
      </SelectTrigger>
      <SelectContent>
        {nullable && !hasNullEnumValue ? (
          <SelectItem value={NULL_SELECT_VALUE}>No value</SelectItem>
        ) : null}
        {enumValues.map((option, index) => (
          <SelectItem
            key={enumOptionValue(index)}
            value={enumOptionValue(index)}
          >
            {enumLabel(option)}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}
components/json-form/scalar/number-control.tsx
"use client";

import {
  DataCell,
  parseDataCellNumberInput,
  type DataCellCommitValue,
  type DataCellValueMeta,
} from "@/components/ui/data-cell";
import { Input } from "@/components/ui/input";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  compactJsonFormDataCellClass,
  type ControlFieldApi,
  type ScalarControlDomProps,
} from "@/components/json-form/scalar/types";

export function NumberControl({
  kind,
  field,
  compact,
  nullable,
  ...controlProps
}: {
  kind: "number" | "integer";
  field: ControlFieldApi;
  compact: boolean;
  nullable: boolean;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();

  if (!compact) {
    return (
      <Input
        {...controlProps}
        nativeInput
        readOnly={readOnly}
        type="number"
        inputMode={kind === "integer" ? "numeric" : "decimal"}
        step={kind === "integer" ? 1 : "any"}
        value={field.value == null ? "" : String(field.value)}
        onChange={(event) =>
          updateNumberValue({
            kind,
            value: event.currentTarget.value,
            nullable,
            field,
          })
        }
        onBlur={field.onBlur}
        name={field.name}
      />
    );
  }

  return (
    <DataCell
      {...controlProps}
      kind={kind}
      active={!readOnly}
      value={dataCellNumberValue(field.value)}
      draftValue={field.value == null ? "" : String(field.value)}
      className={compactJsonFormDataCellClass}
      onDraftValueChange={(value, meta) =>
        updateNumberValue({ kind, value, meta, nullable, field })
      }
      onCommit={(value, meta) =>
        updateNumberValue({ kind, value, meta, nullable, field })
      }
      onBlur={field.onBlur}
      name={field.name}
    />
  );
}

function updateNumberValue({
  kind,
  value,
  meta,
  nullable,
  field,
}: {
  kind: "number" | "integer";
  value: DataCellCommitValue | string;
  meta?: DataCellValueMeta;
  nullable: boolean;
  field: ControlFieldApi;
}) {
  const rawValue = meta?.rawValue ?? (typeof value === "string" ? value : "");
  const parsed = parseDataCellNumberInput({ kind, value: rawValue });

  if (!parsed.isValid) return;
  if (parsed.isEmpty) {
    field.onChange(nullable ? null : undefined);
    return;
  }
  field.onChange(parsed.value);
}

export function dataCellNumberValue(value: unknown): string | number | null {
  return typeof value === "number" || typeof value === "string" ? value : null;
}
components/json-form/scalar/text-control.tsx
"use client";

import { DataCell } from "@/components/ui/data-cell";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/json-form/form-primitives";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  compactJsonFormDataCellClass,
  type ControlFieldApi,
  type JsonFormTextInput,
  type ScalarControlDomProps,
} from "@/components/json-form/scalar/types";
import type { Schema } from "@/components/json-form/schema-model";

export function TextControl({
  schema,
  field,
  textInput,
  compact,
  nullable,
  ...controlProps
}: {
  schema: Schema;
  field: ControlFieldApi;
  textInput?: JsonFormTextInput;
  compact: boolean;
  nullable: boolean;
} & ScalarControlDomProps) {
  const readOnly = useJsonFormReadOnly();
  const value = field.value == null ? "" : String(field.value);

  if (!compact && shouldRenderTextarea(schema, textInput)) {
    return (
      <Textarea
        {...controlProps}
        readOnly={readOnly}
        value={value}
        onChange={(event) =>
          field.onChange(
            event.target.value === "" && nullable ? null : event.target.value,
          )
        }
        onBlur={field.onBlur}
        name={field.name}
      />
    );
  }

  if (!compact) {
    return (
      <Input
        {...controlProps}
        readOnly={readOnly}
        value={value}
        onChange={(event) => {
          const nextValue = event.currentTarget.value;
          field.onChange(nextValue === "" && nullable ? null : nextValue);
        }}
        onBlur={field.onBlur}
        name={field.name}
      />
    );
  }

  return (
    <DataCell
      {...controlProps}
      kind="text"
      active={!readOnly}
      value={field.value == null ? null : value}
      draftValue={value}
      className={compactJsonFormDataCellClass}
      onDraftValueChange={(nextValue) =>
        field.onChange(nextValue === "" && nullable ? null : nextValue)
      }
      onCommit={(nextValue) =>
        field.onChange(nextValue === "" && nullable ? null : nextValue)
      }
      onBlur={field.onBlur}
      name={field.name}
    />
  );
}

function shouldRenderTextarea(
  schema: Schema,
  textInput: JsonFormTextInput | undefined,
): boolean {
  if (textInput === "input") return false;
  if (textInput === "textarea") return true;
  return schema.format === "textarea" || (schema.maxLength ?? 0) > 120;
}

export function dataCellTextValue(value: unknown): string | null {
  return value === null || value === undefined ? null : String(value);
}
components/json-form/scalar/types.ts
import type * as React from "react";

export type JsonFormTextInput = "input" | "textarea";
export type DateTimeControlKind = "date" | "time" | "date-time";

export interface ControlFieldApi {
  value: unknown;
  onChange: (value: unknown) => void;
  onBlur: () => void;
  name: string;
  ref?: React.Ref<HTMLElement>;
}

export type ScalarControlDomProps = {
  id?: string;
  "aria-describedby"?: string;
  "aria-invalid"?: boolean;
  "data-slot"?: string;
};

export const compactJsonFormDataCellClass =
  "h-8 rounded-md border-transparent bg-transparent px-2 text-sm shadow-none transition-colors hover:border-border hover:bg-background focus-visible:border-ring focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring/30";

export const compactJsonFormSelectDataCellClass =
  "h-8 rounded-md border-transparent bg-transparent px-2 text-sm shadow-none transition-colors hover:border-border hover:bg-background focus-visible:border-ring focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring/30";
components/json-form/schema-model.ts
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";

export type Schema = JSONSchema7;
export type JsonFormSchemaNode = Schema;

export type FieldKind =
  | "string"
  | "number"
  | "integer"
  | "boolean"
  | "enum"
  | "object"
  | "array";

export type JsonFormFieldKind = FieldKind;

export interface NormalizedSchema {
  schema: Schema;
  nullable: boolean;
}

export interface Column {
  key: string;
  schema: Schema;
  kind: FieldKind;
  required: boolean;
  nullable: boolean;
}

export type JsonFormColumn = Column;

export function isSchema(
  value: JSONSchema7Definition | unknown,
): value is Schema {
  return typeof value === "object" && value !== null;
}

export function isRecordValue(
  value: unknown,
): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function cloneSchema(schema: Schema): Schema {
  return JSON.parse(JSON.stringify(schema)) as Schema;
}

function definitionsFrom(
  schema: Schema,
): Record<string, JSONSchema7Definition> {
  return {
    ...((schema.definitions ?? {}) as Record<string, JSONSchema7Definition>),
    ...((schema.$defs ?? {}) as Record<string, JSONSchema7Definition>),
  };
}

function mergeSchemas(base: Schema, next: Schema): Schema {
  const merged: Schema = { ...base, ...next };
  if (base.properties || next.properties) {
    const baseProperties = (base.properties ?? {}) as Record<
      string,
      JSONSchema7Definition
    >;
    const nextProperties = (next.properties ?? {}) as Record<
      string,
      JSONSchema7Definition
    >;
    merged.properties = { ...baseProperties };
    for (const [key, value] of Object.entries(nextProperties)) {
      const existing = merged.properties[key];
      merged.properties[key] =
        isSchema(existing) && isSchema(value)
          ? mergeSchemas(existing, value)
          : value;
    }
  }
  if (base.required || next.required) {
    merged.required = Array.from(
      new Set([...(base.required ?? []), ...(next.required ?? [])]),
    );
  }
  return merged;
}

function decodePointerSegment(segment: string): string {
  return segment.replace(/~1/g, "/").replace(/~0/g, "~");
}

function resolveLocalPointer(
  schema: Schema,
  ref: string,
): JSONSchema7Definition | undefined {
  if (!ref.startsWith("#/")) return undefined;

  let current: unknown = schema;
  for (const segment of ref.slice(2).split("/").map(decodePointerSegment)) {
    if (!isSchema(current) && !Array.isArray(current)) return undefined;
    current = (current as Record<string, unknown>)[segment];
  }
  return current as JSONSchema7Definition | undefined;
}

function refKey(ref: string): string | null {
  if (ref.startsWith("#/$defs/")) {
    return decodePointerSegment(ref.slice("#/$defs/".length));
  }
  if (ref.startsWith("#/definitions/")) {
    return decodePointerSegment(ref.slice("#/definitions/".length));
  }
  return null;
}

function resolveRef(
  ref: string,
  rootSchema: Schema,
  definitions: Record<string, JSONSchema7Definition>,
): JSONSchema7Definition | undefined {
  return resolveLocalPointer(rootSchema, ref) ?? definitions[refKey(ref) ?? ""];
}

export function normalizeJsonFormSchema(schema: Schema): JsonFormSchemaNode {
  return expandRefs(schema);
}

export function expandRefs(
  schema: Schema,
  definitions: Record<string, JSONSchema7Definition> = definitionsFrom(schema),
  visited: Set<string> = new Set(),
  rootSchema: Schema = schema,
): Schema {
  const working = cloneSchema(schema);

  if (typeof working.$ref === "string") {
    const ref = working.$ref;
    const target = resolveRef(ref, rootSchema, definitions);
    if (!isSchema(target) || visited.has(ref)) return working;

    const nextVisited = new Set(visited);
    nextVisited.add(ref);
    const { $ref: _ref, ...overrides } = working;
    return expandRefs(
      mergeSchemas(
        expandRefs(target, definitions, nextVisited, rootSchema),
        overrides,
      ),
      definitions,
      nextVisited,
      rootSchema,
    );
  }

  let normalized = working;
  if (Array.isArray(normalized.allOf)) {
    const allOf = normalized.allOf;
    delete normalized.allOf;
    normalized = allOf.reduce<Schema>((merged, branch) => {
      return isSchema(branch)
        ? mergeSchemas(
            merged,
            expandRefs(branch, definitions, visited, rootSchema),
          )
        : merged;
    }, normalized);
  }

  for (const key of ["anyOf", "oneOf"] as const) {
    const branches = normalized[key];
    if (Array.isArray(branches)) {
      normalized[key] = branches.map((branch) =>
        isSchema(branch)
          ? expandRefs(branch, definitions, visited, rootSchema)
          : branch,
      );
    }
  }

  if (normalized.properties) {
    const properties: Record<string, JSONSchema7Definition> = {};
    for (const [key, value] of Object.entries(normalized.properties)) {
      properties[key] = isSchema(value)
        ? expandRefs(value, definitions, visited, rootSchema)
        : value;
    }
    normalized.properties = properties;
  }

  if (isSchema(normalized.items)) {
    normalized.items = expandRefs(
      normalized.items,
      definitions,
      visited,
      rootSchema,
    );
  } else if (Array.isArray(normalized.items)) {
    normalized.items = normalized.items.map((item) =>
      isSchema(item)
        ? expandRefs(item, definitions, visited, rootSchema)
        : item,
    );
  }

  if (isSchema(normalized.additionalProperties)) {
    normalized.additionalProperties = expandRefs(
      normalized.additionalProperties,
      definitions,
      visited,
      rootSchema,
    );
  }

  if (normalized.patternProperties) {
    const patternProperties: Record<string, JSONSchema7Definition> = {};
    for (const [key, value] of Object.entries(normalized.patternProperties)) {
      patternProperties[key] = isSchema(value)
        ? expandRefs(value, definitions, visited, rootSchema)
        : value;
    }
    normalized.patternProperties = patternProperties;
  }

  return normalized;
}

/** Resolve nullable unions like `["string","null"]` or `anyOf:[X,{type:"null"}]`. */
export function unwrapNullable(schema: Schema): NormalizedSchema {
  if (Array.isArray(schema.type)) {
    const nonNull = schema.type.filter((type) => type !== "null");
    return {
      schema: {
        ...schema,
        type: nonNull.length === 1 ? nonNull[0] : nonNull,
      },
      nullable: schema.type.includes("null"),
    };
  }

  for (const key of ["anyOf", "oneOf"] as const) {
    const branches = schema[key];
    if (!Array.isArray(branches)) continue;

    const schemaBranches = branches.filter(isSchema);
    const nullable = schemaBranches.some((branch) => branch.type === "null");
    const main = schemaBranches.find((branch) => branch.type !== "null");
    if (main) {
      return {
        schema: {
          ...main,
          title: schema.title ?? main.title,
          description: schema.description ?? main.description,
        },
        nullable,
      };
    }
  }

  return { schema, nullable: false };
}

export function fieldKind(schema: Schema): FieldKind {
  if (Array.isArray(schema.enum)) return "enum";
  const type = Array.isArray(schema.type)
    ? schema.type.find((item) => item !== "null")
    : schema.type;
  switch (type) {
    case "number":
      return "number";
    case "integer":
      return "integer";
    case "boolean":
      return "boolean";
    case "object":
      return "object";
    case "array":
      return "array";
    default:
      return "string";
  }
}

export const jsonFormFieldKind = fieldKind;

export function labelFor(
  name: string,
  schema: Schema,
  explicit?: string,
): string {
  if (explicit) return explicit;
  if (schema.title) return schema.title;
  const leaf = name.split(".").pop() ?? name;
  return leaf
    .replace(/[_-]+/g, " ")
    .replace(/\b\w/g, (char) => char.toUpperCase())
    .trim();
}

export function emptyValueFor(schema: Schema): unknown {
  const { schema: inner, nullable } = unwrapNullable(schema);
  if (nullable) return null;
  switch (fieldKind(inner)) {
    case "boolean":
      return false;
    case "object":
      return {};
    case "array":
      return [];
    case "number":
    case "integer":
      return undefined;
    default:
      return "";
  }
}

export const emptyJsonFormValue = emptyValueFor;

export function isScalarKind(kind: FieldKind): boolean {
  return kind !== "object" && kind !== "array";
}

export function schemaProperties(
  schema: Schema,
): Record<string, JSONSchema7Definition> {
  return (schema.properties ?? {}) as Record<string, JSONSchema7Definition>;
}

export function schemaPatternProperties(
  schema: Schema,
): Record<string, JSONSchema7Definition> {
  return (schema.patternProperties ?? {}) as Record<
    string,
    JSONSchema7Definition
  >;
}

function patternPropertySchemaFor(schema: Schema, key: string): Schema | null {
  for (const [pattern, child] of Object.entries(
    schemaPatternProperties(schema),
  )) {
    if (!isRecordValue(child)) continue;
    try {
      if (new RegExp(pattern).test(key)) return child as Schema;
    } catch {
      continue;
    }
  }
  return null;
}

function additionalPropertySchemaFor(schema: Schema): Schema | null {
  return isRecordValue(schema.additionalProperties)
    ? (schema.additionalProperties as Schema)
    : null;
}

export function dynamicPropertySchemaFor(
  schema: Schema,
  key: string,
): Schema | null {
  return (
    patternPropertySchemaFor(schema, key) ?? additionalPropertySchemaFor(schema)
  );
}

export function hasDynamicObjectProperties(schema: Schema): boolean {
  const { schema: inner } = unwrapNullable(schema);
  if (fieldKind(inner) !== "object") return false;
  return (
    isRecordValue(inner.additionalProperties) ||
    Object.values(schemaPatternProperties(inner)).some(isRecordValue)
  );
}

export function scalarObjectColumns(itemSchema: Schema): Column[] | null {
  const { schema } = unwrapNullable(itemSchema);
  if (fieldKind(schema) !== "object") return null;
  const properties = schemaProperties(schema);
  const required = new Set(schema.required ?? []);
  const columns: Column[] = [];
  for (const [key, child] of Object.entries(properties)) {
    if (!isSchema(child)) return null;
    const { schema: inner, nullable } = unwrapNullable(child);
    const kind = fieldKind(inner);
    if (!isScalarKind(kind)) return null;
    columns.push({
      key,
      schema: inner,
      kind,
      required: required.has(key),
      nullable,
    });
  }
  return columns.length > 0 ? columns : null;
}

export function jsonFormTableColumns(itemSchema: Schema): Column[] | null {
  return scalarObjectColumns(itemSchema);
}

export function arrayItemSchemaAt(schema: Schema, index: number): Schema {
  const items = schema.items;
  if (Array.isArray(items)) {
    const item = items[index];
    if (isRecordValue(item)) return item as Schema;
    if (isRecordValue(schema.additionalItems)) {
      return schema.additionalItems as Schema;
    }
    return { type: "string" };
  }
  return isRecordValue(items) ? (items as Schema) : { type: "string" };
}

export const jsonFormArrayItemNode = arrayItemSchemaAt;

export function canAppendArrayItem(schema: Schema, length: number): boolean {
  if (typeof schema.maxItems === "number" && length >= schema.maxItems) {
    return false;
  }
  if (
    Array.isArray(schema.items) &&
    schema.additionalItems === false &&
    length >= schema.items.length
  ) {
    return false;
  }
  return true;
}

export function canRemoveArrayItem(schema: Schema, length: number): boolean {
  return typeof schema.minItems !== "number" || length > schema.minItems;
}
components/json-form/source-link-table-hover.ts
"use client";

import * as React from "react";

import type { SourceFieldLink } from "@/components/ui/source-field-link";
import { useSourceLinkFocusPreviewIntent } from "@/components/json-form/source-link-focus-intent";
import { useKeyedMountEffect } from "@/hooks/use-keyed-mount-effect";
import { joinEffectKey } from "@/lib/effect-key";

export type JsonFormSourceLinkActions = Omit<
  SourceFieldLink,
  "activeSourcePath"
>;

type SourceTableCell = HTMLElement;
type SourcePointerPoint = { x: number; y: number };
type TableSourceHoverState = {
  phase: "idle" | "focusing" | "hovering" | "scrolling";
  pointerPoint: SourcePointerPoint | null;
  sourcePath: string | null;
};

const SOURCE_PATH_ATTRIBUTE = "data-source-path";
const SOURCE_ACTIVE_ATTRIBUTE = "data-source-active";
const SOURCE_CELL_SELECTOR = `[${SOURCE_PATH_ATTRIBUTE}]`;
const TABLE_CELL_SELECTOR = "[data-table-cell]";
const SCROLL_SOURCE_HOVER_INTERVAL_MS = 32;

export function useSourceTableHoverController({
  tableRef,
  activeSourcePath,
  sourceLinkActions,
  refreshKey,
}: {
  tableRef: React.RefObject<HTMLElement | null>;
  activeSourcePath: string | null;
  sourceLinkActions: JsonFormSourceLinkActions | null;
  refreshKey: unknown;
}) {
  const sourceLinked = Boolean(sourceLinkActions);
  const activeSourceCellRef = React.useRef<Element | null>(null);
  const hoverStateRef = React.useRef<TableSourceHoverState>({
    phase: "idle",
    pointerPoint: null,
    sourcePath: null,
  });
  const pendingHoverPathRef = React.useRef<string | null>(null);
  const pendingHoverFrameRef = React.useRef<number | null>(null);
  const pendingScrollHoverFrameRef = React.useRef<number | null>(null);
  const latestScrollHoverAtRef = React.useRef(Number.NEGATIVE_INFINITY);
  const shouldPreviewFocus = useSourceLinkFocusPreviewIntent();

  const setActiveSourceCell = React.useCallback((cell: Element | null) => {
    if (activeSourceCellRef.current === cell) return;
    activeSourceCellRef.current?.removeAttribute(SOURCE_ACTIVE_ATTRIBUTE);
    if (cell) cell.setAttribute(SOURCE_ACTIVE_ATTRIBUTE, "true");
    activeSourceCellRef.current = cell;
  }, []);

  const sourcePathForCell = React.useCallback(
    (cell: Element | null): string | null =>
      cell?.getAttribute(SOURCE_PATH_ATTRIBUTE) ?? null,
    [],
  );

  useKeyedMountEffect(
    joinEffectKey([
      activeSourcePath,
      sourceLinked,
      refreshKey,
      setActiveSourceCell,
      tableRef,
    ]),
    () => {
      if (!sourceLinked || !activeSourcePath) {
        setActiveSourceCell(null);
        return;
      }
      if (
        hoverStateRef.current.sourcePath === activeSourcePath &&
        activeSourceCellRef.current?.getAttribute(SOURCE_PATH_ATTRIBUTE) ===
          activeSourcePath
      ) {
        return;
      }

      const table = tableRef.current;
      if (!table) return;
      for (const cell of table.querySelectorAll(SOURCE_CELL_SELECTOR)) {
        if (cell.getAttribute(SOURCE_PATH_ATTRIBUTE) === activeSourcePath) {
          setActiveSourceCell(cell);
          return;
        }
      }
      setActiveSourceCell(null);
    },
  );

  const getCellFromTarget = React.useCallback(
    (target: EventTarget | null): SourceTableCell | null => {
      if (!(target instanceof Element)) return null;
      const cell = target.closest<SourceTableCell>(TABLE_CELL_SELECTOR);
      return cell && tableRef.current?.contains(cell) ? cell : null;
    },
    [tableRef],
  );

  const cancelPendingHover = React.useCallback(() => {
    if (pendingHoverFrameRef.current === null) return;
    cancelAnimationFrame(pendingHoverFrameRef.current);
    pendingHoverFrameRef.current = null;
  }, []);

  const cancelPendingScrollHover = React.useCallback(() => {
    if (pendingScrollHoverFrameRef.current === null) return;
    cancelAnimationFrame(pendingScrollHoverFrameRef.current);
    pendingScrollHoverFrameRef.current = null;
  }, []);

  const reportHoverSourcePath = React.useCallback(
    (path: string | null) => {
      if (!sourceLinkActions) return;
      pendingHoverPathRef.current = path;
      if (pendingHoverFrameRef.current !== null) return;
      pendingHoverFrameRef.current = requestAnimationFrame(() => {
        pendingHoverFrameRef.current = null;
        sourceLinkActions.onSourceHover(pendingHoverPathRef.current);
      });
    },
    [sourceLinkActions],
  );

  const setHoverSourcePath = React.useCallback(
    (path: string | null, cell: Element | null) => {
      if (!sourceLinkActions) return;
      const currentState = hoverStateRef.current;
      if (currentState.sourcePath === path) return;
      hoverStateRef.current = {
        phase:
          currentState.phase === "scrolling"
            ? "scrolling"
            : path
              ? "hovering"
              : "idle",
        pointerPoint: currentState.pointerPoint,
        sourcePath: path,
      };
      setActiveSourceCell(cell);
      reportHoverSourcePath(path);
    },
    [sourceLinkActions, reportHoverSourcePath, setActiveSourceCell],
  );

  useKeyedMountEffect(
    joinEffectKey([cancelPendingHover, cancelPendingScrollHover]),
    () => () => {
      cancelPendingHover();
      cancelPendingScrollHover();
    },
  );

  const selectCellSource = React.useCallback(
    (cell: HTMLElement | null) => {
      const sourcePath = sourcePathForCell(cell);
      if (!sourcePath) return false;
      cancelPendingHover();
      sourceLinkActions?.selectSourcePath?.(sourcePath);
      return true;
    },
    [cancelPendingHover, sourceLinkActions, sourcePathForCell],
  );

  const handlePointerMove = React.useCallback(
    (event: React.PointerEvent<HTMLElement>) => {
      if (!sourceLinkActions) return;
      hoverStateRef.current = {
        ...hoverStateRef.current,
        pointerPoint: {
          x: event.clientX,
          y: event.clientY,
        },
      };
      if (hoverStateRef.current.phase === "scrolling") return;
      const cell = getCellFromTarget(event.target);
      setHoverSourcePath(sourcePathForCell(cell), cell);
    },
    [
      sourceLinkActions,
      getCellFromTarget,
      setHoverSourcePath,
      sourcePathForCell,
    ],
  );

  const handlePointerLeave = React.useCallback(
    (event: React.PointerEvent<HTMLElement>) => {
      hoverStateRef.current = {
        ...hoverStateRef.current,
        pointerPoint: {
          x: event.clientX,
          y: event.clientY,
        },
      };
      setHoverSourcePath(null, null);
    },
    [setHoverSourcePath],
  );

  const handleScrollStart = React.useCallback(() => {
    hoverStateRef.current = {
      phase: "scrolling",
      pointerPoint: hoverStateRef.current.pointerPoint,
      sourcePath: hoverStateRef.current.sourcePath,
    };
  }, []);

  const restoreHoverSourceAtPointer = React.useCallback(() => {
    if (!sourceLinkActions) return;
    const point = hoverStateRef.current.pointerPoint;
    if (!point) return;
    const ownerDocument = tableRef.current?.ownerDocument;
    if (!ownerDocument) return;
    const element = ownerDocument.elementFromPoint(point.x, point.y);
    const cell = getCellFromTarget(element);
    setHoverSourcePath(sourcePathForCell(cell), cell);
  }, [
    sourceLinkActions,
    tableRef,
    getCellFromTarget,
    setHoverSourcePath,
    sourcePathForCell,
  ]);

  const handleScrollEnd = React.useCallback(() => {
    hoverStateRef.current = {
      phase: hoverStateRef.current.sourcePath ? "hovering" : "idle",
      pointerPoint: hoverStateRef.current.pointerPoint,
      sourcePath: hoverStateRef.current.sourcePath,
    };
    cancelPendingScrollHover();
    latestScrollHoverAtRef.current = Number.NEGATIVE_INFINITY;
    restoreHoverSourceAtPointer();
  }, [cancelPendingScrollHover, restoreHoverSourceAtPointer]);

  const handleScrollMove = React.useCallback(() => {
    if (!sourceLinkActions || pendingScrollHoverFrameRef.current !== null) {
      return;
    }
    const now = performance.now();
    if (
      now - latestScrollHoverAtRef.current <
      SCROLL_SOURCE_HOVER_INTERVAL_MS
    ) {
      return;
    }
    pendingScrollHoverFrameRef.current = requestAnimationFrame(() => {
      pendingScrollHoverFrameRef.current = null;
      latestScrollHoverAtRef.current = performance.now();
      restoreHoverSourceAtPointer();
    });
  }, [sourceLinkActions, restoreHoverSourceAtPointer]);

  const handleFocus = React.useCallback(
    (event: React.FocusEvent<HTMLElement>) => {
      if (!sourceLinkActions) return;
      if (!shouldPreviewFocus(event)) return;
      const cell = getCellFromTarget(event.target);
      if (!cell) return;
      const sourcePath = sourcePathForCell(cell);
      hoverStateRef.current = {
        phase: sourcePath ? "focusing" : "idle",
        pointerPoint: hoverStateRef.current.pointerPoint,
        sourcePath,
      };
      setActiveSourceCell(cell);
      sourceLinkActions.onSourceHover(sourcePath);
    },
    [
      sourceLinkActions,
      shouldPreviewFocus,
      getCellFromTarget,
      setActiveSourceCell,
      sourcePathForCell,
    ],
  );

  const handleBlur = React.useCallback(
    (event: React.FocusEvent<HTMLElement>) => {
      if (!sourceLinkActions) return;
      const cell = getCellFromTarget(event.target);
      if (!cell || cell.contains(event.relatedTarget as Node | null)) return;
      const sourcePath = sourcePathForCell(cell);
      if (
        hoverStateRef.current.phase !== "focusing" ||
        hoverStateRef.current.sourcePath !== sourcePath
      ) {
        return;
      }
      hoverStateRef.current = {
        phase: "idle",
        pointerPoint: hoverStateRef.current.pointerPoint,
        sourcePath: null,
      };
      setActiveSourceCell(null);
      sourceLinkActions.onSourceHover(null);
    },
    [
      sourceLinkActions,
      getCellFromTarget,
      setActiveSourceCell,
      sourcePathForCell,
    ],
  );

  return {
    sourceLinked,
    getCellFromTarget,
    selectCellSource,
    handlePointerMove,
    handlePointerLeave,
    handleFocus,
    handleBlur,
    handleScrollStart,
    handleScrollMove,
    handleScrollEnd,
  };
}
components/json-form/source-link.tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import type { SourceFieldLink } from "@/components/ui/source-field-link";
import { useSourceLinkFocusPreviewIntent } from "@/components/json-form/source-link-focus-intent";
import {
  useSourceTableHoverController,
  type JsonFormSourceLinkActions,
} from "@/components/json-form/source-link-table-hover";

export type JsonFormSourceLink = SourceFieldLink;

const ActiveSourcePathContext = React.createContext<string | null>(null);
const SourceLinkActionsContext =
  React.createContext<JsonFormSourceLinkActions | null>(null);

export function JsonFormSourceLinkProvider({
  sourceLink,
  children,
}: {
  sourceLink?: JsonFormSourceLink;
  children: React.ReactNode;
}) {
  const onSourceHover = sourceLink?.onSourceHover;
  const selectSourcePath = sourceLink?.selectSourcePath;
  const sourceLinkActions = React.useMemo<JsonFormSourceLinkActions | null>(
    () => (onSourceHover ? { onSourceHover, selectSourcePath } : null),
    [onSourceHover, selectSourcePath],
  );

  return (
    <SourceLinkActionsContext.Provider value={sourceLinkActions}>
      <ActiveSourcePathContext.Provider
        value={sourceLink?.activeSourcePath ?? null}
      >
        {children}
      </ActiveSourcePathContext.Provider>
    </SourceLinkActionsContext.Provider>
  );
}

export function useActiveSourcePath(): string | null {
  return React.useContext(ActiveSourcePathContext);
}

export function useSourceLinkActions(): JsonFormSourceLinkActions | null {
  return React.useContext(SourceLinkActionsContext);
}

export function useSourceLinkedTableCells({
  tableRef,
  refreshKey,
}: {
  tableRef: React.RefObject<HTMLElement | null>;
  refreshKey: unknown;
}) {
  return useSourceTableHoverController({
    activeSourcePath: useActiveSourcePath(),
    refreshKey,
    sourceLinkActions: useSourceLinkActions(),
    tableRef,
  });
}

function shouldSelectSourceFromKeyDown(event: React.KeyboardEvent): boolean {
  if (event.defaultPrevented || event.key !== "Enter") return false;
  return !(event.target instanceof HTMLTextAreaElement);
}

function shouldPreviewSourceFromPointerMove(
  event: React.PointerEvent,
): boolean {
  return !event.defaultPrevented && event.pointerType !== "touch";
}

/**
 * Wraps a scalar leaf so it reports its source path from explicit pointer or
 * keyboard intent. Without a source link, it renders children unchanged.
 */
export function SourceLinkShell({
  sourcePath,
  children,
}: {
  sourcePath: string;
  children: React.ReactNode;
}) {
  const activeSourcePath = useActiveSourcePath();
  const sourceLinkActions = useSourceLinkActions();
  const shouldPreviewFocus = useSourceLinkFocusPreviewIntent();
  const focusPreviewedRef = React.useRef(false);
  const pointerPreviewedRef = React.useRef(false);
  if (!sourceLinkActions) return <>{children}</>;
  const active = activeSourcePath === sourcePath;

  const clearPreviewIfIdle = () => {
    if (focusPreviewedRef.current || pointerPreviewedRef.current) return;
    sourceLinkActions.onSourceHover(null);
  };

  return (
    <div
      data-source-active={active ? "true" : "false"}
      data-source-path={sourcePath}
      onPointerMove={(event) => {
        if (
          pointerPreviewedRef.current ||
          !shouldPreviewSourceFromPointerMove(event)
        ) {
          return;
        }
        pointerPreviewedRef.current = true;
        sourceLinkActions.onSourceHover(sourcePath);
      }}
      onPointerLeave={() => {
        if (!pointerPreviewedRef.current) return;
        pointerPreviewedRef.current = false;
        clearPreviewIfIdle();
      }}
      onFocus={(event) => {
        if (!shouldPreviewFocus(event)) return;
        focusPreviewedRef.current = true;
        sourceLinkActions.onSourceHover(sourcePath);
      }}
      onBlur={() => {
        if (!focusPreviewedRef.current) return;
        focusPreviewedRef.current = false;
        clearPreviewIfIdle();
      }}
      onClick={() => sourceLinkActions.selectSourcePath?.(sourcePath)}
      onKeyDownCapture={(event) => {
        if (shouldSelectSourceFromKeyDown(event)) {
          sourceLinkActions.selectSourcePath?.(sourcePath);
        }
      }}
      className={cn(
        "rounded-md border px-3 py-2 transition-colors",
        active
          ? "border-primary/40 bg-primary/5"
          : "hover:bg-muted/60 border-transparent",
      )}
    >
      {children}
    </div>
  );
}
components/json-form/table/array-table-body.tsx
"use client";

import * as React from "react";

import { FixedGridRowWindow } from "@/components/ui/fixed-grid-row-window";
import { useFixedRowVirtualization } from "@/components/ui/fixed-grid-virtualization";
import {
  TABLE_JUMP_ROW_OVERSCAN,
  TABLE_MAX_HEIGHT,
  TABLE_ROW_HEIGHT,
  TABLE_ROW_OVERSCAN,
} from "@/components/json-form/table/array-table-config";
import {
  useArrayTableScrollActivity,
  type ArrayTableScrollHandlers,
} from "@/components/json-form/table/array-table-scroll";

type ArrayTableField = { id: string };

export function StaticArrayTableBody({
  fields,
  scrollHandlers,
  renderItem,
}: {
  fields: ArrayTableField[];
  scrollHandlers: ArrayTableScrollHandlers;
  renderItem: (index: number) => React.ReactNode;
}) {
  const scrollRef = React.useRef<HTMLDivElement>(null);
  useArrayTableScrollActivity(scrollRef, scrollHandlers);

  return (
    <div
      ref={scrollRef}
      data-slot="json-form-table-scroll"
      className="overflow-y-auto"
      style={{ maxHeight: TABLE_MAX_HEIGHT }}
    >
      <div className="[contain:layout_paint_style]">
        {fields.map((entry, index) => (
          <React.Fragment key={entry.id}>{renderItem(index)}</React.Fragment>
        ))}
      </div>
    </div>
  );
}

export function FixedArrayTableBody({
  fields,
  scrollHandlers,
  renderItem,
}: {
  fields: ArrayTableField[];
  scrollHandlers: ArrayTableScrollHandlers;
  renderItem: (index: number, rowTopPx: number) => React.ReactNode;
}) {
  const scrollRef = React.useRef<HTMLDivElement>(null);
  const { virtualRowWindow, totalRowSize, viewportClientHeight } =
    useFixedRowVirtualization({
      rowCount: fields.length,
      rowSize: TABLE_ROW_HEIGHT,
      rowOverscan: TABLE_ROW_OVERSCAN,
      jumpRowOverscan: TABLE_JUMP_ROW_OVERSCAN,
      scrollRef,
    });
  useArrayTableScrollActivity(scrollRef, scrollHandlers);

  return (
    <div
      ref={scrollRef}
      data-slot="json-form-table-scroll"
      className="overflow-y-auto"
      style={{ maxHeight: TABLE_MAX_HEIGHT }}
    >
      <FixedGridRowWindow
        totalSize={totalRowSize}
        minWidth="100%"
        rowMinWidth="100%"
        virtualRowWindow={virtualRowWindow}
        viewportHeight={viewportClientHeight}
        offsetDataSlot="json-form-table-row-offset"
        windowDataSlot="json-form-table-row-window"
        className="[contain:layout_paint_style]"
      >
        {virtualRowWindow.items.map((virtualRow) => (
          <React.Fragment key={fields[virtualRow.index].id}>
            {renderItem(virtualRow.index, virtualRow.start)}
          </React.Fragment>
        ))}
      </FixedGridRowWindow>
    </div>
  );
}
components/json-form/table/array-table-cell-commit.ts
import { type DataCellValueMeta } from "@/components/ui/data-cell";
import { datetimeLocalInputValue } from "@/components/json-form/scalar-control";
import type { Column } from "@/components/json-form/schema-model";

export type SetArrayTableCellValue = (
  path: string,
  value: unknown,
  options: {
    shouldDirty: true;
    shouldTouch: true;
    shouldValidate: true;
  },
) => void;

export type CommitArrayTableCellValue = (
  value: unknown,
  meta?: DataCellValueMeta,
) => void;

export const NO_ARRAY_TABLE_CELL_COMMIT = Symbol("NO_ARRAY_TABLE_CELL_COMMIT");

export function normalizeArrayTableCellValue({
  column,
  currentValue,
  nextValue,
  meta,
}: {
    column: Column;
    currentValue: unknown;
    nextValue: unknown;
    meta?: DataCellValueMeta;
}): unknown | typeof NO_ARRAY_TABLE_CELL_COMMIT {
  let normalizedValue: unknown;
  if (column.kind === "enum") {
    normalizedValue = nextValue;
  } else if (column.kind === "number" || column.kind === "integer") {
    if (meta && !meta.isValid) return NO_ARRAY_TABLE_CELL_COMMIT;
    normalizedValue =
      typeof nextValue === "number"
        ? nextValue
        : nextValue === null && column.nullable && meta?.isEmpty !== false
          ? null
          : undefined;
    if (normalizedValue === undefined) return NO_ARRAY_TABLE_CELL_COMMIT;
  } else if (column.kind === "boolean") {
    normalizedValue = Boolean(nextValue);
  } else {
    const currentText = currentValue == null ? "" : String(currentValue);
    const currentDisplay =
      column.schema.format === "date-time"
        ? datetimeLocalInputValue(currentText)
        : currentText;
    const nextText = typeof nextValue === "string" ? nextValue : "";
    const nextDisplay =
      column.schema.format === "date-time"
        ? datetimeLocalInputValue(nextText)
        : nextText;

    if (nextDisplay === currentDisplay) return NO_ARRAY_TABLE_CELL_COMMIT;
    normalizedValue =
      nextDisplay === "" && column.nullable ? null : nextDisplay;
  }

  return Object.is(currentValue, normalizedValue)
    ? NO_ARRAY_TABLE_CELL_COMMIT
    : normalizedValue;
}

export function commitArrayTableCellValue({
  column,
  currentValue,
  meta,
  nextValue,
  path,
  setValue,
}: {
    column: Column;
    currentValue: unknown;
    meta?: DataCellValueMeta;
    nextValue: unknown;
    path: string;
    setValue: SetArrayTableCellValue;
}) {
  const normalizedValue = normalizeArrayTableCellValue({
    column,
    currentValue,
    nextValue,
    meta,
  });

  if (normalizedValue === NO_ARRAY_TABLE_CELL_COMMIT) return;
  setValue(path, normalizedValue, {
    shouldDirty: true,
    shouldTouch: true,
    shouldValidate: true,
  });
}
components/json-form/table/array-table-cell-model.ts
import { labelFor, type Column } from "@/components/json-form/schema-model";
import {
  dataCellKindForColumn,
  formatArrayTableCellValue,
  type ArrayTableDataCellKind,
} from "@/components/json-form/table/array-table-format";

export type ArrayTableCellModel = {
  path: string;
  sourcePath: string;
  label: string;
  displayText: string;
  kind: ArrayTableDataCellKind;
  value: unknown;
  isEnum: boolean;
  sourceLinked: boolean;
};

export function createArrayTableCellModel({
  path,
  sourcePath,
  column,
  value,
  sourceLinked,
}: {
  path: string;
  sourcePath: string;
  column: Column;
  value: unknown;
  sourceLinked: boolean;
}): ArrayTableCellModel {
  const isEnum = column.kind === "enum";

  return {
    path,
    sourcePath,
    label: labelFor(column.key, column.schema),
    displayText: formatArrayTableCellValue({ value, column }),
    kind: dataCellKindForColumn(column),
    value,
    isEnum,
    sourceLinked,
  };
}
components/json-form/table/array-table-cell-props.ts
import { cn } from "@/lib/utils";
import type { ArrayTableCellModel } from "@/components/json-form/table/array-table-cell-model";

export function arrayTableCellClassName({
  isEditing,
  model,
}: {
  isEditing: boolean;
  model: ArrayTableCellModel;
}): string {
  return cn(
    "min-w-0 rounded text-sm data-[source-active=true]:bg-primary/5 data-[source-active=true]:ring-1 data-[source-active=true]:ring-primary/30",
    !isEditing
      ? "hover:bg-background focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring/30"
      : "px-1 py-0.5",
    model.sourceLinked && isEditing && "hover:bg-muted/55",
  );
}

export function arrayTableCellProps(
  model: ArrayTableCellModel,
  { isEditing = false }: { isEditing?: boolean } = {},
) {
  return {
    "data-slot": "data-cell",
    "data-table-cell": "",
    "data-source-path": model.sourceLinked ? model.sourcePath : undefined,
    className: arrayTableCellClassName({ isEditing, model }),
  };
}
components/json-form/table/array-table-cell.tsx
"use client";

import * as React from "react";

import { DataCell } from "@/components/ui/data-cell";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import type { Column } from "@/components/json-form/schema-model";
import {
  useArrayTableCellActive,
  type ArrayTableActiveCellStore,
} from "@/components/json-form/table/array-table-active-cell-store";
import {
  commitArrayTableCellValue,
  type CommitArrayTableCellValue,
  type SetArrayTableCellValue,
} from "@/components/json-form/table/array-table-cell-commit";
import { createArrayTableDataCellProps } from "@/components/json-form/table/array-table-data-cell-props";
import type { ArrayTableCellModel } from "@/components/json-form/table/array-table-cell-model";

function ArrayTableCellContent({
  model,
  column,
  activeCellStore,
  setValue,
  closeEditor,
}: {
  model: ArrayTableCellModel;
  column: Column;
  activeCellStore: ArrayTableActiveCellStore;
  setValue: SetArrayTableCellValue;
  closeEditor: () => void;
}) {
  const readOnly = useJsonFormReadOnly();
  const isEditing = useArrayTableCellActive(activeCellStore, model.path);
  const commitValue = React.useCallback<CommitArrayTableCellValue>(
    (nextValue, meta) => {
      commitArrayTableCellValue({
        column,
        currentValue: model.value,
        meta,
        nextValue,
        path: model.path,
        setValue,
      });
    },
    [column, model.path, model.value, setValue],
  );

  return (
    <DataCell
      {...createArrayTableDataCellProps({
        column,
        commitValue,
        isEditing,
        model,
        onEditingEnd: closeEditor,
        readOnly,
      })}
    />
  );
}

export const ArrayTableCell = React.memo(
  ArrayTableCellContent,
  (previous, next) =>
    previous.model.path === next.model.path &&
    previous.model.sourcePath === next.model.sourcePath &&
    previous.model.label === next.model.label &&
    previous.model.displayText === next.model.displayText &&
    previous.model.kind === next.model.kind &&
    Object.is(previous.model.value, next.model.value) &&
    previous.model.isEnum === next.model.isEnum &&
    previous.model.sourceLinked === next.model.sourceLinked &&
    previous.column === next.column &&
    previous.activeCellStore === next.activeCellStore &&
    previous.setValue === next.setValue &&
    previous.closeEditor === next.closeEditor,
);
ArrayTableCell.displayName = "ArrayTableCell";
components/json-form/table/array-table-config.ts
export const TABLE_MAX_HEIGHT = 420;
export const TABLE_ROW_HEIGHT = 44;
export const TABLE_SCROLL_THRESHOLD = Math.floor(
  TABLE_MAX_HEIGHT / TABLE_ROW_HEIGHT,
);
export const TABLE_VIRTUALIZE_THRESHOLD = TABLE_SCROLL_THRESHOLD * 3;
export const TABLE_ROW_OVERSCAN = 3;
export const TABLE_JUMP_ROW_OVERSCAN = 6;
components/json-form/table/array-table-format.ts
import {
  enumLabel,
  enumValueEquals,
} from "@/components/json-form/scalar-control";
import type { Column } from "@/components/json-form/schema-model";

export type ArrayTableDataCellKind =
  | "text"
  | "number"
  | "integer"
  | "boolean"
  | "select"
  | "date"
  | "time"
  | "date-time";

export function formatArrayTableCellValue({
  value,
  column,
}: {
  value: unknown;
  column: Column;
}) {
  if (value == null || value === "") return "—";
  if (column.kind === "enum") {
    const option = column.schema.enum?.find((candidate) =>
      enumValueEquals(candidate, value),
    );
    return option === undefined ? enumLabel(value) : enumLabel(option);
  }
  if (typeof value === "number")
    return Number.isFinite(value) ? String(value) : "—";
  if (typeof value === "boolean") return value ? "True" : "False";
  return String(value);
}

export function dataCellKindForColumn(column: Column): ArrayTableDataCellKind {
  if (column.kind === "enum") return "select";
  if (column.kind === "number" || column.kind === "integer") return column.kind;
  if (column.kind === "boolean") return "boolean";
  if (column.schema.format === "date-time") return "date-time";
  if (column.schema.format === "date") return "date";
  if (column.schema.format === "time") return "time";
  return "text";
}
components/json-form/table/array-table-row.tsx
"use client";

import * as React from "react";
import { X } from "lucide-react";
import { useFormContext, useWatch } from "react-hook-form";

import { cn } from "@/lib/utils";
import { getFixedGridRowStyle } from "@/components/ui/fixed-grid-row-style";
import {
  encodeJsonFormKey,
  joinJsonFormPath,
  joinJsonSourcePath,
} from "@/components/json-form/path-codec";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import type { Column } from "@/components/json-form/schema-model";
import type { ArrayTableActiveCellStore } from "@/components/json-form/table/array-table-active-cell-store";
import { ArrayTableCell } from "@/components/json-form/table/array-table-cell";
import { createArrayTableCellModel } from "@/components/json-form/table/array-table-cell-model";
import { TABLE_ROW_HEIGHT } from "@/components/json-form/table/array-table-config";

export const ArrayTableRow = React.memo(function ArrayTableRow({
  name,
  sourcePath,
  index,
  isLastRow,
  columns,
  remove,
  canRemove,
  sourceLinked,
  template,
  rowTopPx,
  activeCellStore,
}: {
  name: string;
  sourcePath: string;
  index: number;
  isLastRow: boolean;
  columns: Column[];
  remove: (index: number) => void;
  canRemove: boolean;
  sourceLinked: boolean;
  template: string;
  rowTopPx?: number;
  activeCellStore: ArrayTableActiveCellStore;
}) {
  const readOnly = useJsonFormReadOnly();
  const { control, getValues, setValue } = useFormContext();
  const rowPath = joinJsonFormPath(name, index);
  const rowSourcePath = joinJsonSourcePath(sourcePath, index);
  const watchedRowValue = useWatch({
    control,
    name: rowPath,
  }) as Record<string, unknown> | undefined;
  const rowValue = (watchedRowValue ?? getValues(rowPath)) as
    | Record<string, unknown>
    | undefined;
  const rowStyle = React.useMemo(
    () =>
      rowTopPx === undefined
        ? { gridTemplateColumns: template }
        : getFixedGridRowStyle({
            gridTemplate: template,
            rowHeight: TABLE_ROW_HEIGHT,
            top: rowTopPx,
          }),
    [rowTopPx, template],
  );
  const closeEditor = React.useCallback(
    () => activeCellStore.setActivePath(null),
    [activeCellStore],
  );

  return (
    <div
      data-index={index}
      className={cn(
        "hover:bg-muted/25 grid items-center gap-1 border-b px-2 py-1 [contain:layout_paint_style]",
        isLastRow && "border-b-0",
      )}
      style={rowStyle}
    >
      {columns.map((column) => {
        const path = joinJsonFormPath(rowPath, column.key);
        const value = rowValue?.[encodeJsonFormKey(column.key)];

        return (
          <ArrayTableCell
            key={column.key}
            model={createArrayTableCellModel({
              path,
              sourcePath: joinJsonSourcePath(rowSourcePath, column.key),
              column,
              value,
              sourceLinked,
            })}
            column={column}
            activeCellStore={activeCellStore}
            setValue={setValue}
            closeEditor={closeEditor}
          />
        );
      })}
      {readOnly ? null : (
        <button
          type="button"
          className="text-muted-foreground hover:border-border hover:text-destructive focus-visible:ring-ring flex size-8 items-center justify-center rounded-md border border-transparent text-base leading-none transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
          onClick={() => remove(index)}
          aria-label="Remove row"
          disabled={!canRemove}
        >
          <X className="size-4" />
        </button>
      )}
    </div>
  );
});
components/json-form/table/array-table-scroll.ts
"use client";

import * as React from "react";

import { useMountEffect } from "@/hooks/use-mount-effect";

export type ArrayTableScrollHandlers = {
  onScrollStart: () => void;
  onScrollMove: () => void;
  onScrollEnd: () => void;
};

export function useArrayTableScrollActivity(
  scrollRef: React.RefObject<HTMLElement | null>,
  { onScrollStart, onScrollMove, onScrollEnd }: ArrayTableScrollHandlers,
) {
  const isScrollingRef = React.useRef(false);
  const scrollEndTimeoutRef = React.useRef(0);
  const callbacksRef = React.useRef({
    onScrollStart,
    onScrollMove,
    onScrollEnd,
  });
  callbacksRef.current = { onScrollStart, onScrollMove, onScrollEnd };

  const handleScroll = React.useCallback(() => {
    if (!isScrollingRef.current) {
      isScrollingRef.current = true;
      callbacksRef.current.onScrollStart();
    }
    callbacksRef.current.onScrollMove();
    window.clearTimeout(scrollEndTimeoutRef.current);
    scrollEndTimeoutRef.current = window.setTimeout(() => {
      isScrollingRef.current = false;
      callbacksRef.current.onScrollEnd();
    }, 120);
  }, []);

  useMountEffect(() => {
    const scrollElement = scrollRef.current;
    if (!scrollElement) return;
    scrollElement.addEventListener("scroll", handleScroll, { passive: true });
    return () => {
      window.clearTimeout(scrollEndTimeoutRef.current);
      scrollElement.removeEventListener("scroll", handleScroll);
    };
  });
}
components/json-form/table/array-table.tsx
"use client";

import * as React from "react";

import { getFixedGridCanvasStyle } from "@/components/ui/fixed-grid-layout";
import { WithDescription } from "@/components/json-form/disclosure";
import { labelFor, type Column } from "@/components/json-form/schema-model";
import { useSourceLinkedTableCells } from "@/components/json-form/source-link";
import { useJsonFormReadOnly } from "@/components/json-form/read-only";
import {
  createArrayTableActiveCellStore,
  type ArrayTableActiveCellStore,
} from "@/components/json-form/table/array-table-active-cell-store";
import {
  FixedArrayTableBody,
  StaticArrayTableBody,
} from "@/components/json-form/table/array-table-body";
import {
  TABLE_SCROLL_THRESHOLD,
  TABLE_VIRTUALIZE_THRESHOLD,
} from "@/components/json-form/table/array-table-config";
import { ArrayTableRow } from "@/components/json-form/table/array-table-row";

export function ArrayTable({
  name,
  sourcePath,
  fields,
  remove,
  canRemove,
  columns,
}: {
  name: string;
  sourcePath: string;
  fields: { id: string }[];
  remove: (index: number) => void;
  canRemove: boolean;
  columns: Column[];
}) {
  const readOnly = useJsonFormReadOnly();
  const template = `${columns.map(() => "minmax(9rem, 1fr)").join(" ")}${
    readOnly ? "" : " 2.25rem"
  }`;
  const minWidth = columns.length * 150 + (readOnly ? 0 : 36);
  const activeCellStoreRef = React.useRef<ArrayTableActiveCellStore | null>(
    null,
  );
  if (!activeCellStoreRef.current) {
    activeCellStoreRef.current = createArrayTableActiveCellStore();
  }
  const activeCellStore = activeCellStoreRef.current;
  const tableRef = React.useRef<HTMLDivElement>(null);
  const sourceTable = useSourceLinkedTableCells({
    tableRef,
    refreshKey: fields.length,
  });
  const sourceLinked = sourceTable.sourceLinked;
  const virtualize = fields.length > TABLE_VIRTUALIZE_THRESHOLD;
  const scrollHandlers = React.useMemo(
    () => ({
      onScrollStart: sourceTable.handleScrollStart,
      onScrollMove: sourceTable.handleScrollMove,
      onScrollEnd: sourceTable.handleScrollEnd,
    }),
    [sourceTable],
  );

  const handleTableClickCapture = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      const table = tableRef.current;
      const activeElement = table?.ownerDocument.activeElement;
      if (
        !(activeElement instanceof HTMLElement) ||
        activeElement.dataset.tableCellEditor !== "true" ||
        !table?.contains(activeElement) ||
        activeElement === event.target ||
        activeElement.contains(event.target as Node)
      ) {
        return;
      }
      activeElement.blur();
    },
    [],
  );

  const handleTableClick = React.useCallback(
    (event: React.MouseEvent<HTMLDivElement>) => {
      const cell = sourceTable.getCellFromTarget(event.target);
      if (!cell) return;
      sourceTable.selectCellSource(cell);
      if (cell.dataset.tableCellEditable !== "true") return;
      const path = cell.dataset.tableCellPath;
      if (path) activeCellStore.setActivePath(path);
    },
    [activeCellStore, sourceTable],
  );

  const handleTableKeyDown = React.useCallback(
    (event: React.KeyboardEvent<HTMLDivElement>) => {
      if (event.key !== "Enter" && event.key !== " ") return;
      const cell = sourceTable.getCellFromTarget(event.target);
      if (!cell || cell.dataset.tableCellEditable !== "true") return;
      const path = cell.dataset.tableCellPath;
      if (!path) return;
      sourceTable.selectCellSource(cell);
      event.preventDefault();
      activeCellStore.setActivePath(path);
    },
    [activeCellStore, sourceTable],
  );

  const renderRow = React.useCallback(
    (index: number, rowTopPx?: number) => (
      <ArrayTableRow
        name={name}
        sourcePath={sourcePath}
        index={index}
        isLastRow={index === fields.length - 1}
        columns={columns}
        remove={remove}
        canRemove={canRemove}
        sourceLinked={sourceLinked}
        template={template}
        rowTopPx={rowTopPx}
        activeCellStore={activeCellStore}
      />
    ),
    [
      activeCellStore,
      name,
      sourcePath,
      fields.length,
      columns,
      remove,
      canRemove,
      sourceLinked,
      template,
    ],
  );

  return (
    <div
      ref={tableRef}
      onClickCapture={handleTableClickCapture}
      onClick={handleTableClick}
      onKeyDown={handleTableKeyDown}
      onPointerMove={sourceLinked ? sourceTable.handlePointerMove : undefined}
      onPointerLeave={sourceLinked ? sourceTable.handlePointerLeave : undefined}
      onFocus={sourceTable.handleFocus}
      onBlur={sourceTable.handleBlur}
      className="bg-background overflow-x-auto"
    >
      <div style={getFixedGridCanvasStyle({ minWidth })}>
        <div
          className="bg-muted/35 grid h-9 items-center gap-1 border-b px-2"
          style={{ gridTemplateColumns: template }}
        >
          {columns.map((column) => (
            <div
              key={column.key}
              className="text-muted-foreground flex min-w-0 items-center gap-1 px-2 text-xs font-medium"
            >
              <WithDescription text={column.schema.description}>
                <span className="truncate">
                  {labelFor(column.key, column.schema)}
                </span>
              </WithDescription>
              {column.required ? (
                <span className="text-destructive">*</span>
              ) : null}
            </div>
          ))}
          {readOnly ? null : <span className="sr-only">Actions</span>}
        </div>
        {virtualize ? (
          <FixedArrayTableBody
            fields={fields}
            scrollHandlers={scrollHandlers}
            renderItem={renderRow}
          />
        ) : fields.length > TABLE_SCROLL_THRESHOLD ? (
          <StaticArrayTableBody
            fields={fields}
            scrollHandlers={scrollHandlers}
            renderItem={renderRow}
          />
        ) : (
          <div>
            {fields.map((entry, index) => (
              <React.Fragment key={entry.id}>{renderRow(index)}</React.Fragment>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}
components/json-form/virtual-list.tsx
"use client";

import * as React from "react";

import { FixedGridRowWindow } from "@/components/ui/fixed-grid-row-window";
import { useMeasuredRowVirtualization } from "@/components/ui/measured-row-virtualization";

export function VirtualList({
  fields,
  estimateSize,
  renderItem,
  maxHeight = 480,
  gap = 0,
}: {
  fields: { id: string }[];
  estimateSize: number;
  renderItem: (index: number) => React.ReactNode;
  maxHeight?: number;
  gap?: number;
}) {
  const parentRef = React.useRef<HTMLDivElement>(null);
  const getItemKey = React.useCallback(
    (index: number) => fields[index]?.id ?? index,
    [fields],
  );
  const { measureRow, totalSize, viewportClientHeight, virtualRowWindow } =
    useMeasuredRowVirtualization({
      count: fields.length,
      estimateSize: estimateSize + gap,
      getItemKey,
      overscan: 8,
      scrollRef: parentRef,
    });

  return (
    <div ref={parentRef} style={{ maxHeight }} className="overflow-y-auto">
      <FixedGridRowWindow
        data-slot="json-form-virtual-list-spacer"
        totalSize={totalSize}
        virtualRowWindow={virtualRowWindow}
        viewportHeight={viewportClientHeight}
        offsetDataSlot="json-form-virtual-list-row-offset"
        windowDataSlot="json-form-virtual-list-row-window"
      >
        {virtualRowWindow.items.map((virtualRow) => (
          <div
            key={virtualRow.key}
            data-index={virtualRow.index}
            ref={(element) => measureRow(virtualRow.index, element)}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              transform: `translateY(${virtualRow.start}px)`,
              paddingBottom: gap,
            }}
          >
            {renderItem(virtualRow.index)}
          </div>
        ))}
      </FixedGridRowWindow>
    </div>
  );
}
registry/new-york-v4/ui/measured-row-virtualization.ts
"use client";

import * as React from "react";

import { useKeyedLayoutEffect } from "@/hooks/use-keyed-layout-effect";
import { joinEffectKey } from "@/lib/effect-key";

const DEFAULT_INITIAL_VIEWPORT_HEIGHT = 600;
const DEFAULT_OVERSCAN = 6;
const MAX_VIRTUAL_ITEMS = 1_000;

export interface MeasuredRowVirtualItem {
  index: number;
  key: React.Key;
  start: number;
  size: number;
  end: number;
}

export interface MeasuredRowVirtualItemWindow {
  end: number;
  items: MeasuredRowVirtualItem[];
  size: number;
  start: number;
}

export interface MeasuredRowOffsets {
  starts: number[];
  totalSize: number;
}

export interface MeasuredRowScrollTarget {
  align?: "start" | "center" | "end";
  behavior?: ScrollBehavior;
}

export function buildMeasuredRowOffsets({
  rowSizes,
  paddingStart = 0,
  paddingEnd = 0,
}: {
  rowSizes: readonly number[];
  paddingStart?: number;
  paddingEnd?: number;
}): MeasuredRowOffsets {
  const starts: number[] = [];
  let offset = safeSize(paddingStart);

  for (const rowSize of rowSizes) {
    starts.push(offset);
    offset += safeSize(rowSize);
  }

  return {
    starts,
    totalSize: offset + safeSize(paddingEnd),
  };
}

export function getMeasuredRowVirtualItems({
  getItemKey,
  maxItems = MAX_VIRTUAL_ITEMS,
  offsets,
  overscan = DEFAULT_OVERSCAN,
  rowSizes,
  scrollTop,
  viewportHeight,
}: {
  getItemKey?: (index: number) => React.Key;
  maxItems?: number;
  offsets: MeasuredRowOffsets;
  overscan?: number;
  rowSizes: readonly number[];
  scrollTop: number;
  viewportHeight: number;
}): MeasuredRowVirtualItem[] {
  const count = rowSizes.length;
  if (count === 0) return [];

  const safeScrollTop = safeOffset(scrollTop);
  const safeViewportHeight = Math.max(1, safeSize(viewportHeight));
  const visibleStartIndex = findFirstRowEndingAfter({
    offset: safeScrollTop,
    rowSizes,
    starts: offsets.starts,
  });
  const visibleEndExclusive = findFirstRowStartingAtOrAfter({
    offset: safeScrollTop + safeViewportHeight,
    starts: offsets.starts,
  });
  const start = Math.max(0, visibleStartIndex - safeCount(overscan));
  const end = Math.min(
    count,
    Math.max(visibleStartIndex + 1, visibleEndExclusive) + safeCount(overscan),
  );
  const [cappedStart, cappedEnd] = capMeasuredRowRange({
    count,
    end,
    maxItems,
    start,
    visibleEnd: Math.min(
      count,
      Math.max(visibleStartIndex + 1, visibleEndExclusive),
    ),
    visibleStart: visibleStartIndex,
  });

  return Array.from({ length: cappedEnd - cappedStart }, (_, offset) => {
    const index = cappedStart + offset;
    const rowStart = offsets.starts[index] ?? 0;
    const size = safeSize(rowSizes[index]);
    return {
      index,
      key: getItemKey?.(index) ?? index,
      start: rowStart,
      size,
      end: rowStart + size,
    };
  });
}

export function measuredRowVirtualItemWindow(
  items: readonly MeasuredRowVirtualItem[],
): MeasuredRowVirtualItemWindow {
  const start = items[0]?.start ?? 0;
  const end = items.length ? items[items.length - 1]!.end : start;

  return {
    end,
    items: items.map((item) => ({
      ...item,
      start: item.start - start,
      end: item.end - start,
    })),
    size: Math.max(0, end - start),
    start,
  };
}

export function measuredRowScrollTopForIndex({
  align = "center",
  index,
  offsets,
  rowSizes,
  viewportHeight,
}: {
  align?: NonNullable<MeasuredRowScrollTarget["align"]>;
  index: number;
  offsets: MeasuredRowOffsets;
  rowSizes: readonly number[];
  viewportHeight: number;
}) {
  if (!Number.isSafeInteger(index) || index < 0) return 0;

  const rowStart = offsets.starts[index];
  if (rowStart == null) return 0;

  const rowSize = safeSize(rowSizes[index]);
  const safeViewportHeight = safeSize(viewportHeight);

  if (align === "end")
    return Math.max(0, rowStart - safeViewportHeight + rowSize);
  if (align === "center") {
    return Math.max(0, rowStart - safeViewportHeight / 2 + rowSize / 2);
  }
  return Math.max(0, rowStart);
}

export function useMeasuredRowVirtualization({
  count,
  estimateSize,
  getItemKey,
  initialViewportHeight = DEFAULT_INITIAL_VIEWPORT_HEIGHT,
  overscan = DEFAULT_OVERSCAN,
  paddingEnd = 0,
  paddingStart = 0,
  scrollRef,
}: {
  count: number;
  estimateSize: number;
  getItemKey?: (index: number) => React.Key;
  initialViewportHeight?: number;
  overscan?: number;
  paddingEnd?: number;
  paddingStart?: number;
  scrollRef: React.RefObject<HTMLElement | null>;
}) {
  const safeCount = measuredRowCount(count);
  const safeEstimateSize = Math.max(1, safeSize(estimateSize));
  const [version, forceVersion] = React.useReducer(
    (current: number) => current + 1,
    0,
  );
  const [scrollElement, setScrollElement] = React.useState<HTMLElement | null>(
    scrollRef.current,
  );
  const [viewport, setViewport] = React.useState({
    clientHeight: 0,
    scrollTop: 0,
  });
  const measuredSizesRef = React.useRef(new Map<number, number>());
  const rowElementsRef = React.useRef(new Map<number, HTMLElement>());
  const observedIndexesRef = React.useRef(new Map<HTMLElement, number>());
  const rowObserverRef = React.useRef<ResizeObserver | null>(null);
  const offsetsRef = React.useRef<MeasuredRowOffsets>({
    starts: [],
    totalSize: 0,
  });
  const rowSizesRef = React.useRef<number[]>([]);
  const viewportRef = React.useRef(viewport);

  viewportRef.current = viewport;

  const updateMeasuredSize = React.useCallback(
    (index: number, nextSize: number | null) => {
      if (!Number.isSafeInteger(index) || index < 0 || index >= safeCount) {
        return;
      }
      if (nextSize == null) return;

      const safeNextSize = safeSize(nextSize);
      if (safeNextSize <= 0) return;

      const measuredSizes = measuredSizesRef.current;
      const previousSize = measuredSizes.get(index) ?? safeEstimateSize;
      if (previousSize === safeNextSize) return;

      const previousOffsets = offsetsRef.current;
      const previousStart = previousOffsets.starts[index] ?? 0;
      const previousEnd = previousStart + previousSize;
      const delta = safeNextSize - previousSize;
      const currentScrollTop = viewportRef.current.scrollTop;

      measuredSizes.set(index, safeNextSize);

      const currentScrollElement = scrollRef.current;
      if (
        currentScrollElement &&
        previousEnd <= currentScrollTop &&
        delta !== 0
      ) {
        currentScrollElement.scrollTop = Math.max(
          0,
          currentScrollElement.scrollTop + delta,
        );
        setViewport((current) => ({
          ...current,
          scrollTop: safeOffset(currentScrollElement.scrollTop),
        }));
      }

      forceVersion();
    },
    [safeCount, safeEstimateSize, scrollRef],
  );

  const measureRow = React.useCallback(
    (index: number, element: HTMLElement | null) => {
      if (!Number.isSafeInteger(index) || index < 0) return;

      const rowElements = rowElementsRef.current;
      const previousElement = rowElements.get(index);
      if (previousElement && previousElement !== element) {
        rowObserverRef.current?.unobserve(previousElement);
        observedIndexesRef.current.delete(previousElement);
        rowElements.delete(index);
      }

      if (!element) return;

      rowElements.set(index, element);
      observedIndexesRef.current.set(element, index);
      rowObserverRef.current?.observe(element);
      updateMeasuredSize(index, readElementHeight(element));
    },
    [updateMeasuredSize],
  );

  useKeyedLayoutEffect(
    joinEffectKey([scrollRef, scrollRef.current, scrollElement]),
    () => {
      const nextScrollElement = scrollRef.current;
      if (nextScrollElement !== scrollElement) {
        setScrollElement(nextScrollElement);
      }
    },
  );

  useKeyedLayoutEffect(
    joinEffectKey([scrollElement, updateMeasuredSize]),
    () => {
      if (typeof ResizeObserver === "undefined") return;

      const observer = new ResizeObserver((entries) => {
        for (const entry of entries) {
          const index = observedIndexesRef.current.get(
            entry.target as HTMLElement,
          );
          if (index == null) continue;
          updateMeasuredSize(index, readResizeEntryHeight(entry));
        }
      });

      rowObserverRef.current = observer;
      for (const element of rowElementsRef.current.values()) {
        observer.observe(element);
      }

      return () => {
        observer.disconnect();
        if (rowObserverRef.current === observer) rowObserverRef.current = null;
      };
    },
  );

  useKeyedLayoutEffect(
    joinEffectKey([scrollElement, initialViewportHeight]),
    () => {
      if (!scrollElement) {
        setViewport({ clientHeight: 0, scrollTop: 0 });
        return;
      }

      let frame = 0;
      const readViewport = () => {
        frame = 0;
        const next = {
          clientHeight: safeSize(scrollElement.clientHeight),
          scrollTop: safeOffset(scrollElement.scrollTop),
        };
        setViewport((current) =>
          current.clientHeight === next.clientHeight &&
          current.scrollTop === next.scrollTop
            ? current
            : next,
        );
      };
      const scheduleRead = () => {
        if (frame) return;
        frame = requestFrame(readViewport);
      };

      readViewport();
      scrollElement.addEventListener("scroll", scheduleRead, { passive: true });
      const observer =
        typeof ResizeObserver !== "undefined"
          ? new ResizeObserver(scheduleRead)
          : null;
      observer?.observe(scrollElement);

      return () => {
        if (frame) cancelFrame(frame);
        scrollElement.removeEventListener("scroll", scheduleRead);
        observer?.disconnect();
      };
    },
  );

  const rowSizes = React.useMemo(
    () => {
      void version;
      return Array.from(
        { length: safeCount },
        (_, index) => measuredSizesRef.current.get(index) ?? safeEstimateSize,
      );
    },
    [safeCount, safeEstimateSize, version],
  );
  const offsets = React.useMemo(
    () =>
      buildMeasuredRowOffsets({
        paddingEnd,
        paddingStart,
        rowSizes,
      }),
    [paddingEnd, paddingStart, rowSizes],
  );
  const viewportHeight =
    viewport.clientHeight ||
    safeSize(initialViewportHeight) ||
    safeEstimateSize;
  const virtualRows = React.useMemo(
    () =>
      getMeasuredRowVirtualItems({
        getItemKey,
        offsets,
        overscan,
        rowSizes,
        scrollTop: viewport.scrollTop,
        viewportHeight,
      }),
    [
      getItemKey,
      offsets,
      overscan,
      rowSizes,
      viewport.scrollTop,
      viewportHeight,
    ],
  );
  const virtualRowWindow = React.useMemo(
    () => measuredRowVirtualItemWindow(virtualRows),
    [virtualRows],
  );
  const scrollToIndex = React.useCallback(
    (index: number, options?: MeasuredRowScrollTarget) => {
      const currentScrollElement = scrollRef.current;
      if (!currentScrollElement) return;

      const top = measuredRowScrollTopForIndex({
        align: options?.align ?? "center",
        index,
        offsets: offsetsRef.current,
        rowSizes: rowSizesRef.current,
        viewportHeight: currentScrollElement.clientHeight || viewportHeight,
      });
      if (typeof currentScrollElement.scrollTo === "function") {
        currentScrollElement.scrollTo({
          behavior: options?.behavior ?? "smooth",
          top,
        });
      } else {
        currentScrollElement.scrollTop = top;
      }
      setViewport((current) => ({
        ...current,
        scrollTop: safeOffset(currentScrollElement.scrollTop),
      }));
    },
    [scrollRef, viewportHeight],
  );

  offsetsRef.current = offsets;
  rowSizesRef.current = rowSizes;

  return {
    measureRow,
    scrollToIndex,
    totalSize: offsets.totalSize,
    viewportClientHeight: viewportHeight,
    virtualRowWindow,
    virtualRows,
  };
}

function capMeasuredRowRange({
  count,
  end,
  maxItems,
  start,
  visibleEnd,
  visibleStart,
}: {
  count: number;
  end: number;
  maxItems: number;
  start: number;
  visibleEnd: number;
  visibleStart: number;
}) {
  const safeMaxItems = Math.max(1, safeCount(maxItems));
  if (end - start <= safeMaxItems) return [start, end] as const;

  const visibleCount = Math.min(
    safeMaxItems,
    Math.max(1, visibleEnd - visibleStart),
  );
  const leadingBudget = Math.floor((safeMaxItems - visibleCount) / 2);
  let cappedStart = Math.max(start, visibleStart - leadingBudget);
  let cappedEnd = Math.min(end, cappedStart + safeMaxItems);

  if (cappedEnd - cappedStart < safeMaxItems) {
    cappedStart = Math.max(start, cappedEnd - safeMaxItems);
  }
  if (cappedEnd < visibleEnd) {
    cappedEnd = Math.min(end, visibleEnd);
    cappedStart = Math.max(start, cappedEnd - safeMaxItems);
  }
  cappedEnd = Math.min(count, cappedEnd);

  return [cappedStart, cappedEnd] as const;
}

function findFirstRowEndingAfter({
  offset,
  rowSizes,
  starts,
}: {
  offset: number;
  rowSizes: readonly number[];
  starts: readonly number[];
}) {
  let low = 0;
  let high = rowSizes.length - 1;
  let result = rowSizes.length - 1;

  while (low <= high) {
    const middle = Math.floor((low + high) / 2);
    const end = (starts[middle] ?? 0) + safeSize(rowSizes[middle]);
    if (end > offset) {
      result = middle;
      high = middle - 1;
    } else {
      low = middle + 1;
    }
  }

  return result;
}

function findFirstRowStartingAtOrAfter({
  offset,
  starts,
}: {
  offset: number;
  starts: readonly number[];
}) {
  let low = 0;
  let high = starts.length - 1;
  let result = starts.length;

  while (low <= high) {
    const middle = Math.floor((low + high) / 2);
    if ((starts[middle] ?? 0) >= offset) {
      result = middle;
      high = middle - 1;
    } else {
      low = middle + 1;
    }
  }

  return result;
}

function readElementHeight(element: HTMLElement) {
  const rectHeight = element.getBoundingClientRect().height;
  if (Number.isFinite(rectHeight) && rectHeight > 0) return rectHeight;
  return element.offsetHeight;
}

function readResizeEntryHeight(entry: ResizeObserverEntry) {
  const borderBoxSize = entry.borderBoxSize;
  const boxSize = Array.isArray(borderBoxSize)
    ? borderBoxSize[0]
    : borderBoxSize;
  if (boxSize && Number.isFinite(boxSize.blockSize) && boxSize.blockSize > 0) {
    return boxSize.blockSize;
  }
  if (
    Number.isFinite(entry.contentRect.height) &&
    entry.contentRect.height > 0
  ) {
    return entry.contentRect.height;
  }
  return readElementHeight(entry.target as HTMLElement);
}

function requestFrame(callback: FrameRequestCallback) {
  if (typeof window !== "undefined" && window.requestAnimationFrame) {
    return window.requestAnimationFrame(callback);
  }
  return setTimeout(() => callback(performance.now()), 0) as unknown as number;
}

function cancelFrame(frame: number) {
  if (typeof window !== "undefined" && window.cancelAnimationFrame) {
    window.cancelAnimationFrame(frame);
    return;
  }
  clearTimeout(frame);
}

function measuredRowCount(count: number) {
  return Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;
}

function safeCount(value: number) {
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
}

function safeOffset(value: number) {
  return Number.isFinite(value) && value > 0 ? value : 0;
}

function safeSize(value: number | undefined) {
  return Number.isFinite(value) && value != null && value > 0 ? value : 0;
}
components/json-form/table/array-table-active-cell-store.ts
"use client";

import * as React from "react";

export type ArrayTableActiveCellStore = {
  getSnapshot: () => string | null;
  setActivePath: (path: string | null) => void;
  subscribe: (listener: () => void) => () => void;
};

export function createArrayTableActiveCellStore(): ArrayTableActiveCellStore {
  let activePath: string | null = null;
  const listeners = new Set<() => void>();

  return {
    getSnapshot: () => activePath,
    setActivePath: (path) => {
      if (activePath === path) return;
      activePath = path;
      listeners.forEach((listener) => listener());
    },
    subscribe: (listener) => {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

export function useArrayTableCellActive(
  store: ArrayTableActiveCellStore,
  path: string,
): boolean {
  return React.useSyncExternalStore(
    store.subscribe,
    () => store.getSnapshot() === path,
    () => false,
  );
}
components/json-form/table/array-table-data-cell-props.ts
import type {
  DataCellProps,
  DataCellSelectOption,
} from "@/components/ui/data-cell";
import {
  dataCellNumberValue,
  dataCellTextValue,
  enumLabel,
  enumValueEquals,
} from "@/components/json-form/scalar-control";
import { NULL_SELECT_VALUE } from "@/components/json-form/scalar/enum-control";
import type { Column } from "@/components/json-form/schema-model";
import type { CommitArrayTableCellValue } from "@/components/json-form/table/array-table-cell-commit";
import type { ArrayTableCellModel } from "@/components/json-form/table/array-table-cell-model";
import { arrayTableCellProps } from "@/components/json-form/table/array-table-cell-props";

type ArrayTableDataCellSharedProps = {
  active: boolean;
  autoFocus: boolean;
  editable: boolean;
  name: string;
  onCommit: CommitArrayTableCellValue;
  onEditingEnd: () => void;
  role?: "button";
  tabIndex?: 0;
  "aria-label": string;
  "data-table-cell-editable"?: "true";
  "data-table-cell-editor"?: "true";
  "data-table-cell-path"?: string;
};

export function createArrayTableDataCellProps({
  column,
  commitValue,
  isEditing,
  model,
  onEditingEnd,
  readOnly,
}: {
  column: Column;
  commitValue: CommitArrayTableCellValue;
  isEditing: boolean;
  model: ArrayTableCellModel;
  onEditingEnd: () => void;
  readOnly: boolean;
}): DataCellProps {
  const sharedProps = arrayTableDataCellSharedProps({
    commitValue,
    isEditing,
    model,
    onEditingEnd,
    readOnly,
  });

  if (column.kind === "enum") {
    return arrayTableSelectDataCellProps({
      column,
      commitValue,
      model,
      sharedProps,
    });
  }

  if (model.kind === "number" || model.kind === "integer") {
    return {
      ...arrayTableCellProps(model, { isEditing }),
      ...sharedProps,
      kind: model.kind,
      value: dataCellNumberValue(model.value),
      formatValue: () => model.displayText,
      placeholder: "",
    };
  }

  if (model.kind === "boolean") {
    return {
      ...arrayTableCellProps(model, { isEditing }),
      ...sharedProps,
      kind: "boolean",
      value:
        model.value === null || model.value === undefined
          ? null
          : Boolean(model.value),
    };
  }

  if (model.kind === "date") {
    return {
      ...arrayTableCellProps(model, { isEditing }),
      ...sharedProps,
      kind: "date",
      value: dataCellTextValue(model.value),
      formatValue: () => model.displayText,
      placeholder: "",
    };
  }

  if (model.kind === "time") {
    return {
      ...arrayTableCellProps(model, { isEditing }),
      ...sharedProps,
      kind: "time",
      value: dataCellTextValue(model.value),
      formatValue: () => model.displayText,
      placeholder: "",
    };
  }

  if (model.kind === "date-time") {
    return {
      ...arrayTableCellProps(model, { isEditing }),
      ...sharedProps,
      kind: "date-time",
      value: dataCellTextValue(model.value),
      formatValue: () => model.displayText,
      placeholder: "",
    };
  }

  return {
    ...arrayTableCellProps(model, { isEditing }),
    ...sharedProps,
    kind: "text",
    value: dataCellTextValue(model.value),
    formatValue: () => model.displayText,
    placeholder: "",
  };
}

function arrayTableDataCellSharedProps({
  commitValue,
  isEditing,
  model,
  onEditingEnd,
  readOnly,
}: {
  commitValue: CommitArrayTableCellValue;
  isEditing: boolean;
  model: ArrayTableCellModel;
  onEditingEnd: () => void;
  readOnly: boolean;
}): ArrayTableDataCellSharedProps {
  // A read-only cell keeps its source-link affordances and drops every editing
  // one: no activation target, no focus stop, and no editor to open.
  const editing = isEditing && !readOnly;

  return {
    active: editing,
    editable: editing,
    autoFocus: editing,
    name: model.path,
    onCommit: commitValue,
    onEditingEnd,
    role: editing || readOnly ? undefined : "button",
    tabIndex: readOnly ? undefined : 0,
    "aria-label": `${model.label} ${model.displayText}`,
    "data-table-cell-editable": editing || readOnly ? undefined : "true",
    "data-table-cell-editor": editing ? "true" : undefined,
    "data-table-cell-path": editing || readOnly ? undefined : model.path,
  };
}

function arrayTableSelectDataCellProps({
  column,
  commitValue,
  model,
  sharedProps,
}: {
  column: Column;
  commitValue: CommitArrayTableCellValue;
  model: ArrayTableCellModel;
  sharedProps: ArrayTableDataCellSharedProps;
}): DataCellProps {
  return {
    ...arrayTableCellProps(model, { isEditing: sharedProps.active }),
    ...sharedProps,
    kind: "select",
    value: arrayTableSelectValue({
      column,
      value: model.value,
    }),
    selectOptions: arrayTableSelectOptions(column),
    placeholder: "Select...",
    formatValue: () => model.displayText,
    onCommit: (value, meta) => {
      commitValue(
        arrayTableSelectCommitValue({
          column,
          value,
        }),
        meta,
      );
    },
  };
}

function enumOptionValue(index: number): string {
  return `enum:${index}`;
}

function arrayTableSelectOptions(column: Column): DataCellSelectOption[] {
  const enumValues = column.schema.enum ?? [];
  const hasNullEnumValue = enumValues.some((value) => value === null);
  const nullableOptions: DataCellSelectOption[] =
    column.nullable && !hasNullEnumValue
      ? [{ value: NULL_SELECT_VALUE, label: "No value" }]
      : [];

  return [
    ...nullableOptions,
    ...enumValues.map((value, index) => ({
      value: enumOptionValue(index),
      label: enumLabel(value),
    })),
  ];
}

function arrayTableSelectValue({
  column,
  value,
}: {
  column: Column;
  value: unknown;
}): string | null {
  const enumIndex =
    column.schema.enum?.findIndex((candidate) =>
      enumValueEquals(candidate, value),
    ) ?? -1;

  if (enumIndex >= 0) return enumOptionValue(enumIndex);
  if (value === null && column.nullable) return NULL_SELECT_VALUE;
  return null;
}

function arrayTableSelectCommitValue({
  column,
  value,
}: {
  column: Column;
  value: string | null;
}): unknown {
  if (value === NULL_SELECT_VALUE || value === null) return null;
  const match = value.match(/^enum:(\d+)$/);
  if (!match) return undefined;
  return column.schema.enum?.[Number(match[1])];
}
components/json-form/source-link-focus-intent.ts
"use client";

import * as React from "react";

import { useMountEffect } from "@/hooks/use-mount-effect";

type SourceLinkFocusModality = "none" | "keyboard" | "pointer";
type SourceLinkFocusIntentState = {
  modality: SourceLinkFocusModality;
};

const sourceLinkFocusIntentByDocument = new WeakMap<
  Document,
  SourceLinkFocusIntentState
>();

function isKeyboardFocusNavigation(event: KeyboardEvent): boolean {
  if (
    event.defaultPrevented ||
    event.altKey ||
    event.ctrlKey ||
    event.metaKey
  ) {
    return false;
  }
  return (
    event.key === "Tab" ||
    event.key === "ArrowDown" ||
    event.key === "ArrowLeft" ||
    event.key === "ArrowRight" ||
    event.key === "ArrowUp" ||
    event.key === "End" ||
    event.key === "Home" ||
    event.key === "PageDown" ||
    event.key === "PageUp"
  );
}

function getSourceLinkFocusIntentState(
  ownerDocument: Document,
): SourceLinkFocusIntentState {
  const existingState = sourceLinkFocusIntentByDocument.get(ownerDocument);
  if (existingState) return existingState;

  const state: SourceLinkFocusIntentState = { modality: "none" };
  sourceLinkFocusIntentByDocument.set(ownerDocument, state);

  ownerDocument.addEventListener(
    "keydown",
    (event) => {
      if (isKeyboardFocusNavigation(event)) state.modality = "keyboard";
    },
    true,
  );
  ownerDocument.addEventListener(
    "pointerdown",
    () => {
      state.modality = "pointer";
    },
    true,
  );
  ownerDocument.addEventListener(
    "mousedown",
    () => {
      state.modality = "pointer";
    },
    true,
  );
  ownerDocument.addEventListener(
    "touchstart",
    () => {
      state.modality = "pointer";
    },
    true,
  );

  return state;
}

export function useSourceLinkFocusPreviewIntent() {
  useMountEffect(() => {
    getSourceLinkFocusIntentState(document).modality = "none";
  });

  return React.useCallback((event: React.FocusEvent<HTMLElement>): boolean => {
    if (event.defaultPrevented) return false;
    return (
      getSourceLinkFocusIntentState(event.currentTarget.ownerDocument)
        .modality === "keyboard"
    );
  }, []);
}
components/json-form/read-only.tsx
"use client";

import * as React from "react";

/**
 * Read-only mode for a whole form subtree.
 *
 * Viewer surfaces mount the form purely to display a result: nothing there is
 * submitted, so an editable control silently discards whatever the user types.
 * Read-only mode keeps the same layout, labels, and source links, but makes
 * every control inert — scalars render with the native `readOnly` flag (still
 * selectable and copyable), controls with no native read-only state are
 * disabled, and the array add/remove affordances disappear entirely.
 */
const JsonFormReadOnlyContext = React.createContext(false);

export function JsonFormReadOnlyProvider({
  readOnly,
  children,
}: {
  readOnly: boolean;
  children: React.ReactNode;
}) {
  return (
    <JsonFormReadOnlyContext.Provider value={readOnly}>
      {children}
    </JsonFormReadOnlyContext.Provider>
  );
}

export function useJsonFormReadOnly(): boolean {
  return React.useContext(JsonFormReadOnlyContext);
}

More from retab

Browse all components in this library →