Skip to content

Commit 90a6553

Browse files
authored
Merge pull request #553 from embedpdf/chore/update-documentation
Update documentation
2 parents 6079301 + ada2f72 commit 90a6553

71 files changed

Lines changed: 4899 additions & 13 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/fair-cups-smile.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@embedpdf/snippet': patch
3+
---
4+
5+
Add form plugin support to the snippet API and expand the snippet documentation with standalone vanilla HTML examples.
6+
7+
This includes exporting the form plugin types from `@embedpdf/snippet`, updating form-related command/category behavior, and adding vanilla Tailwind examples plus new plugin docs for forms.

examples/svelte-tailwind/src/examples/viewer/disable-categories-example.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
4646
const categories = [
4747
{ id: 'annotation', label: 'Annotations' },
48+
{ id: 'form', label: 'Forms' },
4849
{ id: 'redaction', label: 'Redaction' },
4950
{ id: 'zoom', label: 'Zoom' },
5051
{ id: 'document-print', label: 'Print' },
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<script lang="ts">
2+
import { onDestroy } from 'svelte';
3+
import {
4+
PDFViewer,
5+
type EmbedPdfContainer,
6+
type PluginRegistry,
7+
type FormPlugin,
8+
type FormFieldInfo,
9+
} from '@embedpdf/svelte-pdf-viewer';
10+
11+
interface Props {
12+
themePreference?: 'light' | 'dark';
13+
}
14+
15+
let { themePreference = 'light' }: Props = $props();
16+
17+
const documentId = 'form-doc';
18+
19+
let container = $state<EmbedPdfContainer | null>(null);
20+
let formValues = $state<Record<string, string>>({});
21+
let fields = $state<FormFieldInfo[]>([]);
22+
let changeCount = $state(0);
23+
let cleanups: (() => void)[] = [];
24+
25+
const handleInit = (c: EmbedPdfContainer) => {
26+
container = c;
27+
};
28+
29+
const handleReady = (registry: PluginRegistry) => {
30+
const formPlugin = registry.getPlugin<FormPlugin>('form')?.provides();
31+
const formScope = formPlugin?.forDocument(documentId);
32+
33+
if (!formScope) return;
34+
35+
const syncValues = () => {
36+
formValues = formScope.getFormValues();
37+
};
38+
39+
fields = formScope.getFormFields();
40+
syncValues();
41+
42+
cleanups.push(
43+
formScope.onFormReady((nextFields) => {
44+
fields = nextFields;
45+
syncValues();
46+
}),
47+
);
48+
49+
cleanups.push(
50+
formScope.onFieldValueChange(() => {
51+
syncValues();
52+
changeCount += 1;
53+
}),
54+
);
55+
};
56+
57+
$effect(() => {
58+
container?.setTheme({ preference: themePreference });
59+
});
60+
61+
onDestroy(() => {
62+
cleanups.forEach((cleanup) => cleanup());
63+
});
64+
65+
const filledFieldCount = $derived(
66+
Object.values(formValues).filter((value) => value !== '' && value !== 'Off').length,
67+
);
68+
</script>
69+
70+
<div class="flex flex-col gap-4">
71+
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">
72+
<div
73+
class="h-[500px] overflow-hidden rounded-xl border border-gray-300 shadow-lg dark:border-gray-600"
74+
>
75+
<PDFViewer
76+
oninit={handleInit}
77+
onready={handleReady}
78+
config={{
79+
theme: { preference: themePreference },
80+
documentManager: {
81+
initialDocuments: [
82+
{
83+
url: '/form.pdf',
84+
documentId,
85+
},
86+
],
87+
},
88+
export: {
89+
defaultFileName: 'filled-form.pdf',
90+
},
91+
}}
92+
style="width: 100%; height: 100%;"
93+
/>
94+
</div>
95+
96+
<div
97+
class="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900"
98+
>
99+
<div
100+
class="border-b border-gray-200 bg-gray-50 px-4 py-3 dark:border-gray-700 dark:bg-gray-800"
101+
>
102+
<h4 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Form State</h4>
103+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
104+
Fill the PDF on the left to watch the values update live.
105+
</p>
106+
</div>
107+
108+
<div
109+
class="grid grid-cols-3 border-b border-gray-200 bg-gray-50 text-xs dark:border-gray-700 dark:bg-gray-800"
110+
>
111+
<div class="px-4 py-3">
112+
<div class="text-gray-500 dark:text-gray-400">Fields</div>
113+
<div class="mt-1 text-sm font-semibold text-gray-900 dark:text-gray-100">
114+
{fields.length}
115+
</div>
116+
</div>
117+
<div class="border-x border-gray-200 px-4 py-3 dark:border-gray-700">
118+
<div class="text-gray-500 dark:text-gray-400">Filled</div>
119+
<div class="mt-1 text-sm font-semibold text-gray-900 dark:text-gray-100">
120+
{filledFieldCount}
121+
</div>
122+
</div>
123+
<div class="px-4 py-3">
124+
<div class="text-gray-500 dark:text-gray-400">Changes</div>
125+
<div class="mt-1 text-sm font-semibold text-gray-900 dark:text-gray-100">
126+
{changeCount}
127+
</div>
128+
</div>
129+
</div>
130+
131+
<div class="max-h-[340px] overflow-auto p-4">
132+
{#if Object.keys(formValues).length > 0}
133+
<pre class="text-xs text-gray-800 dark:text-gray-300">{JSON.stringify(
134+
formValues,
135+
null,
136+
2,
137+
)}</pre>
138+
{:else}
139+
<p class="text-sm italic text-gray-400 dark:text-gray-500">Waiting for form fields...</p>
140+
{/if}
141+
</div>
142+
</div>
143+
</div>
144+
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './form-example.svelte';
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<script lang="ts">
2+
import {
3+
PDFViewer,
4+
type EmbedPdfContainer,
5+
type PluginRegistry,
6+
type ExportPlugin,
7+
type ExportScope,
8+
type FormPlugin,
9+
type FormScope,
10+
} from '@embedpdf/svelte-pdf-viewer';
11+
import { Check, Download, Loader2, Trash2, Wand2 } from 'lucide-svelte';
12+
13+
interface Props {
14+
themePreference?: 'light' | 'dark';
15+
}
16+
17+
let { themePreference = 'light' }: Props = $props();
18+
19+
const documentId = 'form-doc';
20+
21+
const demoData: Record<string, string> = {
22+
First_Name: 'Jane',
23+
Last_Name: 'Doe',
24+
Email_Address: '[email protected]',
25+
Phone_Number: '+1 (555) 123-4567',
26+
Home_Address: '123 Main Street',
27+
City: 'San Francisco',
28+
State: 'CA',
29+
Postal_Code: '94102',
30+
Department: 'Design',
31+
Employment_Type: 'Contract',
32+
Office_Location: 'San Francisco',
33+
Start_Date: '2026-04-01',
34+
Programming_Languages: 'TypeScript, Rust, Go',
35+
Framework_Tools: 'React, Node.js, Docker',
36+
Comments: 'I would like to have a standing desk and a dual monitor setup.',
37+
Equipment_Laptop: 'Yes',
38+
Equipment_Monitor: 'Yes',
39+
Equipment_Keyboard: 'Yes',
40+
Equipment_Desk: 'Yes',
41+
Access_Repository: 'Yes',
42+
Access_Cloud: 'Yes',
43+
Access_Internal: 'Yes',
44+
Access_VPN: 'Yes',
45+
Terms: 'Yes',
46+
Preferred_Shift: '4f803c06-508d-4232-bd84-82452b6561f1',
47+
Work_Arrangement: 'd43ec6d3-9e8c-403f-98d7-e2c818070ac4',
48+
};
49+
50+
const emptyData: Record<string, string> = {
51+
First_Name: '',
52+
Last_Name: '',
53+
Email_Address: '',
54+
Phone_Number: '',
55+
Home_Address: '',
56+
City: '',
57+
State: '',
58+
Postal_Code: '',
59+
Department: 'Engineering',
60+
Employment_Type: 'Full-time',
61+
Office_Location: 'New York',
62+
Start_Date: '',
63+
Programming_Languages: '',
64+
Framework_Tools: '',
65+
Comments: '',
66+
Equipment_Laptop: 'Off',
67+
Equipment_Monitor: 'Off',
68+
Equipment_Keyboard: 'Off',
69+
Equipment_Desk: 'Off',
70+
Access_Repository: 'Off',
71+
Access_Cloud: 'Off',
72+
Access_Internal: 'Off',
73+
Access_VPN: 'Off',
74+
Terms: 'Off',
75+
Preferred_Shift: '1a5963ac-8d1e-4c83-9a8b-da53700e46c1',
76+
Work_Arrangement: 'e424be12-71f7-4458-b1a3-a71a0b100729',
77+
};
78+
79+
let container = $state<EmbedPdfContainer | null>(null);
80+
let formScope = $state<FormScope | null>(null);
81+
let exportScope = $state<ExportScope | null>(null);
82+
let isMutating = $state(false);
83+
let saveStatus = $state<'idle' | 'success'>('idle');
84+
85+
const handleInit = (c: EmbedPdfContainer) => {
86+
container = c;
87+
};
88+
89+
const handleReady = (registry: PluginRegistry) => {
90+
const formPlugin = registry.getPlugin<FormPlugin>('form')?.provides();
91+
const exportPlugin = registry.getPlugin<ExportPlugin>('export')?.provides();
92+
93+
formScope = formPlugin?.forDocument(documentId) ?? null;
94+
exportScope = exportPlugin?.forDocument(documentId) ?? null;
95+
};
96+
97+
$effect(() => {
98+
container?.setTheme({ preference: themePreference });
99+
});
100+
101+
const applyFormValues = async (values: Record<string, string>) => {
102+
if (!formScope) return;
103+
104+
isMutating = true;
105+
await formScope.setFormValues(values).toPromise();
106+
isMutating = false;
107+
};
108+
109+
const handleDownload = () => {
110+
exportScope?.download();
111+
};
112+
113+
const handleSaveCopy = async () => {
114+
if (!exportScope) return;
115+
116+
isMutating = true;
117+
118+
const arrayBuffer = await exportScope.saveAsCopy().toPromise();
119+
const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
120+
const file = new File([blob], 'filled-form.pdf');
121+
122+
await new Promise((resolve) => setTimeout(resolve, 1000));
123+
124+
console.log(`Prepared ${file.size} bytes for upload.`);
125+
saveStatus = 'success';
126+
setTimeout(() => (saveStatus = 'idle'), 2500);
127+
isMutating = false;
128+
};
129+
</script>
130+
131+
<div class="flex flex-col gap-4">
132+
<div
133+
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800"
134+
>
135+
<div class="flex flex-wrap items-center gap-2">
136+
<button
137+
type="button"
138+
onclick={() => applyFormValues(demoData)}
139+
disabled={isMutating}
140+
class="inline-flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
141+
>
142+
{#if isMutating}
143+
<Loader2 size={16} class="animate-spin" />
144+
{:else}
145+
<Wand2 size={16} />
146+
{/if}
147+
Auto Fill Data
148+
</button>
149+
150+
<button
151+
type="button"
152+
onclick={() => applyFormValues(emptyData)}
153+
disabled={isMutating}
154+
class="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600"
155+
>
156+
<Trash2 size={16} />
157+
Clear Form
158+
</button>
159+
</div>
160+
161+
<div class="flex flex-wrap items-center gap-2">
162+
<button
163+
type="button"
164+
onclick={handleDownload}
165+
disabled={isMutating}
166+
class="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600"
167+
>
168+
<Download size={16} />
169+
Download PDF
170+
</button>
171+
172+
<button
173+
type="button"
174+
onclick={handleSaveCopy}
175+
disabled={isMutating}
176+
class="inline-flex items-center gap-1.5 rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50"
177+
>
178+
{#if isMutating}
179+
<Loader2 size={16} class="animate-spin" />
180+
{:else if saveStatus === 'success'}
181+
<Check size={16} />
182+
{:else}
183+
<Download size={16} />
184+
{/if}
185+
{isMutating ? 'Working...' : saveStatus === 'success' ? 'Saved!' : 'Save Copy'}
186+
</button>
187+
</div>
188+
</div>
189+
190+
<div
191+
class="h-[500px] w-full overflow-hidden rounded-xl border border-gray-300 shadow-lg dark:border-gray-600"
192+
>
193+
<PDFViewer
194+
oninit={handleInit}
195+
onready={handleReady}
196+
config={{
197+
theme: { preference: themePreference },
198+
documentManager: {
199+
initialDocuments: [
200+
{
201+
url: '/form.pdf',
202+
documentId,
203+
},
204+
],
205+
},
206+
export: {
207+
defaultFileName: 'filled-form.pdf',
208+
},
209+
}}
210+
style="width: 100%; height: 100%;"
211+
/>
212+
</div>
213+
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './form-import-example.svelte';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@embedpdf/example-vanilla-tailwind",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"license": "MIT",
7+
"scripts": {
8+
"dev": "python3 -m http.server 4173"
9+
}
10+
}

0 commit comments

Comments
 (0)