kviewer 0.0.11 → 0.1.1
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 +9 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +22 -2
- package/dist/runtime/annotation/engine/painter.d.ts +8 -1
- package/dist/runtime/annotation/engine/painter.js +11 -6
- package/dist/runtime/annotation/engine/tools/form-field.d.ts +6 -0
- package/dist/runtime/annotation/engine/tools/form-field.js +3 -1
- package/dist/runtime/annotation/engine/types.d.ts +64 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +102 -52
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/PdfPage.vue +6 -1
- package/dist/runtime/components/Viewer.d.vue.ts +41 -1
- package/dist/runtime/components/Viewer.vue +84 -3
- package/dist/runtime/components/Viewer.vue.d.ts +41 -1
- package/dist/runtime/components/ViewerBar.d.vue.ts +7 -1
- package/dist/runtime/components/ViewerBar.vue +36 -0
- package/dist/runtime/components/ViewerBar.vue.d.ts +7 -1
- package/dist/runtime/components/ViewerTabs.d.vue.ts +29 -6
- package/dist/runtime/components/ViewerTabs.vue +11 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +29 -6
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +10 -2
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +1 -1
- package/dist/runtime/composables/useAnnotationEngine.d.ts +1 -0
- package/dist/runtime/composables/useAnnotationEngine.js +2 -1
- package/dist/runtime/composables/useFormFields.d.ts +40 -3
- package/dist/runtime/composables/useFormFields.js +101 -1
- package/dist/runtime/composables/useInertiaPanzoom.js +1 -0
- package/dist/runtime/composables/usePageProxyCache.d.ts +4 -0
- package/dist/runtime/composables/usePageProxyCache.js +4 -1
- package/dist/runtime/composables/useScriptingBridge.d.ts +29 -0
- package/dist/runtime/composables/useScriptingBridge.js +74 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +65 -0
- package/dist/runtime/composables/useScriptingManager.js +123 -0
- package/dist/runtime/embed/bridge-client.d.ts +58 -0
- package/dist/runtime/embed/bridge-client.js +143 -0
- package/dist/runtime/embed/bridge-host.d.ts +59 -0
- package/dist/runtime/embed/bridge-host.js +136 -0
- package/dist/runtime/embed/protocol.d.ts +105 -0
- package/dist/runtime/embed/protocol.js +13 -0
- package/dist/runtime/menu-items.d.ts +34 -0
- package/dist/runtime/menu-items.js +0 -0
- package/dist/runtime/public-types.d.ts +8 -1
- package/dist/runtime/public-types.js +3 -0
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div ref="viewerRoot" class="flex flex-col h-full">
|
|
3
3
|
<!-- Header slot: defaults to ViewerBar -->
|
|
4
4
|
<slot name="header">
|
|
5
|
-
<ViewerBar />
|
|
5
|
+
<ViewerBar :menu-items="props.menuItems" />
|
|
6
6
|
</slot>
|
|
7
7
|
|
|
8
8
|
<div class="flex flex-1 min-h-0">
|
|
@@ -209,6 +209,8 @@ import { provideFormFields } from "../composables/useFormFields";
|
|
|
209
209
|
import { extractCheckboxStyles, extractButtonCaptions } from "../annotation/parsers/extractCheckboxStyles";
|
|
210
210
|
import { isIPad } from "../annotation/engine/input-device";
|
|
211
211
|
import { useInertiaPanzoom } from "../composables/useInertiaPanzoom";
|
|
212
|
+
import { createScriptingManager } from "../composables/useScriptingManager";
|
|
213
|
+
import { createScriptingBridge } from "../composables/useScriptingBridge";
|
|
212
214
|
const props = defineProps({
|
|
213
215
|
source: { type: null, required: true },
|
|
214
216
|
textLayer: { type: Boolean, required: false },
|
|
@@ -221,9 +223,13 @@ const props = defineProps({
|
|
|
221
223
|
shapeDetection: { type: Boolean, required: false, default: false },
|
|
222
224
|
active: { type: Boolean, required: false, default: true },
|
|
223
225
|
freehandGroupingDelay: { type: Number, required: false, default: 1e3 },
|
|
224
|
-
formEditMode: { type: Boolean, required: false, default: void 0 }
|
|
226
|
+
formEditMode: { type: Boolean, required: false, default: void 0 },
|
|
227
|
+
roleColors: { type: Object, required: false, default: void 0 },
|
|
228
|
+
activeRoleId: { type: [String, null], required: false, default: void 0 },
|
|
229
|
+
scripting: { type: Boolean, required: false, default: false },
|
|
230
|
+
menuItems: { type: Array, required: false, default: void 0 }
|
|
225
231
|
});
|
|
226
|
-
const emit = defineEmits(["update:formEditMode"]);
|
|
232
|
+
const emit = defineEmits(["update:formEditMode", "update:activeRoleId"]);
|
|
227
233
|
const viewerRoot = ref(null);
|
|
228
234
|
const scrollContainer = ref(null);
|
|
229
235
|
const { state: viewerState, setScrollToPageFn, setDownloadPdfFn } = provideViewerState();
|
|
@@ -249,6 +255,29 @@ watch(
|
|
|
249
255
|
);
|
|
250
256
|
const viewerSearch = provideViewerSearch();
|
|
251
257
|
const formFieldsState = provideFormFields();
|
|
258
|
+
watch(
|
|
259
|
+
() => props.roleColors,
|
|
260
|
+
(v) => {
|
|
261
|
+
formFieldsState.setRoleColors(v ?? {});
|
|
262
|
+
},
|
|
263
|
+
{ immediate: true }
|
|
264
|
+
);
|
|
265
|
+
watch(
|
|
266
|
+
() => props.activeRoleId,
|
|
267
|
+
(v) => {
|
|
268
|
+
if (v === void 0) return;
|
|
269
|
+
if (formFieldsState.activeRoleId.value !== v) {
|
|
270
|
+
formFieldsState.setActiveRole(v);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{ immediate: true }
|
|
274
|
+
);
|
|
275
|
+
watch(
|
|
276
|
+
() => formFieldsState.activeRoleId.value,
|
|
277
|
+
(v) => {
|
|
278
|
+
if (props.activeRoleId !== v) emit("update:activeRoleId", v);
|
|
279
|
+
}
|
|
280
|
+
);
|
|
252
281
|
const pageSettings = providePageSettings(viewerRoot);
|
|
253
282
|
const virtualization = createPageVirtualization();
|
|
254
283
|
setScrollToPageFn((pageNumber) => {
|
|
@@ -267,6 +296,8 @@ const searchIndex = createSearchIndex();
|
|
|
267
296
|
const shapeDetectionCache = createShapeDetection();
|
|
268
297
|
const doc = shallowRef(null);
|
|
269
298
|
let painter = null;
|
|
299
|
+
let scriptingManager = null;
|
|
300
|
+
let scriptingBridge = null;
|
|
270
301
|
const pageMetas = virtualization.pageMetas;
|
|
271
302
|
const { isPageRendered } = virtualization;
|
|
272
303
|
watch(
|
|
@@ -474,6 +505,33 @@ async function loadDocument() {
|
|
|
474
505
|
totalPages: pdfDoc.numPages
|
|
475
506
|
});
|
|
476
507
|
});
|
|
508
|
+
if (props.scripting) {
|
|
509
|
+
try {
|
|
510
|
+
if (!scriptingManager) {
|
|
511
|
+
scriptingManager = await createScriptingManager({
|
|
512
|
+
viewerState: {
|
|
513
|
+
currentPage: viewerState.currentPage,
|
|
514
|
+
totalPages: viewerState.totalPages,
|
|
515
|
+
scale: viewerState.scale,
|
|
516
|
+
setScale: viewerState.setScale,
|
|
517
|
+
scrollToPage: viewerState.scrollToPage
|
|
518
|
+
},
|
|
519
|
+
virtualization: { isPageRendered: virtualization.isPageRendered },
|
|
520
|
+
proxyCache: { getPageSync: proxyCache.getPageSync }
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
await scriptingManager.setDocument(pdfDoc);
|
|
524
|
+
scriptingBridge = createScriptingBridge({
|
|
525
|
+
formFields: formFieldsState,
|
|
526
|
+
scripting: scriptingManager,
|
|
527
|
+
pdfDoc
|
|
528
|
+
});
|
|
529
|
+
} catch (err) {
|
|
530
|
+
console.warn("[KViewer] Failed to initialize PDF scripting sandbox:", err);
|
|
531
|
+
scriptingBridge?.destroy();
|
|
532
|
+
scriptingBridge = null;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
477
535
|
} catch (err) {
|
|
478
536
|
viewerState.error.value = "Failed to load document";
|
|
479
537
|
console.error("[KViewer] Document load error:", err);
|
|
@@ -686,6 +744,11 @@ onMounted(() => {
|
|
|
686
744
|
formFieldsState.addPlacedField(payload);
|
|
687
745
|
viewerState.selectTool("hand");
|
|
688
746
|
},
|
|
747
|
+
getFormFieldPreviewColor: () => {
|
|
748
|
+
const id = formFieldsState.activeRoleId.value;
|
|
749
|
+
if (!id) return void 0;
|
|
750
|
+
return formFieldsState.roleColors.value[id];
|
|
751
|
+
},
|
|
689
752
|
freehandGroupingDelay: props.freehandGroupingDelay,
|
|
690
753
|
// Suspend panzoom gestures while an annotation is being dragged or
|
|
691
754
|
// transformed. Otherwise single-finger pan runs in parallel with the
|
|
@@ -746,6 +809,9 @@ onMounted(() => {
|
|
|
746
809
|
watch(
|
|
747
810
|
() => props.source,
|
|
748
811
|
() => {
|
|
812
|
+
scriptingBridge?.destroy();
|
|
813
|
+
scriptingBridge = null;
|
|
814
|
+
scriptingManager?.setDocument(null);
|
|
749
815
|
viewerSearch.reset();
|
|
750
816
|
formFieldsState.reset();
|
|
751
817
|
shapeDetectionCache.clearAllShapeCache();
|
|
@@ -787,6 +853,11 @@ onBeforeUnmount(() => {
|
|
|
787
853
|
scrollContainer.value?.removeEventListener("wheel", onWheel);
|
|
788
854
|
inertiaPanzoom.detach();
|
|
789
855
|
resizeObserver?.disconnect();
|
|
856
|
+
scriptingBridge?.destroy();
|
|
857
|
+
scriptingBridge = null;
|
|
858
|
+
scriptingManager?.destroy().catch(() => {
|
|
859
|
+
});
|
|
860
|
+
scriptingManager = null;
|
|
790
861
|
viewerSearch.reset();
|
|
791
862
|
shapeDetectionCache.clearAllShapeCache();
|
|
792
863
|
searchIndex.destroy();
|
|
@@ -802,6 +873,16 @@ defineExpose({
|
|
|
802
873
|
getKonvaCanvasState,
|
|
803
874
|
getFormFieldValues: () => formFieldsState.getAllFieldValues(),
|
|
804
875
|
setFormFieldValue: (fieldName, value) => formFieldsState.setFieldValueByName(fieldName, value),
|
|
876
|
+
/** Add a form field programmatically. Returns the created definition.
|
|
877
|
+
* The new field is exported into the PDF on `exportPdf()` like any
|
|
878
|
+
* field placed via the placement tool. */
|
|
879
|
+
addFormField: (payload) => formFieldsState.addFormField(payload),
|
|
880
|
+
/** Patch a form field by id (rect, name, flags, type-specific props). */
|
|
881
|
+
updateFormField: (id, patch) => formFieldsState.updatePlacedField(id, patch),
|
|
882
|
+
/** Remove a form field by id (drops its value too). */
|
|
883
|
+
removeFormField: (id) => formFieldsState.removePlacedField(id),
|
|
884
|
+
/** All form-field definitions (parsed, detected, and placed), flat. */
|
|
885
|
+
getFormFields: () => formFieldsState.getAllFields(),
|
|
805
886
|
/** Reactive form-edit-mode flag — read with `.value`. */
|
|
806
887
|
formEditMode: viewerState.formEditMode,
|
|
807
888
|
/** Programmatically enter or leave form-edit mode. */
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import type { ViewerMenuItem } from '../menu-items.js';
|
|
4
5
|
type __VLS_Props = {
|
|
5
6
|
source: string | Uint8Array | object;
|
|
6
7
|
textLayer?: boolean;
|
|
@@ -21,6 +22,29 @@ type __VLS_Props = {
|
|
|
21
22
|
* with selection chrome, can be moved/resized, and the property panel
|
|
22
23
|
* is shown. Supports v-model via `v-model:form-edit-mode`. */
|
|
23
24
|
formEditMode?: boolean;
|
|
25
|
+
/** Map of roleId → CSS color used to color-code form fields in
|
|
26
|
+
* form-edit mode. The editor stays unopinionated about role
|
|
27
|
+
* definitions: the host owns the role list, names, and color picker,
|
|
28
|
+
* and just hands a flat lookup table to the viewer. Unknown or
|
|
29
|
+
* missing ids fall back to the default blue. */
|
|
30
|
+
roleColors?: Record<string, string>;
|
|
31
|
+
/** Active role id new field placements are auto-tagged with. The host
|
|
32
|
+
* drives this — typically by binding it to a role picker in its own
|
|
33
|
+
* UI. Supports v-model via `v-model:active-role-id`. */
|
|
34
|
+
activeRoleId?: string | null;
|
|
35
|
+
/** Execute embedded JavaScript inside the PDF — field-level AA scripts
|
|
36
|
+
* (Mouse Up, Calculate, Format, Validate, Keystroke), document-level
|
|
37
|
+
* open actions, and page-level scripts. Powered by pdf.js's QuickJS
|
|
38
|
+
* WASM sandbox (no DOM/network/host-JS escape), but arbitrary JS from
|
|
39
|
+
* untrusted PDFs is still real attack surface — default is `false`.
|
|
40
|
+
* Treated as read-once per document; toggling at runtime requires
|
|
41
|
+
* changing the `source` prop or remounting the component. */
|
|
42
|
+
scripting?: boolean;
|
|
43
|
+
/** Extra items appended to the default burger menu (after Download
|
|
44
|
+
* and the form-field-detection toggle). Use this to expose app-level
|
|
45
|
+
* toggles or actions without replacing the entire header. Ignored
|
|
46
|
+
* when the host supplies a custom `#header` slot. */
|
|
47
|
+
menuItems?: ViewerMenuItem[];
|
|
24
48
|
};
|
|
25
49
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
26
50
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -44,6 +68,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
44
68
|
getKonvaCanvasState: typeof getKonvaCanvasState;
|
|
45
69
|
getFormFieldValues: () => import("../annotation/engine/types.js").FormFieldValue[];
|
|
46
70
|
setFormFieldValue: (fieldName: string, value: string | boolean | string[]) => void;
|
|
71
|
+
/** Add a form field programmatically. Returns the created definition.
|
|
72
|
+
* The new field is exported into the PDF on `exportPdf()` like any
|
|
73
|
+
* field placed via the placement tool. */
|
|
74
|
+
addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
|
|
75
|
+
/** Patch a form field by id (rect, name, flags, type-specific props). */
|
|
76
|
+
updateFormField: (id: string, patch: Partial<FormFieldDefinition>) => void;
|
|
77
|
+
/** Remove a form field by id (drops its value too). */
|
|
78
|
+
removeFormField: (id: string) => void;
|
|
79
|
+
/** All form-field definitions (parsed, detected, and placed), flat. */
|
|
80
|
+
getFormFields: () => FormFieldDefinition[];
|
|
47
81
|
/** Reactive form-edit-mode flag — read with `.value`. */
|
|
48
82
|
formEditMode: import("vue").Ref<boolean, boolean>;
|
|
49
83
|
/** Programmatically enter or leave form-edit mode. */
|
|
@@ -52,19 +86,25 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
52
86
|
toggleFormEditMode: () => boolean;
|
|
53
87
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
54
88
|
"update:formEditMode": (value: boolean) => any;
|
|
89
|
+
"update:activeRoleId": (value: string | null) => any;
|
|
55
90
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
56
91
|
"onUpdate:formEditMode"?: ((value: boolean) => any) | undefined;
|
|
92
|
+
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
57
93
|
}>, {
|
|
58
94
|
userName: string;
|
|
59
95
|
freehandGroupingDelay: number;
|
|
60
96
|
zoom: number;
|
|
61
97
|
readonly: boolean;
|
|
62
98
|
active: boolean;
|
|
99
|
+
menuItems: ViewerMenuItem[];
|
|
100
|
+
scripting: boolean;
|
|
63
101
|
stamps: StampDefinition[];
|
|
64
102
|
signatureHandlers: SignatureHandlers;
|
|
65
103
|
viewMode: ViewMode;
|
|
66
104
|
shapeDetection: boolean;
|
|
67
105
|
formEditMode: boolean;
|
|
106
|
+
roleColors: Record<string, string>;
|
|
107
|
+
activeRoleId: string | null;
|
|
68
108
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
69
109
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
70
110
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ViewerMenuItem } from '../menu-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Extra items appended to the burger menu after the built-ins.
|
|
4
|
+
* See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
|
|
5
|
+
menuItems?: ViewerMenuItem[];
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
2
8
|
declare const _default: typeof __VLS_export;
|
|
3
9
|
export default _default;
|
|
@@ -26,6 +26,39 @@
|
|
|
26
26
|
data-testid="viewer-toggle-shape-detection"
|
|
27
27
|
@click="state.shapeDetection.value = !state.shapeDetection.value"
|
|
28
28
|
/>
|
|
29
|
+
<!-- Host-supplied menu items appended after the built-ins.
|
|
30
|
+
A small separator visually marks the boundary; we only
|
|
31
|
+
render it when there is at least one custom item. -->
|
|
32
|
+
<template v-if="props.menuItems && props.menuItems.length > 0">
|
|
33
|
+
<div class="my-1 border-t border-default" />
|
|
34
|
+
<template v-for="item in props.menuItems" :key="item.key">
|
|
35
|
+
<div v-if="item.type === 'separator'" class="my-1 border-t border-default" />
|
|
36
|
+
<UButton
|
|
37
|
+
v-else-if="item.type === 'button'"
|
|
38
|
+
:icon="item.icon"
|
|
39
|
+
:label="item.label"
|
|
40
|
+
:disabled="item.disabled"
|
|
41
|
+
variant="ghost"
|
|
42
|
+
color="neutral"
|
|
43
|
+
size="xs"
|
|
44
|
+
class="w-full justify-start"
|
|
45
|
+
:data-testid="`viewer-menu-${item.key}`"
|
|
46
|
+
@click="item.onSelect()"
|
|
47
|
+
/>
|
|
48
|
+
<UButton
|
|
49
|
+
v-else-if="item.type === 'checkbox'"
|
|
50
|
+
:icon="item.icon ?? (item.checked ? 'i-lucide-check-square' : 'i-lucide-square')"
|
|
51
|
+
:label="item.label"
|
|
52
|
+
:variant="item.checked ? 'soft' : 'ghost'"
|
|
53
|
+
:color="item.checked ? 'primary' : 'neutral'"
|
|
54
|
+
:disabled="item.disabled"
|
|
55
|
+
size="xs"
|
|
56
|
+
class="w-full justify-start"
|
|
57
|
+
:data-testid="`viewer-menu-${item.key}`"
|
|
58
|
+
@click="item.onUpdate(!item.checked)"
|
|
59
|
+
/>
|
|
60
|
+
</template>
|
|
61
|
+
</template>
|
|
29
62
|
</div>
|
|
30
63
|
</template>
|
|
31
64
|
</UPopover>
|
|
@@ -89,6 +122,9 @@ import ActionTools from "./tools/ActionTools.vue";
|
|
|
89
122
|
import PageInfo from "./tools/PageInfo.vue";
|
|
90
123
|
import SearchTool from "./tools/SearchTool.vue";
|
|
91
124
|
import ToolProperties from "./tools/ToolProperties.vue";
|
|
125
|
+
const props = defineProps({
|
|
126
|
+
menuItems: { type: Array, required: false }
|
|
127
|
+
});
|
|
92
128
|
const state = useViewerState();
|
|
93
129
|
const styleVersion = ref(0);
|
|
94
130
|
function bumpStyleVersion() {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ViewerMenuItem } from '../menu-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Extra items appended to the burger menu after the built-ins.
|
|
4
|
+
* See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
|
|
5
|
+
menuItems?: ViewerMenuItem[];
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
2
8
|
declare const _default: typeof __VLS_export;
|
|
3
9
|
export default _default;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ComponentPublicInstance } from 'vue';
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
3
|
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
|
+
import type { ViewerMenuItem } from '../menu-items.js';
|
|
4
5
|
export interface ViewerTabItem {
|
|
5
6
|
/** Unique identifier for the tab. */
|
|
6
7
|
id: string;
|
|
@@ -18,6 +19,9 @@ export interface ViewerTabItem {
|
|
|
18
19
|
zoom?: number;
|
|
19
20
|
/** Enable shape detection for this document. */
|
|
20
21
|
shapeDetection?: boolean;
|
|
22
|
+
/** Execute embedded PDF JavaScript for this document. See KViewer's
|
|
23
|
+
* `scripting` prop — default false, opt-in per tab. */
|
|
24
|
+
scripting?: boolean;
|
|
21
25
|
}
|
|
22
26
|
export interface AddTabOptions {
|
|
23
27
|
/** Insert at specific index. Defaults to end. */
|
|
@@ -48,6 +52,16 @@ type __VLS_Props = {
|
|
|
48
52
|
zoom?: number;
|
|
49
53
|
/** Minimum number of tabs -- prevents closing below this count. */
|
|
50
54
|
minTabs?: number;
|
|
55
|
+
/** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
|
|
56
|
+
roleColors?: Record<string, string>;
|
|
57
|
+
/** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
|
|
58
|
+
* Supports v-model via `v-model:active-role-id`. */
|
|
59
|
+
activeRoleId?: string | null;
|
|
60
|
+
/** Forwarded to every Viewer instance — see KViewer's `scripting`.
|
|
61
|
+
* Per-tab override via `ViewerTabItem.scripting` wins when set. */
|
|
62
|
+
scripting?: boolean;
|
|
63
|
+
/** Forwarded to every Viewer instance — see KViewer's `menuItems`. */
|
|
64
|
+
menuItems?: ViewerMenuItem[];
|
|
51
65
|
};
|
|
52
66
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
53
67
|
id?: string;
|
|
@@ -55,7 +69,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
|
55
69
|
declare function removeTab(id: string): boolean;
|
|
56
70
|
declare function activateTab(id: string): void;
|
|
57
71
|
declare function getViewer(id: string): ComponentPublicInstance | null;
|
|
58
|
-
declare var __VLS_7: {}, __VLS_21: {},
|
|
72
|
+
declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
59
73
|
tab: {
|
|
60
74
|
id: string;
|
|
61
75
|
label: string;
|
|
@@ -153,8 +167,9 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
|
|
|
153
167
|
viewMode?: ViewMode | undefined;
|
|
154
168
|
zoom?: number | undefined;
|
|
155
169
|
shapeDetection?: boolean | undefined;
|
|
170
|
+
scripting?: boolean | undefined;
|
|
156
171
|
};
|
|
157
|
-
},
|
|
172
|
+
}, __VLS_35: {
|
|
158
173
|
tab: {
|
|
159
174
|
id: string;
|
|
160
175
|
label: string;
|
|
@@ -252,18 +267,19 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
|
|
|
252
267
|
viewMode?: ViewMode | undefined;
|
|
253
268
|
zoom?: number | undefined;
|
|
254
269
|
shapeDetection?: boolean | undefined;
|
|
270
|
+
scripting?: boolean | undefined;
|
|
255
271
|
};
|
|
256
|
-
},
|
|
272
|
+
}, __VLS_37: {};
|
|
257
273
|
type __VLS_Slots = {} & {
|
|
258
274
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
259
275
|
} & {
|
|
260
276
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
261
277
|
} & {
|
|
262
|
-
header?: (props: typeof
|
|
278
|
+
header?: (props: typeof __VLS_32) => any;
|
|
263
279
|
} & {
|
|
264
|
-
footer?: (props: typeof
|
|
280
|
+
footer?: (props: typeof __VLS_35) => any;
|
|
265
281
|
} & {
|
|
266
|
-
empty?: (props: typeof
|
|
282
|
+
empty?: (props: typeof __VLS_37) => any;
|
|
267
283
|
};
|
|
268
284
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
269
285
|
addTab: typeof addTab;
|
|
@@ -368,13 +384,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
368
384
|
viewMode?: ViewMode | undefined;
|
|
369
385
|
zoom?: number | undefined;
|
|
370
386
|
shapeDetection?: boolean | undefined;
|
|
387
|
+
scripting?: boolean | undefined;
|
|
371
388
|
}[];
|
|
372
389
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
390
|
+
"update:activeRoleId": (value: string | null) => any;
|
|
373
391
|
"update:activeTab": (id: string) => any;
|
|
374
392
|
"tab-added": (tab: ViewerTabItem) => any;
|
|
375
393
|
"tab-close": (id: string) => any;
|
|
376
394
|
"tab-removed": (id: string) => any;
|
|
377
395
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
396
|
+
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
378
397
|
"onUpdate:activeTab"?: ((id: string) => any) | undefined;
|
|
379
398
|
"onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
|
|
380
399
|
"onTab-close"?: ((id: string) => any) | undefined;
|
|
@@ -382,9 +401,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
382
401
|
}>, {
|
|
383
402
|
userName: string;
|
|
384
403
|
zoom: number;
|
|
404
|
+
menuItems: ViewerMenuItem[];
|
|
405
|
+
scripting: boolean;
|
|
385
406
|
stamps: StampDefinition[];
|
|
386
407
|
signatureHandlers: SignatureHandlers;
|
|
387
408
|
viewMode: ViewMode;
|
|
409
|
+
roleColors: Record<string, string>;
|
|
410
|
+
activeRoleId: string | null;
|
|
388
411
|
defaultActiveTab: string;
|
|
389
412
|
minTabs: number;
|
|
390
413
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
:zoom="activeTab.zoom ?? zoom"
|
|
60
60
|
:readonly="readonly"
|
|
61
61
|
:shape-detection="activeTab.shapeDetection ?? shapeDetection"
|
|
62
|
+
:scripting="activeTab.scripting ?? scripting"
|
|
63
|
+
:menu-items="menuItems"
|
|
64
|
+
:role-colors="roleColors"
|
|
65
|
+
:active-role-id="activeRoleId"
|
|
66
|
+
@update:active-role-id="(v) => emit('update:activeRoleId', v)"
|
|
62
67
|
>
|
|
63
68
|
<template v-if="$slots.header" #header>
|
|
64
69
|
<slot name="header" :tab="activeTab" />
|
|
@@ -91,9 +96,13 @@ const props = defineProps({
|
|
|
91
96
|
shapeDetection: { type: Boolean, required: false },
|
|
92
97
|
viewMode: { type: String, required: false, default: "fit-width" },
|
|
93
98
|
zoom: { type: Number, required: false, default: 1 },
|
|
94
|
-
minTabs: { type: Number, required: false, default: 0 }
|
|
99
|
+
minTabs: { type: Number, required: false, default: 0 },
|
|
100
|
+
roleColors: { type: Object, required: false, default: void 0 },
|
|
101
|
+
activeRoleId: { type: [String, null], required: false, default: void 0 },
|
|
102
|
+
scripting: { type: Boolean, required: false, default: false },
|
|
103
|
+
menuItems: { type: Array, required: false, default: void 0 }
|
|
95
104
|
});
|
|
96
|
-
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed"]);
|
|
105
|
+
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId"]);
|
|
97
106
|
let idCounter = 0;
|
|
98
107
|
function generateId() {
|
|
99
108
|
return `tab-${Date.now()}-${++idCounter}`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ComponentPublicInstance } from 'vue';
|
|
2
2
|
import type { ViewMode } from '../composables/useViewerState.js';
|
|
3
3
|
import type { StampDefinition, SignatureHandlers } from '../annotation/engine/types.js';
|
|
4
|
+
import type { ViewerMenuItem } from '../menu-items.js';
|
|
4
5
|
export interface ViewerTabItem {
|
|
5
6
|
/** Unique identifier for the tab. */
|
|
6
7
|
id: string;
|
|
@@ -18,6 +19,9 @@ export interface ViewerTabItem {
|
|
|
18
19
|
zoom?: number;
|
|
19
20
|
/** Enable shape detection for this document. */
|
|
20
21
|
shapeDetection?: boolean;
|
|
22
|
+
/** Execute embedded PDF JavaScript for this document. See KViewer's
|
|
23
|
+
* `scripting` prop — default false, opt-in per tab. */
|
|
24
|
+
scripting?: boolean;
|
|
21
25
|
}
|
|
22
26
|
export interface AddTabOptions {
|
|
23
27
|
/** Insert at specific index. Defaults to end. */
|
|
@@ -48,6 +52,16 @@ type __VLS_Props = {
|
|
|
48
52
|
zoom?: number;
|
|
49
53
|
/** Minimum number of tabs -- prevents closing below this count. */
|
|
50
54
|
minTabs?: number;
|
|
55
|
+
/** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
|
|
56
|
+
roleColors?: Record<string, string>;
|
|
57
|
+
/** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
|
|
58
|
+
* Supports v-model via `v-model:active-role-id`. */
|
|
59
|
+
activeRoleId?: string | null;
|
|
60
|
+
/** Forwarded to every Viewer instance — see KViewer's `scripting`.
|
|
61
|
+
* Per-tab override via `ViewerTabItem.scripting` wins when set. */
|
|
62
|
+
scripting?: boolean;
|
|
63
|
+
/** Forwarded to every Viewer instance — see KViewer's `menuItems`. */
|
|
64
|
+
menuItems?: ViewerMenuItem[];
|
|
51
65
|
};
|
|
52
66
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
53
67
|
id?: string;
|
|
@@ -55,7 +69,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
|
55
69
|
declare function removeTab(id: string): boolean;
|
|
56
70
|
declare function activateTab(id: string): void;
|
|
57
71
|
declare function getViewer(id: string): ComponentPublicInstance | null;
|
|
58
|
-
declare var __VLS_7: {}, __VLS_21: {},
|
|
72
|
+
declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
59
73
|
tab: {
|
|
60
74
|
id: string;
|
|
61
75
|
label: string;
|
|
@@ -153,8 +167,9 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
|
|
|
153
167
|
viewMode?: ViewMode | undefined;
|
|
154
168
|
zoom?: number | undefined;
|
|
155
169
|
shapeDetection?: boolean | undefined;
|
|
170
|
+
scripting?: boolean | undefined;
|
|
156
171
|
};
|
|
157
|
-
},
|
|
172
|
+
}, __VLS_35: {
|
|
158
173
|
tab: {
|
|
159
174
|
id: string;
|
|
160
175
|
label: string;
|
|
@@ -252,18 +267,19 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_30: {
|
|
|
252
267
|
viewMode?: ViewMode | undefined;
|
|
253
268
|
zoom?: number | undefined;
|
|
254
269
|
shapeDetection?: boolean | undefined;
|
|
270
|
+
scripting?: boolean | undefined;
|
|
255
271
|
};
|
|
256
|
-
},
|
|
272
|
+
}, __VLS_37: {};
|
|
257
273
|
type __VLS_Slots = {} & {
|
|
258
274
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
259
275
|
} & {
|
|
260
276
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
261
277
|
} & {
|
|
262
|
-
header?: (props: typeof
|
|
278
|
+
header?: (props: typeof __VLS_32) => any;
|
|
263
279
|
} & {
|
|
264
|
-
footer?: (props: typeof
|
|
280
|
+
footer?: (props: typeof __VLS_35) => any;
|
|
265
281
|
} & {
|
|
266
|
-
empty?: (props: typeof
|
|
282
|
+
empty?: (props: typeof __VLS_37) => any;
|
|
267
283
|
};
|
|
268
284
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
269
285
|
addTab: typeof addTab;
|
|
@@ -368,13 +384,16 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
368
384
|
viewMode?: ViewMode | undefined;
|
|
369
385
|
zoom?: number | undefined;
|
|
370
386
|
shapeDetection?: boolean | undefined;
|
|
387
|
+
scripting?: boolean | undefined;
|
|
371
388
|
}[];
|
|
372
389
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
390
|
+
"update:activeRoleId": (value: string | null) => any;
|
|
373
391
|
"update:activeTab": (id: string) => any;
|
|
374
392
|
"tab-added": (tab: ViewerTabItem) => any;
|
|
375
393
|
"tab-close": (id: string) => any;
|
|
376
394
|
"tab-removed": (id: string) => any;
|
|
377
395
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
396
|
+
"onUpdate:activeRoleId"?: ((value: string | null) => any) | undefined;
|
|
378
397
|
"onUpdate:activeTab"?: ((id: string) => any) | undefined;
|
|
379
398
|
"onTab-added"?: ((tab: ViewerTabItem) => any) | undefined;
|
|
380
399
|
"onTab-close"?: ((id: string) => any) | undefined;
|
|
@@ -382,9 +401,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
382
401
|
}>, {
|
|
383
402
|
userName: string;
|
|
384
403
|
zoom: number;
|
|
404
|
+
menuItems: ViewerMenuItem[];
|
|
405
|
+
scripting: boolean;
|
|
385
406
|
stamps: StampDefinition[];
|
|
386
407
|
signatureHandlers: SignatureHandlers;
|
|
387
408
|
viewMode: ViewMode;
|
|
409
|
+
roleColors: Record<string, string>;
|
|
410
|
+
activeRoleId: string | null;
|
|
388
411
|
defaultActiveTab: string;
|
|
389
412
|
minTabs: number;
|
|
390
413
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -6,7 +6,8 @@
|
|
|
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
|
+
:data-element-id="props.field.id"
|
|
10
11
|
>
|
|
11
12
|
<div
|
|
12
13
|
class="kviewer-form-field__body"
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
<script setup>
|
|
35
36
|
import { computed } from "vue";
|
|
36
37
|
import { useViewerState } from "../../composables/useViewerState";
|
|
38
|
+
import { useFormFields } from "../../composables/useFormFields";
|
|
37
39
|
import FormTextField from "./FormTextField.vue";
|
|
38
40
|
import FormCheckbox from "./FormCheckbox.vue";
|
|
39
41
|
import FormRadioButton from "./FormRadioButton.vue";
|
|
@@ -51,9 +53,15 @@ const props = defineProps({
|
|
|
51
53
|
});
|
|
52
54
|
const emit = defineEmits(["select"]);
|
|
53
55
|
const state = useViewerState();
|
|
56
|
+
const formFields = useFormFields();
|
|
54
57
|
const isPlaced = computed(() => props.field.origin === "placed");
|
|
55
58
|
const isFormEditMode = computed(() => state.formEditMode.value);
|
|
56
59
|
const showChrome = computed(() => isFormEditMode.value);
|
|
60
|
+
const chromeColor = computed(() => {
|
|
61
|
+
const id = props.field.roleId;
|
|
62
|
+
if (!id) return "#1677ff";
|
|
63
|
+
return formFields.roleColors.value[id] ?? "#1677ff";
|
|
64
|
+
});
|
|
57
65
|
const positionStyle = computed(() => {
|
|
58
66
|
const rect = props.field.rect;
|
|
59
67
|
const vx = props.viewOffsetX ?? 0;
|
|
@@ -73,5 +81,5 @@ const positionStyle = computed(() => {
|
|
|
73
81
|
</script>
|
|
74
82
|
|
|
75
83
|
<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
|
|
84
|
+
.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
85
|
</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
|
|
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",
|