{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "docx-source",
  "title": "DOCX source adapter",
  "description": "Bridges the document-source model to the DOCX viewer: docxAnchorToTarget and sourceToDocxHighlight for docx_text_span and docx_table_cell anchors.",
  "registryDependencies": [
    "@retab/docx-viewer",
    "@retab/document-source"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/docx-source.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { Source, SourceAnchor } from \"@/lib/document-source\";\nimport type { DocxTarget, DocxViewerHandle } from \"@/components/ui/docx-viewer\";\n\nexport interface SourceTarget {\n  scrollTo?: (source: Source, options: { behavior: ScrollBehavior }) => void;\n}\n\n/**\n * A docx anchor + source payload -> a viewer-ready `DocxTarget`, or null when\n * the anchor is not a docx anchor. Text spans carry the quoted `content` from\n * the source; table cells carry their index. The adapter resolves the source\n * model, while the viewer resolves the rendered DOM.\n */\nexport function docxAnchorToTarget(\n  anchor: SourceAnchor | undefined,\n  source?: Source,\n): DocxTarget | null {\n  if (!anchor) return null;\n  if (anchor.kind === \"docx_text_span\") {\n    if (\n      !isNonNegativeInteger(anchor.paragraph) ||\n      !isValidOptionalRange(anchor.char_start, anchor.char_end)\n    )\n      return null;\n    const text = source?.content.trim();\n    return text ? { kind: \"text\", text } : null;\n  }\n  if (anchor.kind === \"docx_table_cell\") {\n    if (\n      !isNonNegativeInteger(anchor.table) ||\n      !isNonNegativeInteger(anchor.row) ||\n      !isNonNegativeInteger(anchor.column) ||\n      !isValidOptionalRange(anchor.char_start, anchor.char_end)\n    )\n      return null;\n    return {\n      kind: \"cell\",\n      table: anchor.table,\n      row: anchor.row,\n      column: anchor.column,\n    };\n  }\n  return null;\n}\n\nfunction isNonNegativeInteger(value: number) {\n  return Number.isInteger(value) && value >= 0;\n}\n\nfunction isValidOptionalRange(start?: number, end?: number) {\n  if (start == null && end == null) return true;\n  if (start == null || end == null) return false;\n  return (\n    Number.isInteger(start) &&\n    Number.isInteger(end) &&\n    start >= 0 &&\n    end >= start\n  );\n}\n\n/** A stable source target over a `DocxViewer` ref. */\nexport function useDocxSourceTarget(\n  viewerRef: React.RefObject<DocxViewerHandle | null>,\n): SourceTarget {\n  return React.useMemo<SourceTarget>(\n    () => ({\n      scrollTo: (source: Source, options) => {\n        const target = docxAnchorToTarget(source.anchor, source);\n        if (target) viewerRef.current?.scrollToTarget(target, options);\n      },\n    }),\n    [viewerRef],\n  );\n}\n\n/**\n * The `highlight` prop for `DocxViewer` derived from a source; non-docx\n * anchors resolve to null.\n */\nexport function sourceToDocxHighlight(\n  source: Source | undefined,\n): DocxTarget | null {\n  return source ? docxAnchorToTarget(source.anchor, source) : null;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-source.tsx"
    }
  ],
  "type": "registry:ui"
}