npm package: @relevaince/document-viewer
Description: Citation-driven document viewer for Relevaince
Author: Relevaince
Latest version: 0.3.5

# @relevaince/document-viewer

A single React component to render **PDF**, **DOCX**, **Image**, **JSON**, and **EML** files.
Built for citation-driven interfaces where users click a reference and the document opens, scrolled to the right page with the cited region highlighted.

## Install

```bash
npm install @relevaince/document-viewer
```

React 18+ is required as a peer dependency.

## Quick start

```tsx
import { DocumentViewer } from "@relevaince/document-viewer";

function App() {
  return (
    <div style={{ width: "100%", height: "80vh" }}>
      <DocumentViewer
        target={{
          url: "https://example.com/report.pdf",
          page: 3,
          bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },
        }}
      />
    </div>
  );
}
```

Pass `target={null}` to unmount the viewer. Change `target` to navigate between documents or citations.

## Supported formats

| Format | Extensions | Renderer |
| ------ | ---------- | -------- |
| PDF | `.pdf` | [react-pdf-highlighter-extended](https://github.com/DanielArnould/react-pdf-highlighter-extended) with bounding-box highlights |
| DOCX | `.doc`, `.docx` | Microsoft Office Online embed (free, no server needed) |
| Image | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.bmp`, `.avif`, `.svg` | [react-zoom-pan-pinch](https://github.com/prc5/react-zoom-pan-pinch) with pan, zoom and pinch |
| JSON | `.json` | Syntax-highlighted collapsible tree (zero-dep, built-in) |
| E-mail | `.eml` | MIME parser with header extraction and HTML body sanitised via [DOMPurify](https://github.com/cure53/DOMPurify) |

File type is auto-detected from the URL extension. Override it with the `fileType` prop when the URL doesn't contain an extension (e.g. pre-signed S3 URLs):

```tsx
<DocumentViewer target={{ url: presignedUrl, fileType: "pdf" }} />
```

## API

### `<DocumentViewer />`

| Prop | Type | Description |
| ---- | ---- | ----------- |
| `target` | `ViewerTarget \| null` | The document to display. `null` hides the viewer. |
| `pdfWorkerUrl` | `string` | Override the default pdfjs web-worker CDN URL. |
| `loadingComponent` | `ReactNode` | Custom loading indicator. |
| `errorComponent` | `ReactNode` | Custom error state. |
| `onLoadStart` | `() => void` | Fires when loading begins. |
| `onLoadSuccess` | `() => void` | Fires when the document finishes loading. |
| `onLoadError` | `(err: Error) => void` | Fires on load failure. |
| `className` | `string` | Class name for the outermost wrapper. |

### `ViewerTarget`

```ts
type ViewerTarget = {
  url: string;
  fileType?: "pdf" | "docx" | "image" | "json" | "eml";
  page?: number;        // 1-indexed, PDF only
  bbox?: BBox;          // normalised 0–1 coords, PDF only
  text?: string;        // cited text snippet (future use)
  title?: string;       // display title
  jsonDefaultOpen?: boolean; // JSON only: expand all nodes (true), collapse all (false), or omit for default heuristic
};
```

### `BBox`

Bounding box in normalised coordinates (0–1), origin at the top-left of the page.

```ts
type BBox = { x1: number; y1: number; x2: number; y2: number };
```

### `getFileType(url)`

Utility to infer file type from a URL. Returns `"pdf" | "docx" | "image" | "json" | "eml" | "unknown"`.

```ts
import { getFileType } from "@relevaince/document-viewer";

getFileType("https://example.com/report.pdf"); // "pdf"
getFileType("https://example.com/photo.png");  // "image"
```

## Usage with Next.js

The library is SSR-safe out of the box. PDF and Image renderers are lazy-loaded and wrapped in a `ClientOnly` boundary so `pdfjs-dist` and `react-zoom-pan-pinch` never execute on the server.

Add the package to `transpilePackages` in your `next.config.js`:

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: [
    "@relevaince/document-viewer",
    "react-pdf-highlighter-extended",
    "pdfjs-dist",
  ],
};

module.exports = nextConfig;
```

Then use it in any client component:

```tsx
"use client";

import { DocumentViewer } from "@relevaince/document-viewer";

export function CitationPanel({ citation }) {
  return (
    <div style={{ width: "100%", height: "80vh" }}>
      <DocumentViewer
        target={{
          url: citation.documentUrl,
          page: citation.page,
          bbox: citation.bbox,
          title: citation.title,
        }}
      />
    </div>
  );
}
```

## Running the demo

```bash
cd demo
npm install
npm run dev
```

Opens at [localhost:3333](http://localhost:3333). The demo shows a sidebar with PDF, Image, and JSON samples you can switch between.

## Architecture

```
src/
  index.ts                          # Public exports
  types/
    ViewerTarget.ts                 # BBox, ViewerTarget, ViewerFileType
    DocumentViewerProps.ts          # Component props
  utils/
    getFileType.ts                  # URL → file type detection
  components/
    DocumentViewer.tsx              # Router + SSR-safe lazy loading
    renderers/
      PdfRenderer.tsx               # react-pdf-highlighter-extended
      DocxRenderer.tsx              # Office Online iframe
      ImageRenderer.tsx             # react-zoom-pan-pinch
      JsonRenderer.tsx              # Collapsible syntax tree
      EmlRenderer.tsx               # MIME parser + DOMPurify
```

**Design decisions:**

- **Peer deps for React** — the host app controls the React version (18+).
- **Code-split chunks** — `tsup` builds with `splitting: true`. Browser-only renderers (PDF, Image, EML) are in separate chunks loaded via `React.lazy()`. The main entry has **zero** imports of `pdfjs-dist`, `react-pdf-highlighter-extended`, `react-zoom-pan-pinch`, or `dompurify`, making it fully SSR-safe.
- **ClientOnly boundary** — a `useState` + `useEffect` guard ensures lazy chunks are never loaded during server rendering.
- **Normalised BBox** — coordinates are 0–1 with origin at top-left, making them resolution-independent.
- **Zero-config** — auto-detects file type, defaults the pdfjs worker to a CDN, no CSS imports required.

## Troubleshooting

### `ReferenceError: window is not defined` (Next.js SSR)

This was fixed in **v0.3.0**. If you're on an older version, upgrade:

```bash
npm install @relevaince/document-viewer@latest
```

The library uses code-split chunks so that `pdfjs-dist` and other browser-only dependencies are never loaded during server-side rendering. If you still see this error, make sure:

1. You have `transpilePackages` set in `next.config.js` (see [Usage with Next.js](#usage-with-nextjs))
2. You're using the component inside a `"use client"` boundary
3. You're on `@relevaince/document-viewer` v0.3.0 or later

## Building from source

```bash
npm install
npm run build        # ESM + CJS + type declarations in dist/
npm run dev          # watch mode
```

## Publishing

```bash
npm version patch    # or minor / major
npm run build
npm publish
```

## License

MIT