# Events

Subscribe to what the person is doing and to lifecycle changes. Global events come from `Kernstra.events`; per-viewer events from `viewer.on()`. Subscribing to the wrong one is silent.

## Global · Kernstra.events

### `initialized`

Payload: `{ version: string }`

`Kernstra.init()` completed.

```ts
Kernstra.events.on('initialized', ({ version }) => console.log(version));
```

### `licenseChanged`

Payload: `LicenseState`

The license was evaluated, replaced, paused or revoked. A valid license fills `env`, `plan`, `features`, `offline` and, when it has one, `expiresAt`; an invalid one fills `reason` (and `pauseReason` when paused) — see [LicenseState](https://kernstra.com/docs/web-sdk/api/types#ty-licensestate). A verdict that takes rights away is reported at once but reaches only the next load; documents already open keep the rights they were opened with.

```ts
Kernstra.events.on('licenseChanged', (state) => setWatermarked(state.watermark));
```

### `documentLoaded`

Payload: `{ document: PDFDocument; pageCount: number; hasPassword: boolean }`

A document was opened by `fromUrl`, `fromFile`, `fromBytes` or `fromUrlProgressive` (progressive: as soon as it can be shown); `document` is the one, so a host with several knows which. Not fired when the viewer reloads the document after an edit or an undo.

```ts
Kernstra.events.on('documentLoaded', ({ pageCount }) => setPageCount(pageCount));
```

### `documentClosed`

Payload: `{ document: PDFDocument }`

A document was closed: `document` is the one, closed by then — compare it, never call it.

```ts
Kernstra.events.on('documentClosed', ({ document }) => { if (document === doc) setPageCount(0); });
```

## Per viewer · viewer.on()

### `pageChanged`

Payload: `{ pageIndex: number }`

Current page changed — by scrolling, navigation, a link, or a reload that left fewer pages than the one shown. Not by a zoom.

```ts
viewer.on('pageChanged', ({ pageIndex }) => setCurrentPage(pageIndex));
```

### `zoomChanged`

Payload: `{ scale: number }`

Zoom level changed, by any means (1.0 = 100%).

```ts
viewer.on('zoomChanged', ({ scale }) => setZoom(Math.round(scale * 100)));
```

### `toolChanged`

Payload: `{ tool: ToolName | null; previous: ToolName | null }`

The active tool changed — `tool` runs now (between tools the resting tool, `'selectText'` by default), `previous` was reported before it; `null` only with `defaultTool: null`. Fires once the tool has started.

```ts
viewer.on('toolChanged', ({ tool }) => setActiveTool(tool));
```

### `panelChanged`

Payload: `{ panel: SidebarTab | null; previous: SidebarTab | null }`

The open sidebar panel changed — `panel` shows now (`null`: the sidebar is collapsed), `previous` was reported before it. Every change, whoever made it: `openPanel()`, the rail, a toolbar toggle, the drawer's backdrop, Escape inside the drawer, a tab the license takes away. The panel the viewer starts with opens while it is built, before a handler can be added: read [`activePanel`](https://kernstra.com/docs/web-sdk/api/pdfviewer/activepanel) for the state at any moment.

```ts
viewer.on('panelChanged', ({ panel }) => { myPagesButton.ariaPressed = String(panel === 'pages'); });
```

### `pagesChanged`

Payload: `{ pageCount: number }`

The pages changed — one added, removed, moved, duplicated, imported or turned, by a tool, Manage Pages, a thumbnail's menu, the document API, undo or redo — once the view shows them, before the `documentChanged` of the same change. Re-read anything you hold per page. Found by comparing each page's identity and size with what was shown: a page turned by 180° and turned back by undo keeps both and is reported by `documentChanged` alone.

```ts
viewer.on('pagesChanged', ({ pageCount }) => setPageCount(pageCount));
```

### `formFieldChanged`

Payload: `{ field: FormFieldInfo }`

A form field's value changed — by the user (per keystroke in a plain text field; on Enter or leaving it in a formatted one), by a calculation, or once per field `doc.forms.setValues()` applies. `field` is a snapshot carrying the field's **current** value — a check box's or radio group's export value when on, `'Off'` when off, what `doc.forms.setValues` takes. A signature field signed fires it too, the snapshot's `signature` now `'appearance'` and its `value` `''`, and then `signatureCaptured` with the picture.

```ts
viewer.on('formFieldChanged', ({ field }) => console.log(field.name, field.value));
```

### `formFieldFocused`

Payload: `{ field: FormFieldInfo }`

A form field's editor gained focus. `field` is a snapshot as `formFieldChanged` carries it.

```ts
viewer.on('formFieldFocused', ({ field }) => showHelpFor(field.name));
```

### `formFieldBlurred`

Payload: `{ field: FormFieldInfo }`

A form field's editor lost focus. `field` is a snapshot as `formFieldChanged` carries it.

```ts
viewer.on('formFieldBlurred', ({ field }) => validate(field));
```

### `signatureCaptured`

Payload: `{ fieldName: string; pageIndex: number; method: 'draw' | 'type' | 'upload' | 'host'; dataUrl: string }`

A form's signature field was signed (`'host'`: by `callbacks.onSignatureRequest`). The Sign tool placing a signature in the page reports `contentPlaced` instead.

```ts
viewer.on('signatureCaptured', ({ fieldName, method }) => track('signed', { fieldName, method }));
```

### `textChanged`

Payload: `{ changes: TextChange[] }`

Edit Text wrote its edits into the document — on Done, on leaving the tool any other way, before an undo, or from `viewer.tools.editText.commit()`. Once per commit, with every block it wrote; each change is `{ pageIndex, blockId, kind: 'text' | 'style' | 'box' | 'deleted', oldText, newText }` and `kind` says what changed. `oldText` is the block's text as the session found it, `newText` as the commit left it, both with their lines joined by one space; both empty under a license without `extract`.

```ts
viewer.on('textChanged', ({ changes }) => audit(changes));
```

### `documentChanged`

Payload: `{ source: DocumentChangeSource; tool?: ToolName; hasUnsavedChanges: boolean; canUndo: boolean; canRedo: boolean }`

The document changed — a tool wrote (`source: 'tool'`, `tool` names it), a page was turned, moved, added or deleted (`'pages'`), a bookmark, attachment or comment was edited (`'outline'`, `'attachments'`, `'comments'`), a form field was filled in, signed or set by `doc.forms.setValues()` (`'forms'` — every commit of a value, each keystroke), the document API was called (`'api'`; the Security tool's protect and unprotect included), undo or redo stepped (`'undo'`, `'redo'`). Once per change, after it is in the document, with the viewer's state after it. Not for a history step that left the document as it was. The hook for saving as the user works and for undo / redo buttons — see [Saving as the user works](https://kernstra.com/docs/web-sdk/guides/replace).

```ts
viewer.on('documentChanged', ({ canUndo, canRedo }) => setHistoryButtons(canUndo, canRedo));
```

### `contentPlaced`

Payload: `{ tool: ToolName; pageIndex: number }`

The user added something with a tool — a highlight, text mark or black-out, note, link, stamp, image, signature (the Sign tool; a form field reports `signatureCaptured`), added text, shape, drawing or text box. Once per new thing, a duplicate included, when it is committed; not for a move, resize, edit or undo. Selection-bar highlights report `'textHighlight'`, its underline, squiggly underline, strikeout and black-out `'selectText'`.

```ts
viewer.on('contentPlaced', ({ tool, pageIndex }) => track('placed', { tool, pageIndex }));
```

### `annotationsChanged`

Payload: `{ pageIndex: number; changes: AnnotationChange[] }`

Reported from a reading of the page after the change (adding the listener reads every page once, promptly, as the baseline): a page's annotations changed — one added, deleted, or modified (moved, resized, recolored, restyled, reworded), by a tool, a panel, `doc.annotations`, undo or redo; each change is `{ type: 'added' | 'modified' | 'deleted', id }`. Every annotation but pop-ups and form widgets; a reply and a review status are annotations of their own, reported `added`. `id` is what `doc.annotations.get(id)` takes; an undo that brings one back is an `added`. Not for the annotations a document opens with. Needs the `annotations` feature.

```ts
viewer.on('annotationsChanged', ({ pageIndex }) => void refreshMarks(pageIndex));
```

### `commentsChanged`

Payload: `{ pageIndex: number; changes: CommentChange[] }`

Reported from a reading of the page after the change (adding the listener reads every page once, promptly, as the baseline): a page's comments changed — one added, edited, deleted, replied to or given a status, by a tool, the Comments panel, `doc.annotations`, undo or redo; each change is `{ type: 'added' | 'edited' | 'deleted' | 'replied' | 'status', id }`. Not for the comments a document opens with. After pages are added, removed or moved, each page whose comments differ from what that page number held. Read them with `doc.annotations.comments(pageIndex)`. A viewer's event — a document with no viewer reports none. Under a license with `annotations`.

```ts
viewer.on('commentsChanged', async ({ pageIndex }) => syncComments(await doc.annotations.comments(pageIndex)));
```

### `error`

Payload: `{ code: ErrorCodeValue; message: string; fatal: boolean }`

A failure inside the viewer's own work, which no call of yours could catch — see [Error handling](https://kernstra.com/docs/web-sdk/api/errors).

```ts
viewer.on('error', ({ code, message, fatal }) => report(code, message, fatal));
```

### `savedSignaturesChanged`

Payload: `{ signatures: SavedSignature[] }`

The signatures the user keeps for reuse changed — the whole list, to store and pass back as [`tools.sign.savedSignatures`](https://kernstra.com/docs/web-sdk/config/tools/sign-savedsignatures); see [Keeping saved signatures and stamps](https://kernstra.com/docs/web-sdk/guides/replace).

```ts
viewer.on('savedSignaturesChanged', ({ signatures }) => localStorage.setItem('signatures', JSON.stringify(signatures)));
```

### `userStampsChanged`

Payload: `{ stamps: SavedStamp[] }`

The stamps made with "New stamp" changed — the whole list, to store and pass back as [`tools.stamp.userStamps`](https://kernstra.com/docs/web-sdk/config/tools/stamp-userstamps).

```ts
viewer.on('userStampsChanged', ({ stamps }) => localStorage.setItem('stamps', JSON.stringify(stamps)));
```

### `exportStarted`

Payload: `Record<string, never>`

Export began — from the toolbar's Download or an exporter from `viewer.createExporter()`; a `new PDFExporter(doc)` you construct reports to no viewer.

```ts
viewer.on('exportStarted', () => setBusy(true));
```

### `exportCompleted`

Payload: `{ sizeBytes: number; partial: boolean; pdfA?: 'kept' | 'dropped' }`

Export completed — same caveat as `exportStarted`. `partial` is `true` for a page range; an export of the whole document clears `hasUnsavedChanges`. `pdfA` for a PDF/A file: whether the file saved still claims it.

```ts
viewer.on('exportCompleted', ({ sizeBytes }) => { setBusy(false); log(sizeBytes); });
```
