kviewer 0.3.4 → 0.3.5

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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.3.4",
4
+ "version": "0.3.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1,6 +1,6 @@
1
1
  import Konva from 'konva';
2
2
  import type { IRect } from 'konva/lib/types';
3
- import { type IAnnotationStore, type IAnnotationStyle, type IAnnotationType, type PlaceFieldPayload } from './types.js';
3
+ import { type FormFieldType, type IAnnotationStore, type IAnnotationStyle, type IAnnotationType, type PlaceFieldPayload } from './types.js';
4
4
  import { type IEditorFreeTextOptions } from './tools/free-text.js';
5
5
  export interface KonvaCanvas {
6
6
  pageNumber: number;
@@ -26,7 +26,7 @@ export interface PainterCallbacks {
26
26
  /** Optional accessor returning the color to use for the form-field
27
27
  * placement preview. Lets the active role color override the default
28
28
  * blue without rebuilding the editor between placements. */
29
- getFormFieldPreviewColor?: () => string | undefined;
29
+ getFormFieldPreviewColor?: (fieldType: FormFieldType) => string | undefined;
30
30
  }
31
31
  export declare class Painter {
32
32
  private userName?;
@@ -6,11 +6,11 @@ export interface IEditorFormFieldOptions {
6
6
  /** PDF-space page height, needed to flip Konva page-local Y → PDF Y. */
7
7
  pageHeight: number;
8
8
  onPlaceField: (payload: PlaceFieldPayload) => void;
9
- /** Resolves to the active role color so the placement preview matches
10
- * the color the field will be tagged with. Re-read on every drag start
11
- * so the user can switch roles between placements without rebuilding
12
- * the editor. */
13
- getPreviewColor?: () => string | undefined;
9
+ /** Resolves to the fieldColor preset for this field type so the
10
+ * placement preview matches the color the field will land with.
11
+ * Re-read on every drag start so the host can change defaults between
12
+ * placements without rebuilding the editor. */
13
+ getPreviewColor?: (fieldType: FormFieldType) => string | undefined;
14
14
  }
15
15
  export declare class EditorFormField extends Editor {
16
16
  private fieldType;
@@ -38,7 +38,7 @@ export class EditorFormField extends Editor {
38
38
  y: pos.y,
39
39
  width: 0,
40
40
  height: 0,
41
- stroke: this.getPreviewColor?.() ?? "#1677ff",
41
+ stroke: this.getPreviewColor?.(this.fieldType) ?? "#1677ff",
42
42
  strokeWidth: 1,
43
43
  dash: [4, 4],
44
44
  strokeScaleEnabled: false,
@@ -216,11 +216,17 @@ export interface FormFieldDefinition {
216
216
  color?: number[];
217
217
  backgroundColor?: number[];
218
218
  textAlignment?: number;
219
- /** Opaque role tag for color-coding in form-edit mode. The editor
220
- * doesn't own role definitions the host (integrating app) maps
221
- * roleId color via the `roleColors` prop on KViewer. Not exported
222
- * to the PDF. */
223
- roleId?: string;
219
+ /** CSS color driving the field's editor chrome (outline, grip, hover
220
+ * accents). Host-owned; falls back to the default blue when unset.
221
+ * Not exported to the PDF use `color`/`backgroundColor` for the
222
+ * widget's native appearance. Typically set per placement via
223
+ * `fieldDefaults` (e.g. one color per signer). */
224
+ fieldColor?: string;
225
+ /** Host-owned metadata bag. The editor carries it around untouched —
226
+ * it is never read, validated, or exported to the PDF. Must stay
227
+ * JSON-serializable so it survives the embed bridge. Replaced
228
+ * wholesale by an `updateFormField()` patch (no deep merge). */
229
+ meta?: Record<string, unknown>;
224
230
  }
225
231
  export interface FormFieldValue {
226
232
  fieldId: string;
@@ -251,8 +257,13 @@ export interface AddFormFieldPayload {
251
257
  * mirrored across them on edit. Radios with the same `fieldName` form
252
258
  * one option group. */
253
259
  fieldName?: string;
254
- /** Opaque role tag for color-coding in form-edit mode. */
255
- roleId?: string;
260
+ /** CSS color for the field's editor chrome (see
261
+ * {@link FormFieldDefinition.fieldColor}). */
262
+ fieldColor?: string;
263
+ /** Host-owned metadata bag, carried untouched (see
264
+ * {@link FormFieldDefinition.meta}). Also usable as a preset in
265
+ * `fieldDefaults` so tool placements get their metadata atomically. */
266
+ meta?: Record<string, unknown>;
256
267
  /** Default value for the field. Becomes the initial value too. */
257
268
  defaultValue?: string;
258
269
  readOnly?: boolean;
@@ -316,8 +327,6 @@ export interface SignatureFieldStatus {
316
327
  fieldName: string;
317
328
  pageNumber: number;
318
329
  signed: boolean;
319
- /** Present when the field is tagged with a host role. */
320
- roleId?: string;
321
330
  }
322
331
  export interface DetectedShapeRect {
323
332
  x: number;
@@ -1 +1 @@
1
- .kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-input{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;box-sizing:border-box;cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-checkbox--detected{background:transparent}.kviewer-form-checkbox--circle{border-radius:50%}.kviewer-form-checkbox__icon{color:#1a1a1a;height:65%;width:65%}.kviewer-form-checkbox--circle .kviewer-form-checkbox__icon{height:50%;width:50%}.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-form-field__body--locked .kviewer-form-signature,.kviewer-form-field__body--locked .kviewer-form-signature:hover{border-color:transparent}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
1
+ .kviewer-text-layer{left:0;line-height:1;overflow:hidden;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:1}.kviewer-text-layer--interactive{cursor:text;-webkit-user-select:text;-moz-user-select:text;user-select:text}.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_12 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_13 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_5 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_6 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_7 .kviewer-annotation-layer *,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_8 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_4 .kviewer-annotation-layer *{cursor:text!important}.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer,.KViewer_is_painting.KViewer_painting_type_11 .kviewer-annotation-layer *{cursor:crosshair!important}.KViewer_selector_hover{cursor:pointer!important}.kviewer-form-layer{left:0;overflow:hidden;position:absolute;top:0}.kviewer-form-field{box-sizing:border-box;position:absolute}.kviewer-form-field--readonly.kviewer-form-field--placed{filter:grayscale(.6);opacity:.5}.kviewer-form-field--readonly .kviewer-form-signature,.kviewer-form-field--readonly .kviewer-form-signature:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2);cursor:not-allowed}.kviewer-form-input{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;box-sizing:border-box;color:#000;display:block;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:500;height:100%;line-height:normal;margin:0;outline:none;padding:1px 2px;resize:none;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;vertical-align:top}.kviewer-form-input:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6));outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-uselect{align-items:center;background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 14%,transparent);border:1px solid transparent;border-radius:0;box-sizing:border-box;color:#000;cursor:pointer;display:flex;height:100%;line-height:1;padding:0 11px 0 2px;width:100%}.kviewer-form-uselect:hover{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 22%,transparent);border-color:rgba(0,0,0,.2)}.kviewer-form-uselect:focus-visible{background-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 28%,transparent);outline:2px solid var(--kvw-field-color,var(--color-primary,#3b82f6));outline-offset:-2px}.kviewer-form-uselect:disabled{cursor:default}.kviewer-form-listbox{overflow-y:auto;padding:0}.kviewer-form-listbox option{padding:1px 3px}.kviewer-form-editable-combo{height:100%;position:relative;width:100%}.kviewer-form-editable-combo input{height:100%;width:100%}.kviewer-form-checkbox,.kviewer-form-radio{align-items:center;background:#fff;box-sizing:border-box;cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-checkbox input,.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-checkbox--detected{background:transparent}.kviewer-form-checkbox--circle{border-radius:50%}.kviewer-form-checkbox__icon{color:#1a1a1a;height:65%;width:65%}.kviewer-form-checkbox--circle .kviewer-form-checkbox__icon{height:50%;width:50%}.kviewer-form-radio input{accent-color:var(--kvw-field-color,var(--color-primary,#3b82f6));cursor:pointer;height:80%;margin:0;max-height:18px;max-width:18px;width:80%}.kviewer-form-button{align-items:center;background:#c8c8c8;border:1px solid rgba(0,0,0,.35);box-sizing:border-box;color:#000;cursor:pointer;display:flex;font-family:inherit;font-weight:700;height:100%;justify-content:center;line-height:1;padding:0 6px;text-align:center;width:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.kviewer-form-button:hover{background:#bdbdbd}.kviewer-form-button:active{background:#b0b0b0}.kviewer-form-signature{align-items:center;background:rgba(255,255,200,.15);border:1px dashed rgba(0,0,0,.2);cursor:pointer;display:flex;height:100%;justify-content:center;width:100%}.kviewer-form-signature:hover{background:color-mix(in srgb,var(--kvw-field-color,#3b82f6) 8%,transparent);border-color:var(--kvw-field-color,var(--color-primary,#3b82f6))}.kviewer-form-signature--filled{background:transparent;border-color:transparent;border-style:solid}.kviewer-form-signature__preview{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.kviewer-form-signature__placeholder{color:rgba(0,0,0,.4);font-size:10px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-form-field--editable .kviewer-form-signature,.kviewer-form-field__body--locked .kviewer-form-signature,.kviewer-form-field__body--locked .kviewer-form-signature:hover{border-color:transparent}.kviewer-form-signature--static{cursor:default}.kviewer-form-signature--static:hover{background:rgba(255,255,200,.15);border-color:rgba(0,0,0,.2)}.kviewer-form-signature__marker{align-items:center;display:inline-flex;gap:4px}.kviewer-form-signature__marker-icon{flex:0 0 auto;height:12px;width:12px}
@@ -26,16 +26,6 @@ type __VLS_Props = {
26
26
  * with selection chrome, can be moved/resized, and the property panel
27
27
  * is shown. Supports v-model via `v-model:form-edit-mode`. */
28
28
  formEditMode?: boolean;
29
- /** Map of roleId → CSS color used to color-code form fields in
30
- * form-edit mode. The editor stays unopinionated about role
31
- * definitions: the host owns the role list, names, and color picker,
32
- * and just hands a flat lookup table to the viewer. Unknown or
33
- * missing ids fall back to the default blue. */
34
- roleColors?: Record<string, string>;
35
- /** Active role id new field placements are auto-tagged with. The host
36
- * drives this — typically by binding it to a role picker in its own
37
- * UI. Supports v-model via `v-model:active-role-id`. */
38
- activeRoleId?: string | null;
39
29
  /** When true, clicking an unsigned signature field opens kviewer's own
40
30
  * signing flow (saved signature or draw modal). Off by default so hosts
41
31
  * that own the signing process (e.g. an embedding e-sign app) don't get
@@ -122,9 +112,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
122
112
  * The new field is exported into the PDF on `exportPdf()` like any
123
113
  * field placed via the placement tool. */
124
114
  addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
125
- /** Patch a form field by id (rect, name, flags, type-specific props). */
115
+ /** Patch a form field by id (rect, name, flags, type-specific props).
116
+ * Does not fire `field-updated` — that event is for user-driven edits. */
126
117
  updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
127
- /** Remove a form field by id (drops its value too). */
118
+ /** Remove a form field by id (drops its value too). Does not fire
119
+ * `field-removed` — that event is for user-driven removals. */
128
120
  removeFormField: (id: string) => void;
129
121
  /** All form-field definitions (parsed, detected, and placed), flat. */
130
122
  getFormFields: () => FormFieldDefinition[];
@@ -138,6 +130,14 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
138
130
  * `field-placed` event, used by the embed bridge). Returns an
139
131
  * unsubscribe function. */
140
132
  onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => (() => void);
133
+ /** Subscribe to user-driven field edits (imperative mirror of the
134
+ * `field-updated` event, used by the embed bridge). Returns an
135
+ * unsubscribe function. */
136
+ onFieldUpdated: (cb: (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => void) => (() => void);
137
+ /** Subscribe to user-driven field removals (imperative mirror of the
138
+ * `field-removed` event, used by the embed bridge). Returns an
139
+ * unsubscribe function. */
140
+ onFieldRemoved: (cb: (field: FormFieldDefinition) => void) => (() => void);
141
141
  /** True once every signature field in the document is signed — via a
142
142
  * value applied in this session or a digital signature already in the
143
143
  * source PDF. Vacuously true when the document has no signature
@@ -145,7 +145,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
145
145
  * need "at least one". Use to gate e.g. envelope finalization. */
146
146
  getSignedStatus: () => boolean;
147
147
  /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
148
- * signed, roleId). */
148
+ * signed). Correlate to host data by `id`. */
149
149
  getSignatureFieldStatus: () => SignatureFieldStatus[];
150
150
  /** Subscribe to signed-status changes (imperative mirror of the
151
151
  * `signed-status-changed` event, used by the embed bridge). Returns
@@ -171,8 +171,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
171
171
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
172
172
  "all-pages-read": () => any;
173
173
  "field-placed": (field: FormFieldDefinition) => any;
174
+ "field-updated": (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any;
175
+ "field-removed": (field: FormFieldDefinition) => any;
174
176
  "update:formEditMode": (value: boolean) => any;
175
- "update:activeRoleId": (value: string | null) => any;
176
177
  "update:viewedPages": (pages: number[]) => any;
177
178
  "signed-status-changed": (payload: {
178
179
  allSigned: boolean;
@@ -181,8 +182,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
181
182
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
182
183
  "onAll-pages-read"?: (() => any) | undefined;
183
184
  "onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
185
+ "onField-updated"?: ((field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any) | undefined;
186
+ "onField-removed"?: ((field: FormFieldDefinition) => any) | undefined;
184
187
  "onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
185
- "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
186
188
  "onUpdate:viewedPages"?: ((pages: number[]) => any) | undefined;
187
189
  "onSigned-status-changed"?: ((payload: {
188
190
  allSigned: boolean;
@@ -204,8 +206,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
204
206
  signatureHandlers: SignatureHandlers;
205
207
  viewMode: ViewMode;
206
208
  shapeDetection: boolean;
207
- roleColors: Record<string, string>;
208
- activeRoleId: string | null;
209
209
  editablePlacedFields: boolean;
210
210
  fieldDefaults: PlacedFieldDefaults;
211
211
  selectionItems: ViewerSelectionItem[];
@@ -243,8 +243,6 @@ const props = defineProps({
243
243
  active: { type: Boolean, required: false, default: true },
244
244
  freehandGroupingDelay: { type: Number, required: false, default: 1e3 },
245
245
  formEditMode: { type: Boolean, required: false, default: void 0 },
246
- roleColors: { type: Object, required: false, default: void 0 },
247
- activeRoleId: { type: [String, null], required: false, default: void 0 },
248
246
  clickToSign: { type: Boolean, required: false, default: false },
249
247
  editablePlacedFields: { type: Boolean, required: false, default: false },
250
248
  fieldDefaults: { type: Object, required: false, default: void 0 },
@@ -260,9 +258,11 @@ const slots = useSlots();
260
258
  const toolSlotNames = computed(
261
259
  () => Object.keys(slots).filter((name) => name.startsWith("tool-"))
262
260
  );
263
- const emit = defineEmits(["update:formEditMode", "update:activeRoleId", "update:viewedPages", "all-pages-read", "field-placed", "signed-status-changed"]);
261
+ const emit = defineEmits(["update:formEditMode", "update:viewedPages", "all-pages-read", "field-placed", "field-updated", "field-removed", "signed-status-changed"]);
264
262
  const fieldPlacedCallbacks = /* @__PURE__ */ new Set();
265
263
  const signedStatusCallbacks = /* @__PURE__ */ new Set();
264
+ const fieldUpdatedCallbacks = /* @__PURE__ */ new Set();
265
+ const fieldRemovedCallbacks = /* @__PURE__ */ new Set();
266
266
  const viewerRoot = ref(null);
267
267
  const scrollContainer = ref(null);
268
268
  const { state: viewerState, setScrollToPageFn, setDownloadPdfFn, setPrintPdfFn, setApplyScaleFn } = provideViewerState();
@@ -291,6 +291,15 @@ watch(
291
291
  );
292
292
  const viewerSearch = provideViewerSearch();
293
293
  const formFieldsState = provideFormFields();
294
+ formFieldsState.setFieldEventSink((event) => {
295
+ if (event.type === "updated") {
296
+ emit("field-updated", event.field, event.patch);
297
+ for (const cb of fieldUpdatedCallbacks) cb(event.field, event.patch);
298
+ } else {
299
+ emit("field-removed", event.field);
300
+ for (const cb of fieldRemovedCallbacks) cb(event.field);
301
+ }
302
+ });
294
303
  const kviewerPublicConfig = useRuntimeConfig().public.kviewer;
295
304
  provideKviewerI18n(
296
305
  computed(() => {
@@ -299,13 +308,6 @@ provideKviewerI18n(
299
308
  }),
300
309
  computed(() => props.messages ?? {})
301
310
  );
302
- watch(
303
- () => props.roleColors,
304
- (v) => {
305
- formFieldsState.setRoleColors(v ?? {});
306
- },
307
- { immediate: true }
308
- );
309
311
  watch(
310
312
  () => props.fieldDefaults,
311
313
  (v) => {
@@ -347,22 +349,6 @@ watch(signatureStatus, (next, prev) => {
347
349
  emit("signed-status-changed", payload);
348
350
  for (const cb of signedStatusCallbacks) cb(payload);
349
351
  });
350
- watch(
351
- () => props.activeRoleId,
352
- (v) => {
353
- if (v === void 0) return;
354
- if (formFieldsState.activeRoleId.value !== v) {
355
- formFieldsState.setActiveRole(v);
356
- }
357
- },
358
- { immediate: true }
359
- );
360
- watch(
361
- () => formFieldsState.activeRoleId.value,
362
- (v) => {
363
- if (props.activeRoleId !== v) emit("update:activeRoleId", v);
364
- }
365
- );
366
352
  const pageSettings = providePageSettings(viewerRoot);
367
353
  const virtualization = createPageVirtualization();
368
354
  setScrollToPageFn((pageNumber) => {
@@ -992,11 +978,9 @@ onMounted(() => {
992
978
  for (const cb of fieldPlacedCallbacks) cb(def);
993
979
  viewerState.selectTool("hand");
994
980
  },
995
- getFormFieldPreviewColor: () => {
996
- const id = formFieldsState.activeRoleId.value;
997
- if (!id) return void 0;
998
- return formFieldsState.roleColors.value[id];
999
- },
981
+ // The placement drag preview adopts the fieldColor preset of the tool's
982
+ // field type, so the rubber-band already shows the signer's color.
983
+ getFormFieldPreviewColor: (fieldType) => formFieldsState.placedFieldDefaults.value[fieldType]?.fieldColor,
1000
984
  freehandGroupingDelay: props.freehandGroupingDelay,
1001
985
  // Suspend panzoom gestures while an annotation is being dragged or
1002
986
  // transformed. Otherwise single-finger pan runs in parallel with the
@@ -1144,10 +1128,12 @@ defineExpose({
1144
1128
  * The new field is exported into the PDF on `exportPdf()` like any
1145
1129
  * field placed via the placement tool. */
1146
1130
  addFormField: (payload) => formFieldsState.addFormField(payload),
1147
- /** Patch a form field by id (rect, name, flags, type-specific props). */
1148
- updateFormField: (id, patch) => formFieldsState.updatePlacedField(id, patch),
1149
- /** Remove a form field by id (drops its value too). */
1150
- removeFormField: (id) => formFieldsState.removePlacedField(id),
1131
+ /** Patch a form field by id (rect, name, flags, type-specific props).
1132
+ * Does not fire `field-updated` — that event is for user-driven edits. */
1133
+ updateFormField: (id, patch) => formFieldsState.updatePlacedField(id, patch, { silent: true }),
1134
+ /** Remove a form field by id (drops its value too). Does not fire
1135
+ * `field-removed` — that event is for user-driven removals. */
1136
+ removeFormField: (id) => formFieldsState.removePlacedField(id, { silent: true }),
1151
1137
  /** All form-field definitions (parsed, detected, and placed), flat. */
1152
1138
  getFormFields: () => formFieldsState.getAllFields(),
1153
1139
  /** Replace the per-field-type presets applied to tool placements.
@@ -1165,6 +1151,20 @@ defineExpose({
1165
1151
  fieldPlacedCallbacks.add(cb);
1166
1152
  return () => fieldPlacedCallbacks.delete(cb);
1167
1153
  },
1154
+ /** Subscribe to user-driven field edits (imperative mirror of the
1155
+ * `field-updated` event, used by the embed bridge). Returns an
1156
+ * unsubscribe function. */
1157
+ onFieldUpdated: (cb) => {
1158
+ fieldUpdatedCallbacks.add(cb);
1159
+ return () => fieldUpdatedCallbacks.delete(cb);
1160
+ },
1161
+ /** Subscribe to user-driven field removals (imperative mirror of the
1162
+ * `field-removed` event, used by the embed bridge). Returns an
1163
+ * unsubscribe function. */
1164
+ onFieldRemoved: (cb) => {
1165
+ fieldRemovedCallbacks.add(cb);
1166
+ return () => fieldRemovedCallbacks.delete(cb);
1167
+ },
1168
1168
  /** True once every signature field in the document is signed — via a
1169
1169
  * value applied in this session or a digital signature already in the
1170
1170
  * source PDF. Vacuously true when the document has no signature
@@ -1172,7 +1172,7 @@ defineExpose({
1172
1172
  * need "at least one". Use to gate e.g. envelope finalization. */
1173
1173
  getSignedStatus: () => formFieldsState.getSignatureFieldStatus().every((s) => s.signed),
1174
1174
  /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
1175
- * signed, roleId). */
1175
+ * signed). Correlate to host data by `id`. */
1176
1176
  getSignatureFieldStatus: () => formFieldsState.getSignatureFieldStatus(),
1177
1177
  /** Subscribe to signed-status changes (imperative mirror of the
1178
1178
  * `signed-status-changed` event, used by the embed bridge). Returns
@@ -26,16 +26,6 @@ type __VLS_Props = {
26
26
  * with selection chrome, can be moved/resized, and the property panel
27
27
  * is shown. Supports v-model via `v-model:form-edit-mode`. */
28
28
  formEditMode?: boolean;
29
- /** Map of roleId → CSS color used to color-code form fields in
30
- * form-edit mode. The editor stays unopinionated about role
31
- * definitions: the host owns the role list, names, and color picker,
32
- * and just hands a flat lookup table to the viewer. Unknown or
33
- * missing ids fall back to the default blue. */
34
- roleColors?: Record<string, string>;
35
- /** Active role id new field placements are auto-tagged with. The host
36
- * drives this — typically by binding it to a role picker in its own
37
- * UI. Supports v-model via `v-model:active-role-id`. */
38
- activeRoleId?: string | null;
39
29
  /** When true, clicking an unsigned signature field opens kviewer's own
40
30
  * signing flow (saved signature or draw modal). Off by default so hosts
41
31
  * that own the signing process (e.g. an embedding e-sign app) don't get
@@ -122,9 +112,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
122
112
  * The new field is exported into the PDF on `exportPdf()` like any
123
113
  * field placed via the placement tool. */
124
114
  addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
125
- /** Patch a form field by id (rect, name, flags, type-specific props). */
115
+ /** Patch a form field by id (rect, name, flags, type-specific props).
116
+ * Does not fire `field-updated` — that event is for user-driven edits. */
126
117
  updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
127
- /** Remove a form field by id (drops its value too). */
118
+ /** Remove a form field by id (drops its value too). Does not fire
119
+ * `field-removed` — that event is for user-driven removals. */
128
120
  removeFormField: (id: string) => void;
129
121
  /** All form-field definitions (parsed, detected, and placed), flat. */
130
122
  getFormFields: () => FormFieldDefinition[];
@@ -138,6 +130,14 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
138
130
  * `field-placed` event, used by the embed bridge). Returns an
139
131
  * unsubscribe function. */
140
132
  onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => (() => void);
133
+ /** Subscribe to user-driven field edits (imperative mirror of the
134
+ * `field-updated` event, used by the embed bridge). Returns an
135
+ * unsubscribe function. */
136
+ onFieldUpdated: (cb: (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => void) => (() => void);
137
+ /** Subscribe to user-driven field removals (imperative mirror of the
138
+ * `field-removed` event, used by the embed bridge). Returns an
139
+ * unsubscribe function. */
140
+ onFieldRemoved: (cb: (field: FormFieldDefinition) => void) => (() => void);
141
141
  /** True once every signature field in the document is signed — via a
142
142
  * value applied in this session or a digital signature already in the
143
143
  * source PDF. Vacuously true when the document has no signature
@@ -145,7 +145,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
145
145
  * need "at least one". Use to gate e.g. envelope finalization. */
146
146
  getSignedStatus: () => boolean;
147
147
  /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
148
- * signed, roleId). */
148
+ * signed). Correlate to host data by `id`. */
149
149
  getSignatureFieldStatus: () => SignatureFieldStatus[];
150
150
  /** Subscribe to signed-status changes (imperative mirror of the
151
151
  * `signed-status-changed` event, used by the embed bridge). Returns
@@ -171,8 +171,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
171
171
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
172
172
  "all-pages-read": () => any;
173
173
  "field-placed": (field: FormFieldDefinition) => any;
174
+ "field-updated": (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any;
175
+ "field-removed": (field: FormFieldDefinition) => any;
174
176
  "update:formEditMode": (value: boolean) => any;
175
- "update:activeRoleId": (value: string | null) => any;
176
177
  "update:viewedPages": (pages: number[]) => any;
177
178
  "signed-status-changed": (payload: {
178
179
  allSigned: boolean;
@@ -181,8 +182,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
181
182
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
182
183
  "onAll-pages-read"?: (() => any) | undefined;
183
184
  "onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
185
+ "onField-updated"?: ((field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any) | undefined;
186
+ "onField-removed"?: ((field: FormFieldDefinition) => any) | undefined;
184
187
  "onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
185
- "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
186
188
  "onUpdate:viewedPages"?: ((pages: number[]) => any) | undefined;
187
189
  "onSigned-status-changed"?: ((payload: {
188
190
  allSigned: boolean;
@@ -204,8 +206,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
204
206
  signatureHandlers: SignatureHandlers;
205
207
  viewMode: ViewMode;
206
208
  shapeDetection: boolean;
207
- roleColors: Record<string, string>;
208
- activeRoleId: string | null;
209
209
  editablePlacedFields: boolean;
210
210
  fieldDefaults: PlacedFieldDefaults;
211
211
  selectionItems: ViewerSelectionItem[];
@@ -54,11 +54,6 @@ type __VLS_Props = {
54
54
  zoom?: number;
55
55
  /** Minimum number of tabs -- prevents closing below this count. */
56
56
  minTabs?: number;
57
- /** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
58
- roleColors?: Record<string, string>;
59
- /** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
60
- * Supports v-model via `v-model:active-role-id`. */
61
- activeRoleId?: string | null;
62
57
  /** Forwarded to every Viewer instance — see KViewer's `scripting`.
63
58
  * Per-tab override via `ViewerTabItem.scripting` wins when set. */
64
59
  scripting?: boolean;
@@ -82,7 +77,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
82
77
  declare function removeTab(id: string): boolean;
83
78
  declare function activateTab(id: string): void;
84
79
  declare function getViewer(id: string): ComponentPublicInstance | null;
85
- declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
80
+ declare var __VLS_7: {}, __VLS_21: {}, __VLS_35: {
86
81
  tab: {
87
82
  id: string;
88
83
  label: string;
@@ -182,7 +177,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
182
177
  shapeDetection?: boolean | undefined;
183
178
  scripting?: boolean | undefined;
184
179
  };
185
- }, __VLS_37: {
180
+ }, __VLS_38: {
186
181
  tab: {
187
182
  id: string;
188
183
  label: string;
@@ -282,21 +277,21 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
282
277
  shapeDetection?: boolean | undefined;
283
278
  scripting?: boolean | undefined;
284
279
  };
285
- }, __VLS_41: string, __VLS_42: {
280
+ }, __VLS_42: string, __VLS_43: {
286
281
  state: import("../composables/useViewerState.js").ViewerState;
287
- }, __VLS_44: {};
282
+ }, __VLS_45: {};
288
283
  type __VLS_Slots = {} & {
289
- [K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
284
+ [K in NonNullable<typeof __VLS_42>]?: (props: typeof __VLS_43) => any;
290
285
  } & {
291
286
  'tabs-leading'?: (props: typeof __VLS_7) => any;
292
287
  } & {
293
288
  'tabs-trailing'?: (props: typeof __VLS_21) => any;
294
289
  } & {
295
- header?: (props: typeof __VLS_34) => any;
290
+ header?: (props: typeof __VLS_35) => any;
296
291
  } & {
297
- footer?: (props: typeof __VLS_37) => any;
292
+ footer?: (props: typeof __VLS_38) => any;
298
293
  } & {
299
- empty?: (props: typeof __VLS_44) => any;
294
+ empty?: (props: typeof __VLS_45) => any;
300
295
  };
301
296
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
302
297
  addTab: typeof addTab;
@@ -405,7 +400,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
405
400
  }[];
406
401
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
407
402
  "field-placed": (field: FormFieldDefinition) => any;
408
- "update:activeRoleId": (value: string | null) => any;
403
+ "field-updated": (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any;
404
+ "field-removed": (field: FormFieldDefinition) => any;
409
405
  "signed-status-changed": (payload: {
410
406
  allSigned: boolean;
411
407
  fields: SignatureFieldStatus[];
@@ -416,7 +412,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
416
412
  "tab-removed": (id: string) => any;
417
413
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
418
414
  "onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
419
- "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
415
+ "onField-updated"?: ((field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any) | undefined;
416
+ "onField-removed"?: ((field: FormFieldDefinition) => any) | undefined;
420
417
  "onSigned-status-changed"?: ((payload: {
421
418
  allSigned: boolean;
422
419
  fields: SignatureFieldStatus[];
@@ -435,8 +432,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
435
432
  stamps: StampDefinition[];
436
433
  signatureHandlers: SignatureHandlers;
437
434
  viewMode: ViewMode;
438
- roleColors: Record<string, string>;
439
- activeRoleId: string | null;
440
435
  editablePlacedFields: boolean;
441
436
  fieldDefaults: PlacedFieldDefaults;
442
437
  selectionItems: ViewerSelectionItem[];
@@ -63,13 +63,12 @@
63
63
  :menu-items="menuItems"
64
64
  :selection-items="selectionItems"
65
65
  :tools="tools"
66
- :role-colors="roleColors"
67
- :active-role-id="activeRoleId"
68
66
  :click-to-sign="clickToSign"
69
67
  :editable-placed-fields="editablePlacedFields"
70
68
  :field-defaults="fieldDefaults"
71
- @update:active-role-id="(v) => emit('update:activeRoleId', v)"
72
69
  @field-placed="(f) => emit('field-placed', f)"
70
+ @field-updated="(f, p) => emit('field-updated', f, p)"
71
+ @field-removed="(f) => emit('field-removed', f)"
73
72
  @signed-status-changed="(p) => emit('signed-status-changed', p)"
74
73
  >
75
74
  <template v-if="$slots.header" #header>
@@ -109,8 +108,6 @@ const props = defineProps({
109
108
  viewMode: { type: String, required: false, default: "fit-width" },
110
109
  zoom: { type: Number, required: false, default: 1 },
111
110
  minTabs: { type: Number, required: false, default: 0 },
112
- roleColors: { type: Object, required: false, default: void 0 },
113
- activeRoleId: { type: [String, null], required: false, default: void 0 },
114
111
  scripting: { type: Boolean, required: false, default: false },
115
112
  menuItems: { type: Array, required: false, default: void 0 },
116
113
  selectionItems: { type: Array, required: false, default: void 0 },
@@ -123,7 +120,7 @@ const slots = useSlots();
123
120
  const toolSlotNames = computed(
124
121
  () => Object.keys(slots).filter((name) => name.startsWith("tool-"))
125
122
  );
126
- const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId", "field-placed", "signed-status-changed"]);
123
+ const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "field-placed", "field-updated", "field-removed", "signed-status-changed"]);
127
124
  let idCounter = 0;
128
125
  function generateId() {
129
126
  return `tab-${Date.now()}-${++idCounter}`;
@@ -54,11 +54,6 @@ type __VLS_Props = {
54
54
  zoom?: number;
55
55
  /** Minimum number of tabs -- prevents closing below this count. */
56
56
  minTabs?: number;
57
- /** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
58
- roleColors?: Record<string, string>;
59
- /** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
60
- * Supports v-model via `v-model:active-role-id`. */
61
- activeRoleId?: string | null;
62
57
  /** Forwarded to every Viewer instance — see KViewer's `scripting`.
63
58
  * Per-tab override via `ViewerTabItem.scripting` wins when set. */
64
59
  scripting?: boolean;
@@ -82,7 +77,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
82
77
  declare function removeTab(id: string): boolean;
83
78
  declare function activateTab(id: string): void;
84
79
  declare function getViewer(id: string): ComponentPublicInstance | null;
85
- declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
80
+ declare var __VLS_7: {}, __VLS_21: {}, __VLS_35: {
86
81
  tab: {
87
82
  id: string;
88
83
  label: string;
@@ -182,7 +177,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
182
177
  shapeDetection?: boolean | undefined;
183
178
  scripting?: boolean | undefined;
184
179
  };
185
- }, __VLS_37: {
180
+ }, __VLS_38: {
186
181
  tab: {
187
182
  id: string;
188
183
  label: string;
@@ -282,21 +277,21 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
282
277
  shapeDetection?: boolean | undefined;
283
278
  scripting?: boolean | undefined;
284
279
  };
285
- }, __VLS_41: string, __VLS_42: {
280
+ }, __VLS_42: string, __VLS_43: {
286
281
  state: import("../composables/useViewerState.js").ViewerState;
287
- }, __VLS_44: {};
282
+ }, __VLS_45: {};
288
283
  type __VLS_Slots = {} & {
289
- [K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
284
+ [K in NonNullable<typeof __VLS_42>]?: (props: typeof __VLS_43) => any;
290
285
  } & {
291
286
  'tabs-leading'?: (props: typeof __VLS_7) => any;
292
287
  } & {
293
288
  'tabs-trailing'?: (props: typeof __VLS_21) => any;
294
289
  } & {
295
- header?: (props: typeof __VLS_34) => any;
290
+ header?: (props: typeof __VLS_35) => any;
296
291
  } & {
297
- footer?: (props: typeof __VLS_37) => any;
292
+ footer?: (props: typeof __VLS_38) => any;
298
293
  } & {
299
- empty?: (props: typeof __VLS_44) => any;
294
+ empty?: (props: typeof __VLS_45) => any;
300
295
  };
301
296
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
302
297
  addTab: typeof addTab;
@@ -405,7 +400,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
405
400
  }[];
406
401
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
407
402
  "field-placed": (field: FormFieldDefinition) => any;
408
- "update:activeRoleId": (value: string | null) => any;
403
+ "field-updated": (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any;
404
+ "field-removed": (field: FormFieldDefinition) => any;
409
405
  "signed-status-changed": (payload: {
410
406
  allSigned: boolean;
411
407
  fields: SignatureFieldStatus[];
@@ -416,7 +412,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
416
412
  "tab-removed": (id: string) => any;
417
413
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
418
414
  "onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
419
- "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
415
+ "onField-updated"?: ((field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any) | undefined;
416
+ "onField-removed"?: ((field: FormFieldDefinition) => any) | undefined;
420
417
  "onSigned-status-changed"?: ((payload: {
421
418
  allSigned: boolean;
422
419
  fields: SignatureFieldStatus[];
@@ -435,8 +432,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
435
432
  stamps: StampDefinition[];
436
433
  signatureHandlers: SignatureHandlers;
437
434
  viewMode: ViewMode;
438
- roleColors: Record<string, string>;
439
- activeRoleId: string | null;
440
435
  editablePlacedFields: boolean;
441
436
  fieldDefaults: PlacedFieldDefaults;
442
437
  selectionItems: ViewerSelectionItem[];
@@ -4,17 +4,19 @@
4
4
  :class="{
5
5
  'kviewer-form-field--editable': showChrome,
6
6
  'kviewer-form-field--placed': isPlaced,
7
- 'kviewer-form-field--selected': props.selected
7
+ 'kviewer-form-field--selected': props.selected,
8
+ 'kviewer-form-field--readonly': isReadonlyOutsideAuthoring
8
9
  }"
9
10
  :style="[positionStyle, {
10
11
  '--kvw-field-color': chromeColor,
11
12
  '--kvw-handle-zoom': handleZoom
12
13
  }]"
13
14
  :data-element-id="props.field.id"
15
+ @contextmenu="onContextMenu"
14
16
  >
15
17
  <div
16
18
  class="kviewer-form-field__body"
17
- :class="{ 'kviewer-form-field__body--locked': showChrome }"
19
+ :class="{ 'kviewer-form-field__body--locked': chromeMode === 'authoring' }"
18
20
  >
19
21
  <FormTextField v-if="props.field.fieldType === 'text'" :field="props.field" />
20
22
  <FormCheckbox v-else-if="props.field.fieldType === 'checkbox'" :field="props.field" />
@@ -30,6 +32,7 @@
30
32
  :selected="props.selected"
31
33
  :scale="props.scale"
32
34
  :page-height="props.pageTransform.height"
35
+ :mode="chromeMode ?? 'authoring'"
33
36
  @select="emit('select')"
34
37
  />
35
38
 
@@ -64,18 +67,27 @@ const state = useViewerState();
64
67
  const formFields = useFormFields();
65
68
  const isPlaced = computed(() => props.field.origin === "placed");
66
69
  const isFormEditMode = computed(() => state.formEditMode.value);
67
- const showChrome = computed(
68
- () => isFormEditMode.value || formFields.editablePlacedFields.value && isPlaced.value
70
+ const chromeMode = computed(() => {
71
+ if (isFormEditMode.value) return "authoring";
72
+ if (formFields.editablePlacedFields.value && isPlaced.value && !props.field.readOnly) {
73
+ return "live";
74
+ }
75
+ return null;
76
+ });
77
+ const showChrome = computed(() => chromeMode.value !== null);
78
+ const isReadonlyOutsideAuthoring = computed(
79
+ () => props.field.readOnly && !isFormEditMode.value
69
80
  );
81
+ function onContextMenu(e) {
82
+ if (chromeMode.value !== "live") return;
83
+ e.preventDefault();
84
+ emit("select");
85
+ }
70
86
  const handleZoom = computed(() => {
71
87
  const totalZoom = (props.scale || 1) * (state.visualZoom.value || 1);
72
88
  return (1 / totalZoom).toFixed(3);
73
89
  });
74
- const chromeColor = computed(() => {
75
- const id = props.field.roleId;
76
- if (!id) return "#1677ff";
77
- return formFields.roleColors.value[id] ?? "#1677ff";
78
- });
90
+ const chromeColor = computed(() => props.field.fieldColor ?? "#1677ff");
79
91
  const positionStyle = computed(() => {
80
92
  const rect = props.field.rect;
81
93
  const r = convertPdfRectToCanvas(props.pageTransform, [rect[0], rect[1], rect[2], rect[3]]);
@@ -55,10 +55,18 @@ const signatureUrl = computed(() => {
55
55
  });
56
56
  const hasSigned = computed(() => !!signatureUrl.value);
57
57
  const clickToSignEnabled = computed(() => state.clickToSign.value);
58
- async function onClickSign() {
59
- if (!clickToSignEnabled.value) return;
58
+ const liveEditing = computed(
59
+ () => formFields.editablePlacedFields.value && props.field.origin === "placed" && !state.formEditMode.value
60
+ );
61
+ async function onClickSign(e) {
60
62
  if (props.field.readOnly || state.readonly.value) return;
61
63
  if (formFields.isFieldLocked(props.field.id)) return;
64
+ if (liveEditing.value && (hasSigned.value || !clickToSignEnabled.value)) {
65
+ e.stopPropagation();
66
+ formFields.selectPlacedField(props.field.id);
67
+ return;
68
+ }
69
+ if (!clickToSignEnabled.value) return;
62
70
  if (state.signatures.value.length > 0) {
63
71
  const sig = state.signatures.value[0];
64
72
  if (sig) {
@@ -6,6 +6,10 @@ type __VLS_Props = {
6
6
  scale: number;
7
7
  /** PDF-space page height (kept for parity; unused here). */
8
8
  pageHeight: number;
9
+ /** 'authoring' = form-edit mode (chrome is the whole interactive
10
+ * surface). 'live' = editablePlacedFields outside form-edit mode
11
+ * (chrome is transparent except grip and handles). */
12
+ mode: 'authoring' | 'live';
9
13
  };
10
14
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
15
  select: () => any;
@@ -1,11 +1,16 @@
1
1
  <template>
2
- <!-- The whole chrome is the interactive surface in form-edit mode:
3
- click to select, drag anywhere to move, plus a label bar above and
4
- eight resize handles when selected. -->
2
+ <!-- Two interaction modes. Authoring (form-edit mode): the whole chrome
3
+ is the interactive surface — click to select, drag anywhere to move.
4
+ Live (editablePlacedFields): the surface is transparent so the field
5
+ body stays usable (click-to-sign, text input); management happens on
6
+ the always-visible grip ("handle = manage, body = use"). -->
5
7
  <div
6
8
  ref="rootEl"
7
9
  class="kviewer-placed-chrome"
8
- :class="{ 'kviewer-placed-chrome--selected': selected }"
10
+ :class="{
11
+ 'kviewer-placed-chrome--selected': selected,
12
+ 'kviewer-placed-chrome--live': mode === 'live'
13
+ }"
9
14
  @pointerdown.stop="onChromePointerDown"
10
15
  @click.stop
11
16
  >
@@ -17,9 +22,11 @@
17
22
  }"
18
23
  />
19
24
  <div
20
- v-if="selected"
25
+ v-if="selected || mode === 'live'"
21
26
  class="kviewer-placed-chrome__grip"
22
27
  :title="field.fieldName"
28
+ @pointerdown.stop="onChromePointerDown"
29
+ @click.stop
23
30
  >
24
31
  <UIcon name="i-lucide-grip-vertical" class="kviewer-placed-chrome__grip-dots" />
25
32
  <span class="kviewer-placed-chrome__grip-label">{{ field.fieldName }}</span>
@@ -46,7 +53,8 @@ const props = defineProps({
46
53
  field: { type: Object, required: true },
47
54
  selected: { type: Boolean, required: true },
48
55
  scale: { type: Number, required: true },
49
- pageHeight: { type: Number, required: true }
56
+ pageHeight: { type: Number, required: true },
57
+ mode: { type: String, required: true }
50
58
  });
51
59
  const emit = defineEmits(["select"]);
52
60
  const formFields = useFormFields();
@@ -91,12 +99,16 @@ function onChromePointerDown(e) {
91
99
  startRect[2] + dx,
92
100
  startRect[3] + pdfDy
93
101
  ];
94
- formFields.updatePlacedField(props.field.id, { rect: next });
102
+ formFields.updatePlacedField(props.field.id, { rect: next }, { silent: true });
95
103
  };
96
104
  const onUp = () => {
97
105
  window.removeEventListener("pointermove", onMove);
98
106
  window.removeEventListener("pointerup", onUp);
99
- if (!dragging) emit("select");
107
+ if (!dragging) {
108
+ emit("select");
109
+ return;
110
+ }
111
+ formFields.updatePlacedField(props.field.id, { rect: [...props.field.rect] });
100
112
  };
101
113
  window.addEventListener("pointermove", onMove);
102
114
  window.addEventListener("pointerup", onUp);
@@ -106,6 +118,7 @@ function onHandlePointerDown(e, name) {
106
118
  const startY = e.clientY;
107
119
  const [sx1, sy1, sx2, sy2] = props.field.rect;
108
120
  const s = effectiveScale();
121
+ let resized = false;
109
122
  const onMove = (ev) => {
110
123
  const dx = (ev.clientX - startX) / s;
111
124
  const dy = (ev.clientY - startY) / s;
@@ -115,11 +128,15 @@ function onHandlePointerDown(e, name) {
115
128
  if (name.includes("s")) y1 = Math.min(y2 - 4, sy1 + pdfDy);
116
129
  if (name.includes("e")) x2 = Math.max(x1 + 4, sx2 + dx);
117
130
  if (name.includes("w")) x1 = Math.min(x2 - 4, sx1 + dx);
118
- formFields.updatePlacedField(props.field.id, { rect: [x1, y1, x2, y2] });
131
+ resized = true;
132
+ formFields.updatePlacedField(props.field.id, { rect: [x1, y1, x2, y2] }, { silent: true });
119
133
  };
120
134
  const onUp = () => {
121
135
  window.removeEventListener("pointermove", onMove);
122
136
  window.removeEventListener("pointerup", onUp);
137
+ if (resized) {
138
+ formFields.updatePlacedField(props.field.id, { rect: [...props.field.rect] });
139
+ }
123
140
  };
124
141
  window.addEventListener("pointermove", onMove);
125
142
  window.addEventListener("pointerup", onUp);
@@ -127,5 +144,5 @@ function onHandlePointerDown(e, name) {
127
144
  </script>
128
145
 
129
146
  <style>
130
- .kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome__outline{border:1px dashed color-mix(in srgb,var(--kvw-field-color,#1677ff) 45%,transparent);box-sizing:border-box;height:calc(100%/var(--kvw-handle-zoom, 1));left:0;pointer-events:none;position:absolute;top:0;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 0;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__outline--placed{border-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 75%,transparent)}.kviewer-placed-chrome__outline--selected{border:1.5px dashed var(--kvw-field-color,#1677ff)}.kviewer-placed-chrome__grip{align-items:center;background:color-mix(in srgb,var(--kvw-field-color,#1677ff) 15%,transparent);border:1px solid color-mix(in srgb,var(--kvw-field-color,#1677ff) 40%,transparent);border-radius:4px;bottom:calc(100% + 4px*var(--kvw-handle-zoom, 1));box-sizing:border-box;color:var(--kvw-field-color,#1677ff);cursor:move;display:flex;font-size:12px;gap:4px;height:22px;left:0;line-height:1;overflow:hidden;padding:0 8px 0 3px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__grip-dots{color:var(--kvw-field-color,#1677ff);flex:0 0 auto;height:13px;width:13px}.kviewer-placed-chrome__grip-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kviewer-placed-chrome__handle{background:#fff;border:1px solid var(--kvw-field-color,#1677ff);border-radius:2px;height:8px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));width:8px}.kviewer-placed-chrome__handle:before{content:"";inset:-8px;position:absolute}.kviewer-placed-chrome__handle--n{left:50%;top:-4px;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--s{bottom:-4px;left:50%;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--e{right:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--w{left:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--ne{right:-4px;top:-4px}.kviewer-placed-chrome__handle--nw{left:-4px;top:-4px}.kviewer-placed-chrome__handle--se{bottom:-4px;right:-4px}.kviewer-placed-chrome__handle--sw{bottom:-4px;left:-4px}
147
+ .kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome--live{cursor:default;pointer-events:none}.kviewer-placed-chrome__outline{border:1px dashed color-mix(in srgb,var(--kvw-field-color,#1677ff) 45%,transparent);box-sizing:border-box;height:calc(100%/var(--kvw-handle-zoom, 1));left:0;pointer-events:none;position:absolute;top:0;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 0;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__outline--placed{border-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 75%,transparent)}.kviewer-placed-chrome__outline--selected{border:1.5px dashed var(--kvw-field-color,#1677ff)}.kviewer-placed-chrome__grip{align-items:center;background:color-mix(in srgb,var(--kvw-field-color,#1677ff) 15%,transparent);border:1px solid color-mix(in srgb,var(--kvw-field-color,#1677ff) 40%,transparent);border-radius:4px;bottom:calc(100% + 4px*var(--kvw-handle-zoom, 1));box-sizing:border-box;color:var(--kvw-field-color,#1677ff);cursor:move;display:flex;font-size:12px;gap:4px;height:22px;left:0;line-height:1;overflow:hidden;padding:0 8px 0 3px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__grip-dots{color:var(--kvw-field-color,#1677ff);flex:0 0 auto;height:13px;width:13px}.kviewer-placed-chrome__grip-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kviewer-placed-chrome__handle{background:#fff;border:1px solid var(--kvw-field-color,#1677ff);border-radius:2px;height:8px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));width:8px}.kviewer-placed-chrome__handle:before{content:"";inset:-8px;position:absolute}.kviewer-placed-chrome__handle--n{left:50%;top:-4px;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--s{bottom:-4px;left:50%;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--e{right:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--w{left:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--ne{right:-4px;top:-4px}.kviewer-placed-chrome__handle--nw{left:-4px;top:-4px}.kviewer-placed-chrome__handle--se{bottom:-4px;right:-4px}.kviewer-placed-chrome__handle--sw{bottom:-4px;left:-4px}
131
148
  </style>
@@ -6,6 +6,10 @@ type __VLS_Props = {
6
6
  scale: number;
7
7
  /** PDF-space page height (kept for parity; unused here). */
8
8
  pageHeight: number;
9
+ /** 'authoring' = form-edit mode (chrome is the whole interactive
10
+ * surface). 'live' = editablePlacedFields outside form-edit mode
11
+ * (chrome is transparent except grip and handles). */
12
+ mode: 'authoring' | 'live';
9
13
  };
10
14
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
15
  select: () => any;
@@ -27,10 +27,22 @@ export interface FormFieldsState {
27
27
  * `addPlacedField` with full control over the field's properties.
28
28
  * Returns the created definition. */
29
29
  addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
30
- /** Update a form field's rect, name, or flags. */
31
- updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>) => void;
32
- /** Remove a form field and its value. */
33
- removePlacedField: (id: string) => void;
30
+ /** Update a form field's rect, name, or flags. Notifies the field-event
31
+ * sink unless `opts.silent` pass silent from the public API (the host
32
+ * knows about its own calls) and from high-frequency gesture updates
33
+ * (commit one non-silent update on gesture end instead). */
34
+ updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>, opts?: {
35
+ silent?: boolean;
36
+ }) => void;
37
+ /** Remove a form field and its value. Same `silent` contract as
38
+ * `updatePlacedField`. */
39
+ removePlacedField: (id: string, opts?: {
40
+ silent?: boolean;
41
+ }) => void;
42
+ /** Install the sink that receives user-driven field mutations. Viewer.vue
43
+ * owns it and fans out to the `field-updated` / `field-removed` emits and
44
+ * the embed bridge. Survives document changes (unlike the value sink). */
45
+ setFieldEventSink: (sink: FieldEventSink | null) => void;
34
46
  /** All form-field definitions, flattened across pages. */
35
47
  getAllFields: () => FormFieldDefinition[];
36
48
  /** All user-placed field definitions (used by export). */
@@ -45,19 +57,6 @@ export interface FormFieldsState {
45
57
  selectedPlacedFieldId: Ref<string | null>;
46
58
  /** Select or deselect a placed field. Pass null to deselect. */
47
59
  selectPlacedField: (id: string | null) => void;
48
- /** Opaque role id new placements are auto-tagged with. The host owns
49
- * what roles exist; the editor only carries the active id forward to
50
- * newly placed fields and looks up its color via roleColors. */
51
- activeRoleId: Ref<string | null>;
52
- /** Set or clear the active role for upcoming placements. */
53
- setActiveRole: (id: string | null) => void;
54
- /** Map from roleId → CSS color, supplied by the host via the KViewer
55
- * `roleColors` prop. Reading this drives the chrome and placement
56
- * preview color. The editor never mutates role definitions itself. */
57
- roleColors: ShallowRef<Record<string, string>>;
58
- /** Replace the host-provided color map. Called by KViewer when the
59
- * prop changes. */
60
- setRoleColors: (colors: Record<string, string>) => void;
61
60
  /** Per-field-type presets merged into every tool placement. Host-owned
62
61
  * via the KViewer `fieldDefaults` prop or the bridge; programmatic
63
62
  * `addFormField` calls are NOT affected (the payload is explicit). */
@@ -110,5 +109,17 @@ export interface FormFieldsState {
110
109
  /** Sink signature used by [[setValueWriteSink]]. Receives every user-driven
111
110
  * field value write (the primary id AND each mirrored sibling). */
112
111
  export type ValueWriteSink = (fieldId: string, value: string | boolean | string[]) => void;
112
+ /** User-driven field mutation, delivered to the sink installed via
113
+ * [[setFieldEventSink]]. `field` is the post-mutation definition for
114
+ * updates and the last-known definition for removals. */
115
+ export type FieldEvent = {
116
+ type: 'updated';
117
+ field: FormFieldDefinition;
118
+ patch: Partial<FormFieldDefinition>;
119
+ } | {
120
+ type: 'removed';
121
+ field: FormFieldDefinition;
122
+ };
123
+ export type FieldEventSink = (event: FieldEvent) => void;
113
124
  export declare function provideFormFields(): FormFieldsState;
114
125
  export declare function useFormFields(): FormFieldsState;
@@ -12,16 +12,8 @@ export function provideFormFields() {
12
12
  const fieldDefinitions = shallowRef(/* @__PURE__ */ new Map());
13
13
  const fieldValues = shallowRef(/* @__PURE__ */ new Map());
14
14
  const selectedPlacedFieldId = ref(null);
15
- const activeRoleId = ref(null);
16
- const roleColors = shallowRef({});
17
15
  const placedFieldDefaults = shallowRef({});
18
16
  const editablePlacedFields = ref(false);
19
- function setActiveRole(id) {
20
- activeRoleId.value = id;
21
- }
22
- function setRoleColors(colors) {
23
- roleColors.value = colors;
24
- }
25
17
  function setPlacedFieldDefaults(defaults) {
26
18
  placedFieldDefaults.value = defaults;
27
19
  }
@@ -43,6 +35,10 @@ export function provideFormFields() {
43
35
  function setValueWriteSink(sink) {
44
36
  valueWriteSink = sink;
45
37
  }
38
+ let fieldEventSink = null;
39
+ function setFieldEventSink(sink) {
40
+ fieldEventSink = sink;
41
+ }
46
42
  let checkboxStyleMap = null;
47
43
  let buttonCaptionMap = null;
48
44
  let signedSignatureNames = null;
@@ -297,9 +293,7 @@ export function provideFormFields() {
297
293
  )
298
294
  );
299
295
  }
300
- if (activeRoleId.value) {
301
- def.roleId = activeRoleId.value;
302
- }
296
+ if (def.meta) def.meta = { ...def.meta };
303
297
  if (payload.fieldType === "radio") {
304
298
  def.buttonValue = nextRadioOptionValue(fieldName);
305
299
  }
@@ -340,7 +334,8 @@ export function provideFormFields() {
340
334
  required: payload.required ?? false,
341
335
  origin: "placed"
342
336
  };
343
- if (payload.roleId !== void 0) def.roleId = payload.roleId;
337
+ if (payload.fieldColor !== void 0) def.fieldColor = payload.fieldColor;
338
+ if (payload.meta !== void 0) def.meta = { ...payload.meta };
344
339
  if (payload.defaultValue !== void 0) def.defaultValue = payload.defaultValue;
345
340
  if (payload.fontSize !== void 0) def.fontSize = payload.fontSize;
346
341
  if (payload.fontName !== void 0) def.fontName = payload.fontName;
@@ -401,7 +396,7 @@ export function provideFormFields() {
401
396
  while (used.has(`Option_${n}`)) n += 1;
402
397
  return `Option_${n}`;
403
398
  }
404
- function updatePlacedField(id, patch) {
399
+ function updatePlacedField(id, patch, opts) {
405
400
  let found;
406
401
  for (const [page, defs] of fieldDefinitions.value.entries()) {
407
402
  const idx = defs.findIndex((d) => d.id === id);
@@ -422,12 +417,16 @@ export function provideFormFields() {
422
417
  fv.fieldType = found.fieldType;
423
418
  triggerRef(fieldValues);
424
419
  }
420
+ if (!opts?.silent && fieldEventSink) {
421
+ fieldEventSink({ type: "updated", field: found, patch });
422
+ }
425
423
  }
426
- function removePlacedField(id) {
427
- let removed = false;
424
+ function removePlacedField(id, opts) {
425
+ let removed;
428
426
  for (const [page, defs] of fieldDefinitions.value.entries()) {
429
427
  const idx = defs.findIndex((d) => d.id === id);
430
428
  if (idx === -1) continue;
429
+ removed = defs[idx];
431
430
  const nextDefs = defs.slice();
432
431
  nextDefs.splice(idx, 1);
433
432
  if (nextDefs.length === 0) {
@@ -435,7 +434,6 @@ export function provideFormFields() {
435
434
  } else {
436
435
  fieldDefinitions.value.set(page, nextDefs);
437
436
  }
438
- removed = true;
439
437
  break;
440
438
  }
441
439
  if (!removed) return;
@@ -446,6 +444,9 @@ export function provideFormFields() {
446
444
  if (selectedPlacedFieldId.value === id) {
447
445
  selectedPlacedFieldId.value = null;
448
446
  }
447
+ if (!opts?.silent && fieldEventSink) {
448
+ fieldEventSink({ type: "removed", field: removed });
449
+ }
449
450
  }
450
451
  function getPlacedFields() {
451
452
  const out = [];
@@ -477,14 +478,12 @@ export function provideFormFields() {
477
478
  if (d.fieldType !== "signature") continue;
478
479
  const fv = fieldValues.value.get(d.id);
479
480
  const signed = typeof fv?.value === "string" && fv.value !== "" || d.signedInSource === true;
480
- const status = {
481
+ out.push({
481
482
  id: d.id,
482
483
  fieldName: d.fieldName,
483
484
  pageNumber: d.pageNumber,
484
485
  signed
485
- };
486
- if (d.roleId) status.roleId = d.roleId;
487
- out.push(status);
486
+ });
488
487
  }
489
488
  }
490
489
  return out;
@@ -556,11 +555,8 @@ export function provideFormFields() {
556
555
  resetValues,
557
556
  reset,
558
557
  setValueWriteSink,
558
+ setFieldEventSink,
559
559
  applyFieldValueFromExternal,
560
- activeRoleId,
561
- setActiveRole,
562
- roleColors,
563
- setRoleColors,
564
560
  placedFieldDefaults,
565
561
  setPlacedFieldDefaults,
566
562
  editablePlacedFields,
@@ -21,7 +21,8 @@ type EventHandler<E extends EventName> = (payload: EventPayloads[E]) => void;
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
23
  * (`ready`, `formEditMode-changed`, `viewedPages-changed`, `all-pages-read`,
24
- * `field-placed`, `signedStatus-changed`) via `on()`.
24
+ * `field-placed`, `field-updated`, `field-removed`, `signedStatus-changed`)
25
+ * via `on()`.
25
26
  */
26
27
  export declare class KViewerEmbedClient {
27
28
  private readonly iframe;
@@ -68,7 +69,8 @@ export declare class KViewerEmbedClient {
68
69
  * a push-style signal subscribe via `on('signedStatus-changed', …)`. */
69
70
  getSignedStatus(): Promise<boolean>;
70
71
  /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
71
- * signed, roleId). */
72
+ * signed). Correlate to host data by `id` (see the `meta` bag on
73
+ * `getFormFields()` for grouping, e.g. by signer). */
72
74
  getSignatureFieldStatus(): Promise<SignatureFieldStatus[]>;
73
75
  getFormEditMode(): Promise<boolean>;
74
76
  setFormEditMode(enabled: boolean): Promise<void>;
@@ -104,7 +104,8 @@ export class KViewerEmbedClient {
104
104
  return this.call("getSignedStatus");
105
105
  }
106
106
  /** Per-widget signed-state snapshot (id, fieldName, pageNumber,
107
- * signed, roleId). */
107
+ * signed). Correlate to host data by `id` (see the `meta` bag on
108
+ * `getFormFields()` for grouping, e.g. by signer). */
108
109
  getSignatureFieldStatus() {
109
110
  return this.call("getSignatureFieldStatus");
110
111
  }
@@ -27,6 +27,8 @@ export interface KViewerApi {
27
27
  setFieldDefaults: (defaults: PlacedFieldDefaults) => void;
28
28
  setClickToSign: (enabled: boolean) => void;
29
29
  onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => () => void;
30
+ onFieldUpdated: (cb: (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => void) => () => void;
31
+ onFieldRemoved: (cb: (field: FormFieldDefinition) => void) => () => void;
30
32
  getSignedStatus: () => boolean;
31
33
  getSignatureFieldStatus: () => SignatureFieldStatus[];
32
34
  onSignedStatusChanged: (cb: (payload: {
@@ -202,6 +202,22 @@ export function useKViewerEmbedBridge(viewerRef, options) {
202
202
  )
203
203
  );
204
204
  }
205
+ if (typeof viewer.onFieldUpdated === "function") {
206
+ events.push("field-updated");
207
+ stopViewerStateWatches.push(
208
+ viewer.onFieldUpdated(
209
+ (field, patch) => postEvent("field-updated", { field, patch })
210
+ )
211
+ );
212
+ }
213
+ if (typeof viewer.onFieldRemoved === "function") {
214
+ events.push("field-removed");
215
+ stopViewerStateWatches.push(
216
+ viewer.onFieldRemoved(
217
+ (field) => postEvent("field-removed", { field })
218
+ )
219
+ );
220
+ }
205
221
  if (typeof viewer.onSignedStatusChanged === "function") {
206
222
  events.push("signedStatus-changed");
207
223
  stopViewerStateWatches.push(
@@ -150,6 +150,18 @@ export interface EventPayloads {
150
150
  'field-placed': {
151
151
  field: FormFieldDefinition;
152
152
  };
153
+ /** The user changed a field through the viewer UI (moved/resized it or
154
+ * edited its properties). Not fired for `updateFormField()` calls.
155
+ * Continuous edits may fire repeatedly — treat as an upsert. */
156
+ 'field-updated': {
157
+ field: FormFieldDefinition;
158
+ patch: Partial<FormFieldDefinition>;
159
+ };
160
+ /** The user removed a field through the viewer UI. Not fired for
161
+ * `removeFormField()` calls. `field` is the last-known definition. */
162
+ 'field-removed': {
163
+ field: FormFieldDefinition;
164
+ };
153
165
  /** A signature field's signed state changed (signed, cleared, added,
154
166
  * removed, or renamed). Push-style counterpart to `getSignedStatus()`. */
155
167
  'signedStatus-changed': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",