kviewer 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +29 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -0
  4. package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
  5. package/dist/runtime/annotation/checkbox-styles.js +27 -0
  6. package/dist/runtime/annotation/engine/config.js +44 -0
  7. package/dist/runtime/annotation/engine/painter.d.ts +4 -1
  8. package/dist/runtime/annotation/engine/painter.js +26 -1
  9. package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
  10. package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
  11. package/dist/runtime/annotation/engine/types.d.ts +54 -1
  12. package/dist/runtime/annotation/engine/types.js +4 -0
  13. package/dist/runtime/annotation/font-style.d.ts +23 -0
  14. package/dist/runtime/annotation/font-style.js +57 -0
  15. package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
  16. package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
  17. package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
  18. package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
  19. package/dist/runtime/annotation/pdf-export/export-form-fields.js +245 -1
  20. package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
  21. package/dist/runtime/annotation/pdf-export/export.js +9 -2
  22. package/dist/runtime/assets/kviewer.css +1 -1
  23. package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
  24. package/dist/runtime/components/FormFieldLayer.vue +40 -3
  25. package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
  26. package/dist/runtime/components/PdfPage.vue +24 -0
  27. package/dist/runtime/components/Viewer.d.vue.ts +18 -3
  28. package/dist/runtime/components/Viewer.vue +66 -7
  29. package/dist/runtime/components/Viewer.vue.d.ts +18 -3
  30. package/dist/runtime/components/ViewerBar.vue +24 -2
  31. package/dist/runtime/components/form-fields/FormButton.vue +16 -4
  32. package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
  33. package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
  34. package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
  35. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +45 -9
  36. package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
  37. package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
  38. package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
  39. package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
  40. package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
  41. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
  42. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
  43. package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
  44. package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
  45. package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
  46. package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
  47. package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
  48. package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
  49. package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
  50. package/dist/runtime/composables/useAnnotationEngine.d.ts +1 -0
  51. package/dist/runtime/composables/useAnnotationEngine.js +2 -1
  52. package/dist/runtime/composables/useFormFields.d.ts +29 -2
  53. package/dist/runtime/composables/useFormFields.js +259 -5
  54. package/dist/runtime/composables/useInertiaPanzoom.js +3 -0
  55. package/dist/runtime/composables/usePageVirtualization.d.ts +6 -0
  56. package/dist/runtime/composables/usePageVirtualization.js +11 -3
  57. package/dist/runtime/composables/useViewerState.d.ts +3 -0
  58. package/dist/runtime/composables/useViewerState.js +22 -1
  59. package/package.json +2 -2
@@ -0,0 +1,505 @@
1
+ <template>
2
+ <!-- Inline sidebar that takes its place in the viewer's flex row.
3
+ Block-level positioning means it lives strictly inside the page-
4
+ view section — no fixed-position overlay, so it can never spill
5
+ under the ViewerBar at the top, and z-index battles with the
6
+ AnnotationToolbar are moot.
7
+
8
+ Always shown while form-edit mode is active so the user has a
9
+ persistent surface for field properties (and a placeholder hint
10
+ when nothing is selected yet). -->
11
+ <aside
12
+ v-show="state.formEditMode.value"
13
+ class="kviewer-placed-sidebar relative flex flex-col h-full w-80 shrink-0 border-s border-default bg-elevated overflow-hidden"
14
+ >
15
+ <header class="px-4 py-3 border-b border-default shrink-0">
16
+ <p class="text-highlighted font-semibold truncate">{{ title }}</p>
17
+ <p v-if="description" class="text-muted text-sm truncate">{{ description }}</p>
18
+ </header>
19
+
20
+ <div class="flex-1 min-h-0 overflow-y-auto">
21
+ <div v-if="field" class="flex flex-col gap-4 p-4">
22
+ <div class="flex flex-col gap-1">
23
+ <label class="text-xs font-medium text-muted">Field name</label>
24
+ <UInput
25
+ v-model="nameDraft"
26
+ size="sm"
27
+ spellcheck="false"
28
+ @blur="commitName"
29
+ @keydown.enter.prevent="commitName"
30
+ />
31
+ <p class="text-xs text-muted">
32
+ Shared names group radio buttons and bind multiple widgets to the same value.
33
+ </p>
34
+ </div>
35
+
36
+ <div class="flex flex-col gap-2">
37
+ <UCheckbox
38
+ :model-value="field.required"
39
+ label="Required"
40
+ @update:model-value="onRequired"
41
+ />
42
+ <UCheckbox
43
+ :model-value="field.readOnly"
44
+ label="Read-only"
45
+ @update:model-value="onReadOnly"
46
+ />
47
+ </div>
48
+
49
+ <template v-if="field.fieldType === 'text'">
50
+ <div class="flex flex-col gap-2">
51
+ <UCheckbox
52
+ :model-value="!!field.multiLine"
53
+ label="Multi-line"
54
+ @update:model-value="onMultiLine"
55
+ />
56
+ <div class="flex flex-col gap-1">
57
+ <label class="text-xs font-medium text-muted">Max length</label>
58
+ <UInput
59
+ type="number"
60
+ :model-value="field.maxLen != null ? String(field.maxLen) : ''"
61
+ placeholder="No limit"
62
+ size="sm"
63
+ min="0"
64
+ @update:model-value="onMaxLen"
65
+ />
66
+ </div>
67
+ </div>
68
+ </template>
69
+
70
+ <template v-if="field.fieldType === 'checkbox'">
71
+ <div class="flex flex-col gap-1">
72
+ <label class="text-xs font-medium text-muted">Style</label>
73
+ <USelect
74
+ :model-value="field.checkboxStyle ?? 'check'"
75
+ :items="checkboxStyleItems"
76
+ size="sm"
77
+ @update:model-value="onCheckboxStyle"
78
+ />
79
+ <p class="text-xs text-muted">
80
+ Glyph drawn when checked. Saved as <code>/MK /CA</code> on export.
81
+ </p>
82
+ </div>
83
+ </template>
84
+
85
+ <template v-if="field.fieldType === 'dropdown'">
86
+ <div class="flex flex-col gap-1">
87
+ <label class="text-xs font-medium text-muted">Options</label>
88
+ <div class="flex flex-col gap-1">
89
+ <div
90
+ v-for="(opt, i) in field.options ?? []"
91
+ :key="i"
92
+ class="flex items-center gap-1"
93
+ >
94
+ <UInput
95
+ :model-value="opt.displayValue"
96
+ size="sm"
97
+ spellcheck="false"
98
+ placeholder="Label"
99
+ class="flex-1"
100
+ @update:model-value="(v) => updateOption(i, v)"
101
+ />
102
+ <UButton
103
+ icon="i-lucide-x"
104
+ variant="ghost"
105
+ color="neutral"
106
+ size="xs"
107
+ square
108
+ @click="removeOption(i)"
109
+ />
110
+ </div>
111
+ </div>
112
+ <UButton
113
+ icon="i-lucide-plus"
114
+ label="Add option"
115
+ variant="soft"
116
+ color="neutral"
117
+ size="xs"
118
+ class="mt-1 w-full justify-center"
119
+ @click="addOption"
120
+ />
121
+ <UCheckbox
122
+ :model-value="field.combo !== false"
123
+ label="Combo box (collapsible)"
124
+ class="mt-2"
125
+ @update:model-value="onCombo"
126
+ />
127
+ <UCheckbox
128
+ :model-value="!!field.editable"
129
+ label="Allow custom text"
130
+ @update:model-value="onEditable"
131
+ />
132
+ <UCheckbox
133
+ :model-value="!!field.multiSelect"
134
+ label="Multi-select"
135
+ @update:model-value="onMultiSelect"
136
+ />
137
+ </div>
138
+ </template>
139
+
140
+ <template v-if="field.fieldType === 'signature'">
141
+ <div class="flex flex-col gap-1">
142
+ <label class="text-xs font-medium text-muted">Prompt text</label>
143
+ <UInput
144
+ v-model="promptDraft"
145
+ size="sm"
146
+ spellcheck="false"
147
+ placeholder="Click to sign"
148
+ @blur="commitPrompt"
149
+ @keydown.enter.prevent="commitPrompt"
150
+ />
151
+ <p class="text-xs text-muted">
152
+ Shown on the unsigned widget. Defaults to "Click to sign".
153
+ </p>
154
+ </div>
155
+
156
+ <div class="flex flex-col gap-2">
157
+ <div class="flex flex-col gap-1">
158
+ <label class="text-xs font-medium text-muted">Lock fields after signing</label>
159
+ <USelect
160
+ :model-value="field.lockAction ?? 'none'"
161
+ :items="lockActionItems"
162
+ size="sm"
163
+ @update:model-value="onLockAction"
164
+ />
165
+ <p class="text-xs text-muted">
166
+ Once this signature is filled, listed fields become read-only
167
+ inside the viewer. Not preserved on PDF export.
168
+ </p>
169
+ </div>
170
+
171
+ <div
172
+ v-if="field.lockAction === 'include' || field.lockAction === 'exclude'"
173
+ class="flex flex-col gap-1"
174
+ >
175
+ <label class="text-xs font-medium text-muted">
176
+ {{ field.lockAction === "include" ? "Lock these fields" : "Lock all fields except" }}
177
+ </label>
178
+ <div
179
+ v-if="otherFieldNames.length === 0"
180
+ class="text-xs text-muted italic"
181
+ >
182
+ No other named fields in the document.
183
+ </div>
184
+ <div v-else class="flex flex-col gap-1 max-h-48 overflow-y-auto">
185
+ <UCheckbox
186
+ v-for="name in otherFieldNames"
187
+ :key="name"
188
+ :model-value="(field.lockFieldNames ?? []).includes(name)"
189
+ :label="name"
190
+ @update:model-value="(v) => toggleLockField(name, v === true)"
191
+ />
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </template>
196
+
197
+ <template v-if="field.fieldType === 'radio'">
198
+ <div class="flex flex-col gap-1">
199
+ <label class="text-xs font-medium text-muted">Group</label>
200
+ <USelect
201
+ :model-value="field.fieldName"
202
+ :items="radioGroupItems"
203
+ size="sm"
204
+ @update:model-value="onRadioGroup"
205
+ />
206
+ <p class="text-xs text-muted">
207
+ Radios sharing a group act as one — picking one clears the others.
208
+ </p>
209
+ </div>
210
+
211
+ <div class="flex flex-col gap-1">
212
+ <label class="text-xs font-medium text-muted">Option value</label>
213
+ <UInput
214
+ v-model="buttonValueDraft"
215
+ size="sm"
216
+ spellcheck="false"
217
+ @blur="commitButtonValue"
218
+ @keydown.enter.prevent="commitButtonValue"
219
+ />
220
+ <p class="text-xs text-muted">
221
+ Value stored when this option is selected. Must be unique inside the group.
222
+ </p>
223
+ </div>
224
+ </template>
225
+
226
+ <div class="flex flex-col gap-1 text-xs text-muted">
227
+ <div>Type: <span class="font-medium">{{ field.fieldType }}</span></div>
228
+ <div>Page: <span class="font-medium">{{ field.pageNumber }}</span></div>
229
+ </div>
230
+
231
+ <UButton
232
+ color="error"
233
+ variant="soft"
234
+ icon="i-lucide-trash-2"
235
+ block
236
+ @click="onDelete"
237
+ >
238
+ Delete field
239
+ </UButton>
240
+ </div>
241
+ <div v-else class="text-sm text-muted p-4">
242
+ Select a placed field to edit its properties.
243
+ </div>
244
+ </div>
245
+ </aside>
246
+ </template>
247
+
248
+ <script setup>
249
+ import { computed, onBeforeUnmount, ref, watch } from "vue";
250
+ import { CHECKBOX_STYLE_OPTIONS } from "../../annotation/checkbox-styles";
251
+ import { useFormFields } from "../../composables/useFormFields";
252
+ import { useViewerState } from "../../composables/useViewerState";
253
+ const checkboxStyleItems = CHECKBOX_STYLE_OPTIONS;
254
+ const formFields = useFormFields();
255
+ const state = useViewerState();
256
+ const field = computed(() => {
257
+ const id = formFields.selectedPlacedFieldId.value;
258
+ if (!id) return null;
259
+ return formFields.getFieldById(id) ?? null;
260
+ });
261
+ const title = computed(() => {
262
+ if (!field.value) return "Field properties";
263
+ return field.value.fieldName;
264
+ });
265
+ const description = computed(() => {
266
+ if (!field.value) return void 0;
267
+ return `${labelForType(field.value.fieldType)} on page ${field.value.pageNumber}`;
268
+ });
269
+ function labelForType(t) {
270
+ switch (t) {
271
+ case "text":
272
+ return "Text field";
273
+ case "checkbox":
274
+ return "Checkbox";
275
+ case "radio":
276
+ return "Radio button";
277
+ case "signature":
278
+ return "Signature field";
279
+ case "dropdown":
280
+ return "Dropdown";
281
+ case "button":
282
+ return "Button";
283
+ default:
284
+ return "Field";
285
+ }
286
+ }
287
+ const nameDraft = ref("");
288
+ const buttonValueDraft = ref("");
289
+ const promptDraft = ref("");
290
+ watch(field, (f) => {
291
+ nameDraft.value = f?.fieldName ?? "";
292
+ buttonValueDraft.value = f?.buttonValue ?? "";
293
+ promptDraft.value = f?.promptText ?? "";
294
+ }, { immediate: true });
295
+ const lockActionItems = [
296
+ { label: "None", value: "none" },
297
+ { label: "All other fields", value: "all" },
298
+ { label: "Selected fields only", value: "include" },
299
+ { label: "All except selected", value: "exclude" }
300
+ ];
301
+ const NEW_GROUP_SENTINEL = "__new_radio_group__";
302
+ const radioGroupItems = computed(() => {
303
+ const names = /* @__PURE__ */ new Set();
304
+ for (const defs of formFields.fieldDefinitions.value.values()) {
305
+ for (const d of defs) {
306
+ if (d.fieldType === "radio" && d.fieldName) names.add(d.fieldName);
307
+ }
308
+ }
309
+ const sorted = Array.from(names).sort();
310
+ return [
311
+ ...sorted.map((n) => ({ label: n, value: n })),
312
+ { label: "+ New group", value: NEW_GROUP_SENTINEL }
313
+ ];
314
+ });
315
+ const otherFieldNames = computed(() => {
316
+ const current = field.value;
317
+ if (!current) return [];
318
+ const names = /* @__PURE__ */ new Set();
319
+ for (const defs of formFields.fieldDefinitions.value.values()) {
320
+ for (const d of defs) {
321
+ if (d.id === current.id) continue;
322
+ if (!d.fieldName) continue;
323
+ names.add(d.fieldName);
324
+ }
325
+ }
326
+ return Array.from(names).sort();
327
+ });
328
+ function commitName() {
329
+ if (!field.value) return;
330
+ const v = nameDraft.value.trim();
331
+ if (!v || v === field.value.fieldName) {
332
+ nameDraft.value = field.value.fieldName;
333
+ return;
334
+ }
335
+ formFields.updatePlacedField(field.value.id, { fieldName: v });
336
+ }
337
+ function commitButtonValue() {
338
+ if (!field.value) return;
339
+ const v = buttonValueDraft.value.trim();
340
+ if (!v || v === field.value.buttonValue) {
341
+ buttonValueDraft.value = field.value.buttonValue ?? "";
342
+ return;
343
+ }
344
+ formFields.updatePlacedField(field.value.id, { buttonValue: v });
345
+ }
346
+ function commitPrompt() {
347
+ if (!field.value) return;
348
+ const v = promptDraft.value.trim();
349
+ const next = v === "" ? void 0 : v;
350
+ if (next === field.value.promptText) return;
351
+ formFields.updatePlacedField(field.value.id, { promptText: next });
352
+ }
353
+ function onLockAction(v) {
354
+ if (!field.value) return;
355
+ if (v === "none") {
356
+ formFields.updatePlacedField(field.value.id, {
357
+ lockAction: void 0,
358
+ lockFieldNames: void 0
359
+ });
360
+ return;
361
+ }
362
+ formFields.updatePlacedField(field.value.id, {
363
+ lockAction: v,
364
+ // Initialize an empty list when switching to include/exclude so the
365
+ // checkbox UI binds against an array immediately.
366
+ lockFieldNames: field.value.lockFieldNames ?? []
367
+ });
368
+ }
369
+ function nextFreeRadioGroupName() {
370
+ const used = /* @__PURE__ */ new Set();
371
+ for (const defs of formFields.fieldDefinitions.value.values()) {
372
+ for (const d of defs) used.add(d.fieldName);
373
+ }
374
+ let n = 1;
375
+ while (used.has(`Radio_${n}`)) n += 1;
376
+ return `Radio_${n}`;
377
+ }
378
+ function nextFreeOptionValue(groupName, ignoreId) {
379
+ const used = /* @__PURE__ */ new Set();
380
+ for (const defs of formFields.fieldDefinitions.value.values()) {
381
+ for (const d of defs) {
382
+ if (d.id === ignoreId) continue;
383
+ if (d.fieldType === "radio" && d.fieldName === groupName && d.buttonValue) {
384
+ used.add(d.buttonValue);
385
+ }
386
+ }
387
+ }
388
+ let n = 1;
389
+ while (used.has(`Option_${n}`)) n += 1;
390
+ return `Option_${n}`;
391
+ }
392
+ function onRadioGroup(v) {
393
+ if (!field.value) return;
394
+ if (v === field.value.fieldName) return;
395
+ if (v === NEW_GROUP_SENTINEL) {
396
+ const name = nextFreeRadioGroupName();
397
+ formFields.updatePlacedField(field.value.id, {
398
+ fieldName: name,
399
+ buttonValue: "Option_1"
400
+ });
401
+ return;
402
+ }
403
+ const current = field.value.buttonValue ?? "";
404
+ const collides = (() => {
405
+ if (!current) return true;
406
+ for (const defs of formFields.fieldDefinitions.value.values()) {
407
+ for (const d of defs) {
408
+ if (d.id === field.value.id) continue;
409
+ if (d.fieldType === "radio" && d.fieldName === v && d.buttonValue === current) {
410
+ return true;
411
+ }
412
+ }
413
+ }
414
+ return false;
415
+ })();
416
+ const buttonValue = collides ? nextFreeOptionValue(v, field.value.id) : current;
417
+ formFields.updatePlacedField(field.value.id, {
418
+ fieldName: v,
419
+ buttonValue
420
+ });
421
+ }
422
+ function toggleLockField(name, checked) {
423
+ if (!field.value) return;
424
+ const current = new Set(field.value.lockFieldNames ?? []);
425
+ if (checked) current.add(name);
426
+ else current.delete(name);
427
+ formFields.updatePlacedField(field.value.id, {
428
+ lockFieldNames: Array.from(current)
429
+ });
430
+ }
431
+ function onRequired(v) {
432
+ if (!field.value) return;
433
+ formFields.updatePlacedField(field.value.id, { required: v === true });
434
+ }
435
+ function onReadOnly(v) {
436
+ if (!field.value) return;
437
+ formFields.updatePlacedField(field.value.id, { readOnly: v === true });
438
+ }
439
+ function onMultiLine(v) {
440
+ if (!field.value) return;
441
+ formFields.updatePlacedField(field.value.id, { multiLine: v === true });
442
+ }
443
+ function onMaxLen(v) {
444
+ if (!field.value) return;
445
+ const n = typeof v === "number" ? v : Number.parseInt(v, 10);
446
+ const next = Number.isFinite(n) && n > 0 ? n : void 0;
447
+ formFields.updatePlacedField(field.value.id, { maxLen: next });
448
+ }
449
+ function onDelete() {
450
+ if (!field.value) return;
451
+ formFields.removePlacedField(field.value.id);
452
+ }
453
+ function onCheckboxStyle(v) {
454
+ if (!field.value) return;
455
+ formFields.updatePlacedField(field.value.id, { checkboxStyle: v });
456
+ }
457
+ function updateOption(i, displayValue) {
458
+ if (!field.value) return;
459
+ const next = (field.value.options ?? []).slice();
460
+ if (!next[i]) return;
461
+ next[i] = { exportValue: displayValue, displayValue };
462
+ formFields.updatePlacedField(field.value.id, { options: next });
463
+ }
464
+ function addOption() {
465
+ if (!field.value) return;
466
+ const existing = field.value.options ?? [];
467
+ const label = `Option ${existing.length + 1}`;
468
+ const next = [...existing, { exportValue: label, displayValue: label }];
469
+ formFields.updatePlacedField(field.value.id, { options: next });
470
+ }
471
+ function removeOption(i) {
472
+ if (!field.value) return;
473
+ const next = (field.value.options ?? []).slice();
474
+ next.splice(i, 1);
475
+ formFields.updatePlacedField(field.value.id, { options: next });
476
+ }
477
+ function onCombo(v) {
478
+ if (!field.value) return;
479
+ formFields.updatePlacedField(field.value.id, { combo: v === true });
480
+ }
481
+ function onEditable(v) {
482
+ if (!field.value) return;
483
+ formFields.updatePlacedField(field.value.id, { editable: v === true });
484
+ }
485
+ function onMultiSelect(v) {
486
+ if (!field.value) return;
487
+ formFields.updatePlacedField(field.value.id, { multiSelect: v === true });
488
+ }
489
+ function onDocPointerDown(e) {
490
+ if (!formFields.selectedPlacedFieldId.value) return;
491
+ const target = e.target;
492
+ if (!target) return;
493
+ if (target.closest(".kviewer-form-field")) return;
494
+ if (target.closest(".kviewer-placed-sidebar")) return;
495
+ if (target.closest('[role="dialog"]')) return;
496
+ if (target.closest("[data-reka-popper-content-wrapper]")) return;
497
+ formFields.selectPlacedField(null);
498
+ }
499
+ if (typeof window !== "undefined") {
500
+ window.addEventListener("pointerdown", onDocPointerDown, true);
501
+ onBeforeUnmount(() => {
502
+ window.removeEventListener("pointerdown", onDocPointerDown, true);
503
+ });
504
+ }
505
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div class="flex items-center gap-1">
3
+ <ToolButton
4
+ :tool="getDef(AnnotationType.FORM_TEXT)"
5
+ :active="state.activeTool.value === AnnotationType.FORM_TEXT"
6
+ @click="state.selectTool(AnnotationType.FORM_TEXT)"
7
+ />
8
+ <ToolButton
9
+ :tool="getDef(AnnotationType.FORM_CHECKBOX)"
10
+ :active="state.activeTool.value === AnnotationType.FORM_CHECKBOX"
11
+ @click="state.selectTool(AnnotationType.FORM_CHECKBOX)"
12
+ />
13
+ <ToolButton
14
+ :tool="getDef(AnnotationType.FORM_RADIO)"
15
+ :active="state.activeTool.value === AnnotationType.FORM_RADIO"
16
+ @click="state.selectTool(AnnotationType.FORM_RADIO)"
17
+ />
18
+ <ToolButton
19
+ :tool="getDef(AnnotationType.FORM_SIGNATURE)"
20
+ :active="state.activeTool.value === AnnotationType.FORM_SIGNATURE"
21
+ @click="state.selectTool(AnnotationType.FORM_SIGNATURE)"
22
+ />
23
+ </div>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { useViewerState } from "../../composables/useViewerState";
28
+ import { AnnotationType } from "../../annotation/engine/types";
29
+ import { annotationDefinitions } from "../../annotation/engine/config";
30
+ import ToolButton from "../ToolButton.vue";
31
+ const state = useViewerState();
32
+ function getDef(type) {
33
+ return annotationDefinitions.find((d) => d.type === type);
34
+ }
35
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -4,6 +4,7 @@ export declare function createAnnotationEngine(viewerState: ViewerState, options
4
4
  userName?: string;
5
5
  onRequestTextInput: PainterCallbacks['onRequestTextInput'];
6
6
  onRequestDeleteConfirm?: PainterCallbacks['onRequestDeleteConfirm'];
7
+ onPlaceField?: PainterCallbacks['onPlaceField'];
7
8
  freehandGroupingDelay?: number;
8
9
  setExternalGesturesEnabled?: (enabled: boolean) => void;
9
10
  }): Painter;
@@ -60,7 +60,8 @@ export function createAnnotationEngine(viewerState, options) {
60
60
  viewerState.history.commit();
61
61
  },
62
62
  onRequestTextInput: options.onRequestTextInput,
63
- onRequestDeleteConfirm: options.onRequestDeleteConfirm
63
+ onRequestDeleteConfirm: options.onRequestDeleteConfirm,
64
+ onPlaceField: options.onPlaceField
64
65
  };
65
66
  const painter = new Painter({
66
67
  userName: options.userName ?? "User",
@@ -1,5 +1,5 @@
1
- import { type ShallowRef } from 'vue';
2
- import type { DetectedShape, FormFieldDefinition, FormFieldValue } from '../annotation/engine/types.js';
1
+ import { type Ref, type ShallowRef } from 'vue';
2
+ import type { CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload } from '../annotation/engine/types.js';
3
3
  export interface FormFieldsState {
4
4
  /** Field definitions grouped by page number */
5
5
  fieldDefinitions: ShallowRef<Map<number, FormFieldDefinition[]>>;
@@ -21,6 +21,33 @@ export interface FormFieldsState {
21
21
  resetValues: () => void;
22
22
  /** Create synthetic checkbox form fields from detected shapes */
23
23
  registerDetectedCheckboxes: (pageNumber: number, shapes: DetectedShape[], scale: number, pageHeight: number) => void;
24
+ /** Add a user-placed form field (from the placement tool). Returns the created def. */
25
+ addPlacedField: (payload: PlaceFieldPayload) => FormFieldDefinition;
26
+ /** Update a placed field's rect, name, or flags. */
27
+ updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>) => void;
28
+ /** Remove a placed field and its value. */
29
+ removePlacedField: (id: string) => void;
30
+ /** All user-placed field definitions (used by export). */
31
+ getPlacedFields: () => FormFieldDefinition[];
32
+ /** Look up a field definition by id (any origin). */
33
+ getFieldById: (id: string) => FormFieldDefinition | undefined;
34
+ /** Id of the currently selected placed field (null if none). */
35
+ selectedPlacedFieldId: Ref<string | null>;
36
+ /** Select or deselect a placed field. Pass null to deselect. */
37
+ selectPlacedField: (id: string | null) => void;
38
+ /** Apply checkbox-style hints (extracted from `/MK /CA` in the source PDF)
39
+ * to existing parsed checkbox defs and store the map for any defs
40
+ * parsed afterwards. Keyed by checkbox fieldName. */
41
+ applyCheckboxStyles: (styles: Map<string, CheckboxStyle>) => void;
42
+ /** Apply push-button captions (extracted from `/MK /CA` in the source
43
+ * PDF) to existing parsed button defs and store the map for any defs
44
+ * parsed afterwards. Keyed by button fieldName. */
45
+ applyButtonCaptions: (captions: Map<string, string>) => void;
46
+ /** Whether this field is locked by another (signed) signature field's
47
+ * lock rule. Reactive to both definitions and values, so a UI computed
48
+ * that calls this re-runs when any signature is signed or its lock
49
+ * rule changes. */
50
+ isFieldLocked: (fieldId: string) => boolean;
24
51
  /** Clear all values and definitions (for document change) */
25
52
  reset: () => void;
26
53
  }