Several documents
A viewer can hold several documents at once, like tabs in a browser. One of them is the active document, and every component talks to it unless you say otherwise:
<script lang="ts">
import { useDocuments, useDocumentsState } from '@embedpdf/svelte/runtime';
const documents = useDocuments();
const state = useDocumentsState();
</script>
<div role="tablist">
{#each state.documents as document (document.id)}
<div>
<button
role="tab"
aria-selected={document.id === state.activeId}
onclick={() => documents.setActive(document.id)}
>
{document.name}
</button>
<button aria-label="Close {document.name}" onclick={() => documents.close(document.id)}>×</button>
</div>
{/each}
</div>Each document keeps its own state: its zoom and scroll position, its selection, its search and its active tool. Switch tabs and back, and everything is where you left it.
Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
import { localEngine } from '@embedpdf/engine';
import TabTools from './TabTools.svelte';
import Tabs from './Tabs.svelte';
import '../tabs.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<Viewer
{engine}
{plugins}
initialDocuments={[
{ source: ebook, name: 'Contract' },
{ source: ebook, name: 'Report' },
]}
>
<Tabs {ebook} />
<DocumentGate>
{#snippet fallback()}
<p class="loading">Opening…</p>
{/snippet}
<TabTools />
<Stage class="stage">
<RenderLayer />
</Stage>
</DocumentGate>
</Viewer>Manage the tabs#
documents.setActive(id);
documents.rename(id, 'Signed contract');
documents.move(id, 0); // to the front
await documents.close(id);Each entry in documents, from useDocumentsState(), has the document’s id, its name, and its
status: 'loading', 'locked', 'ready' or 'error', so a tab can show a spinner or a lock.
Every field is listed under State.
Talk to a specific document#
A component talks to the active document. Inside <DocumentScope>, it talks to the one you name,
whatever is active:
<script lang="ts">
import { DocumentScope } from '@embedpdf/svelte/runtime';
import { RenderLayer } from '@embedpdf/svelte/render';
import { Stage } from '@embedpdf/svelte/stage';
import ZoomControls from './ZoomControls.svelte';
let { documentId }: { documentId: string } = $props();
</script>
<DocumentScope id={documentId}>
<Stage>
<RenderLayer />
</Stage>
<ZoomControls />
</DocumentScope>Everything inside, such as the Stage, useSearch() and useSelection(), uses that document.
That’s how you show two documents at once, or a preview of one while another is active.
Loading live preview…
<script lang="ts" module>
import { Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { renderPlugin } from '@embedpdf/svelte/render';
import { stagePlugin } from '@embedpdf/svelte/stage';
import { localEngine } from '@embedpdf/engine';
import SideBySide from './SideBySide.svelte';
import '../scope.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin()];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<Viewer
{engine}
{plugins}
initialDocuments={[
{ source: ebook, name: 'Original' },
{ source: ebook, name: 'Revised' },
]}
>
<SideBySide />
</Viewer>Side by side#
The view manager arranges documents into panes, each with its own tabs, like split editors in a
code editor. Register viewManagerPlugin(), then render one Stage per pane:
<script lang="ts">
import { DocumentScope } from '@embedpdf/svelte/runtime';
import { RenderLayer } from '@embedpdf/svelte/render';
import { Stage } from '@embedpdf/svelte/stage';
import { useViewManager, useViewManagerState } from '@embedpdf/svelte/view-manager';
import PaneTabs from './PaneTabs.svelte';
const views = useViewManager();
const state = useViewManagerState();
</script>
<div class="panes">
{#each state.panes as pane (pane.id)}
<section data-focused={pane.id === state.focusedPaneId} onfocusin={() => views.setFocusedPane(pane.id)}>
<PaneTabs {pane} />
{#if pane.activeDocumentId}
<DocumentScope id={pane.activeDocumentId}>
<Stage>
<RenderLayer />
</Stage>
</DocumentScope>
{/if}
</section>
{/each}
</div>views.splitPane(documentId); // the document in a new pane, beside the focused one
views.setActiveDocument(paneId, documentId); // show another of the pane's tabs
views.moveDocumentBetween(fromPaneId, toPaneId, documentId); // drag a tab to another pane
views.removePane(paneId); // its documents move to another paneA document opened with documents.open() lands in the focused pane. Every call is listed under
Methods.
Loading live preview…
<script lang="ts" module>
import { Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { renderPlugin } from '@embedpdf/svelte/render';
import { stagePlugin } from '@embedpdf/svelte/stage';
import { viewManagerPlugin } from '@embedpdf/svelte/view-manager';
import { localEngine } from '@embedpdf/engine';
import Panes from './Panes.svelte';
import '../panes.css';
const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), viewManagerPlugin()];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<Viewer
{engine}
{plugins}
initialDocuments={[
{ source: ebook, name: 'Contract' },
{ source: ebook, name: 'Report' },
]}
>
<Panes />
</Viewer>Methods#
What useDocuments() gives you for several documents. Opening and closing are on
Opening documents, downloading and printing on
Saving & downloading.
| Method | What it does |
|---|---|
setActive(id) | Makes a document the active one: the one every component talks to outside <DocumentScope>. Manage the tabs |
rename(id, name) | Changes the name its tab shows. The file doesn’t change. |
move(id, index) | Moves a document to another place in the tab order |
swap(id, otherId) | Swaps two documents in the tab order |
setOrder(ids) | Puts every document in a new order at once. ids holds the id of every open document. |
Everything useViewManager() gives you.
| Method | What it does |
|---|---|
splitPane(documentId, { from? }) | Moves an open document into a new pane beside the focused one, or beside from, and focuses it. Returns the new pane’s id. Side by side |
createPane({ documentIds? }) | Adds a pane, empty or with these documents moved into it, and focuses it. Returns its id. |
removePane(paneId, { moveDocumentsTo? }) | Closes a pane. Its documents move to moveDocumentsTo, or else to the focused or the last pane. |
movePane(paneId, index) | Moves a pane to another place in the order, for reordering panes by dragging |
setFocusedPane(paneId) | Makes a pane the focused one, where new documents open. null focuses none. |
setActiveDocument(paneId, documentId) | Shows another of the pane’s documents |
addDocument(paneId, documentId, index?) | Adds a document to a pane’s tabs. A document is in one pane at a time, so it leaves the pane it was in. |
removeDocument(paneId, documentId) | Takes a document out of a pane, without closing it |
moveDocumentWithin(paneId, documentId, index) | Moves a tab to another place inside its pane |
moveDocumentBetween(fromPaneId, toPaneId, documentId, index?) | Moves a tab from one pane to another, for dragging tabs between panes |
State#
Everything useDocumentsState() gives you, as one reactive object: read its fields, and your markup updates when they change. The method in the last column reads the same value once.
| Field | What it is | Read once |
|---|---|---|
documents | Every open document, in tab order, including ones that are loading, locked or failed. Each has the fields useDocument() gives you. | list(), has(id), getCount(), getOrder() |
activeId | The active document’s id, or null with no document open | getActiveId(), or getActive() for its fields |
has(id) says whether a document is open, getCount() how many are, and getOrder() gives just their
ids. One document’s fields come from get(id), on Opening documents.
Everything useViewManagerState() gives you, as one reactive object: read its fields, and your markup updates when they change. The method in the last column reads the same value once.
| Field | What it is | Read once |
|---|---|---|
panes | Every pane, in order. Each has its id, its documentIds (its tabs, in order) and the activeDocumentId it shows. | listPanes(), getPane(paneId), getPaneOrder(), getPaneOfDocument(documentId) |
focusedPaneId | The pane new documents open in, or null | getFocusedPaneId() |
getPane(paneId) reads one pane, getPaneOrder() gives just their ids, and
getPaneOfDocument(documentId) says which pane holds a document.
Events#
<script lang="ts">
import { goto } from '$app/navigation';
import { useDocumentsEvent } from '@embedpdf/svelte/runtime';
useDocumentsEvent(
(documents) => documents.onActiveChanged,
({ documentId }) => goto(`/documents/${documentId}`, { replaceState: true }),
);
</script>| Event | Fires when | Carries |
|---|---|---|
onActiveChanged | Another document became the active one | documentId, previousDocumentId |
View manager onPaneCreated | A pane was added | paneId |
View manager onPaneRemoved | A pane was closed | paneId |
View manager onFocusChanged | Another pane got the focus | paneId, or null when none has it |
View manager onDocumentMoved | A document moved between panes | documentId, fromPaneId, toPaneId |
The view manager’s events come from useViewManagerEvent(). Opening and closing documents are on
Opening documents.
Good to know#
Memory#
Each open document holds its file in the engine’s memory. Close documents the user is done with, especially large ones.
One Stage per view#
A Stage shows one document. For two documents at once, render two Stages, each in its own
<DocumentScope>. For two views of one document, see
Two views of one document.
Your feedback goes directly to the documentation team.