# Viewer

`<nexa-viewer>` is a custom element (Web Component) that renders NexaReport definitions. It works in any framework (Vue, React, Angular, Svelte, Blazor, plain HTML) and requires no Vue dependency in the consumer application.

## Usage

### HTML / Vanilla JS

```html
<script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">

<nexa-viewer id="viewer" minimal></nexa-viewer>

<script>
  NexaReport.registerNexaReport();

  const viewer = document.getElementById('viewer');
  viewer.definition = { /* report JSON */ };
  viewer.data = [ /* data array */ ];
</script>
```

### Vue 3

```vue
<script setup>
import { registerNexaReport } from 'nexabase-report';

registerNexaReport();
</script>

<template>
  <nexa-viewer
    :definition="reportDef"
    :data="reportData"
    :parameters="{ year: 2024 }"
    skip-params-dialog
    show-toolbar
  />
</template>
```

### React

```tsx
import { useEffect, useRef } from 'react';

export function ReportViewer({ definition, data }) {
  const viewerRef = useRef(null);

  useEffect(() => {
    window.NexaReport?.registerNexaReport();
  }, []);

  useEffect(() => {
    if (viewerRef.current) {
      viewerRef.current.definition = definition;
      viewerRef.current.data = data;
    }
  }, [definition, data]);

  return <nexa-viewer ref={viewerRef} />;
}
```

### Blazor

```razor
@page "/report/{ReportId}"

<div class="viewer-container">
  <nexa-viewer id="reportViewer" style="width:100%; height:1200px;"></nexa-viewer>
</div>

<script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
<script>
  window.loadReport = async function(reportJson, data) {
    if (!window._nexaReg) {
      NexaReport.registerNexaReport();
      window._nexaReg = true;
    }
    const viewer = document.getElementById('reportViewer');
    viewer.definition = reportJson;
    viewer.data = data;
  };
</script>

@code {
  [Parameter] public string ReportId { get; set; }

  protected override async Task OnInitializedAsync() {
    await JSRuntime.InvokeVoidAsync("loadReport", reportDef, reportData);
  }
}
```

## Viewer Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `definition` | `NexaReportDefinition \| string` | — | Report definition object or JSON string. |
| `data` | `any[] \| Record<string, any[]> \| string` | — | Data for the report. Array (single source) or object keyed by datasource alias. |
| `parameters` | `Record<string, any>` | `{}` | Initial values for report parameters. |
| `minimal` | `boolean \| string` | `false` | Compact mode: hides toolbar and sidebar. |
| `showToolbar` | `boolean \| string` | `true` | Show/hide the export toolbar. |
| `showThumbs` | `boolean \| string` | `true` | Show/hide the thumbnail sidebar. |
| `skipParamsDialog` | `boolean` | `false` | Skip the parameter input dialog at load time. |
| `currentPage` | `number` | `1` | Initial page number to display. |
| `apiBaseUrl` | `string` | — | NexaBase API URL for dynamic data loading. |
| `apiKey` | `string` | — | NexaBase API key for authenticated data. |
| `locale` | `string` | `'es'` | UI language: `'es'`, `'en'`, or `'pt'`. |
| `licenseKey` | `string` | — | Commercial license key. Without a valid key, an "Unlicensed" watermark appears at the bottom. |

## Viewer Methods

Access via `document.querySelector('nexa-viewer')` or a template ref:

```javascript
const viewer = document.querySelector('nexa-viewer');

// Navigation
viewer.goToPage(3);        // Jump to page 3
viewer.nextPage();          // Go to next page
viewer.previousPage();      // Go to previous page

// Export
await viewer.exportPdf();           // Standard PDF
await viewer.exportPdfWithBookmarks(); // PDF with bookmarks/TOC
await viewer.exportExcel();          // Excel .xlsx
await viewer.exportWord();           // Word .docx
await viewer.exportCsv();            // CSV with UTF-8 BOM

// State
console.log(viewer.pageNumber);     // Current page (get/set)
console.log(viewer.totalPages);      // Total pages (read-only)
```

## Events

```javascript
const viewer = document.querySelector('nexa-viewer');

viewer.addEventListener('data-request', (e) => {
  console.log('Data requested for:', e.detail.alias);
  // Fetch data and set: viewer.data = { [e.detail.alias]: [...] }
});

viewer.addEventListener('page-change', (e) => {
  console.log('Page changed to:', e.detail);
});

viewer.addEventListener('drill-click', (e) => {
  console.log('Drill-down clicked:', e.detail);
  // e.detail = { element, sourceData, targetReportId, parameters, config }
});
```

## Renderers

The viewer includes specialized renderers for each element type:

| Renderer | Description |
|----------|-------------|
| `TextRenderer` | Text, expressions, bindings with highlight search |
| `ImageRenderer` | Image from URL or data field, with `object-fit` support (contain/cover/fill) |
| `TableRenderer` | Tabular data with headers, alternating row colors, conditional styling |
| `ChartRenderer` | ECharts (bar, line, pie, area, scatter, radar, etc.) |
| `CrosstabRenderer` | Pivot table with row/column grouping and aggregations |
| `BarcodeRenderer` | CODE128, EAN13, CODE39, etc. via jsbarcode |
| `QRCodeRenderer` | QR codes via qrcode library |
| `ShapeRenderer` | Rectangle, Ellipse, Line, Arrow rendered as SVG/canvas |
| `DashboardWidgetRenderer` | Dashboard widgets (Indicator, Chart, Gauge, Table) |

## Pagination

NexaReport uses a two-phase pagination engine:

1. **Streaming Phase** (`generateStream`): Processes all bands and data rows, computing page breaks based on element heights, table row heights, and watermark space.

2. **Page Generation Phase** (`generatePages`): Splits the stream into individual pages, handling table row splitting across page boundaries and accounting for `PageHeader`, `PageFooter`, and `ReportFooter` space.

The `currentPage` prop allows programmatically navigating to any page. The thumbnail sidebar shows all pages and lets users click to navigate.

## Watermark

Without a valid `licenseKey`, the viewer displays an "Unlicensed · Trial version" banner at the bottom. This does not block functionality but serves as a reminder to activate a commercial license.

## Styling

The viewer uses scoped styles. Global overrides can be applied via CSS custom properties on the host element:

```css
nexa-viewer {
  --viewer-bg: #f8fafc;
  --viewer-toolbar-bg: #ffffff;
  --viewer-border: #e2e8f0;
}
```