Controlled mode
By default Inkbar holds the annotations itself and gives you undo and redo. In controlled mode your app holds the list, and Inkbar proposes each change to you. Use it when the annotations belong in your own store, for example to save them as they change or to sync them between people.
Annotations embedded in the PDF stay inside Inkbar and never appear in your list.
In the examples, loadAnnotations stands for your own call to your server.
With v-model:annotations, Inkbar proposes each change through update:annotations and shows whatever list you pass back. To change annotations yourself, replace the array: annotations.value = [...annotations.value, next]. In-place mutation (push, splice, editing an item) is not observed, so the viewer never sees it.
Undo and redo are off in controlled mode unless you pass :history="true". Inkbar then keeps a stack of your list’s past values and applies undo and redo through v-model. Every change to the list is a step, including the list you load from your server when the document opens. Clear the history once that load has reached the viewer, so the first undo does not empty the list:
<script setup lang="ts">import { nextTick, ref, useTemplateRef } from 'vue'import { InkRoot, InkViewport } from '@inkbar/vue'import type { Annotation, InkDocument } from '@inkbar/core'import { assets } from 'virtual:inkbar-assets'
defineProps<{ signedUrl: string; documentId: string }>()
const annotations = ref<Annotation[]>([])const root = useTemplateRef<InstanceType<typeof InkRoot>>('root')
async function onReady(doc: InkDocument) { annotations.value = await loadAnnotations(doc.key) await nextTick() root.value?.history.clear()}</script>
<template> <InkRoot ref="root" v-model:annotations="annotations" :history="true" :src="{ url: signedUrl, key: documentId }" :assets="assets" @ready="onReady" > <InkViewport /> </InkRoot></template>Inside InkRoot, useHistory() returns the same object.
key identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.
With annotations and onAnnotationsChange, Inkbar proposes each change through onAnnotationsChange and shows whatever list you pass back. To change annotations yourself, set a new array: setAnnotations([...annotations, next]). In-place mutation (push, splice, editing an item) is not observed, so the viewer never sees it.
Undo and redo are off in controlled mode unless you pass history. Inkbar then keeps a stack of your list’s past values and applies undo and redo through onAnnotationsChange. Every change to the list is a step, including the list you load from your server when the document opens. Clear the history once that load has reached the viewer, so the first undo does not empty the list:
import { useEffect, useRef, useState } from 'react'import { InkRoot, InkViewport } from '@inkbar/react'import type { InkRootHandle } from '@inkbar/react'import type { Annotation, InkDocument } from '@inkbar/core'import { assets } from 'virtual:inkbar-assets'
export function Review({ signedUrl, documentId,}: { signedUrl: string documentId: string}) { const [annotations, setAnnotations] = useState<Annotation[]>([]) const [loaded, setLoaded] = useState<string | null>(null) const root = useRef<InkRootHandle>(null)
async function onReady(doc: InkDocument) { setAnnotations(await loadAnnotations(doc.key)) setLoaded(doc.key) }
// Runs after InkRoot has seen the loaded list. useEffect(() => { if (loaded) root.current?.history.clear() }, [loaded])
return ( <InkRoot ref={root} annotations={annotations} onAnnotationsChange={setAnnotations} history src={{ url: signedUrl, key: documentId }} assets={assets} onReady={onReady} > <InkViewport /> </InkRoot> )}Inside InkRoot, useHistory() returns the same object.
key identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.
With bind:annotations, Inkbar proposes each change by writing the binding and shows whatever list it holds. To change annotations yourself, replace the array: annotations = [...annotations, next]. In-place mutation (push, splice, editing an item) is not observed, so the viewer never sees it. Declare the list with $state.raw, since Inkbar replaces it whole.
To decide on each proposal before it applies, use a function binding. The setter receives the proposed list; apply it, change it or drop it:
<InkRoot src={{ url: signedUrl, key: documentId }} {assets} bind:annotations={() => annotations, (next) => (annotations = next.filter(allowed))}> <InkViewport /></InkRoot>Undo and redo are off in controlled mode unless you pass history={true}. Inkbar then keeps a stack of your list’s past values and applies undo and redo through the binding. Every change to the list is a step, including the list you load from your server when the document opens. Clear the history once that load has reached the viewer, so the first undo does not empty the list:
<script lang="ts"> import { tick } from 'svelte' import { InkRoot, InkViewport } from '@inkbar/svelte' import type { InkRootHandle } from '@inkbar/svelte' import type { Annotation, InkDocument } from '@inkbar/core' import { assets } from 'virtual:inkbar-assets'
let { signedUrl, documentId }: { signedUrl: string; documentId: string } = $props()
let annotations = $state.raw<Annotation[]>([]) let root = $state<InkRootHandle | undefined>()
async function onReady(doc: InkDocument) { annotations = await loadAnnotations(doc.key) await tick() root?.history.clear() }</script>
<InkRoot bind:this={root} bind:annotations history={true} src={{ url: signedUrl, key: documentId }} {assets} {onReady}> <InkViewport /></InkRoot>Inside InkRoot, useHistory() returns the same object.
key identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.