kviewer 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -1
- package/dist/runtime/annotation/engine/editor/editor.d.ts +17 -0
- package/dist/runtime/annotation/engine/editor/editor.js +44 -15
- package/dist/runtime/annotation/engine/tools/signature.js +1 -1
- package/dist/runtime/annotation/engine/tools/stamp.d.ts +8 -0
- package/dist/runtime/annotation/engine/tools/stamp.js +27 -2
- package/dist/runtime/annotation/engine/types.d.ts +25 -0
- package/dist/runtime/annotation/pdf-export/export.js +0 -3
- package/dist/runtime/annotation/utils/signature_image.d.ts +26 -0
- package/dist/runtime/annotation/utils/signature_image.js +20 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/FormFieldLayer.vue +6 -1
- package/dist/runtime/components/Viewer.d.vue.ts +56 -2
- package/dist/runtime/components/Viewer.vue +68 -2
- package/dist/runtime/components/Viewer.vue.d.ts +56 -2
- package/dist/runtime/components/ViewerTabs.d.vue.ts +29 -9
- package/dist/runtime/components/ViewerTabs.vue +10 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +29 -9
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +4 -2
- package/dist/runtime/components/form-fields/FormSignatureField.vue +15 -2
- package/dist/runtime/components/modals/FreeTextModal.vue +1 -1
- package/dist/runtime/components/modals/SignatureDrawModal.vue +170 -4
- package/dist/runtime/components/tools/ToolProperties.vue +98 -101
- package/dist/runtime/composables/useFormFields.d.ts +17 -1
- package/dist/runtime/composables/useFormFields.js +48 -9
- package/dist/runtime/composables/useViewerSearch.js +1 -1
- package/dist/runtime/composables/useViewerState.d.ts +1 -0
- package/dist/runtime/composables/useViewerState.js +12 -13
- package/dist/runtime/embed/bridge-client.d.ts +19 -3
- package/dist/runtime/embed/bridge-client.js +24 -0
- package/dist/runtime/embed/bridge-host.d.ts +10 -1
- package/dist/runtime/embed/bridge-host.js +20 -0
- package/dist/runtime/embed/protocol.d.ts +28 -1
- package/dist/runtime/i18n/messages.d.ts +16 -2
- package/dist/runtime/i18n/messages.js +16 -2
- package/dist/runtime/public-types.d.ts +1 -1
- package/dist/runtime/toolbar-tools.d.ts +1 -1
- package/dist/runtime/toolbar-tools.js +8 -0
- package/package.json +18 -18
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ViewMode } from '../composables/useViewerState.js';
|
|
2
|
-
import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
2
|
+
import type { AddFormFieldPayload, FormFieldDefinition, IAnnotationStore, PlacedFieldDefaults, SignatureFieldStatus, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
3
3
|
import { type ExportPdfOptions } from '../annotation/pdf-export/export.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
5
|
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
@@ -35,6 +35,21 @@ type __VLS_Props = {
|
|
|
35
35
|
* drives this — typically by binding it to a role picker in its own
|
|
36
36
|
* UI. Supports v-model via `v-model:active-role-id`. */
|
|
37
37
|
activeRoleId?: string | null;
|
|
38
|
+
/** When true, clicking an unsigned signature field opens kviewer's own
|
|
39
|
+
* signing flow (saved signature or draw modal). Off by default so hosts
|
|
40
|
+
* that own the signing process (e.g. an embedding e-sign app) don't get
|
|
41
|
+
* a competing canvas — fields then render as passive markers. */
|
|
42
|
+
clickToSign?: boolean;
|
|
43
|
+
/** When true, user-placed form fields show selection chrome and can be
|
|
44
|
+
* moved/resized/deleted in hand mode even outside form-edit mode.
|
|
45
|
+
* Fields parsed from the source PDF are unaffected. */
|
|
46
|
+
editablePlacedFields?: boolean;
|
|
47
|
+
/** Per-field-type presets merged into every field the user places via
|
|
48
|
+
* the placement tools (e.g. a signature `promptText` or `lockAction`).
|
|
49
|
+
* `fieldName` stays auto-generated — rename via the `field-placed`
|
|
50
|
+
* event and `updateFormField()`. Programmatic `addFormField()` calls
|
|
51
|
+
* are not affected. */
|
|
52
|
+
fieldDefaults?: PlacedFieldDefaults;
|
|
38
53
|
/** Execute embedded JavaScript inside the PDF — field-level AA scripts
|
|
39
54
|
* (Mouse Up, Calculate, Format, Validate, Keystroke), document-level
|
|
40
55
|
* open actions, and page-level scripts. Powered by pdf.js's QuickJS
|
|
@@ -98,6 +113,32 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
98
113
|
removeFormField: (id: string) => void;
|
|
99
114
|
/** All form-field definitions (parsed, detected, and placed), flat. */
|
|
100
115
|
getFormFields: () => FormFieldDefinition[];
|
|
116
|
+
/** Replace the per-field-type presets applied to tool placements.
|
|
117
|
+
* Programmatic counterpart to the `fieldDefaults` prop. */
|
|
118
|
+
setFieldDefaults: (defaults: PlacedFieldDefaults) => void;
|
|
119
|
+
/** Enable/disable kviewer's built-in click-to-sign flow at runtime.
|
|
120
|
+
* Programmatic counterpart to the `clickToSign` prop. */
|
|
121
|
+
setClickToSign: (enabled: boolean) => void;
|
|
122
|
+
/** Subscribe to tool placements (imperative mirror of the
|
|
123
|
+
* `field-placed` event, used by the embed bridge). Returns an
|
|
124
|
+
* unsubscribe function. */
|
|
125
|
+
onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => (() => void);
|
|
126
|
+
/** True once every signature field in the document is signed — via a
|
|
127
|
+
* value applied in this session or a digital signature already in the
|
|
128
|
+
* source PDF. Vacuously true when the document has no signature
|
|
129
|
+
* fields, so combine with `getSignatureFieldStatus().length` if you
|
|
130
|
+
* need "at least one". Use to gate e.g. envelope finalization. */
|
|
131
|
+
getSignedStatus: () => boolean;
|
|
132
|
+
/** Per-widget signed-state snapshot (id, fieldName, pageNumber,
|
|
133
|
+
* signed, roleId). */
|
|
134
|
+
getSignatureFieldStatus: () => SignatureFieldStatus[];
|
|
135
|
+
/** Subscribe to signed-status changes (imperative mirror of the
|
|
136
|
+
* `signed-status-changed` event, used by the embed bridge). Returns
|
|
137
|
+
* an unsubscribe function. */
|
|
138
|
+
onSignedStatusChanged: (cb: (payload: {
|
|
139
|
+
allSigned: boolean;
|
|
140
|
+
fields: SignatureFieldStatus[];
|
|
141
|
+
}) => void) => (() => void);
|
|
101
142
|
/** Reactive form-edit-mode flag — read with `.value`. */
|
|
102
143
|
formEditMode: import("vue").Ref<boolean, boolean>;
|
|
103
144
|
/** Programmatically enter or leave form-edit mode. */
|
|
@@ -114,20 +155,32 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
114
155
|
resetViewedPages: () => void;
|
|
115
156
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
116
157
|
"all-pages-read": () => any;
|
|
158
|
+
"field-placed": (field: FormFieldDefinition) => any;
|
|
117
159
|
"update:formEditMode": (value: boolean) => any;
|
|
118
160
|
"update:activeRoleId": (value: string | null) => any;
|
|
119
161
|
"update:viewedPages": (pages: number[]) => any;
|
|
162
|
+
"signed-status-changed": (payload: {
|
|
163
|
+
allSigned: boolean;
|
|
164
|
+
fields: SignatureFieldStatus[];
|
|
165
|
+
}) => any;
|
|
120
166
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
121
167
|
"onAll-pages-read"?: (() => any) | undefined;
|
|
168
|
+
"onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
|
|
122
169
|
"onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
|
|
123
170
|
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
124
171
|
"onUpdate:viewedPages"?: ((pages: number[]) => any) | undefined;
|
|
172
|
+
"onSigned-status-changed"?: ((payload: {
|
|
173
|
+
allSigned: boolean;
|
|
174
|
+
fields: SignatureFieldStatus[];
|
|
175
|
+
}) => any) | undefined;
|
|
125
176
|
}>, {
|
|
126
177
|
userName: string;
|
|
127
178
|
freehandGroupingDelay: number;
|
|
128
179
|
zoom: number;
|
|
180
|
+
clickToSign: boolean;
|
|
129
181
|
readonly: boolean;
|
|
130
182
|
active: boolean;
|
|
183
|
+
locale: KviewerLocale;
|
|
131
184
|
menuItems: ViewerMenuItem[];
|
|
132
185
|
tools: ViewerToolEntry[];
|
|
133
186
|
scripting: boolean;
|
|
@@ -138,7 +191,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
138
191
|
formEditMode: boolean;
|
|
139
192
|
roleColors: Record<string, string>;
|
|
140
193
|
activeRoleId: string | null;
|
|
141
|
-
|
|
194
|
+
editablePlacedFields: boolean;
|
|
195
|
+
fieldDefaults: PlacedFieldDefaults;
|
|
142
196
|
messages: KviewerMessageOverrides;
|
|
143
197
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
144
198
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentPublicInstance } from 'vue';
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
|
-
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
3
|
+
import type { FormFieldDefinition, PlacedFieldDefaults, SignatureFieldStatus, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
5
|
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
6
6
|
export interface ViewerTabItem {
|
|
@@ -65,6 +65,13 @@ type __VLS_Props = {
|
|
|
65
65
|
menuItems?: ViewerMenuItem[];
|
|
66
66
|
/** Forwarded to every Viewer instance — see KViewer's `tools`. */
|
|
67
67
|
tools?: ViewerToolEntry[];
|
|
68
|
+
/** Forwarded to every Viewer instance — see KViewer's `clickToSign`. */
|
|
69
|
+
clickToSign?: boolean;
|
|
70
|
+
/** Forwarded to every Viewer instance — see KViewer's
|
|
71
|
+
* `editablePlacedFields`. */
|
|
72
|
+
editablePlacedFields?: boolean;
|
|
73
|
+
/** Forwarded to every Viewer instance — see KViewer's `fieldDefaults`. */
|
|
74
|
+
fieldDefaults?: PlacedFieldDefaults;
|
|
68
75
|
};
|
|
69
76
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
70
77
|
id?: string;
|
|
@@ -72,7 +79,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
|
72
79
|
declare function removeTab(id: string): boolean;
|
|
73
80
|
declare function activateTab(id: string): void;
|
|
74
81
|
declare function getViewer(id: string): ComponentPublicInstance | null;
|
|
75
|
-
declare var __VLS_7: {}, __VLS_21: {},
|
|
82
|
+
declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
|
|
76
83
|
tab: {
|
|
77
84
|
id: string;
|
|
78
85
|
label: string;
|
|
@@ -172,7 +179,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
172
179
|
shapeDetection?: boolean | undefined;
|
|
173
180
|
scripting?: boolean | undefined;
|
|
174
181
|
};
|
|
175
|
-
},
|
|
182
|
+
}, __VLS_37: {
|
|
176
183
|
tab: {
|
|
177
184
|
id: string;
|
|
178
185
|
label: string;
|
|
@@ -272,21 +279,21 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
272
279
|
shapeDetection?: boolean | undefined;
|
|
273
280
|
scripting?: boolean | undefined;
|
|
274
281
|
};
|
|
275
|
-
},
|
|
282
|
+
}, __VLS_41: string, __VLS_42: {
|
|
276
283
|
state: import("../composables/useViewerState.js").ViewerState;
|
|
277
|
-
},
|
|
284
|
+
}, __VLS_44: {};
|
|
278
285
|
type __VLS_Slots = {} & {
|
|
279
|
-
[K in NonNullable<typeof
|
|
286
|
+
[K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
|
|
280
287
|
} & {
|
|
281
288
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
282
289
|
} & {
|
|
283
290
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
284
291
|
} & {
|
|
285
|
-
header?: (props: typeof
|
|
292
|
+
header?: (props: typeof __VLS_34) => any;
|
|
286
293
|
} & {
|
|
287
|
-
footer?: (props: typeof
|
|
294
|
+
footer?: (props: typeof __VLS_37) => any;
|
|
288
295
|
} & {
|
|
289
|
-
empty?: (props: typeof
|
|
296
|
+
empty?: (props: typeof __VLS_44) => any;
|
|
290
297
|
};
|
|
291
298
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
292
299
|
addTab: typeof addTab;
|
|
@@ -394,13 +401,23 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
394
401
|
scripting?: boolean | undefined;
|
|
395
402
|
}[];
|
|
396
403
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
404
|
+
"field-placed": (field: FormFieldDefinition) => any;
|
|
397
405
|
"update:activeRoleId": (value: string | null) => any;
|
|
406
|
+
"signed-status-changed": (payload: {
|
|
407
|
+
allSigned: boolean;
|
|
408
|
+
fields: SignatureFieldStatus[];
|
|
409
|
+
}) => any;
|
|
398
410
|
"update:activeTab": (id: string) => any;
|
|
399
411
|
"tab-added": (tab: ViewerTabItem) => any;
|
|
400
412
|
"tab-close": (id: string) => any;
|
|
401
413
|
"tab-removed": (id: string) => any;
|
|
402
414
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
415
|
+
"onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
|
|
403
416
|
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
417
|
+
"onSigned-status-changed"?: ((payload: {
|
|
418
|
+
allSigned: boolean;
|
|
419
|
+
fields: SignatureFieldStatus[];
|
|
420
|
+
}) => any) | undefined;
|
|
404
421
|
"onUpdate:activeTab"?: ((id: string) => any) | undefined;
|
|
405
422
|
"onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
|
|
406
423
|
"onTab-close"?: ((id: string) => any) | undefined;
|
|
@@ -408,6 +425,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
408
425
|
}>, {
|
|
409
426
|
userName: string;
|
|
410
427
|
zoom: number;
|
|
428
|
+
clickToSign: boolean;
|
|
411
429
|
menuItems: ViewerMenuItem[];
|
|
412
430
|
tools: ViewerToolEntry[];
|
|
413
431
|
scripting: boolean;
|
|
@@ -416,6 +434,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
416
434
|
viewMode: ViewMode;
|
|
417
435
|
roleColors: Record<string, string>;
|
|
418
436
|
activeRoleId: string | null;
|
|
437
|
+
editablePlacedFields: boolean;
|
|
438
|
+
fieldDefaults: PlacedFieldDefaults;
|
|
419
439
|
defaultActiveTab: string;
|
|
420
440
|
minTabs: number;
|
|
421
441
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -64,7 +64,12 @@
|
|
|
64
64
|
:tools="tools"
|
|
65
65
|
:role-colors="roleColors"
|
|
66
66
|
:active-role-id="activeRoleId"
|
|
67
|
+
:click-to-sign="clickToSign"
|
|
68
|
+
:editable-placed-fields="editablePlacedFields"
|
|
69
|
+
:field-defaults="fieldDefaults"
|
|
67
70
|
@update:active-role-id="(v) => emit('update:activeRoleId', v)"
|
|
71
|
+
@field-placed="(f) => emit('field-placed', f)"
|
|
72
|
+
@signed-status-changed="(p) => emit('signed-status-changed', p)"
|
|
68
73
|
>
|
|
69
74
|
<template v-if="$slots.header" #header>
|
|
70
75
|
<slot name="header" :tab="activeTab" />
|
|
@@ -107,13 +112,16 @@ const props = defineProps({
|
|
|
107
112
|
activeRoleId: { type: [String, null], required: false, default: void 0 },
|
|
108
113
|
scripting: { type: Boolean, required: false, default: false },
|
|
109
114
|
menuItems: { type: Array, required: false, default: void 0 },
|
|
110
|
-
tools: { type: Array, required: false, default: void 0 }
|
|
115
|
+
tools: { type: Array, required: false, default: void 0 },
|
|
116
|
+
clickToSign: { type: Boolean, required: false, default: false },
|
|
117
|
+
editablePlacedFields: { type: Boolean, required: false, default: false },
|
|
118
|
+
fieldDefaults: { type: Object, required: false, default: void 0 }
|
|
111
119
|
});
|
|
112
120
|
const slots = useSlots();
|
|
113
121
|
const toolSlotNames = computed(
|
|
114
122
|
() => Object.keys(slots).filter((name) => name.startsWith("tool-"))
|
|
115
123
|
);
|
|
116
|
-
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId"]);
|
|
124
|
+
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId", "field-placed", "signed-status-changed"]);
|
|
117
125
|
let idCounter = 0;
|
|
118
126
|
function generateId() {
|
|
119
127
|
return `tab-${Date.now()}-${++idCounter}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentPublicInstance } from 'vue';
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
|
-
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
3
|
+
import type { FormFieldDefinition, PlacedFieldDefaults, SignatureFieldStatus, StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
4
|
import type { ViewerMenuItem } from '../menu-items.js';
|
|
5
5
|
import type { ViewerToolEntry } from '../toolbar-tools.js';
|
|
6
6
|
export interface ViewerTabItem {
|
|
@@ -65,6 +65,13 @@ type __VLS_Props = {
|
|
|
65
65
|
menuItems?: ViewerMenuItem[];
|
|
66
66
|
/** Forwarded to every Viewer instance — see KViewer's `tools`. */
|
|
67
67
|
tools?: ViewerToolEntry[];
|
|
68
|
+
/** Forwarded to every Viewer instance — see KViewer's `clickToSign`. */
|
|
69
|
+
clickToSign?: boolean;
|
|
70
|
+
/** Forwarded to every Viewer instance — see KViewer's
|
|
71
|
+
* `editablePlacedFields`. */
|
|
72
|
+
editablePlacedFields?: boolean;
|
|
73
|
+
/** Forwarded to every Viewer instance — see KViewer's `fieldDefaults`. */
|
|
74
|
+
fieldDefaults?: PlacedFieldDefaults;
|
|
68
75
|
};
|
|
69
76
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
70
77
|
id?: string;
|
|
@@ -72,7 +79,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
|
72
79
|
declare function removeTab(id: string): boolean;
|
|
73
80
|
declare function activateTab(id: string): void;
|
|
74
81
|
declare function getViewer(id: string): ComponentPublicInstance | null;
|
|
75
|
-
declare var __VLS_7: {}, __VLS_21: {},
|
|
82
|
+
declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
|
|
76
83
|
tab: {
|
|
77
84
|
id: string;
|
|
78
85
|
label: string;
|
|
@@ -172,7 +179,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
172
179
|
shapeDetection?: boolean | undefined;
|
|
173
180
|
scripting?: boolean | undefined;
|
|
174
181
|
};
|
|
175
|
-
},
|
|
182
|
+
}, __VLS_37: {
|
|
176
183
|
tab: {
|
|
177
184
|
id: string;
|
|
178
185
|
label: string;
|
|
@@ -272,21 +279,21 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
272
279
|
shapeDetection?: boolean | undefined;
|
|
273
280
|
scripting?: boolean | undefined;
|
|
274
281
|
};
|
|
275
|
-
},
|
|
282
|
+
}, __VLS_41: string, __VLS_42: {
|
|
276
283
|
state: import("../composables/useViewerState.js").ViewerState;
|
|
277
|
-
},
|
|
284
|
+
}, __VLS_44: {};
|
|
278
285
|
type __VLS_Slots = {} & {
|
|
279
|
-
[K in NonNullable<typeof
|
|
286
|
+
[K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
|
|
280
287
|
} & {
|
|
281
288
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
282
289
|
} & {
|
|
283
290
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
284
291
|
} & {
|
|
285
|
-
header?: (props: typeof
|
|
292
|
+
header?: (props: typeof __VLS_34) => any;
|
|
286
293
|
} & {
|
|
287
|
-
footer?: (props: typeof
|
|
294
|
+
footer?: (props: typeof __VLS_37) => any;
|
|
288
295
|
} & {
|
|
289
|
-
empty?: (props: typeof
|
|
296
|
+
empty?: (props: typeof __VLS_44) => any;
|
|
290
297
|
};
|
|
291
298
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
292
299
|
addTab: typeof addTab;
|
|
@@ -394,13 +401,23 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
394
401
|
scripting?: boolean | undefined;
|
|
395
402
|
}[];
|
|
396
403
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
404
|
+
"field-placed": (field: FormFieldDefinition) => any;
|
|
397
405
|
"update:activeRoleId": (value: string | null) => any;
|
|
406
|
+
"signed-status-changed": (payload: {
|
|
407
|
+
allSigned: boolean;
|
|
408
|
+
fields: SignatureFieldStatus[];
|
|
409
|
+
}) => any;
|
|
398
410
|
"update:activeTab": (id: string) => any;
|
|
399
411
|
"tab-added": (tab: ViewerTabItem) => any;
|
|
400
412
|
"tab-close": (id: string) => any;
|
|
401
413
|
"tab-removed": (id: string) => any;
|
|
402
414
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
415
|
+
"onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
|
|
403
416
|
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
417
|
+
"onSigned-status-changed"?: ((payload: {
|
|
418
|
+
allSigned: boolean;
|
|
419
|
+
fields: SignatureFieldStatus[];
|
|
420
|
+
}) => any) | undefined;
|
|
404
421
|
"onUpdate:activeTab"?: ((id: string) => any) | undefined;
|
|
405
422
|
"onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
|
|
406
423
|
"onTab-close"?: ((id: string) => any) | undefined;
|
|
@@ -408,6 +425,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
408
425
|
}>, {
|
|
409
426
|
userName: string;
|
|
410
427
|
zoom: number;
|
|
428
|
+
clickToSign: boolean;
|
|
411
429
|
menuItems: ViewerMenuItem[];
|
|
412
430
|
tools: ViewerToolEntry[];
|
|
413
431
|
scripting: boolean;
|
|
@@ -416,6 +434,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
416
434
|
viewMode: ViewMode;
|
|
417
435
|
roleColors: Record<string, string>;
|
|
418
436
|
activeRoleId: string | null;
|
|
437
|
+
editablePlacedFields: boolean;
|
|
438
|
+
fieldDefaults: PlacedFieldDefaults;
|
|
419
439
|
defaultActiveTab: string;
|
|
420
440
|
minTabs: number;
|
|
421
441
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
>
|
|
12
12
|
<div
|
|
13
13
|
class="kviewer-form-field__body"
|
|
14
|
-
:class="{ 'kviewer-form-field__body--locked':
|
|
14
|
+
:class="{ 'kviewer-form-field__body--locked': showChrome }"
|
|
15
15
|
>
|
|
16
16
|
<FormTextField v-if="props.field.fieldType === 'text'" :field="props.field" />
|
|
17
17
|
<FormCheckbox v-else-if="props.field.fieldType === 'checkbox'" :field="props.field" />
|
|
@@ -55,7 +55,9 @@ const state = useViewerState();
|
|
|
55
55
|
const formFields = useFormFields();
|
|
56
56
|
const isPlaced = computed(() => props.field.origin === "placed");
|
|
57
57
|
const isFormEditMode = computed(() => state.formEditMode.value);
|
|
58
|
-
const showChrome = computed(
|
|
58
|
+
const showChrome = computed(
|
|
59
|
+
() => isFormEditMode.value || formFields.editablePlacedFields.value && isPlaced.value
|
|
60
|
+
);
|
|
59
61
|
const chromeColor = computed(() => {
|
|
60
62
|
const id = props.field.roleId;
|
|
61
63
|
if (!id) return "#1677ff";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="kviewer-form-signature"
|
|
4
|
-
:class="{
|
|
4
|
+
:class="{
|
|
5
|
+
'kviewer-form-signature--filled': hasSigned,
|
|
6
|
+
'kviewer-form-signature--static': !clickToSignEnabled
|
|
7
|
+
}"
|
|
5
8
|
@click="onClickSign"
|
|
6
9
|
>
|
|
7
10
|
<img
|
|
@@ -10,13 +13,21 @@
|
|
|
10
13
|
class="kviewer-form-signature__preview"
|
|
11
14
|
:alt="t('signature')"
|
|
12
15
|
>
|
|
13
|
-
<span v-else class="kviewer-form-signature__placeholder">
|
|
16
|
+
<span v-else-if="clickToSignEnabled" class="kviewer-form-signature__placeholder">
|
|
14
17
|
{{ props.field.promptText || t("clickToSign") }}
|
|
15
18
|
</span>
|
|
19
|
+
<!-- click-to-sign off: passive marker — the host owns the signing flow.
|
|
20
|
+
promptText is a call to action ("Sign here"), so it only applies to
|
|
21
|
+
the click-to-sign placeholder; the marker stays a neutral label. -->
|
|
22
|
+
<span v-else class="kviewer-form-signature__placeholder kviewer-form-signature__marker">
|
|
23
|
+
<UIcon name="i-lucide-signature" class="kviewer-form-signature__marker-icon" />
|
|
24
|
+
{{ t("signature") }}
|
|
25
|
+
</span>
|
|
16
26
|
</div>
|
|
17
27
|
|
|
18
28
|
<ClientOnly>
|
|
19
29
|
<SignatureDrawModal
|
|
30
|
+
v-if="clickToSignEnabled"
|
|
20
31
|
v-model:open="drawModalOpen"
|
|
21
32
|
@submit="onDrawSubmit"
|
|
22
33
|
@cancel="drawModalOpen = false"
|
|
@@ -43,7 +54,9 @@ const signatureUrl = computed(() => {
|
|
|
43
54
|
return fv.value;
|
|
44
55
|
});
|
|
45
56
|
const hasSigned = computed(() => !!signatureUrl.value);
|
|
57
|
+
const clickToSignEnabled = computed(() => state.clickToSign.value);
|
|
46
58
|
async function onClickSign() {
|
|
59
|
+
if (!clickToSignEnabled.value) return;
|
|
47
60
|
if (props.field.readOnly || state.readonly.value) return;
|
|
48
61
|
if (formFields.isFieldLocked(props.field.id)) return;
|
|
49
62
|
if (state.signatures.value.length > 0) {
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<UModal v-model:open="isOpen" :title="t('
|
|
2
|
+
<UModal v-model:open="isOpen" :title="t('addSignature')" :ui="{ content: 'sm:max-w-2xl' }">
|
|
3
3
|
<template #body>
|
|
4
4
|
<div class="flex flex-col gap-3">
|
|
5
|
+
<div class="flex gap-1" role="tablist">
|
|
6
|
+
<button
|
|
7
|
+
v-for="tab in tabs"
|
|
8
|
+
:key="tab"
|
|
9
|
+
role="tab"
|
|
10
|
+
:aria-selected="mode === tab"
|
|
11
|
+
class="px-3 py-1.5 text-sm rounded cursor-pointer"
|
|
12
|
+
:class="
|
|
13
|
+
mode === tab ? 'bg-elevated text-highlighted font-medium' : 'text-muted hover:text-default'
|
|
14
|
+
"
|
|
15
|
+
@click="mode = tab"
|
|
16
|
+
>
|
|
17
|
+
{{ tab === "draw" ? t("drawSignature") : t("uploadSignature") }}
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
5
21
|
<div
|
|
22
|
+
v-show="mode === 'draw'"
|
|
6
23
|
ref="canvasWrapEl"
|
|
7
24
|
class="border border-default rounded bg-white relative w-full"
|
|
8
25
|
:style="{ aspectRatio: `${canvasAspect}` }"
|
|
@@ -16,7 +33,8 @@
|
|
|
16
33
|
@pointerleave="onPointerUp"
|
|
17
34
|
/>
|
|
18
35
|
</div>
|
|
19
|
-
|
|
36
|
+
|
|
37
|
+
<div v-if="mode === 'draw'" class="flex items-center gap-3">
|
|
20
38
|
<div class="flex gap-1">
|
|
21
39
|
<button
|
|
22
40
|
v-for="c in signatureColors"
|
|
@@ -33,6 +51,50 @@
|
|
|
33
51
|
{{ t("clear") }}
|
|
34
52
|
</UButton>
|
|
35
53
|
</div>
|
|
54
|
+
|
|
55
|
+
<template v-if="mode === 'upload'">
|
|
56
|
+
<!-- The checkerboard makes transparency visible, so users can
|
|
57
|
+
tell whether background removal actually worked before
|
|
58
|
+
placing the signature on the page. -->
|
|
59
|
+
<div
|
|
60
|
+
class="border border-dashed border-default rounded relative w-full flex items-center justify-center bg-checkerboard"
|
|
61
|
+
:style="{ aspectRatio: `${canvasAspect}` }"
|
|
62
|
+
@dragover.prevent
|
|
63
|
+
@drop.prevent="onDrop"
|
|
64
|
+
>
|
|
65
|
+
<img
|
|
66
|
+
v-if="uploadedUrl"
|
|
67
|
+
:src="uploadedUrl"
|
|
68
|
+
class="max-w-full max-h-full object-contain"
|
|
69
|
+
:alt="t('uploadSignature')"
|
|
70
|
+
>
|
|
71
|
+
<button
|
|
72
|
+
v-else
|
|
73
|
+
class="w-full h-full flex flex-col items-center justify-center gap-1 text-muted text-sm cursor-pointer"
|
|
74
|
+
@click="fileInputEl?.click()"
|
|
75
|
+
>
|
|
76
|
+
<span>{{ t("dropSignatureImage") }}</span>
|
|
77
|
+
<span class="text-xs">{{ acceptHint }}</span>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<input
|
|
82
|
+
ref="fileInputEl"
|
|
83
|
+
type="file"
|
|
84
|
+
:accept="accept"
|
|
85
|
+
class="hidden"
|
|
86
|
+
@change="onFileChange"
|
|
87
|
+
>
|
|
88
|
+
|
|
89
|
+
<p v-if="uploadError" class="text-sm text-error">{{ uploadError }}</p>
|
|
90
|
+
|
|
91
|
+
<div class="flex items-center gap-3">
|
|
92
|
+
<UButton variant="ghost" color="neutral" size="xs" @click="fileInputEl?.click()">
|
|
93
|
+
{{ t("chooseImage") }}
|
|
94
|
+
</UButton>
|
|
95
|
+
<UCheckbox v-model="removeBackground" size="sm" :label="t('removeWhiteBackground')" />
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
36
98
|
</div>
|
|
37
99
|
</template>
|
|
38
100
|
<template #footer>
|
|
@@ -40,7 +102,7 @@
|
|
|
40
102
|
<UButton variant="ghost" color="neutral" @click="cancel">
|
|
41
103
|
{{ t("cancel") }}
|
|
42
104
|
</UButton>
|
|
43
|
-
<UButton :disabled="!
|
|
105
|
+
<UButton :disabled="!hasContent" @click="submit">
|
|
44
106
|
{{ t("save") }}
|
|
45
107
|
</UButton>
|
|
46
108
|
</div>
|
|
@@ -49,8 +111,9 @@
|
|
|
49
111
|
</template>
|
|
50
112
|
|
|
51
113
|
<script setup>
|
|
52
|
-
import { ref, watch, nextTick, onBeforeUnmount } from "vue";
|
|
114
|
+
import { ref, computed, watch, nextTick, onBeforeUnmount } from "vue";
|
|
53
115
|
import { defaultOptions } from "../../annotation/engine/config";
|
|
116
|
+
import { fitWithin, knockOutWhite } from "../../annotation/utils/signature_image";
|
|
54
117
|
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
55
118
|
const { t } = useKviewerI18n();
|
|
56
119
|
const props = defineProps({
|
|
@@ -64,6 +127,8 @@ watch(
|
|
|
64
127
|
isOpen.value = v;
|
|
65
128
|
if (v) {
|
|
66
129
|
hasDrawn.value = false;
|
|
130
|
+
mode.value = "draw";
|
|
131
|
+
resetUpload();
|
|
67
132
|
await nextTick();
|
|
68
133
|
syncCanvasSize();
|
|
69
134
|
clearCanvas();
|
|
@@ -71,6 +136,8 @@ watch(
|
|
|
71
136
|
}
|
|
72
137
|
);
|
|
73
138
|
watch(isOpen, (v) => emit("update:open", v));
|
|
139
|
+
const tabs = ["draw", "upload"];
|
|
140
|
+
const mode = ref("draw");
|
|
74
141
|
const canvasAspect = `${defaultOptions.signature.WIDTH} / ${defaultOptions.signature.HEIGHT}`;
|
|
75
142
|
const signatureColors = defaultOptions.signature.COLORS;
|
|
76
143
|
const canvasRef = ref(null);
|
|
@@ -165,7 +232,102 @@ function clearCanvas() {
|
|
|
165
232
|
ctx.restore();
|
|
166
233
|
hasDrawn.value = false;
|
|
167
234
|
}
|
|
235
|
+
const accept = defaultOptions.signature.ACCEPT;
|
|
236
|
+
const maxSize = defaultOptions.signature.MAX_SIZE;
|
|
237
|
+
const fileInputEl = ref(null);
|
|
238
|
+
const uploadedUrl = ref(null);
|
|
239
|
+
const uploadError = ref(null);
|
|
240
|
+
const removeBackground = ref(true);
|
|
241
|
+
let sourceImage = null;
|
|
242
|
+
const acceptHint = accept.replace(/\./g, "").toUpperCase().replace(/,/g, ", ");
|
|
243
|
+
const maxSizeMb = Math.round(maxSize / (1024 * 1024));
|
|
244
|
+
const hasContent = computed(
|
|
245
|
+
() => mode.value === "draw" ? hasDrawn.value : !!uploadedUrl.value
|
|
246
|
+
);
|
|
247
|
+
function resetUpload() {
|
|
248
|
+
uploadedUrl.value = null;
|
|
249
|
+
uploadError.value = null;
|
|
250
|
+
sourceImage = null;
|
|
251
|
+
if (fileInputEl.value) fileInputEl.value.value = "";
|
|
252
|
+
}
|
|
253
|
+
function onDrop(e) {
|
|
254
|
+
const file = e.dataTransfer?.files?.[0];
|
|
255
|
+
if (file) void handleFile(file);
|
|
256
|
+
}
|
|
257
|
+
function onFileChange(e) {
|
|
258
|
+
const file = e.target.files?.[0];
|
|
259
|
+
if (file) void handleFile(file);
|
|
260
|
+
}
|
|
261
|
+
async function handleFile(file) {
|
|
262
|
+
uploadError.value = null;
|
|
263
|
+
const extension = `.${file.name.split(".").pop()?.toLowerCase() ?? ""}`;
|
|
264
|
+
if (!accept.split(",").includes(extension)) {
|
|
265
|
+
uploadError.value = t("unsupportedImageType", { accept: acceptHint });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (file.size > maxSize) {
|
|
269
|
+
uploadError.value = t("imageTooLarge", { size: maxSizeMb });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
sourceImage = await loadImage(file);
|
|
274
|
+
renderUpload();
|
|
275
|
+
} catch {
|
|
276
|
+
sourceImage = null;
|
|
277
|
+
uploadError.value = t("imageReadFailed");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function loadImage(file) {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const reader = new FileReader();
|
|
283
|
+
reader.onerror = () => reject(new Error("read failed"));
|
|
284
|
+
reader.onload = () => {
|
|
285
|
+
const img = new Image();
|
|
286
|
+
img.onload = () => resolve(img);
|
|
287
|
+
img.onerror = () => reject(new Error("decode failed"));
|
|
288
|
+
img.src = reader.result;
|
|
289
|
+
};
|
|
290
|
+
reader.readAsDataURL(file);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const MAX_OUTPUT_SCALE = 3;
|
|
294
|
+
function renderUpload() {
|
|
295
|
+
const img = sourceImage;
|
|
296
|
+
if (!img) return;
|
|
297
|
+
const { width, height } = fitWithin(
|
|
298
|
+
img.naturalWidth,
|
|
299
|
+
img.naturalHeight,
|
|
300
|
+
defaultOptions.signature.WIDTH * MAX_OUTPUT_SCALE,
|
|
301
|
+
defaultOptions.signature.HEIGHT * MAX_OUTPUT_SCALE
|
|
302
|
+
);
|
|
303
|
+
const canvas = document.createElement("canvas");
|
|
304
|
+
canvas.width = width;
|
|
305
|
+
canvas.height = height;
|
|
306
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
307
|
+
if (!ctx) {
|
|
308
|
+
uploadError.value = t("imageReadFailed");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
312
|
+
if (removeBackground.value) {
|
|
313
|
+
try {
|
|
314
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
315
|
+
knockOutWhite(imageData.data);
|
|
316
|
+
ctx.putImageData(imageData, 0, 0);
|
|
317
|
+
} catch {
|
|
318
|
+
uploadError.value = t("backgroundRemovalFailed");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
uploadedUrl.value = canvas.toDataURL("image/png");
|
|
322
|
+
}
|
|
323
|
+
watch(removeBackground, () => {
|
|
324
|
+
if (sourceImage) renderUpload();
|
|
325
|
+
});
|
|
168
326
|
function submit() {
|
|
327
|
+
if (mode.value === "upload") {
|
|
328
|
+
if (uploadedUrl.value) emit("submit", uploadedUrl.value);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
169
331
|
const canvas = canvasRef.value;
|
|
170
332
|
if (!canvas) return;
|
|
171
333
|
const imageUrl = canvas.toDataURL("image/png");
|
|
@@ -175,3 +337,7 @@ function cancel() {
|
|
|
175
337
|
emit("cancel");
|
|
176
338
|
}
|
|
177
339
|
</script>
|
|
340
|
+
|
|
341
|
+
<style scoped>
|
|
342
|
+
.bg-checkerboard{background-color:#fff;background-image:linear-gradient(45deg,#e5e5e5 25%,transparent 0),linear-gradient(-45deg,#e5e5e5 25%,transparent 0),linear-gradient(45deg,transparent 75%,#e5e5e5 0),linear-gradient(-45deg,transparent 75%,#e5e5e5 0);background-position:0 0,0 8px,8px -8px,-8px 0;background-size:16px 16px}
|
|
343
|
+
</style>
|