kviewer 0.2.4 → 0.3.0

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 (40) hide show
  1. package/README.md +13 -1
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/annotation/engine/editor/editor.d.ts +17 -0
  4. package/dist/runtime/annotation/engine/editor/editor.js +44 -15
  5. package/dist/runtime/annotation/engine/tools/signature.js +1 -1
  6. package/dist/runtime/annotation/engine/tools/stamp.d.ts +8 -0
  7. package/dist/runtime/annotation/engine/tools/stamp.js +27 -2
  8. package/dist/runtime/annotation/engine/types.d.ts +25 -0
  9. package/dist/runtime/annotation/pdf-export/export.js +0 -3
  10. package/dist/runtime/annotation/utils/signature_image.d.ts +26 -0
  11. package/dist/runtime/annotation/utils/signature_image.js +20 -0
  12. package/dist/runtime/assets/kviewer.css +1 -1
  13. package/dist/runtime/components/FormFieldLayer.vue +6 -1
  14. package/dist/runtime/components/Viewer.d.vue.ts +56 -2
  15. package/dist/runtime/components/Viewer.vue +68 -2
  16. package/dist/runtime/components/Viewer.vue.d.ts +56 -2
  17. package/dist/runtime/components/ViewerTabs.d.vue.ts +29 -9
  18. package/dist/runtime/components/ViewerTabs.vue +10 -2
  19. package/dist/runtime/components/ViewerTabs.vue.d.ts +29 -9
  20. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +4 -2
  21. package/dist/runtime/components/form-fields/FormSignatureField.vue +15 -2
  22. package/dist/runtime/components/modals/FreeTextModal.vue +1 -1
  23. package/dist/runtime/components/modals/SignatureDrawModal.vue +170 -4
  24. package/dist/runtime/components/tools/ToolProperties.vue +98 -101
  25. package/dist/runtime/composables/useFormFields.d.ts +17 -1
  26. package/dist/runtime/composables/useFormFields.js +48 -9
  27. package/dist/runtime/composables/useViewerSearch.js +1 -1
  28. package/dist/runtime/composables/useViewerState.d.ts +1 -0
  29. package/dist/runtime/composables/useViewerState.js +12 -13
  30. package/dist/runtime/embed/bridge-client.d.ts +19 -3
  31. package/dist/runtime/embed/bridge-client.js +24 -0
  32. package/dist/runtime/embed/bridge-host.d.ts +10 -1
  33. package/dist/runtime/embed/bridge-host.js +20 -0
  34. package/dist/runtime/embed/protocol.d.ts +28 -1
  35. package/dist/runtime/i18n/messages.d.ts +16 -2
  36. package/dist/runtime/i18n/messages.js +16 -2
  37. package/dist/runtime/public-types.d.ts +1 -1
  38. package/dist/runtime/toolbar-tools.d.ts +1 -1
  39. package/dist/runtime/toolbar-tools.js +8 -0
  40. package/package.json +18 -18
@@ -1,110 +1,107 @@
1
1
  <template>
2
- <div class="flex items-center gap-2 px-2 py-1 rounded-md border border-default bg-default/60 lg:min-w-64">
3
- <template v-if="supportsProperties">
4
- <!-- ── Desktop: inline color buttons + slider (md and above) ── -->
5
- <div class="hidden lg:flex items-center gap-1">
6
- <button
7
- v-for="color in colors"
8
- :key="color"
9
- class="w-4 h-4 rounded-full border-2 cursor-pointer transition-transform hover:scale-110"
10
- :class="color === currentColor ? 'border-white ring-1 ring-primary' : 'border-transparent'"
11
- :style="{ backgroundColor: color }"
12
- :title="`Color: ${color}`"
13
- @click="setColor(color)"
14
- />
15
- </div>
16
-
17
- <div
18
- v-if="activeType === AnnotationType.FREEHAND"
19
- class="hidden lg:flex items-center gap-2 min-w-40"
20
- >
21
- <UIcon name="i-lucide-minus" class="text-[10px] text-muted" />
22
- <USlider
23
- :model-value="freehandStrokeWidth"
24
- :min="1"
25
- :max="24"
26
- :step="1"
27
- size="sm"
28
- class="w-28"
29
- @update:model-value="setFreehandSize"
30
- />
31
- <UIcon name="i-lucide-plus" class="text-[10px] text-muted" />
32
- <span class="w-9 text-[11px] text-muted text-right tabular-nums">
33
- {{ freehandStrokeWidth }}px
34
- </span>
35
- </div>
2
+ <!-- Rendered only while a tool with editable style is active — an empty
3
+ "no properties" pill would just be toolbar noise the rest of the
4
+ time. The second row is a grid, so the center column stays put. -->
5
+ <div
6
+ v-if="supportsProperties"
7
+ class="flex items-center gap-2 px-2 py-1 rounded-md border border-default bg-default/60 lg:min-w-64"
8
+ >
9
+ <!-- ── Desktop: inline color buttons + slider (md and above) ── -->
10
+ <div class="hidden lg:flex items-center gap-1">
11
+ <button
12
+ v-for="color in colors"
13
+ :key="color"
14
+ class="w-4 h-4 rounded-full border-2 cursor-pointer transition-transform hover:scale-110"
15
+ :class="color === currentColor ? 'border-white ring-1 ring-primary' : 'border-transparent'"
16
+ :style="{ backgroundColor: color }"
17
+ :title="`Color: ${color}`"
18
+ @click="setColor(color)"
19
+ />
20
+ </div>
36
21
 
37
- <!-- ── Mobile: two popover buttons (below md) ── -->
38
- <div class="flex lg:hidden items-center gap-1">
39
- <!-- Color popover -->
40
- <UPopover v-model:open="colorPopoverOpen">
41
- <button
42
- class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
43
- :title="t('color')"
44
- >
45
- <span
46
- class="w-4 h-4 rounded-full border border-white/30"
47
- :style="{ backgroundColor: currentColor ?? colors[0] }"
48
- />
49
- </button>
50
- <template #content>
51
- <div class="p-3">
52
- <div class="grid grid-cols-4 gap-1.5">
53
- <button
54
- v-for="color in colors"
55
- :key="color"
56
- class="w-7 h-7 rounded-full border-2 cursor-pointer transition-transform hover:scale-110"
57
- :class="color === currentColor ? 'border-white ring-2 ring-primary' : 'border-transparent'"
58
- :style="{ backgroundColor: color }"
59
- @click="setColorAndClose(color)"
60
- />
61
- </div>
62
- </div>
63
- </template>
64
- </UPopover>
22
+ <div
23
+ v-if="activeType === AnnotationType.FREEHAND"
24
+ class="hidden lg:flex items-center gap-2 min-w-40"
25
+ >
26
+ <UIcon name="i-lucide-minus" class="text-[10px] text-muted" />
27
+ <USlider
28
+ :model-value="freehandStrokeWidth"
29
+ :min="1"
30
+ :max="24"
31
+ :step="1"
32
+ size="sm"
33
+ class="w-28"
34
+ @update:model-value="setFreehandSize"
35
+ />
36
+ <UIcon name="i-lucide-plus" class="text-[10px] text-muted" />
37
+ <span class="w-9 text-[11px] text-muted text-right tabular-nums">
38
+ {{ freehandStrokeWidth }}px
39
+ </span>
40
+ </div>
65
41
 
66
- <!-- Stroke width popover (freehand only) -->
67
- <UPopover v-if="activeType === AnnotationType.FREEHAND">
68
- <button
69
- class="flex items-center justify-center h-7 px-1.5 gap-0.5 rounded-md hover:bg-default cursor-pointer"
70
- :title="t('strokeWidth')"
71
- >
72
- <UIcon name="i-lucide-pencil" class="text-sm text-muted" />
73
- <span class="text-[10px] text-muted tabular-nums">{{ freehandStrokeWidth }}</span>
74
- </button>
75
- <template #content>
76
- <div class="p-3 flex items-center gap-3">
77
- <button
78
- class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
79
- :disabled="freehandStrokeWidth <= 1"
80
- :title="t('decreaseStrokeWidth')"
81
- @click="decrementStrokeWidth"
82
- >
83
- <UIcon name="i-lucide-minus" class="text-sm" />
84
- </button>
85
- <span class="w-9 text-center text-sm tabular-nums">
86
- {{ freehandStrokeWidth }}px
87
- </span>
42
+ <!-- ── Mobile: two popover buttons (below md) ── -->
43
+ <div class="flex lg:hidden items-center gap-1">
44
+ <!-- Color popover -->
45
+ <UPopover v-model:open="colorPopoverOpen">
46
+ <button
47
+ class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
48
+ :title="t('color')"
49
+ >
50
+ <span
51
+ class="w-4 h-4 rounded-full border border-white/30"
52
+ :style="{ backgroundColor: currentColor ?? colors[0] }"
53
+ />
54
+ </button>
55
+ <template #content>
56
+ <div class="p-3">
57
+ <div class="grid grid-cols-4 gap-1.5">
88
58
  <button
89
- class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
90
- :disabled="freehandStrokeWidth >= 24"
91
- :title="t('increaseStrokeWidth')"
92
- @click="incrementStrokeWidth"
93
- >
94
- <UIcon name="i-lucide-plus" class="text-sm" />
95
- </button>
59
+ v-for="color in colors"
60
+ :key="color"
61
+ class="w-7 h-7 rounded-full border-2 cursor-pointer transition-transform hover:scale-110"
62
+ :class="color === currentColor ? 'border-white ring-2 ring-primary' : 'border-transparent'"
63
+ :style="{ backgroundColor: color }"
64
+ @click="setColorAndClose(color)"
65
+ />
96
66
  </div>
97
- </template>
98
- </UPopover>
99
- </div>
100
- </template>
67
+ </div>
68
+ </template>
69
+ </UPopover>
101
70
 
102
- <template v-else>
103
- <div class="flex items-center gap-1.5 text-xs text-muted">
104
- <UIcon name="i-lucide-sliders-horizontal" class="text-sm" />
105
- <span>{{ t("noProperties") }}</span>
106
- </div>
107
- </template>
71
+ <!-- Stroke width popover (freehand only) -->
72
+ <UPopover v-if="activeType === AnnotationType.FREEHAND">
73
+ <button
74
+ class="flex items-center justify-center h-7 px-1.5 gap-0.5 rounded-md hover:bg-default cursor-pointer"
75
+ :title="t('strokeWidth')"
76
+ >
77
+ <UIcon name="i-lucide-pencil" class="text-sm text-muted" />
78
+ <span class="text-[10px] text-muted tabular-nums">{{ freehandStrokeWidth }}</span>
79
+ </button>
80
+ <template #content>
81
+ <div class="p-3 flex items-center gap-3">
82
+ <button
83
+ class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
84
+ :disabled="freehandStrokeWidth <= 1"
85
+ :title="t('decreaseStrokeWidth')"
86
+ @click="decrementStrokeWidth"
87
+ >
88
+ <UIcon name="i-lucide-minus" class="text-sm" />
89
+ </button>
90
+ <span class="w-9 text-center text-sm tabular-nums">
91
+ {{ freehandStrokeWidth }}px
92
+ </span>
93
+ <button
94
+ class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
95
+ :disabled="freehandStrokeWidth >= 24"
96
+ :title="t('increaseStrokeWidth')"
97
+ @click="incrementStrokeWidth"
98
+ >
99
+ <UIcon name="i-lucide-plus" class="text-sm" />
100
+ </button>
101
+ </div>
102
+ </template>
103
+ </UPopover>
104
+ </div>
108
105
  </div>
109
106
  </template>
110
107
 
@@ -1,5 +1,5 @@
1
1
  import { type Ref, type ShallowRef } from 'vue';
2
- import type { AddFormFieldPayload, CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload, PlacedFieldDefaults, SignatureFieldStatus } 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[]>>;
@@ -35,6 +35,10 @@ export interface FormFieldsState {
35
35
  getAllFields: () => FormFieldDefinition[];
36
36
  /** All user-placed field definitions (used by export). */
37
37
  getPlacedFields: () => FormFieldDefinition[];
38
+ /** Signed-state snapshot of every signature widget (any origin).
39
+ * Reads the reactive defs/values, so computeds over it re-run on
40
+ * every signing or field change. */
41
+ getSignatureFieldStatus: () => SignatureFieldStatus[];
38
42
  /** Look up a field definition by id (any origin). */
39
43
  getFieldById: (id: string) => FormFieldDefinition | undefined;
40
44
  /** Id of the currently selected placed field (null if none). */
@@ -54,6 +58,18 @@ export interface FormFieldsState {
54
58
  /** Replace the host-provided color map. Called by KViewer when the
55
59
  * prop changes. */
56
60
  setRoleColors: (colors: Record<string, string>) => void;
61
+ /** Per-field-type presets merged into every tool placement. Host-owned
62
+ * via the KViewer `fieldDefaults` prop or the bridge; programmatic
63
+ * `addFormField` calls are NOT affected (the payload is explicit). */
64
+ placedFieldDefaults: ShallowRef<PlacedFieldDefaults>;
65
+ /** Replace the placement presets. */
66
+ setPlacedFieldDefaults: (defaults: PlacedFieldDefaults) => void;
67
+ /** When true, user-placed fields show selection chrome and can be
68
+ * moved/resized/deleted in hand mode even outside form-edit mode.
69
+ * Parsed/detected fields are unaffected. */
70
+ editablePlacedFields: Ref<boolean>;
71
+ /** Enable or disable placed-field editing outside form-edit mode. */
72
+ setEditablePlacedFields: (enabled: boolean) => void;
57
73
  /** Apply checkbox-style hints (extracted from `/MK /CA` in the source PDF)
58
74
  * to existing parsed checkbox defs and store the map for any defs
59
75
  * parsed afterwards. Keyed by checkbox fieldName. */
@@ -14,12 +14,20 @@ export function provideFormFields() {
14
14
  const selectedPlacedFieldId = ref(null);
15
15
  const activeRoleId = ref(null);
16
16
  const roleColors = shallowRef({});
17
+ const placedFieldDefaults = shallowRef({});
18
+ const editablePlacedFields = ref(false);
17
19
  function setActiveRole(id) {
18
20
  activeRoleId.value = id;
19
21
  }
20
22
  function setRoleColors(colors) {
21
23
  roleColors.value = colors;
22
24
  }
25
+ function setPlacedFieldDefaults(defaults) {
26
+ placedFieldDefaults.value = defaults;
27
+ }
28
+ function setEditablePlacedFields(enabled) {
29
+ editablePlacedFields.value = enabled;
30
+ }
23
31
  function selectPlacedField(id) {
24
32
  selectedPlacedFieldId.value = id;
25
33
  }
@@ -261,10 +269,6 @@ export function provideFormFields() {
261
269
  while (existing.has(`${base}_${n}`)) n += 1;
262
270
  return `${base}_${n}`;
263
271
  }
264
- function placedDefaultValue(fieldType) {
265
- if (fieldType === "checkbox") return false;
266
- return "";
267
- }
268
272
  function addPlacedField(payload) {
269
273
  const id = `placed-${generateUUID()}`;
270
274
  let fieldName;
@@ -284,6 +288,15 @@ export function provideFormFields() {
284
288
  required: false,
285
289
  origin: "placed"
286
290
  };
291
+ const defaults = placedFieldDefaults.value[payload.fieldType];
292
+ if (defaults) {
293
+ Object.assign(
294
+ def,
295
+ Object.fromEntries(
296
+ Object.entries(defaults).filter(([, value]) => value !== void 0)
297
+ )
298
+ );
299
+ }
287
300
  if (activeRoleId.value) {
288
301
  def.roleId = activeRoleId.value;
289
302
  }
@@ -291,11 +304,11 @@ export function provideFormFields() {
291
304
  def.buttonValue = nextRadioOptionValue(fieldName);
292
305
  }
293
306
  if (payload.fieldType === "checkbox") {
294
- def.checkboxStyle = "check";
307
+ def.checkboxStyle ??= "check";
295
308
  }
296
309
  if (payload.fieldType === "dropdown") {
297
- def.combo = true;
298
- def.options = [{ exportValue: "Option 1", displayValue: "Option 1" }];
310
+ def.combo ??= true;
311
+ def.options ??= [{ exportValue: "Option 1", displayValue: "Option 1" }];
299
312
  }
300
313
  const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
301
314
  fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
@@ -304,7 +317,9 @@ export function provideFormFields() {
304
317
  fieldId: id,
305
318
  fieldName: def.fieldName,
306
319
  fieldType: def.fieldType,
307
- value: placedDefaultValue(def.fieldType)
320
+ // Honors a `defaultValue` preset from placedFieldDefaults, matching
321
+ // how addFormField initializes values.
322
+ value: getDefaultValue(def)
308
323
  });
309
324
  triggerRef(fieldValues);
310
325
  if (payload.fieldType === "radio") {
@@ -455,6 +470,25 @@ export function provideFormFields() {
455
470
  }
456
471
  return false;
457
472
  }
473
+ function getSignatureFieldStatus() {
474
+ const out = [];
475
+ for (const defs of fieldDefinitions.value.values()) {
476
+ for (const d of defs) {
477
+ if (d.fieldType !== "signature") continue;
478
+ const fv = fieldValues.value.get(d.id);
479
+ const signed = typeof fv?.value === "string" && fv.value !== "" || d.signedInSource === true;
480
+ const status = {
481
+ id: d.id,
482
+ fieldName: d.fieldName,
483
+ pageNumber: d.pageNumber,
484
+ signed
485
+ };
486
+ if (d.roleId) status.roleId = d.roleId;
487
+ out.push(status);
488
+ }
489
+ }
490
+ return out;
491
+ }
458
492
  function isFieldLocked(fieldId) {
459
493
  const target = getFieldById(fieldId);
460
494
  if (!target) return false;
@@ -511,6 +545,7 @@ export function provideFormFields() {
511
545
  removePlacedField,
512
546
  getAllFields,
513
547
  getPlacedFields,
548
+ getSignatureFieldStatus,
514
549
  getFieldById,
515
550
  selectedPlacedFieldId,
516
551
  selectPlacedField,
@@ -525,7 +560,11 @@ export function provideFormFields() {
525
560
  activeRoleId,
526
561
  setActiveRole,
527
562
  roleColors,
528
- setRoleColors
563
+ setRoleColors,
564
+ placedFieldDefaults,
565
+ setPlacedFieldDefaults,
566
+ editablePlacedFields,
567
+ setEditablePlacedFields
529
568
  };
530
569
  provide(FORM_FIELDS_KEY, state);
531
570
  return state;
@@ -156,7 +156,7 @@ export function provideViewerSearch() {
156
156
  const end = match.end;
157
157
  const isSelected = isSelectedPage && i === selectedMatchIndex;
158
158
  const highlightSuffix = isSelected ? " selected" : "";
159
- let selectedSpan = null;
159
+ let selectedSpan;
160
160
  if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
161
161
  if (prevEnd) {
162
162
  appendTextToDiv(binding, prevEnd.divIdx, prevEnd.offset, void 0);
@@ -60,6 +60,7 @@ export interface ViewerState {
60
60
  visualZoom: Ref<number>;
61
61
  formEditMode: Ref<boolean>;
62
62
  setFormEditMode: (enabled: boolean) => void;
63
+ clickToSign: Ref<boolean>;
63
64
  }
64
65
  export interface ViewerStateProvider {
65
66
  state: ViewerState;
@@ -73,6 +73,7 @@ export function provideViewerState() {
73
73
  const visualZoom = ref(1);
74
74
  const stylusMode = ref(false);
75
75
  const formEditMode = ref(false);
76
+ const clickToSign = ref(false);
76
77
  function isFormFieldTool(t) {
77
78
  return typeof t === "number" && (t === AnnotationType.FORM_TEXT || t === AnnotationType.FORM_CHECKBOX || t === AnnotationType.FORM_RADIO || t === AnnotationType.FORM_SIGNATURE);
78
79
  }
@@ -96,28 +97,25 @@ export function provideViewerState() {
96
97
  const activeStamp = ref(null);
97
98
  const signatures = ref([]);
98
99
  const activeSignature = ref(null);
99
- let _signatureHandlers = null;
100
+ const _signatureHandlers = shallowRef(null);
100
101
  async function loadSignatures() {
101
- if (!_signatureHandlers) return;
102
- signatures.value = await _signatureHandlers.onLoad();
102
+ if (!_signatureHandlers.value) return;
103
+ signatures.value = await _signatureHandlers.value.onLoad();
103
104
  }
104
105
  async function saveSignature(imageUrl) {
105
- if (!_signatureHandlers) return null;
106
- const sig = await _signatureHandlers.onSave(imageUrl);
106
+ if (!_signatureHandlers.value) return null;
107
+ const sig = await _signatureHandlers.value.onSave(imageUrl);
107
108
  signatures.value = [...signatures.value, sig];
108
109
  return sig;
109
110
  }
110
111
  async function deleteSignature(id) {
111
- if (!_signatureHandlers) return;
112
- await _signatureHandlers.onDelete(id);
112
+ if (!_signatureHandlers.value) return;
113
+ await _signatureHandlers.value.onDelete(id);
113
114
  signatures.value = signatures.value.filter((s) => s.id !== id);
114
115
  if (activeSignature.value?.id === id) activeSignature.value = null;
115
116
  }
116
117
  function selectTool(tool, dataTransfer) {
117
118
  if (readonly.value && tool !== "hand") return;
118
- if (isFormFieldTool(tool) && !formEditMode.value) {
119
- formEditMode.value = true;
120
- }
121
119
  activeTool.value = tool;
122
120
  selectedAnnotation.value = null;
123
121
  selectedAnnotations.value = [];
@@ -187,10 +185,10 @@ export function provideViewerState() {
187
185
  signatures,
188
186
  activeSignature,
189
187
  get signatureHandlers() {
190
- return _signatureHandlers;
188
+ return _signatureHandlers.value;
191
189
  },
192
190
  set signatureHandlers(v) {
193
- _signatureHandlers = v;
191
+ _signatureHandlers.value = v;
194
192
  },
195
193
  loadSignatures,
196
194
  saveSignature,
@@ -207,7 +205,8 @@ export function provideViewerState() {
207
205
  shapeDetection: shapeDetectionEnabled,
208
206
  visualZoom,
209
207
  formEditMode,
210
- setFormEditMode
208
+ setFormEditMode,
209
+ clickToSign
211
210
  };
212
211
  provide(VIEWER_STATE_KEY, state);
213
212
  return {
@@ -1,5 +1,5 @@
1
1
  import type { ExportPdfOptions } from '../annotation/pdf-export/export.js';
2
- import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore, PlacedFieldDefaults, SignatureFieldStatus } from '../annotation/engine/types.js';
3
3
  import { type EventName, type EventPayloads, type ImportAnnotationsMode } from './protocol.js';
4
4
  export interface KViewerEmbedClientOptions {
5
5
  /**
@@ -20,8 +20,8 @@ type EventHandler<E extends EventName> = (payload: EventPayloads[E]) => void;
20
20
  * Parent-page client that drives a `<KViewer>` instance running inside
21
21
  * an iframe. Wraps `postMessage` into a Promise-based RPC mirroring the
22
22
  * viewer's exposed methods, and surfaces iframe-emitted events
23
- * (`ready`, `formEditMode-changed`, `viewedPages-changed`, `all-pages-read`)
24
- * via `on()`.
23
+ * (`ready`, `formEditMode-changed`, `viewedPages-changed`, `all-pages-read`,
24
+ * `field-placed`, `signedStatus-changed`) via `on()`.
25
25
  */
26
26
  export declare class KViewerEmbedClient {
27
27
  private readonly iframe;
@@ -50,6 +50,22 @@ export declare class KViewerEmbedClient {
50
50
  updateFormField(id: string, patch: Partial<FormFieldDefinition>): Promise<void>;
51
51
  removeFormField(id: string): Promise<void>;
52
52
  getFormFields(): Promise<FormFieldDefinition[]>;
53
+ /** Replace the per-field-type presets applied when the user places a
54
+ * field via the placement tools. Subscribe to `on('field-placed', …)`
55
+ * for per-field follow-ups (e.g. renaming). */
56
+ setFieldDefaults(defaults: PlacedFieldDefaults): Promise<void>;
57
+ /** Enable/disable kviewer's built-in click-to-sign flow on signature
58
+ * fields. Off by default — fields render as passive markers so the
59
+ * host's own signing process stays the only entry point. */
60
+ setClickToSign(enabled: boolean): Promise<void>;
61
+ /** True once every signature field is signed (in-session value or a
62
+ * digital signature already in the source PDF). Vacuously true with
63
+ * zero signature fields. Use to gate e.g. envelope finalization; for
64
+ * a push-style signal subscribe via `on('signedStatus-changed', …)`. */
65
+ getSignedStatus(): Promise<boolean>;
66
+ /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
67
+ * signed, roleId). */
68
+ getSignatureFieldStatus(): Promise<SignatureFieldStatus[]>;
53
69
  getFormEditMode(): Promise<boolean>;
54
70
  setFormEditMode(enabled: boolean): Promise<void>;
55
71
  toggleFormEditMode(): Promise<boolean>;
@@ -78,6 +78,30 @@ export class KViewerEmbedClient {
78
78
  getFormFields() {
79
79
  return this.call("getFormFields");
80
80
  }
81
+ /** Replace the per-field-type presets applied when the user places a
82
+ * field via the placement tools. Subscribe to `on('field-placed', …)`
83
+ * for per-field follow-ups (e.g. renaming). */
84
+ setFieldDefaults(defaults) {
85
+ return this.call("setFieldDefaults", defaults);
86
+ }
87
+ /** Enable/disable kviewer's built-in click-to-sign flow on signature
88
+ * fields. Off by default — fields render as passive markers so the
89
+ * host's own signing process stays the only entry point. */
90
+ setClickToSign(enabled) {
91
+ return this.call("setClickToSign", enabled);
92
+ }
93
+ /** True once every signature field is signed (in-session value or a
94
+ * digital signature already in the source PDF). Vacuously true with
95
+ * zero signature fields. Use to gate e.g. envelope finalization; for
96
+ * a push-style signal subscribe via `on('signedStatus-changed', …)`. */
97
+ getSignedStatus() {
98
+ return this.call("getSignedStatus");
99
+ }
100
+ /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
101
+ * signed, roleId). */
102
+ getSignatureFieldStatus() {
103
+ return this.call("getSignatureFieldStatus");
104
+ }
81
105
  getFormEditMode() {
82
106
  return this.call("getFormEditMode");
83
107
  }
@@ -1,6 +1,6 @@
1
1
  import { type Ref } from 'vue';
2
2
  import type { ExportPdfOptions } from '../annotation/pdf-export/export.js';
3
- import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore } from '../annotation/engine/types.js';
3
+ import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore, PlacedFieldDefaults, SignatureFieldStatus } from '../annotation/engine/types.js';
4
4
  import { type MethodName } from './protocol.js';
5
5
  /**
6
6
  * Surface the bridge calls on the KViewer template ref. Mirrors the
@@ -23,6 +23,15 @@ export interface KViewerApi {
23
23
  updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
24
24
  removeFormField: (id: string) => void;
25
25
  getFormFields: () => FormFieldDefinition[];
26
+ setFieldDefaults: (defaults: PlacedFieldDefaults) => void;
27
+ setClickToSign: (enabled: boolean) => void;
28
+ onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => () => void;
29
+ getSignedStatus: () => boolean;
30
+ getSignatureFieldStatus: () => SignatureFieldStatus[];
31
+ onSignedStatusChanged: (cb: (payload: {
32
+ allSigned: boolean;
33
+ fields: SignatureFieldStatus[];
34
+ }) => void) => () => void;
26
35
  formEditMode: Ref<boolean>;
27
36
  setFormEditMode: (enabled: boolean) => void;
28
37
  toggleFormEditMode: () => boolean;
@@ -66,6 +66,20 @@ export function useKViewerEmbedBridge(viewerRef, options) {
66
66
  }
67
67
  case "getFormFields":
68
68
  return viewer.getFormFields();
69
+ case "setFieldDefaults": {
70
+ const [defaults] = req.args;
71
+ viewer.setFieldDefaults(defaults);
72
+ return void 0;
73
+ }
74
+ case "setClickToSign": {
75
+ const [enabled] = req.args;
76
+ viewer.setClickToSign(enabled);
77
+ return void 0;
78
+ }
79
+ case "getSignedStatus":
80
+ return viewer.getSignedStatus();
81
+ case "getSignatureFieldStatus":
82
+ return viewer.getSignatureFieldStatus();
69
83
  case "getFormEditMode":
70
84
  return viewer.formEditMode.value;
71
85
  case "setFormEditMode": {
@@ -130,6 +144,12 @@ export function useKViewerEmbedBridge(viewerRef, options) {
130
144
  viewer.formEditMode,
131
145
  (enabled) => postEvent("formEditMode-changed", { enabled })
132
146
  ),
147
+ viewer.onFieldPlaced(
148
+ (field) => postEvent("field-placed", { field })
149
+ ),
150
+ viewer.onSignedStatusChanged(
151
+ (payload) => postEvent("signedStatus-changed", payload)
152
+ ),
133
153
  // Read-tracking. The getters touch the viewer's reactive viewed-pages
134
154
  // state, so watch re-runs them whenever a page is viewed or tracking
135
155
  // is reset.
@@ -1,5 +1,5 @@
1
1
  import type { ExportPdfOptions } from '../annotation/pdf-export/export.js';
2
- import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, FormFieldDefinition, FormFieldValue, IAnnotationStore, PlacedFieldDefaults, SignatureFieldStatus } from '../annotation/engine/types.js';
3
3
  export declare const KVIEWER_EMBED_PROTOCOL_VERSION = 1;
4
4
  export declare const REQUEST_KIND: "kviewer:request";
5
5
  export declare const RESPONSE_KIND: "kviewer:response";
@@ -47,6 +47,22 @@ export interface MethodSignatures {
47
47
  args: [];
48
48
  result: FormFieldDefinition[];
49
49
  };
50
+ setFieldDefaults: {
51
+ args: [defaults: PlacedFieldDefaults];
52
+ result: undefined;
53
+ };
54
+ setClickToSign: {
55
+ args: [enabled: boolean];
56
+ result: undefined;
57
+ };
58
+ getSignedStatus: {
59
+ args: [];
60
+ result: boolean;
61
+ };
62
+ getSignatureFieldStatus: {
63
+ args: [];
64
+ result: SignatureFieldStatus[];
65
+ };
50
66
  getFormEditMode: {
51
67
  args: [];
52
68
  result: boolean;
@@ -112,6 +128,17 @@ export interface EventPayloads {
112
128
  /** Fires once when the final unseen page is viewed. Re-arms after
113
129
  * `resetViewedPages()` or a new document. */
114
130
  'all-pages-read': Record<string, never>;
131
+ /** The user placed a form field via a placement tool. Patch it with
132
+ * `updateFormField()` to set host-driven properties. */
133
+ 'field-placed': {
134
+ field: FormFieldDefinition;
135
+ };
136
+ /** A signature field's signed state changed (signed, cleared, added,
137
+ * removed, or renamed). Push-style counterpart to `getSignedStatus()`. */
138
+ 'signedStatus-changed': {
139
+ allSigned: boolean;
140
+ fields: SignatureFieldStatus[];
141
+ };
115
142
  }
116
143
  export type EventName = keyof EventPayloads;
117
144
  export interface EventMessage<E extends EventName = EventName> {