kviewer 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +29 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -0
  4. package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
  5. package/dist/runtime/annotation/checkbox-styles.js +27 -0
  6. package/dist/runtime/annotation/engine/config.js +44 -0
  7. package/dist/runtime/annotation/engine/editor/editor.d.ts +6 -3
  8. package/dist/runtime/annotation/engine/editor/editor.js +22 -9
  9. package/dist/runtime/annotation/engine/editor/selector.d.ts +9 -1
  10. package/dist/runtime/annotation/engine/editor/selector.js +72 -30
  11. package/dist/runtime/annotation/engine/painter.d.ts +6 -2
  12. package/dist/runtime/annotation/engine/painter.js +42 -5
  13. package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
  14. package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
  15. package/dist/runtime/annotation/engine/types.d.ts +54 -1
  16. package/dist/runtime/annotation/engine/types.js +4 -0
  17. package/dist/runtime/annotation/font-style.d.ts +23 -0
  18. package/dist/runtime/annotation/font-style.js +57 -0
  19. package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
  20. package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
  21. package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
  22. package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
  23. package/dist/runtime/annotation/pdf-export/export-form-fields.js +245 -1
  24. package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
  25. package/dist/runtime/annotation/pdf-export/export.js +9 -2
  26. package/dist/runtime/assets/kviewer.css +1 -1
  27. package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
  28. package/dist/runtime/components/FormFieldLayer.vue +40 -3
  29. package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
  30. package/dist/runtime/components/PdfPage.d.vue.ts +6 -1
  31. package/dist/runtime/components/PdfPage.vue +75 -18
  32. package/dist/runtime/components/PdfPage.vue.d.ts +6 -1
  33. package/dist/runtime/components/ToolButton.d.vue.ts +4 -2
  34. package/dist/runtime/components/ToolButton.vue.d.ts +4 -2
  35. package/dist/runtime/components/Viewer.d.vue.ts +18 -3
  36. package/dist/runtime/components/Viewer.vue +278 -170
  37. package/dist/runtime/components/Viewer.vue.d.ts +18 -3
  38. package/dist/runtime/components/ViewerBar.vue +24 -2
  39. package/dist/runtime/components/form-fields/FormButton.vue +16 -4
  40. package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
  41. package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
  42. package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
  43. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +45 -9
  44. package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
  45. package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
  46. package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
  47. package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
  48. package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
  49. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
  50. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
  51. package/dist/runtime/components/modals/FreeTextModal.vue +27 -3
  52. package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
  53. package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
  54. package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
  55. package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
  56. package/dist/runtime/components/tools/ActionTools.vue +4 -5
  57. package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
  58. package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
  59. package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
  60. package/dist/runtime/composables/useAnnotationEngine.d.ts +2 -0
  61. package/dist/runtime/composables/useAnnotationEngine.js +6 -2
  62. package/dist/runtime/composables/useAnnotationHistory.d.ts +1 -0
  63. package/dist/runtime/composables/useAnnotationHistory.js +10 -3
  64. package/dist/runtime/composables/useFormFields.d.ts +29 -2
  65. package/dist/runtime/composables/useFormFields.js +259 -5
  66. package/dist/runtime/composables/useInertiaPanzoom.d.ts +40 -0
  67. package/dist/runtime/composables/useInertiaPanzoom.js +475 -0
  68. package/dist/runtime/composables/usePageVirtualization.d.ts +9 -0
  69. package/dist/runtime/composables/usePageVirtualization.js +34 -3
  70. package/dist/runtime/composables/useViewerSearch.d.ts +1 -0
  71. package/dist/runtime/composables/useViewerSearch.js +15 -5
  72. package/dist/runtime/composables/useViewerState.d.ts +3 -0
  73. package/dist/runtime/composables/useViewerState.js +22 -1
  74. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
- import { type ShallowRef } from 'vue';
2
- import type { DetectedShape, FormFieldDefinition, FormFieldValue } from '../annotation/engine/types.js';
1
+ import { type Ref, type ShallowRef } from 'vue';
2
+ import type { CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload } from '../annotation/engine/types.js';
3
3
  export interface FormFieldsState {
4
4
  /** Field definitions grouped by page number */
5
5
  fieldDefinitions: ShallowRef<Map<number, FormFieldDefinition[]>>;
@@ -21,6 +21,33 @@ export interface FormFieldsState {
21
21
  resetValues: () => void;
22
22
  /** Create synthetic checkbox form fields from detected shapes */
23
23
  registerDetectedCheckboxes: (pageNumber: number, shapes: DetectedShape[], scale: number, pageHeight: number) => void;
24
+ /** Add a user-placed form field (from the placement tool). Returns the created def. */
25
+ addPlacedField: (payload: PlaceFieldPayload) => FormFieldDefinition;
26
+ /** Update a placed field's rect, name, or flags. */
27
+ updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>) => void;
28
+ /** Remove a placed field and its value. */
29
+ removePlacedField: (id: string) => void;
30
+ /** All user-placed field definitions (used by export). */
31
+ getPlacedFields: () => FormFieldDefinition[];
32
+ /** Look up a field definition by id (any origin). */
33
+ getFieldById: (id: string) => FormFieldDefinition | undefined;
34
+ /** Id of the currently selected placed field (null if none). */
35
+ selectedPlacedFieldId: Ref<string | null>;
36
+ /** Select or deselect a placed field. Pass null to deselect. */
37
+ selectPlacedField: (id: string | null) => void;
38
+ /** Apply checkbox-style hints (extracted from `/MK /CA` in the source PDF)
39
+ * to existing parsed checkbox defs and store the map for any defs
40
+ * parsed afterwards. Keyed by checkbox fieldName. */
41
+ applyCheckboxStyles: (styles: Map<string, CheckboxStyle>) => void;
42
+ /** Apply push-button captions (extracted from `/MK /CA` in the source
43
+ * PDF) to existing parsed button defs and store the map for any defs
44
+ * parsed afterwards. Keyed by button fieldName. */
45
+ applyButtonCaptions: (captions: Map<string, string>) => void;
46
+ /** Whether this field is locked by another (signed) signature field's
47
+ * lock rule. Reactive to both definitions and values, so a UI computed
48
+ * that calls this re-runs when any signature is signed or its lock
49
+ * rule changes. */
50
+ isFieldLocked: (fieldId: string) => boolean;
24
51
  /** Clear all values and definitions (for document change) */
25
52
  reset: () => void;
26
53
  }
@@ -1,18 +1,92 @@
1
1
  import {
2
2
  shallowRef,
3
+ ref,
3
4
  triggerRef,
4
5
  provide,
5
6
  inject
6
7
  } from "vue";
7
8
  import { parseFormFields } from "../annotation/parsers/parseFormFields.js";
9
+ import { generateUUID } from "../annotation/engine/utils.js";
8
10
  const FORM_FIELDS_KEY = Symbol("kviewer-form-fields");
9
11
  export function provideFormFields() {
10
12
  const fieldDefinitions = shallowRef(/* @__PURE__ */ new Map());
11
13
  const fieldValues = shallowRef(/* @__PURE__ */ new Map());
14
+ const selectedPlacedFieldId = ref(null);
15
+ function selectPlacedField(id) {
16
+ selectedPlacedFieldId.value = id;
17
+ }
18
+ function getFieldById(id) {
19
+ for (const defs of fieldDefinitions.value.values()) {
20
+ for (const d of defs) {
21
+ if (d.id === id) return d;
22
+ }
23
+ }
24
+ return void 0;
25
+ }
26
+ let checkboxStyleMap = null;
27
+ let buttonCaptionMap = null;
28
+ function applyCheckboxStyles(styles) {
29
+ checkboxStyleMap = styles;
30
+ let changed = false;
31
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
32
+ let pageChanged = false;
33
+ const next = defs.map((d) => {
34
+ if (d.fieldType !== "checkbox" || d.origin !== "parsed") return d;
35
+ const style = styles.get(d.fieldName);
36
+ if (!style) return d;
37
+ if (d.originalCheckboxStyle) return d;
38
+ pageChanged = true;
39
+ return { ...d, checkboxStyle: d.checkboxStyle ?? style, originalCheckboxStyle: style };
40
+ });
41
+ if (pageChanged) {
42
+ fieldDefinitions.value.set(page, next);
43
+ changed = true;
44
+ }
45
+ }
46
+ if (changed) triggerRef(fieldDefinitions);
47
+ }
48
+ function applyButtonCaptions(captions) {
49
+ buttonCaptionMap = captions;
50
+ let changed = false;
51
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
52
+ let pageChanged = false;
53
+ const next = defs.map((d) => {
54
+ if (d.fieldType !== "button" || d.origin !== "parsed") return d;
55
+ const caption = captions.get(d.fieldName);
56
+ if (!caption) return d;
57
+ if (d.buttonLabel === caption) return d;
58
+ pageChanged = true;
59
+ return { ...d, buttonLabel: caption };
60
+ });
61
+ if (pageChanged) {
62
+ fieldDefinitions.value.set(page, next);
63
+ changed = true;
64
+ }
65
+ }
66
+ if (changed) triggerRef(fieldDefinitions);
67
+ }
12
68
  function parsePageFields(pageNumber, rawAnnotations) {
13
69
  if (fieldDefinitions.value.has(pageNumber)) return;
14
70
  const defs = parseFormFields(rawAnnotations, pageNumber);
15
71
  if (defs.length === 0) return;
72
+ if (checkboxStyleMap) {
73
+ for (const def of defs) {
74
+ if (def.fieldType === "checkbox" && !def.checkboxStyle) {
75
+ const style = checkboxStyleMap.get(def.fieldName);
76
+ if (style) {
77
+ def.checkboxStyle = style;
78
+ def.originalCheckboxStyle = style;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ if (buttonCaptionMap) {
84
+ for (const def of defs) {
85
+ if (def.fieldType !== "button") continue;
86
+ const caption = buttonCaptionMap.get(def.fieldName);
87
+ if (caption) def.buttonLabel = caption;
88
+ }
89
+ }
16
90
  fieldDefinitions.value.set(pageNumber, defs);
17
91
  triggerRef(fieldDefinitions);
18
92
  for (const def of defs) {
@@ -30,11 +104,19 @@ export function provideFormFields() {
30
104
  const existing = fieldValues.value.get(fieldId);
31
105
  if (!existing) return;
32
106
  existing.value = value;
33
- if (existing.fieldType === "radio" && typeof value === "string" && value !== "") {
34
- const fieldName = existing.fieldName;
35
- for (const [id, fv] of fieldValues.value.entries()) {
36
- if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
37
- fv.value = "";
107
+ const { fieldName, fieldType } = existing;
108
+ if (fieldName) {
109
+ if (fieldType === "radio" && typeof value === "string" && value !== "") {
110
+ for (const [id, fv] of fieldValues.value.entries()) {
111
+ if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
112
+ fv.value = "";
113
+ }
114
+ }
115
+ } else if (fieldType !== "radio") {
116
+ for (const [id, fv] of fieldValues.value.entries()) {
117
+ if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === fieldType) {
118
+ fv.value = value;
119
+ }
38
120
  }
39
121
  }
40
122
  }
@@ -97,6 +179,7 @@ export function provideFormFields() {
97
179
  rect: [pdfX1, pdfY1, pdfX2, pdfY2],
98
180
  readOnly: false,
99
181
  required: false,
182
+ origin: "detected",
100
183
  exportValue: "Yes",
101
184
  defaultValue: "Off",
102
185
  shapeType: s.shapeType
@@ -115,6 +198,164 @@ export function provideFormFields() {
115
198
  }
116
199
  triggerRef(fieldValues);
117
200
  }
201
+ function nextPlacedFieldName(fieldType) {
202
+ const base = fieldType === "text" ? "Text" : fieldType === "checkbox" ? "Checkbox" : fieldType === "signature" ? "Signature" : fieldType === "dropdown" ? "Dropdown" : fieldType === "radio" ? "Radio" : "Field";
203
+ const existing = /* @__PURE__ */ new Set();
204
+ for (const defs of fieldDefinitions.value.values()) {
205
+ for (const d of defs) existing.add(d.fieldName);
206
+ }
207
+ let n = 1;
208
+ while (existing.has(`${base}_${n}`)) n += 1;
209
+ return `${base}_${n}`;
210
+ }
211
+ function placedDefaultValue(fieldType) {
212
+ if (fieldType === "checkbox") return false;
213
+ return "";
214
+ }
215
+ function addPlacedField(payload) {
216
+ const id = `placed-${generateUUID()}`;
217
+ let fieldName;
218
+ if (payload.fieldType === "radio" && selectedPlacedFieldId.value) {
219
+ const sel = getFieldById(selectedPlacedFieldId.value);
220
+ fieldName = sel && sel.fieldType === "radio" ? sel.fieldName : nextPlacedFieldName(payload.fieldType);
221
+ } else {
222
+ fieldName = nextPlacedFieldName(payload.fieldType);
223
+ }
224
+ const def = {
225
+ id,
226
+ pageNumber: payload.pageNumber,
227
+ fieldType: payload.fieldType,
228
+ fieldName,
229
+ rect: [...payload.rectPdf],
230
+ readOnly: false,
231
+ required: false,
232
+ origin: "placed"
233
+ };
234
+ if (payload.fieldType === "radio") {
235
+ def.buttonValue = nextRadioOptionValue(fieldName);
236
+ }
237
+ if (payload.fieldType === "checkbox") {
238
+ def.checkboxStyle = "check";
239
+ }
240
+ if (payload.fieldType === "dropdown") {
241
+ def.combo = true;
242
+ def.options = [{ exportValue: "Option 1", displayValue: "Option 1" }];
243
+ }
244
+ const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
245
+ fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
246
+ triggerRef(fieldDefinitions);
247
+ fieldValues.value.set(id, {
248
+ fieldId: id,
249
+ fieldName: def.fieldName,
250
+ fieldType: def.fieldType,
251
+ value: placedDefaultValue(def.fieldType)
252
+ });
253
+ triggerRef(fieldValues);
254
+ if (payload.fieldType === "radio") {
255
+ selectedPlacedFieldId.value = id;
256
+ }
257
+ return def;
258
+ }
259
+ function nextRadioOptionValue(groupName) {
260
+ const used = /* @__PURE__ */ new Set();
261
+ for (const defs of fieldDefinitions.value.values()) {
262
+ for (const d of defs) {
263
+ if (d.fieldType === "radio" && d.fieldName === groupName && d.buttonValue) {
264
+ used.add(d.buttonValue);
265
+ }
266
+ }
267
+ }
268
+ let n = 1;
269
+ while (used.has(`Option_${n}`)) n += 1;
270
+ return `Option_${n}`;
271
+ }
272
+ function updatePlacedField(id, patch) {
273
+ let found;
274
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
275
+ const idx = defs.findIndex((d) => d.id === id);
276
+ if (idx === -1) continue;
277
+ const existing = defs[idx];
278
+ const next = { ...existing, ...patch, id: existing.id };
279
+ const nextDefs = defs.slice();
280
+ nextDefs[idx] = next;
281
+ fieldDefinitions.value.set(page, nextDefs);
282
+ found = next;
283
+ break;
284
+ }
285
+ if (!found) return;
286
+ triggerRef(fieldDefinitions);
287
+ const fv = fieldValues.value.get(id);
288
+ if (fv && (fv.fieldName !== found.fieldName || fv.fieldType !== found.fieldType)) {
289
+ fv.fieldName = found.fieldName;
290
+ fv.fieldType = found.fieldType;
291
+ triggerRef(fieldValues);
292
+ }
293
+ }
294
+ function removePlacedField(id) {
295
+ let removed = false;
296
+ for (const [page, defs] of fieldDefinitions.value.entries()) {
297
+ const idx = defs.findIndex((d) => d.id === id);
298
+ if (idx === -1) continue;
299
+ const nextDefs = defs.slice();
300
+ nextDefs.splice(idx, 1);
301
+ if (nextDefs.length === 0) {
302
+ fieldDefinitions.value.delete(page);
303
+ } else {
304
+ fieldDefinitions.value.set(page, nextDefs);
305
+ }
306
+ removed = true;
307
+ break;
308
+ }
309
+ if (!removed) return;
310
+ triggerRef(fieldDefinitions);
311
+ if (fieldValues.value.delete(id)) {
312
+ triggerRef(fieldValues);
313
+ }
314
+ if (selectedPlacedFieldId.value === id) {
315
+ selectedPlacedFieldId.value = null;
316
+ }
317
+ }
318
+ function getPlacedFields() {
319
+ const out = [];
320
+ for (const defs of fieldDefinitions.value.values()) {
321
+ for (const d of defs) {
322
+ if (d.origin === "placed" || d.origin === "detected") {
323
+ out.push(d);
324
+ continue;
325
+ }
326
+ const rectMoved = d.originalRect && rectChanged(d.rect, d.originalRect);
327
+ const styleChanged = d.fieldType === "checkbox" && (d.checkboxStyle ?? "check") !== (d.originalCheckboxStyle ?? "check");
328
+ if (rectMoved || styleChanged) {
329
+ out.push(d);
330
+ }
331
+ }
332
+ }
333
+ return out;
334
+ }
335
+ function rectChanged(a, b) {
336
+ for (let i = 0; i < 4; i++) {
337
+ if (Math.abs((a[i] ?? 0) - (b[i] ?? 0)) > 0.01) return true;
338
+ }
339
+ return false;
340
+ }
341
+ function isFieldLocked(fieldId) {
342
+ const target = getFieldById(fieldId);
343
+ if (!target) return false;
344
+ for (const defs of fieldDefinitions.value.values()) {
345
+ for (const sig of defs) {
346
+ if (sig.fieldType !== "signature") continue;
347
+ if (!sig.lockAction) continue;
348
+ if (sig.id === fieldId) continue;
349
+ const fv = fieldValues.value.get(sig.id);
350
+ if (!fv || typeof fv.value !== "string" || fv.value === "") continue;
351
+ if (sig.lockAction === "all") return true;
352
+ const list = sig.lockFieldNames ?? [];
353
+ if (sig.lockAction === "include" && list.includes(target.fieldName)) return true;
354
+ if (sig.lockAction === "exclude" && !list.includes(target.fieldName)) return true;
355
+ }
356
+ }
357
+ return false;
358
+ }
118
359
  function resetValues() {
119
360
  for (const [, defs] of fieldDefinitions.value.entries()) {
120
361
  for (const def of defs) {
@@ -129,6 +370,9 @@ export function provideFormFields() {
129
370
  function reset() {
130
371
  fieldDefinitions.value = /* @__PURE__ */ new Map();
131
372
  fieldValues.value = /* @__PURE__ */ new Map();
373
+ selectedPlacedFieldId.value = null;
374
+ checkboxStyleMap = null;
375
+ buttonCaptionMap = null;
132
376
  triggerRef(fieldDefinitions);
133
377
  triggerRef(fieldValues);
134
378
  }
@@ -142,6 +386,16 @@ export function provideFormFields() {
142
386
  getAllFieldValues,
143
387
  setFieldValueByName,
144
388
  registerDetectedCheckboxes,
389
+ addPlacedField,
390
+ updatePlacedField,
391
+ removePlacedField,
392
+ getPlacedFields,
393
+ getFieldById,
394
+ selectedPlacedFieldId,
395
+ selectPlacedField,
396
+ applyCheckboxStyles,
397
+ applyButtonCaptions,
398
+ isFieldLocked,
145
399
  resetValues,
146
400
  reset
147
401
  };
@@ -0,0 +1,40 @@
1
+ export interface InertiaPanzoomOptions {
2
+ minScale?: number;
3
+ maxScale?: number;
4
+ /** Minimum pointer movement (px) before axis direction is committed. */
5
+ axisLockThreshold?: number;
6
+ /** Vertical must exceed horizontal by this ratio to lock to Y-axis. */
7
+ axisLockRatio?: number;
8
+ }
9
+ export interface ScrollToElementOptions {
10
+ /** Vertical alignment within the viewport. Default: 'start'. */
11
+ block?: 'start' | 'center';
12
+ /** Animate to the target with inertia, or snap instantly. Default: true. */
13
+ animate?: boolean;
14
+ }
15
+ export interface Transform {
16
+ scale: number;
17
+ tx: number;
18
+ ty: number;
19
+ }
20
+ export interface InertiaPanzoomHandle {
21
+ /** Bind gesture handlers to the given element (its parent becomes the viewport). */
22
+ attach: (target: HTMLElement) => void;
23
+ /** Remove handlers and clear inline styles. */
24
+ detach: () => void;
25
+ /** Reset transform and cancel any in-flight momentum. */
26
+ reset: () => void;
27
+ /** Cancel in-flight momentum without changing transform. */
28
+ stop: () => void;
29
+ getScale: () => number;
30
+ getTransform: () => Transform;
31
+ /** Animate the view so `elem` is at the top (or center) of the viewport. */
32
+ scrollToElement: (elem: HTMLElement, opts?: ScrollToElementOptions) => void;
33
+ /** Pan by a delta in viewport pixels (clamped to bounds). */
34
+ scrollBy: (dx: number, dy: number) => void;
35
+ /** Enable/disable pan+pinch gestures (wheel/scrollbar/keyboard still work). */
36
+ setGesturesEnabled: (enabled: boolean) => void;
37
+ /** Subscribe to transform updates. Returns an unsubscribe function. */
38
+ onChange: (cb: (t: Transform) => void) => () => void;
39
+ }
40
+ export declare function useInertiaPanzoom(options?: InertiaPanzoomOptions): InertiaPanzoomHandle;