kviewer 0.0.11 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- export { ExportPdfOptions, SignatureData, SignatureHandlers, ViewMode } from '../dist/runtime/public-types.js';
2
+ export { AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, SignatureData, SignatureHandlers, ViewMode } from '../dist/runtime/public-types.js';
3
3
 
4
4
  interface ModuleOptions {
5
5
  /**
@@ -7,6 +7,14 @@ interface ModuleOptions {
7
7
  * @defaultValue `K`
8
8
  */
9
9
  prefix?: string;
10
+ /**
11
+ * Minimum pixel ratio used to rasterize PDF pages. The canvas is rendered
12
+ * at `cssSize * max(devicePixelRatio, minRenderPixelRatio)` and downscaled
13
+ * by CSS, producing a supersampled, sharper image on non-HiDPI displays.
14
+ * Higher values cost ~quadratically more memory and CPU.
15
+ * @defaultValue `2`
16
+ */
17
+ minRenderPixelRatio?: number;
10
18
  }
11
19
 
12
20
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.0.11",
4
+ "version": "0.1.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -9,10 +9,15 @@ const module$1 = defineNuxtModule({
9
9
  },
10
10
  // Default configuration options of the Nuxt module
11
11
  defaults: {
12
- prefix: "K"
12
+ prefix: "K",
13
+ minRenderPixelRatio: 2
13
14
  },
14
15
  setup(options, nuxt) {
15
16
  const { resolve } = createResolver(import.meta.url);
17
+ nuxt.options.runtimeConfig.public.kviewer = {
18
+ ...nuxt.options.runtimeConfig.public.kviewer,
19
+ minRenderPixelRatio: options.minRenderPixelRatio ?? 2
20
+ };
16
21
  nuxt.options.css.push("pdfjs-dist/web/pdf_viewer.css");
17
22
  nuxt.options.css.push(resolve("./runtime/assets/kviewer.css"));
18
23
  addPlugin(resolve("./runtime/plugin"));
@@ -23,6 +23,10 @@ export interface PainterCallbacks {
23
23
  onRequestTextInput: IEditorFreeTextOptions['onRequestTextInput'];
24
24
  onRequestDeleteConfirm?: (id: string) => Promise<boolean>;
25
25
  onPlaceField?: (payload: PlaceFieldPayload) => void;
26
+ /** Optional accessor returning the color to use for the form-field
27
+ * placement preview. Lets the active role color override the default
28
+ * blue without rebuilding the editor between placements. */
29
+ getFormFieldPreviewColor?: () => string | undefined;
26
30
  }
27
31
  export declare class Painter {
28
32
  private userName;
@@ -54,7 +58,10 @@ export declare class Painter {
54
58
  private updateStore;
55
59
  /**
56
60
  * Convert a Konva stage-relative rect to viewport-relative coordinates
57
- * by combining with the stage container's DOM position.
61
+ * by combining with the stage container's DOM position. `getBoundingClientRect`
62
+ * already includes ancestor CSS transforms (panzoom), but `stageRect` is in
63
+ * pre-transform canvas pixels — scale it by the same factor so the offset
64
+ * matches the on-screen size.
58
65
  */
59
66
  private toViewportRect;
60
67
  private parseStampData;
@@ -182,17 +182,21 @@ export class Painter {
182
182
  }
183
183
  /**
184
184
  * Convert a Konva stage-relative rect to viewport-relative coordinates
185
- * by combining with the stage container's DOM position.
185
+ * by combining with the stage container's DOM position. `getBoundingClientRect`
186
+ * already includes ancestor CSS transforms (panzoom), but `stageRect` is in
187
+ * pre-transform canvas pixels — scale it by the same factor so the offset
188
+ * matches the on-screen size.
186
189
  */
187
190
  toViewportRect(pageNumber, stageRect) {
188
191
  const canvas = this.konvaCanvasStore.get(pageNumber);
189
192
  if (!canvas) return stageRect;
190
193
  const containerBounds = canvas.wrapper.getBoundingClientRect();
194
+ const visualScale = canvas.wrapper.offsetWidth > 0 ? containerBounds.width / canvas.wrapper.offsetWidth : 1;
191
195
  return {
192
- x: containerBounds.left + stageRect.x,
193
- y: containerBounds.top + stageRect.y,
194
- width: stageRect.width,
195
- height: stageRect.height
196
+ x: containerBounds.left + stageRect.x * visualScale,
197
+ y: containerBounds.top + stageRect.y * visualScale,
198
+ width: stageRect.width * visualScale,
199
+ height: stageRect.height * visualScale
196
200
  };
197
201
  }
198
202
  parseStampData() {
@@ -332,7 +336,8 @@ export class Painter {
332
336
  editor = new EditorFormField(editorOpts, {
333
337
  fieldType,
334
338
  pageHeight,
335
- onPlaceField
339
+ onPlaceField,
340
+ getPreviewColor: this.callbacks.getFormFieldPreviewColor
336
341
  });
337
342
  break;
338
343
  }
@@ -6,11 +6,17 @@ 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
14
  }
10
15
  export declare class EditorFormField extends Editor {
11
16
  private fieldType;
12
17
  private pageHeight;
13
18
  private onPlaceField;
19
+ private getPreviewColor?;
14
20
  private preview;
15
21
  private vertex;
16
22
  constructor(editorOptions: IEditorOptions, opts: IEditorFormFieldOptions);
@@ -11,6 +11,7 @@ export class EditorFormField extends Editor {
11
11
  fieldType;
12
12
  pageHeight;
13
13
  onPlaceField;
14
+ getPreviewColor;
14
15
  preview = null;
15
16
  vertex = { x: 0, y: 0 };
16
17
  constructor(editorOptions, opts) {
@@ -21,6 +22,7 @@ export class EditorFormField extends Editor {
21
22
  this.fieldType = opts.fieldType;
22
23
  this.pageHeight = opts.pageHeight;
23
24
  this.onPlaceField = opts.onPlaceField;
25
+ this.getPreviewColor = opts.getPreviewColor;
24
26
  }
25
27
  setPageHeight(pageHeight) {
26
28
  this.pageHeight = pageHeight;
@@ -36,7 +38,7 @@ export class EditorFormField extends Editor {
36
38
  y: pos.y,
37
39
  width: 0,
38
40
  height: 0,
39
- stroke: "#1677ff",
41
+ stroke: this.getPreviewColor?.() ?? "#1677ff",
40
42
  strokeWidth: 1,
41
43
  dash: [4, 4],
42
44
  strokeScaleEnabled: false,
@@ -209,6 +209,11 @@ export interface FormFieldDefinition {
209
209
  color?: number[];
210
210
  backgroundColor?: number[];
211
211
  textAlignment?: number;
212
+ /** Opaque role tag for color-coding in form-edit mode. The editor
213
+ * doesn't own role definitions — the host (integrating app) maps
214
+ * roleId → color via the `roleColors` prop on KViewer. Not exported
215
+ * to the PDF. */
216
+ roleId?: string;
212
217
  }
213
218
  export interface FormFieldValue {
214
219
  fieldId: string;
@@ -223,6 +228,65 @@ export interface PlaceFieldPayload {
223
228
  /** PDF-space rect [x1, y1, x2, y2], bottom-left origin. */
224
229
  rectPdf: [number, number, number, number];
225
230
  }
231
+ /**
232
+ * Public payload for adding a form field programmatically. All optional
233
+ * fields fall back to sensible defaults — only `pageNumber`, `fieldType`,
234
+ * and `rect` are required. The created field has `origin: 'placed'` so it
235
+ * is exported into the PDF as a new widget.
236
+ */
237
+ export interface AddFormFieldPayload {
238
+ pageNumber: number;
239
+ fieldType: FormFieldType;
240
+ /** PDF-space rect [x1, y1, x2, y2], bottom-left origin. */
241
+ rect: [number, number, number, number];
242
+ /** Field name. Auto-generated when omitted. Multiple widgets that share
243
+ * a `fieldName` (and `fieldType`) act as one PDF field — values are
244
+ * mirrored across them on edit. Radios with the same `fieldName` form
245
+ * one option group. */
246
+ fieldName?: string;
247
+ /** Opaque role tag for color-coding in form-edit mode. */
248
+ roleId?: string;
249
+ /** Default value for the field. Becomes the initial value too. */
250
+ defaultValue?: string;
251
+ readOnly?: boolean;
252
+ required?: boolean;
253
+ /** checkbox: glyph style. Defaults to 'check'. */
254
+ checkboxStyle?: CheckboxStyle;
255
+ /** signature: placeholder text on the unsigned widget. */
256
+ promptText?: string;
257
+ /** signature: lock other fields once signed. */
258
+ lockAction?: 'all' | 'include' | 'exclude';
259
+ /** signature: field names referenced by lockAction = 'include' / 'exclude'. */
260
+ lockFieldNames?: string[];
261
+ /** dropdown: editable combo box (true) vs list box (false). Defaults to true. */
262
+ combo?: boolean;
263
+ /** dropdown / listbox: allow multiple selections. */
264
+ multiSelect?: boolean;
265
+ /** dropdown / listbox: choices. */
266
+ options?: {
267
+ displayValue: string;
268
+ exportValue: string;
269
+ }[];
270
+ /** text: render as multiple lines. */
271
+ multiLine?: boolean;
272
+ /** text: render as a password field. */
273
+ password?: boolean;
274
+ /** text: render as evenly-spaced character cells (requires `maxLen`). */
275
+ comb?: boolean;
276
+ /** text: maximum input length. */
277
+ maxLen?: number;
278
+ /** radio: this widget's option value within its group. Auto-generated
279
+ * when omitted (e.g. "Option_1", "Option_2", …). */
280
+ buttonValue?: string;
281
+ fontSize?: number;
282
+ fontName?: string;
283
+ /** RGB color tuple in 0–1 (PDF native). */
284
+ color?: number[];
285
+ /** RGB background color tuple in 0–1 (PDF native). */
286
+ backgroundColor?: number[];
287
+ /** 0 = left, 1 = center, 2 = right. */
288
+ textAlignment?: number;
289
+ }
226
290
  export interface DetectedShapeRect {
227
291
  x: number;
228
292
  y: number;
@@ -1,4 +1,14 @@
1
- import { PDFBool, PDFDict, PDFName, PDFString, StandardFonts, rgb } from "pdf-lib";
1
+ import {
2
+ AcroFieldFlags,
3
+ AnnotationFlags,
4
+ PDFAcroSignature,
5
+ PDFBool,
6
+ PDFDict,
7
+ PDFName,
8
+ PDFRawStream,
9
+ PDFString,
10
+ StandardFonts
11
+ } from "pdf-lib";
2
12
  import { CHECKBOX_STYLE_TABLE } from "../checkbox-styles.js";
3
13
  export async function writeFormFieldsToPdf(pdfDoc, fieldValues, placedDefinitions = []) {
4
14
  const valueByFieldId = new Map(fieldValues.map((v) => [v.fieldId, v]));
@@ -206,13 +216,7 @@ async function createWidgetForDefinition(pdfDoc, def, fv, radioGroupCache) {
206
216
  break;
207
217
  }
208
218
  case "signature": {
209
- const v = fv?.value;
210
- const signed = typeof v === "string" && v.startsWith("data:image");
211
- if (signed) {
212
- await drawSignatureImage(pdfDoc, page, v, box);
213
- } else {
214
- await drawSignaturePlaceholder(pdfDoc, page, box);
215
- }
219
+ await createSignatureField(pdfDoc, page, def, fv);
216
220
  break;
217
221
  }
218
222
  case "dropdown": {
@@ -244,55 +248,101 @@ function applyFlags(field, def) {
244
248
  if (def.readOnly) field.enableReadOnly();
245
249
  if (def.required) field.enableRequired();
246
250
  }
247
- async function drawSignatureImage(pdfDoc, page, dataUrl, box) {
251
+ async function createSignatureField(pdfDoc, page, def, fv) {
252
+ const { context } = pdfDoc;
253
+ const form = pdfDoc.getForm();
254
+ const [x1, y1, x2, y2] = def.rect;
255
+ const width = Math.max(1, x2 - x1);
256
+ const height = Math.max(1, y2 - y1);
257
+ const fieldDict = context.obj({
258
+ FT: "Sig",
259
+ Kids: []
260
+ });
261
+ const fieldRef = context.register(fieldDict);
262
+ const sig = PDFAcroSignature.fromDict(fieldDict, fieldRef);
263
+ sig.setPartialName(def.fieldName);
264
+ if (def.readOnly) sig.setFlagTo(AcroFieldFlags.ReadOnly, true);
265
+ if (def.required) sig.setFlagTo(AcroFieldFlags.Required, true);
266
+ const v = fv?.value;
267
+ const signed = typeof v === "string" && v.startsWith("data:image");
268
+ const appearanceRef = signed ? await buildSignedSignatureAppearance(pdfDoc, v, width, height) : await buildSignaturePlaceholderAppearance(pdfDoc, width, height);
269
+ const widgetDict = context.obj({
270
+ Type: "Annot",
271
+ Subtype: "Widget",
272
+ Rect: [x1, y1, x2, y2],
273
+ P: page.ref,
274
+ Parent: fieldRef,
275
+ F: 1 << AnnotationFlags.Print,
276
+ AP: { N: appearanceRef }
277
+ });
278
+ const widgetRef = context.register(widgetDict);
279
+ sig.addWidget(widgetRef);
280
+ form.acroForm.addField(fieldRef);
281
+ page.node.addAnnot(widgetRef);
282
+ }
283
+ async function buildSignaturePlaceholderAppearance(pdfDoc, width, height) {
284
+ const { context } = pdfDoc;
285
+ const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
286
+ const label = "Sign here";
287
+ const fontSize = Math.max(6, Math.min(12, height * 0.4));
288
+ const textWidth = font.widthOfTextAtSize(label, fontSize);
289
+ const textHeight = font.heightAtSize(fontSize);
290
+ const textX = (width - textWidth) / 2;
291
+ const textY = (height - textHeight) / 2;
292
+ const ops = [
293
+ "q",
294
+ "0.55 0.55 0.55 RG",
295
+ "0.75 w",
296
+ "[3 2] 0 d",
297
+ `0.5 0.5 ${(width - 1).toFixed(3)} ${(height - 1).toFixed(3)} re`,
298
+ "S",
299
+ "0.55 0.55 0.55 rg",
300
+ "BT",
301
+ `/F1 ${fontSize.toFixed(3)} Tf`,
302
+ `${textX.toFixed(3)} ${textY.toFixed(3)} Td`,
303
+ `(${label}) Tj`,
304
+ "ET",
305
+ "Q"
306
+ ].join("\n");
307
+ const dict = context.obj({
308
+ Type: "XObject",
309
+ Subtype: "Form",
310
+ BBox: [0, 0, width, height],
311
+ Matrix: [1, 0, 0, 1, 0, 0],
312
+ Resources: { Font: { F1: font.ref } }
313
+ });
314
+ const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
315
+ return context.register(stream);
316
+ }
317
+ async function buildSignedSignatureAppearance(pdfDoc, dataUrl, width, height) {
248
318
  const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/);
249
- if (!match) return;
319
+ if (!match) return buildSignaturePlaceholderAppearance(pdfDoc, width, height);
250
320
  const mimeType = match[1] ?? "";
251
321
  const base64 = match[2] ?? "";
252
- let image;
253
- if (mimeType.includes("jpeg") || mimeType.includes("jpg")) {
254
- image = await pdfDoc.embedJpg(base64);
255
- } else {
256
- image = await pdfDoc.embedPng(base64);
257
- }
322
+ const image = mimeType.includes("jpeg") || mimeType.includes("jpg") ? await pdfDoc.embedJpg(base64) : await pdfDoc.embedPng(base64);
258
323
  const imgAspect = image.width / image.height;
259
- const boxAspect = box.width / box.height;
260
- let drawW = box.width;
261
- let drawH = box.height;
262
- if (imgAspect > boxAspect) {
263
- drawH = box.width / imgAspect;
264
- } else {
265
- drawW = box.height * imgAspect;
266
- }
267
- const drawX = box.x + (box.width - drawW) / 2;
268
- const drawY = box.y + (box.height - drawH) / 2;
269
- page.drawImage(image, { x: drawX, y: drawY, width: drawW, height: drawH });
270
- }
271
- async function drawSignaturePlaceholder(pdfDoc, page, box) {
272
- page.drawRectangle({
273
- x: box.x,
274
- y: box.y,
275
- width: box.width,
276
- height: box.height,
277
- borderColor: rgb(0.55, 0.55, 0.55),
278
- borderWidth: 0.75,
279
- borderDashArray: [3, 2]
324
+ const boxAspect = width / height;
325
+ let drawW = width;
326
+ let drawH = height;
327
+ if (imgAspect > boxAspect) drawH = width / imgAspect;
328
+ else drawW = height * imgAspect;
329
+ const drawX = (width - drawW) / 2;
330
+ const drawY = (height - drawH) / 2;
331
+ const ops = [
332
+ "q",
333
+ `${drawW.toFixed(3)} 0 0 ${drawH.toFixed(3)} ${drawX.toFixed(3)} ${drawY.toFixed(3)} cm`,
334
+ "/Img Do",
335
+ "Q"
336
+ ].join("\n");
337
+ const dict = pdfDoc.context.obj({
338
+ Type: "XObject",
339
+ Subtype: "Form",
340
+ BBox: [0, 0, width, height],
341
+ Matrix: [1, 0, 0, 1, 0, 0],
342
+ Resources: { XObject: { Img: image.ref } }
280
343
  });
281
- try {
282
- const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
283
- const label = "Sign here";
284
- const fontSize = Math.max(6, Math.min(12, box.height * 0.4));
285
- const textWidth = font.widthOfTextAtSize(label, fontSize);
286
- const textHeight = font.heightAtSize(fontSize);
287
- page.drawText(label, {
288
- x: box.x + (box.width - textWidth) / 2,
289
- y: box.y + (box.height - textHeight) / 2,
290
- size: fontSize,
291
- font,
292
- color: rgb(0.55, 0.55, 0.55)
293
- });
294
- } catch {
295
- }
344
+ const stream = PDFRawStream.of(dict, new TextEncoder().encode(ops));
345
+ return pdfDoc.context.register(stream);
296
346
  }
297
347
  async function embedSignatureImage(pdfDoc, field) {
298
348
  const dataUrl = field.value;
@@ -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:rgba(224,232,255,.6);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:rgba(224,232,255,.8);border-color:rgba(0,0,0,.2)}.kviewer-form-input:focus{background-color:rgba(224,232,255,.9);border-color:var(--color-primary,#3b82f6);outline:2px solid var(--color-primary,#3b82f6)}.kviewer-form-select{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto;cursor:pointer}.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(--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(--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:rgba(59,130,246,.08);border-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}
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-select{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto;cursor:pointer}.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}
@@ -54,6 +54,7 @@
54
54
 
55
55
  <script setup>
56
56
  import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from "vue";
57
+ import { useRuntimeConfig } from "#imports";
57
58
  import { useViewerState } from "../composables/useViewerState";
58
59
  import { useViewerSearch } from "../composables/useViewerSearch";
59
60
  import { usePageProxyCache } from "../composables/usePageProxyCache";
@@ -71,6 +72,10 @@ const props = defineProps({
71
72
  const canvasRef = ref(null);
72
73
  const textLayerRef = ref(null);
73
74
  const annotationRef = ref(null);
75
+ const runtimeConfig = useRuntimeConfig();
76
+ const minRenderPixelRatio = Number(
77
+ runtimeConfig.public.kviewer?.minRenderPixelRatio ?? 2
78
+ );
74
79
  const state = useViewerState();
75
80
  const search = useViewerSearch();
76
81
  const proxyCache = usePageProxyCache();
@@ -181,7 +186,7 @@ async function renderCanvas(opts) {
181
186
  currentRenderTask.cancel();
182
187
  currentRenderTask = null;
183
188
  }
184
- const dpr = window.devicePixelRatio || 1;
189
+ const dpr = Math.max(window.devicePixelRatio || 1, minRenderPixelRatio);
185
190
  const quality = props.renderQuality ?? 1;
186
191
  const viewport = proxy.getViewport({
187
192
  scale: props.scale * quality * dpr
@@ -1,5 +1,5 @@
1
1
  import { type ViewMode } from '../composables/useViewerState.js';
2
- import type { IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
3
3
  import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
4
4
  type __VLS_Props = {
5
5
  source: string | Uint8Array | object;
@@ -21,6 +21,16 @@ type __VLS_Props = {
21
21
  * with selection chrome, can be moved/resized, and the property panel
22
22
  * is shown. Supports v-model via `v-model:form-edit-mode`. */
23
23
  formEditMode?: boolean;
24
+ /** Map of roleId → CSS color used to color-code form fields in
25
+ * form-edit mode. The editor stays unopinionated about role
26
+ * definitions: the host owns the role list, names, and color picker,
27
+ * and just hands a flat lookup table to the viewer. Unknown or
28
+ * missing ids fall back to the default blue. */
29
+ roleColors?: Record<string, string>;
30
+ /** Active role id new field placements are auto-tagged with. The host
31
+ * drives this — typically by binding it to a role picker in its own
32
+ * UI. Supports v-model via `v-model:active-role-id`. */
33
+ activeRoleId?: string | null;
24
34
  };
25
35
  declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
26
36
  type ImportMode = 'replace' | 'merge';
@@ -44,6 +54,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
44
54
  getKonvaCanvasState: typeof getKonvaCanvasState;
45
55
  getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
46
56
  setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
57
+ /** Add a form field programmatically. Returns the created definition.
58
+ * The new field is exported into the PDF on `exportPdf()` like any
59
+ * field placed via the placement tool. */
60
+ addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
61
+ /** Patch a form field by id (rect, name, flags, type-specific props). */
62
+ updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
63
+ /** Remove a form field by id (drops its value too). */
64
+ removeFormField: (id: string) => void;
65
+ /** All form-field definitions (parsed, detected, and placed), flat. */
66
+ getFormFields: () => FormFieldDefinition[];
47
67
  /** Reactive form-edit-mode flag — read with `.value`. */
48
68
  formEditMode: import("vue").Ref<boolean, boolean>;
49
69
  /** Programmatically enter or leave form-edit mode. */
@@ -52,8 +72,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
52
72
  toggleFormEditMode: () => boolean;
53
73
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
54
74
  "update:formEditMode": (value: boolean) => any;
75
+ "update:activeRoleId": (value: string | null) => any;
55
76
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
56
77
  "onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
78
+ "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
57
79
  }>, {
58
80
  userName: string;
59
81
  freehandGroupingDelay: number;
@@ -65,6 +87,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
65
87
  viewMode: ViewMode;
66
88
  shapeDetection: boolean;
67
89
  formEditMode: boolean;
90
+ roleColors: Record<string, string>;
91
+ activeRoleId: string | null;
68
92
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
69
93
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
70
94
  declare const _default: typeof __VLS_export;
@@ -221,9 +221,11 @@ const props = defineProps({
221
221
  shapeDetection: { type: Boolean, required: false, default: false },
222
222
  active: { type: Boolean, required: false, default: true },
223
223
  freehandGroupingDelay: { type: Number, required: false, default: 1e3 },
224
- formEditMode: { type: Boolean, required: false, default: void 0 }
224
+ formEditMode: { type: Boolean, required: false, default: void 0 },
225
+ roleColors: { type: Object, required: false, default: void 0 },
226
+ activeRoleId: { type: [String, null], required: false, default: void 0 }
225
227
  });
226
- const emit = defineEmits(["update:formEditMode"]);
228
+ const emit = defineEmits(["update:formEditMode", "update:activeRoleId"]);
227
229
  const viewerRoot = ref(null);
228
230
  const scrollContainer = ref(null);
229
231
  const { state: viewerState, setScrollToPageFn, setDownloadPdfFn } = provideViewerState();
@@ -249,6 +251,29 @@ watch(
249
251
  );
250
252
  const viewerSearch = provideViewerSearch();
251
253
  const formFieldsState = provideFormFields();
254
+ watch(
255
+ () => props.roleColors,
256
+ (v) => {
257
+ formFieldsState.setRoleColors(v ?? {});
258
+ },
259
+ { immediate: true }
260
+ );
261
+ watch(
262
+ () => props.activeRoleId,
263
+ (v) => {
264
+ if (v === void 0) return;
265
+ if (formFieldsState.activeRoleId.value !== v) {
266
+ formFieldsState.setActiveRole(v);
267
+ }
268
+ },
269
+ { immediate: true }
270
+ );
271
+ watch(
272
+ () => formFieldsState.activeRoleId.value,
273
+ (v) => {
274
+ if (props.activeRoleId !== v) emit("update:activeRoleId", v);
275
+ }
276
+ );
252
277
  const pageSettings = providePageSettings(viewerRoot);
253
278
  const virtualization = createPageVirtualization();
254
279
  setScrollToPageFn((pageNumber) => {
@@ -686,6 +711,11 @@ onMounted(() => {
686
711
  formFieldsState.addPlacedField(payload);
687
712
  viewerState.selectTool("hand");
688
713
  },
714
+ getFormFieldPreviewColor: () => {
715
+ const id = formFieldsState.activeRoleId.value;
716
+ if (!id) return void 0;
717
+ return formFieldsState.roleColors.value[id];
718
+ },
689
719
  freehandGroupingDelay: props.freehandGroupingDelay,
690
720
  // Suspend panzoom gestures while an annotation is being dragged or
691
721
  // transformed. Otherwise single-finger pan runs in parallel with the
@@ -802,6 +832,16 @@ defineExpose({
802
832
  getKonvaCanvasState,
803
833
  getFormFieldValues: () => formFieldsState.getAllFieldValues(),
804
834
  setFormFieldValue: (fieldName, value) => formFieldsState.setFieldValueByName(fieldName, value),
835
+ /** Add a form field programmatically. Returns the created definition.
836
+ * The new field is exported into the PDF on `exportPdf()` like any
837
+ * field placed via the placement tool. */
838
+ addFormField: (payload) => formFieldsState.addFormField(payload),
839
+ /** Patch a form field by id (rect, name, flags, type-specific props). */
840
+ updateFormField: (id, patch) => formFieldsState.updatePlacedField(id, patch),
841
+ /** Remove a form field by id (drops its value too). */
842
+ removeFormField: (id) => formFieldsState.removePlacedField(id),
843
+ /** All form-field definitions (parsed, detected, and placed), flat. */
844
+ getFormFields: () => formFieldsState.getAllFields(),
805
845
  /** Reactive form-edit-mode flag — read with `.value`. */
806
846
  formEditMode: viewerState.formEditMode,
807
847
  /** Programmatically enter or leave form-edit mode. */
@@ -1,5 +1,5 @@
1
1
  import { type ViewMode } from '../composables/useViewerState.js';
2
- import type { IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
3
3
  import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
4
4
  type __VLS_Props = {
5
5
  source: string | Uint8Array | object;
@@ -21,6 +21,16 @@ type __VLS_Props = {
21
21
  * with selection chrome, can be moved/resized, and the property panel
22
22
  * is shown. Supports v-model via `v-model:form-edit-mode`. */
23
23
  formEditMode?: boolean;
24
+ /** Map of roleId → CSS color used to color-code form fields in
25
+ * form-edit mode. The editor stays unopinionated about role
26
+ * definitions: the host owns the role list, names, and color picker,
27
+ * and just hands a flat lookup table to the viewer. Unknown or
28
+ * missing ids fall back to the default blue. */
29
+ roleColors?: Record<string, string>;
30
+ /** Active role id new field placements are auto-tagged with. The host
31
+ * drives this — typically by binding it to a role picker in its own
32
+ * UI. Supports v-model via `v-model:active-role-id`. */
33
+ activeRoleId?: string | null;
24
34
  };
25
35
  declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
26
36
  type ImportMode = 'replace' | 'merge';
@@ -44,6 +54,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
44
54
  getKonvaCanvasState: typeof getKonvaCanvasState;
45
55
  getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
46
56
  setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
57
+ /** Add a form field programmatically. Returns the created definition.
58
+ * The new field is exported into the PDF on `exportPdf()` like any
59
+ * field placed via the placement tool. */
60
+ addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
61
+ /** Patch a form field by id (rect, name, flags, type-specific props). */
62
+ updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
63
+ /** Remove a form field by id (drops its value too). */
64
+ removeFormField: (id: string) => void;
65
+ /** All form-field definitions (parsed, detected, and placed), flat. */
66
+ getFormFields: () => FormFieldDefinition[];
47
67
  /** Reactive form-edit-mode flag — read with `.value`. */
48
68
  formEditMode: import("vue").Ref<boolean, boolean>;
49
69
  /** Programmatically enter or leave form-edit mode. */
@@ -52,8 +72,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
52
72
  toggleFormEditMode: () => boolean;
53
73
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
54
74
  "update:formEditMode": (value: boolean) => any;
75
+ "update:activeRoleId": (value: string | null) => any;
55
76
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
56
77
  "onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
78
+ "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
57
79
  }>, {
58
80
  userName: string;
59
81
  freehandGroupingDelay: number;
@@ -65,6 +87,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
65
87
  viewMode: ViewMode;
66
88
  shapeDetection: boolean;
67
89
  formEditMode: boolean;
90
+ roleColors: Record<string, string>;
91
+ activeRoleId: string | null;
68
92
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
69
93
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
70
94
  declare const _default: typeof __VLS_export;
@@ -48,6 +48,11 @@ type __VLS_Props = {
48
48
  zoom?: number;
49
49
  /** Minimum number of tabs -- prevents closing below this count. */
50
50
  minTabs?: number;
51
+ /** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
52
+ roleColors?: Record<string, string>;
53
+ /** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
54
+ * Supports v-model via `v-model:active-role-id`. */
55
+ activeRoleId?: string | null;
51
56
  };
52
57
  declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
53
58
  id?: string;
@@ -55,7 +60,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
55
60
  declare function removeTab(id: string): boolean;
56
61
  declare function activateTab(id: string): void;
57
62
  declare function getViewer(id: string): ComponentPublicInstance | null;
58
- declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
63
+ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
59
64
  tab: {
60
65
  id: string;
61
66
  label: string;
@@ -154,7 +159,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
154
159
  zoom?: number | undefined;
155
160
  shapeDetection?: boolean | undefined;
156
161
  };
157
- }, __VLS_33: {
162
+ }, __VLS_35: {
158
163
  tab: {
159
164
  id: string;
160
165
  label: string;
@@ -253,17 +258,17 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
253
258
  zoom?: number | undefined;
254
259
  shapeDetection?: boolean | undefined;
255
260
  };
256
- }, __VLS_35: {};
261
+ }, __VLS_37: {};
257
262
  type __VLS_Slots = {} & {
258
263
  'tabs-leading'?: (props: typeof __VLS_7) => any;
259
264
  } & {
260
265
  'tabs-trailing'?: (props: typeof __VLS_21) => any;
261
266
  } & {
262
- header?: (props: typeof __VLS_30) => any;
267
+ header?: (props: typeof __VLS_32) => any;
263
268
  } & {
264
- footer?: (props: typeof __VLS_33) => any;
269
+ footer?: (props: typeof __VLS_35) => any;
265
270
  } & {
266
- empty?: (props: typeof __VLS_35) => any;
271
+ empty?: (props: typeof __VLS_37) => any;
267
272
  };
268
273
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
269
274
  addTab: typeof addTab;
@@ -370,11 +375,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
370
375
  shapeDetection?: boolean | undefined;
371
376
  }[];
372
377
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
378
+ "update:activeRoleId": (value: string | null) => any;
373
379
  "update:activeTab": (id: string) => any;
374
380
  "tab-added": (tab: ViewerTabItem) => any;
375
381
  "tab-close": (id: string) => any;
376
382
  "tab-removed": (id: string) => any;
377
383
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
384
+ "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
378
385
  "onUpdate:activeTab"?: ((id: string) => any) | undefined;
379
386
  "onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
380
387
  "onTab-close"?: ((id: string) => any) | undefined;
@@ -385,6 +392,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
385
392
  stamps: StampDefinition[];
386
393
  signatureHandlers: SignatureHandlers;
387
394
  viewMode: ViewMode;
395
+ roleColors: Record<string, string>;
396
+ activeRoleId: string | null;
388
397
  defaultActiveTab: string;
389
398
  minTabs: number;
390
399
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -59,6 +59,9 @@
59
59
  :zoom="activeTab.zoom ?? zoom"
60
60
  :readonly="readonly"
61
61
  :shape-detection="activeTab.shapeDetection ?? shapeDetection"
62
+ :role-colors="roleColors"
63
+ :active-role-id="activeRoleId"
64
+ @update:active-role-id="(v) => emit('update:activeRoleId', v)"
62
65
  >
63
66
  <template v-if="$slots.header" #header>
64
67
  <slot name="header" :tab="activeTab" />
@@ -91,9 +94,11 @@ const props = defineProps({
91
94
  shapeDetection: { type: Boolean, required: false },
92
95
  viewMode: { type: String, required: false, default: "fit-width" },
93
96
  zoom: { type: Number, required: false, default: 1 },
94
- minTabs: { type: Number, required: false, default: 0 }
97
+ minTabs: { type: Number, required: false, default: 0 },
98
+ roleColors: { type: Object, required: false, default: void 0 },
99
+ activeRoleId: { type: [String, null], required: false, default: void 0 }
95
100
  });
96
- const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed"]);
101
+ const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId"]);
97
102
  let idCounter = 0;
98
103
  function generateId() {
99
104
  return `tab-${Date.now()}-${++idCounter}`;
@@ -48,6 +48,11 @@ type __VLS_Props = {
48
48
  zoom?: number;
49
49
  /** Minimum number of tabs -- prevents closing below this count. */
50
50
  minTabs?: number;
51
+ /** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
52
+ roleColors?: Record<string, string>;
53
+ /** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
54
+ * Supports v-model via `v-model:active-role-id`. */
55
+ activeRoleId?: string | null;
51
56
  };
52
57
  declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
53
58
  id?: string;
@@ -55,7 +60,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
55
60
  declare function removeTab(id: string): boolean;
56
61
  declare function activateTab(id: string): void;
57
62
  declare function getViewer(id: string): ComponentPublicInstance | null;
58
- declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
63
+ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
59
64
  tab: {
60
65
  id: string;
61
66
  label: string;
@@ -154,7 +159,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
154
159
  zoom?: number | undefined;
155
160
  shapeDetection?: boolean | undefined;
156
161
  };
157
- }, __VLS_33: {
162
+ }, __VLS_35: {
158
163
  tab: {
159
164
  id: string;
160
165
  label: string;
@@ -253,17 +258,17 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
253
258
  zoom?: number | undefined;
254
259
  shapeDetection?: boolean | undefined;
255
260
  };
256
- }, __VLS_35: {};
261
+ }, __VLS_37: {};
257
262
  type __VLS_Slots = {} & {
258
263
  'tabs-leading'?: (props: typeof __VLS_7) => any;
259
264
  } & {
260
265
  'tabs-trailing'?: (props: typeof __VLS_21) => any;
261
266
  } & {
262
- header?: (props: typeof __VLS_30) => any;
267
+ header?: (props: typeof __VLS_32) => any;
263
268
  } & {
264
- footer?: (props: typeof __VLS_33) => any;
269
+ footer?: (props: typeof __VLS_35) => any;
265
270
  } & {
266
- empty?: (props: typeof __VLS_35) => any;
271
+ empty?: (props: typeof __VLS_37) => any;
267
272
  };
268
273
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
269
274
  addTab: typeof addTab;
@@ -370,11 +375,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
370
375
  shapeDetection?: boolean | undefined;
371
376
  }[];
372
377
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
378
+ "update:activeRoleId": (value: string | null) => any;
373
379
  "update:activeTab": (id: string) => any;
374
380
  "tab-added": (tab: ViewerTabItem) => any;
375
381
  "tab-close": (id: string) => any;
376
382
  "tab-removed": (id: string) => any;
377
383
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
384
+ "onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
378
385
  "onUpdate:activeTab"?: ((id: string) => any) | undefined;
379
386
  "onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
380
387
  "onTab-close"?: ((id: string) => any) | undefined;
@@ -385,6 +392,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
385
392
  stamps: StampDefinition[];
386
393
  signatureHandlers: SignatureHandlers;
387
394
  viewMode: ViewMode;
395
+ roleColors: Record<string, string>;
396
+ activeRoleId: string | null;
388
397
  defaultActiveTab: string;
389
398
  minTabs: number;
390
399
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -6,7 +6,7 @@
6
6
  'kviewer-form-field--placed': isPlaced,
7
7
  'kviewer-form-field--selected': props.selected
8
8
  }"
9
- :style="positionStyle"
9
+ :style="[positionStyle, { '--kvw-field-color': chromeColor }]"
10
10
  >
11
11
  <div
12
12
  class="kviewer-form-field__body"
@@ -34,6 +34,7 @@
34
34
  <script setup>
35
35
  import { computed } from "vue";
36
36
  import { useViewerState } from "../../composables/useViewerState";
37
+ import { useFormFields } from "../../composables/useFormFields";
37
38
  import FormTextField from "./FormTextField.vue";
38
39
  import FormCheckbox from "./FormCheckbox.vue";
39
40
  import FormRadioButton from "./FormRadioButton.vue";
@@ -51,9 +52,15 @@ const props = defineProps({
51
52
  });
52
53
  const emit = defineEmits(["select"]);
53
54
  const state = useViewerState();
55
+ const formFields = useFormFields();
54
56
  const isPlaced = computed(() => props.field.origin === "placed");
55
57
  const isFormEditMode = computed(() => state.formEditMode.value);
56
58
  const showChrome = computed(() => isFormEditMode.value);
59
+ const chromeColor = computed(() => {
60
+ const id = props.field.roleId;
61
+ if (!id) return "#1677ff";
62
+ return formFields.roleColors.value[id] ?? "#1677ff";
63
+ });
57
64
  const positionStyle = computed(() => {
58
65
  const rect = props.field.rect;
59
66
  const vx = props.viewOffsetX ?? 0;
@@ -73,5 +80,5 @@ const positionStyle = computed(() => {
73
80
  </script>
74
81
 
75
82
  <style>
76
- .kviewer-form-field__body{height:100%;width:100%}.kviewer-form-field__body--locked{pointer-events:none}.kviewer-form-field--editable{outline:1px dashed rgba(22,119,255,.45);outline-offset:0}.kviewer-form-field--editable.kviewer-form-field--placed{outline-color:rgba(22,119,255,.75)}.kviewer-form-field--editable.kviewer-form-field--selected{outline:none}
83
+ .kviewer-form-field__body{height:100%;width:100%}.kviewer-form-field__body--locked{pointer-events:none}.kviewer-form-field--editable{outline:1px dashed color-mix(in srgb,var(--kvw-field-color,#1677ff) 45%,transparent);outline-offset:0}.kviewer-form-field--editable.kviewer-form-field--placed{outline-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 75%,transparent)}.kviewer-form-field--editable.kviewer-form-field--selected{outline:none}
77
84
  </style>
@@ -127,5 +127,5 @@ function onHandlePointerDown(e, name) {
127
127
  </script>
128
128
 
129
129
  <style>
130
- .kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome--selected{outline:1.5px dashed #1677ff;outline-offset:0}.kviewer-placed-chrome__grip{align-items:center;background:rgba(22,119,255,.15);border:1px solid rgba(22,119,255,.4);border-radius:calc(3px*var(--kvw-handle-zoom, 1));bottom:calc(100% + 3px*var(--kvw-handle-zoom, 1));color:#1677ff;cursor:move;display:flex;font-size:calc(9px*var(--kvw-handle-zoom, 1));gap:calc(3px*var(--kvw-handle-zoom, 1));height:calc(16px*var(--kvw-handle-zoom, 1));left:0;line-height:1;overflow:hidden;padding:0 calc(6px*var(--kvw-handle-zoom, 1)) 0 calc(2px*var(--kvw-handle-zoom, 1));pointer-events:auto;position:absolute;right:0;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-placed-chrome__grip-dots{color:#1677ff;flex:0 0 auto;height:calc(10px*var(--kvw-handle-zoom, 1));width:calc(10px*var(--kvw-handle-zoom, 1))}.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 #1677ff;border-radius:2px;height:calc(8px*var(--kvw-handle-zoom, 1));pointer-events:auto;position:absolute;touch-action:none;width:calc(8px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle:before{content:"";inset:calc(-8px*var(--kvw-handle-zoom, 1));position:absolute}.kviewer-placed-chrome__handle--n{left:50%;top:calc(-4px*var(--kvw-handle-zoom, 1));transform:translateX(-50%)}.kviewer-placed-chrome__handle--s{bottom:calc(-4px*var(--kvw-handle-zoom, 1));left:50%;transform:translateX(-50%)}.kviewer-placed-chrome__handle--e{right:calc(-4px*var(--kvw-handle-zoom, 1));top:50%;transform:translateY(-50%)}.kviewer-placed-chrome__handle--w{left:calc(-4px*var(--kvw-handle-zoom, 1));top:50%;transform:translateY(-50%)}.kviewer-placed-chrome__handle--ne{right:calc(-4px*var(--kvw-handle-zoom, 1));top:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--nw{left:calc(-4px*var(--kvw-handle-zoom, 1));top:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--se{bottom:calc(-4px*var(--kvw-handle-zoom, 1));right:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--sw{bottom:calc(-4px*var(--kvw-handle-zoom, 1));left:calc(-4px*var(--kvw-handle-zoom, 1))}
130
+ .kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome--selected{outline:1.5px dashed var(--kvw-field-color,#1677ff);outline-offset:0}.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:calc(3px*var(--kvw-handle-zoom, 1));bottom:calc(100% + 3px*var(--kvw-handle-zoom, 1));color:var(--kvw-field-color,#1677ff);cursor:move;display:flex;font-size:calc(9px*var(--kvw-handle-zoom, 1));gap:calc(3px*var(--kvw-handle-zoom, 1));height:calc(16px*var(--kvw-handle-zoom, 1));left:0;line-height:1;overflow:hidden;padding:0 calc(6px*var(--kvw-handle-zoom, 1)) 0 calc(2px*var(--kvw-handle-zoom, 1));pointer-events:auto;position:absolute;right:0;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.kviewer-placed-chrome__grip-dots{color:var(--kvw-field-color,#1677ff);flex:0 0 auto;height:calc(10px*var(--kvw-handle-zoom, 1));width:calc(10px*var(--kvw-handle-zoom, 1))}.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:calc(8px*var(--kvw-handle-zoom, 1));pointer-events:auto;position:absolute;touch-action:none;width:calc(8px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle:before{content:"";inset:calc(-8px*var(--kvw-handle-zoom, 1));position:absolute}.kviewer-placed-chrome__handle--n{left:50%;top:calc(-4px*var(--kvw-handle-zoom, 1));transform:translateX(-50%)}.kviewer-placed-chrome__handle--s{bottom:calc(-4px*var(--kvw-handle-zoom, 1));left:50%;transform:translateX(-50%)}.kviewer-placed-chrome__handle--e{right:calc(-4px*var(--kvw-handle-zoom, 1));top:50%;transform:translateY(-50%)}.kviewer-placed-chrome__handle--w{left:calc(-4px*var(--kvw-handle-zoom, 1));top:50%;transform:translateY(-50%)}.kviewer-placed-chrome__handle--ne{right:calc(-4px*var(--kvw-handle-zoom, 1));top:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--nw{left:calc(-4px*var(--kvw-handle-zoom, 1));top:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--se{bottom:calc(-4px*var(--kvw-handle-zoom, 1));right:calc(-4px*var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__handle--sw{bottom:calc(-4px*var(--kvw-handle-zoom, 1));left:calc(-4px*var(--kvw-handle-zoom, 1))}
131
131
  </style>
@@ -5,6 +5,7 @@ export declare function createAnnotationEngine(viewerState: ViewerState, options
5
5
  onRequestTextInput: PainterCallbacks['onRequestTextInput'];
6
6
  onRequestDeleteConfirm?: PainterCallbacks['onRequestDeleteConfirm'];
7
7
  onPlaceField?: PainterCallbacks['onPlaceField'];
8
+ getFormFieldPreviewColor?: PainterCallbacks['getFormFieldPreviewColor'];
8
9
  freehandGroupingDelay?: number;
9
10
  setExternalGesturesEnabled?: (enabled: boolean) => void;
10
11
  }): Painter;
@@ -61,7 +61,8 @@ export function createAnnotationEngine(viewerState, options) {
61
61
  },
62
62
  onRequestTextInput: options.onRequestTextInput,
63
63
  onRequestDeleteConfirm: options.onRequestDeleteConfirm,
64
- onPlaceField: options.onPlaceField
64
+ onPlaceField: options.onPlaceField,
65
+ getFormFieldPreviewColor: options.getFormFieldPreviewColor
65
66
  };
66
67
  const painter = new Painter({
67
68
  userName: options.userName ?? "User",
@@ -1,5 +1,5 @@
1
1
  import { type Ref, type ShallowRef } from 'vue';
2
- import type { CheckboxStyle, DetectedShape, FormFieldDefinition, FormFieldValue, PlaceFieldPayload } from '../annotation/engine/types.js';
2
+ import type { AddFormFieldPayload, 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[]>>;
@@ -23,10 +23,16 @@ export interface FormFieldsState {
23
23
  registerDetectedCheckboxes: (pageNumber: number, shapes: DetectedShape[], scale: number, pageHeight: number) => void;
24
24
  /** Add a user-placed form field (from the placement tool). Returns the created def. */
25
25
  addPlacedField: (payload: PlaceFieldPayload) => FormFieldDefinition;
26
- /** Update a placed field's rect, name, or flags. */
26
+ /** Add a form field programmatically. Public API counterpart to
27
+ * `addPlacedField` with full control over the field's properties.
28
+ * Returns the created definition. */
29
+ addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
30
+ /** Update a form field's rect, name, or flags. */
27
31
  updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>) => void;
28
- /** Remove a placed field and its value. */
32
+ /** Remove a form field and its value. */
29
33
  removePlacedField: (id: string) => void;
34
+ /** All form-field definitions, flattened across pages. */
35
+ getAllFields: () => FormFieldDefinition[];
30
36
  /** All user-placed field definitions (used by export). */
31
37
  getPlacedFields: () => FormFieldDefinition[];
32
38
  /** Look up a field definition by id (any origin). */
@@ -35,6 +41,19 @@ export interface FormFieldsState {
35
41
  selectedPlacedFieldId: Ref<string | null>;
36
42
  /** Select or deselect a placed field. Pass null to deselect. */
37
43
  selectPlacedField: (id: string | null) => void;
44
+ /** Opaque role id new placements are auto-tagged with. The host owns
45
+ * what roles exist; the editor only carries the active id forward to
46
+ * newly placed fields and looks up its color via roleColors. */
47
+ activeRoleId: Ref<string | null>;
48
+ /** Set or clear the active role for upcoming placements. */
49
+ setActiveRole: (id: string | null) => void;
50
+ /** Map from roleId → CSS color, supplied by the host via the KViewer
51
+ * `roleColors` prop. Reading this drives the chrome and placement
52
+ * preview color. The editor never mutates role definitions itself. */
53
+ roleColors: ShallowRef<Record<string, string>>;
54
+ /** Replace the host-provided color map. Called by KViewer when the
55
+ * prop changes. */
56
+ setRoleColors: (colors: Record<string, string>) => void;
38
57
  /** Apply checkbox-style hints (extracted from `/MK /CA` in the source PDF)
39
58
  * to existing parsed checkbox defs and store the map for any defs
40
59
  * parsed afterwards. Keyed by checkbox fieldName. */
@@ -12,6 +12,14 @@ 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
+ function setActiveRole(id) {
18
+ activeRoleId.value = id;
19
+ }
20
+ function setRoleColors(colors) {
21
+ roleColors.value = colors;
22
+ }
15
23
  function selectPlacedField(id) {
16
24
  selectedPlacedFieldId.value = id;
17
25
  }
@@ -231,6 +239,9 @@ export function provideFormFields() {
231
239
  required: false,
232
240
  origin: "placed"
233
241
  };
242
+ if (activeRoleId.value) {
243
+ def.roleId = activeRoleId.value;
244
+ }
234
245
  if (payload.fieldType === "radio") {
235
246
  def.buttonValue = nextRadioOptionValue(fieldName);
236
247
  }
@@ -256,6 +267,67 @@ export function provideFormFields() {
256
267
  }
257
268
  return def;
258
269
  }
270
+ function addFormField(payload) {
271
+ const id = `placed-${generateUUID()}`;
272
+ const fieldName = payload.fieldName ?? nextPlacedFieldName(payload.fieldType);
273
+ const def = {
274
+ id,
275
+ pageNumber: payload.pageNumber,
276
+ fieldType: payload.fieldType,
277
+ fieldName,
278
+ rect: [...payload.rect],
279
+ readOnly: payload.readOnly ?? false,
280
+ required: payload.required ?? false,
281
+ origin: "placed"
282
+ };
283
+ if (payload.roleId !== void 0) def.roleId = payload.roleId;
284
+ if (payload.defaultValue !== void 0) def.defaultValue = payload.defaultValue;
285
+ if (payload.fontSize !== void 0) def.fontSize = payload.fontSize;
286
+ if (payload.fontName !== void 0) def.fontName = payload.fontName;
287
+ if (payload.color !== void 0) def.color = payload.color;
288
+ if (payload.backgroundColor !== void 0) def.backgroundColor = payload.backgroundColor;
289
+ if (payload.textAlignment !== void 0) def.textAlignment = payload.textAlignment;
290
+ if (payload.fieldType === "radio") {
291
+ def.buttonValue = payload.buttonValue ?? nextRadioOptionValue(fieldName);
292
+ }
293
+ if (payload.fieldType === "checkbox") {
294
+ def.checkboxStyle = payload.checkboxStyle ?? "check";
295
+ }
296
+ if (payload.fieldType === "dropdown") {
297
+ def.combo = payload.combo ?? true;
298
+ def.options = payload.options ?? [{ exportValue: "Option 1", displayValue: "Option 1" }];
299
+ if (payload.multiSelect !== void 0) def.multiSelect = payload.multiSelect;
300
+ }
301
+ if (payload.fieldType === "text") {
302
+ if (payload.multiLine !== void 0) def.multiLine = payload.multiLine;
303
+ if (payload.password !== void 0) def.password = payload.password;
304
+ if (payload.comb !== void 0) def.comb = payload.comb;
305
+ if (payload.maxLen !== void 0) def.maxLen = payload.maxLen;
306
+ }
307
+ if (payload.fieldType === "signature") {
308
+ if (payload.promptText !== void 0) def.promptText = payload.promptText;
309
+ if (payload.lockAction !== void 0) def.lockAction = payload.lockAction;
310
+ if (payload.lockFieldNames !== void 0) def.lockFieldNames = payload.lockFieldNames;
311
+ }
312
+ const existingDefs = fieldDefinitions.value.get(payload.pageNumber) ?? [];
313
+ fieldDefinitions.value.set(payload.pageNumber, [...existingDefs, def]);
314
+ triggerRef(fieldDefinitions);
315
+ fieldValues.value.set(id, {
316
+ fieldId: id,
317
+ fieldName: def.fieldName,
318
+ fieldType: def.fieldType,
319
+ value: getDefaultValue(def)
320
+ });
321
+ triggerRef(fieldValues);
322
+ return def;
323
+ }
324
+ function getAllFields() {
325
+ const out = [];
326
+ for (const defs of fieldDefinitions.value.values()) {
327
+ for (const d of defs) out.push(d);
328
+ }
329
+ return out;
330
+ }
259
331
  function nextRadioOptionValue(groupName) {
260
332
  const used = /* @__PURE__ */ new Set();
261
333
  for (const defs of fieldDefinitions.value.values()) {
@@ -387,8 +459,10 @@ export function provideFormFields() {
387
459
  setFieldValueByName,
388
460
  registerDetectedCheckboxes,
389
461
  addPlacedField,
462
+ addFormField,
390
463
  updatePlacedField,
391
464
  removePlacedField,
465
+ getAllFields,
392
466
  getPlacedFields,
393
467
  getFieldById,
394
468
  selectedPlacedFieldId,
@@ -397,7 +471,11 @@ export function provideFormFields() {
397
471
  applyButtonCaptions,
398
472
  isFieldLocked,
399
473
  resetValues,
400
- reset
474
+ reset,
475
+ activeRoleId,
476
+ setActiveRole,
477
+ roleColors,
478
+ setRoleColors
401
479
  };
402
480
  provide(FORM_FIELDS_KEY, state);
403
481
  return state;
@@ -281,6 +281,7 @@ export function useInertiaPanzoom(options = {}) {
281
281
  }
282
282
  function onDown(e) {
283
283
  if (e.pointerType === "pen") return;
284
+ if (e.pointerType === "mouse") return;
284
285
  if (!gesturesEnabled) return;
285
286
  stopMomentum();
286
287
  setWillChangeActive(true);
@@ -1,4 +1,4 @@
1
1
  export type { ExportPdfOptions } from './annotation/pdf-export/export.js';
2
2
  export type { ViewerTabItem, AddTabOptions } from './components/ViewerTabs.vue.js';
3
- export type { SignatureData, SignatureHandlers } from './annotation/engine/types.js';
3
+ export type { AddFormFieldPayload, CheckboxStyle, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, SignatureData, SignatureHandlers, } from './annotation/engine/types.js';
4
4
  export type { ViewMode } from './composables/viewMode.js';
package/dist/types.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { type ExportPdfOptions, type SignatureData, type SignatureHandlers, type ViewMode } from '../dist/runtime/public-types.js'
1
+ export { type AddFormFieldPayload, type CheckboxStyle, type ExportPdfOptions, type FormFieldDefinition, type FormFieldOrigin, type FormFieldType, type FormFieldValue, type SignatureData, type SignatureHandlers, type ViewMode } from '../dist/runtime/public-types.js'
2
2
 
3
3
  export { default } from './module.mjs'
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.11",
3
+ "version": "0.1.0",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",