kviewer 0.1.0 → 0.1.2
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 +12 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -2
- package/dist/runtime/components/Viewer.d.vue.ts +16 -0
- package/dist/runtime/components/Viewer.vue +43 -2
- package/dist/runtime/components/Viewer.vue.d.ts +16 -0
- 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 +14 -0
- package/dist/runtime/components/ViewerTabs.vue +5 -1
- package/dist/runtime/components/ViewerTabs.vue.d.ts +14 -0
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +1 -0
- package/dist/runtime/composables/useFormFields.d.ts +18 -0
- package/dist/runtime/composables/useFormFields.js +22 -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 +7 -0
- package/dist/runtime/public-types.js +3 -0
- package/dist/runtime/style.css +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -34,12 +34,24 @@ Add it to your `nuxt.config.ts`:
|
|
|
34
34
|
```ts
|
|
35
35
|
export default defineNuxtConfig({
|
|
36
36
|
modules: ['kviewer'],
|
|
37
|
+
css: ['~/assets/css/main.css'],
|
|
37
38
|
kviewer: {
|
|
38
39
|
prefix: 'K', // component prefix (default)
|
|
39
40
|
},
|
|
40
41
|
})
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
KViewer's UI is built on [Nuxt UI](https://ui.nuxt.com) + Tailwind CSS. Create the main stylesheet with three imports:
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
/* assets/css/main.css */
|
|
48
|
+
@import 'tailwindcss';
|
|
49
|
+
@import '@nuxt/ui';
|
|
50
|
+
@import 'kviewer';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`@nuxt/ui` is installed and registered automatically — you don't add it to `modules`, and there's no `@source` path to maintain. The `@import 'kviewer'` line registers KViewer's components as a Tailwind source.
|
|
54
|
+
|
|
43
55
|
## Basic Usage
|
|
44
56
|
|
|
45
57
|
```vue
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
export { AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, SignatureData, SignatureHandlers, ViewMode } from '../dist/runtime/public-types.js';
|
|
2
|
+
export { AddFormFieldPayload, CheckboxStyle, ExportPdfOptions, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, KVIEWER_EMBED_PROTOCOL_VERSION, KViewerApi, KViewerEmbedBridgeOptions, KViewerEmbedClient, KViewerEmbedClientOptions, KViewerEmbedEventName, KViewerEmbedEventPayloads, KViewerEmbedMethodName, SignatureData, SignatureHandlers, ViewMode, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuItem, ViewerMenuSeparatorItem, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
3
3
|
|
|
4
4
|
interface ModuleOptions {
|
|
5
5
|
/**
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import { defineNuxtModule, createResolver, addPlugin, addComponentsDir } from '@nuxt/kit';
|
|
3
|
+
import { defineNuxtModule, createResolver, hasNuxtModule, installModule, addPlugin, addComponentsDir, addImports } from '@nuxt/kit';
|
|
4
|
+
export { KVIEWER_EMBED_PROTOCOL_VERSION, KViewerEmbedClient, useKViewerEmbedBridge } from '../dist/runtime/public-types.js';
|
|
4
5
|
|
|
5
6
|
const module$1 = defineNuxtModule({
|
|
6
7
|
meta: {
|
|
@@ -12,8 +13,11 @@ const module$1 = defineNuxtModule({
|
|
|
12
13
|
prefix: "K",
|
|
13
14
|
minRenderPixelRatio: 2
|
|
14
15
|
},
|
|
15
|
-
setup(options, nuxt) {
|
|
16
|
+
async setup(options, nuxt) {
|
|
16
17
|
const { resolve } = createResolver(import.meta.url);
|
|
18
|
+
if (!hasNuxtModule("@nuxt/ui")) {
|
|
19
|
+
await installModule("@nuxt/ui");
|
|
20
|
+
}
|
|
17
21
|
nuxt.options.runtimeConfig.public.kviewer = {
|
|
18
22
|
...nuxt.options.runtimeConfig.public.kviewer,
|
|
19
23
|
minRenderPixelRatio: options.minRenderPixelRatio ?? 2
|
|
@@ -27,6 +31,11 @@ const module$1 = defineNuxtModule({
|
|
|
27
31
|
prefix: options.prefix,
|
|
28
32
|
ignore: ["color-mode/**", "content/**", "prose/**"]
|
|
29
33
|
});
|
|
34
|
+
addImports([
|
|
35
|
+
{ name: "useKViewerEmbedBridge", from: resolve("./runtime/embed/bridge-host") },
|
|
36
|
+
{ name: "KViewerEmbedClient", from: resolve("./runtime/embed/bridge-client") },
|
|
37
|
+
{ name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") }
|
|
38
|
+
]);
|
|
30
39
|
const require_ = createRequire(import.meta.url);
|
|
31
40
|
const pdfjsDir = dirname(require_.resolve("pdfjs-dist/package.json"));
|
|
32
41
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
@@ -41,6 +50,15 @@ const module$1 = defineNuxtModule({
|
|
|
41
50
|
dir: join(pdfjsDir, "cmaps"),
|
|
42
51
|
baseURL: "/_kviewer/cmaps",
|
|
43
52
|
maxAge: 60 * 60 * 24 * 365
|
|
53
|
+
},
|
|
54
|
+
// PDF.js scripting sandbox bundle. Required when the viewer's
|
|
55
|
+
// `scripting` prop is enabled — the sandbox is a QuickJS WASM
|
|
56
|
+
// module that executes embedded PDF JavaScript (field AA scripts,
|
|
57
|
+
// calculate/format/validate, document/page-open actions).
|
|
58
|
+
{
|
|
59
|
+
dir: join(pdfjsDir, "legacy/build"),
|
|
60
|
+
baseURL: "/_kviewer/pdfjs",
|
|
61
|
+
maxAge: 60 * 60 * 24 * 365
|
|
44
62
|
}
|
|
45
63
|
);
|
|
46
64
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ViewMode } from '../composables/useViewerState.js';
|
|
2
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;
|
|
@@ -31,6 +32,19 @@ type __VLS_Props = {
|
|
|
31
32
|
* drives this — typically by binding it to a role picker in its own
|
|
32
33
|
* UI. Supports v-model via `v-model:active-role-id`. */
|
|
33
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[];
|
|
34
48
|
};
|
|
35
49
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
36
50
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -82,6 +96,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
82
96
|
zoom: number;
|
|
83
97
|
readonly: boolean;
|
|
84
98
|
active: boolean;
|
|
99
|
+
menuItems: ViewerMenuItem[];
|
|
100
|
+
scripting: boolean;
|
|
85
101
|
stamps: StampDefinition[];
|
|
86
102
|
signatureHandlers: SignatureHandlers;
|
|
87
103
|
viewMode: ViewMode;
|
|
@@ -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 },
|
|
@@ -223,7 +225,9 @@ const props = defineProps({
|
|
|
223
225
|
freehandGroupingDelay: { type: Number, required: false, default: 1e3 },
|
|
224
226
|
formEditMode: { type: Boolean, required: false, default: void 0 },
|
|
225
227
|
roleColors: { type: Object, required: false, default: void 0 },
|
|
226
|
-
activeRoleId: { type: [String, null], 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 }
|
|
227
231
|
});
|
|
228
232
|
const emit = defineEmits(["update:formEditMode", "update:activeRoleId"]);
|
|
229
233
|
const viewerRoot = ref(null);
|
|
@@ -292,6 +296,8 @@ const searchIndex = createSearchIndex();
|
|
|
292
296
|
const shapeDetectionCache = createShapeDetection();
|
|
293
297
|
const doc = shallowRef(null);
|
|
294
298
|
let painter = null;
|
|
299
|
+
let scriptingManager = null;
|
|
300
|
+
let scriptingBridge = null;
|
|
295
301
|
const pageMetas = virtualization.pageMetas;
|
|
296
302
|
const { isPageRendered } = virtualization;
|
|
297
303
|
watch(
|
|
@@ -499,6 +505,33 @@ async function loadDocument() {
|
|
|
499
505
|
totalPages: pdfDoc.numPages
|
|
500
506
|
});
|
|
501
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
|
+
}
|
|
502
535
|
} catch (err) {
|
|
503
536
|
viewerState.error.value = "Failed to load document";
|
|
504
537
|
console.error("[KViewer] Document load error:", err);
|
|
@@ -776,6 +809,9 @@ onMounted(() => {
|
|
|
776
809
|
watch(
|
|
777
810
|
() => props.source,
|
|
778
811
|
() => {
|
|
812
|
+
scriptingBridge?.destroy();
|
|
813
|
+
scriptingBridge = null;
|
|
814
|
+
scriptingManager?.setDocument(null);
|
|
779
815
|
viewerSearch.reset();
|
|
780
816
|
formFieldsState.reset();
|
|
781
817
|
shapeDetectionCache.clearAllShapeCache();
|
|
@@ -817,6 +853,11 @@ onBeforeUnmount(() => {
|
|
|
817
853
|
scrollContainer.value?.removeEventListener("wheel", onWheel);
|
|
818
854
|
inertiaPanzoom.detach();
|
|
819
855
|
resizeObserver?.disconnect();
|
|
856
|
+
scriptingBridge?.destroy();
|
|
857
|
+
scriptingBridge = null;
|
|
858
|
+
scriptingManager?.destroy().catch(() => {
|
|
859
|
+
});
|
|
860
|
+
scriptingManager = null;
|
|
820
861
|
viewerSearch.reset();
|
|
821
862
|
shapeDetectionCache.clearAllShapeCache();
|
|
822
863
|
searchIndex.destroy();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ViewMode } from '../composables/useViewerState.js';
|
|
2
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;
|
|
@@ -31,6 +32,19 @@ type __VLS_Props = {
|
|
|
31
32
|
* drives this — typically by binding it to a role picker in its own
|
|
32
33
|
* UI. Supports v-model via `v-model:active-role-id`. */
|
|
33
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[];
|
|
34
48
|
};
|
|
35
49
|
declare function exportPdf(options?: ExportPdfOptions): Promise<Uint8Array>;
|
|
36
50
|
type ImportMode = 'replace' | 'merge';
|
|
@@ -82,6 +96,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
82
96
|
zoom: number;
|
|
83
97
|
readonly: boolean;
|
|
84
98
|
active: boolean;
|
|
99
|
+
menuItems: ViewerMenuItem[];
|
|
100
|
+
scripting: boolean;
|
|
85
101
|
stamps: StampDefinition[];
|
|
86
102
|
signatureHandlers: SignatureHandlers;
|
|
87
103
|
viewMode: ViewMode;
|
|
@@ -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. */
|
|
@@ -53,6 +57,11 @@ type __VLS_Props = {
|
|
|
53
57
|
/** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
|
|
54
58
|
* Supports v-model via `v-model:active-role-id`. */
|
|
55
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[];
|
|
56
65
|
};
|
|
57
66
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
58
67
|
id?: string;
|
|
@@ -158,6 +167,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
158
167
|
viewMode?: ViewMode | undefined;
|
|
159
168
|
zoom?: number | undefined;
|
|
160
169
|
shapeDetection?: boolean | undefined;
|
|
170
|
+
scripting?: boolean | undefined;
|
|
161
171
|
};
|
|
162
172
|
}, __VLS_35: {
|
|
163
173
|
tab: {
|
|
@@ -257,6 +267,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
257
267
|
viewMode?: ViewMode | undefined;
|
|
258
268
|
zoom?: number | undefined;
|
|
259
269
|
shapeDetection?: boolean | undefined;
|
|
270
|
+
scripting?: boolean | undefined;
|
|
260
271
|
};
|
|
261
272
|
}, __VLS_37: {};
|
|
262
273
|
type __VLS_Slots = {} & {
|
|
@@ -373,6 +384,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
373
384
|
viewMode?: ViewMode | undefined;
|
|
374
385
|
zoom?: number | undefined;
|
|
375
386
|
shapeDetection?: boolean | undefined;
|
|
387
|
+
scripting?: boolean | undefined;
|
|
376
388
|
}[];
|
|
377
389
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
378
390
|
"update:activeRoleId": (value: string | null) => any;
|
|
@@ -389,6 +401,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
389
401
|
}>, {
|
|
390
402
|
userName: string;
|
|
391
403
|
zoom: number;
|
|
404
|
+
menuItems: ViewerMenuItem[];
|
|
405
|
+
scripting: boolean;
|
|
392
406
|
stamps: StampDefinition[];
|
|
393
407
|
signatureHandlers: SignatureHandlers;
|
|
394
408
|
viewMode: ViewMode;
|
|
@@ -59,6 +59,8 @@
|
|
|
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"
|
|
62
64
|
:role-colors="roleColors"
|
|
63
65
|
:active-role-id="activeRoleId"
|
|
64
66
|
@update:active-role-id="(v) => emit('update:activeRoleId', v)"
|
|
@@ -96,7 +98,9 @@ const props = defineProps({
|
|
|
96
98
|
zoom: { type: Number, required: false, default: 1 },
|
|
97
99
|
minTabs: { type: Number, required: false, default: 0 },
|
|
98
100
|
roleColors: { type: Object, required: false, default: void 0 },
|
|
99
|
-
activeRoleId: { type: [String, null], 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 }
|
|
100
104
|
});
|
|
101
105
|
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "update:activeRoleId"]);
|
|
102
106
|
let idCounter = 0;
|
|
@@ -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. */
|
|
@@ -53,6 +57,11 @@ type __VLS_Props = {
|
|
|
53
57
|
/** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
|
|
54
58
|
* Supports v-model via `v-model:active-role-id`. */
|
|
55
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[];
|
|
56
65
|
};
|
|
57
66
|
declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
58
67
|
id?: string;
|
|
@@ -158,6 +167,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
158
167
|
viewMode?: ViewMode | undefined;
|
|
159
168
|
zoom?: number | undefined;
|
|
160
169
|
shapeDetection?: boolean | undefined;
|
|
170
|
+
scripting?: boolean | undefined;
|
|
161
171
|
};
|
|
162
172
|
}, __VLS_35: {
|
|
163
173
|
tab: {
|
|
@@ -257,6 +267,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_32: {
|
|
|
257
267
|
viewMode?: ViewMode | undefined;
|
|
258
268
|
zoom?: number | undefined;
|
|
259
269
|
shapeDetection?: boolean | undefined;
|
|
270
|
+
scripting?: boolean | undefined;
|
|
260
271
|
};
|
|
261
272
|
}, __VLS_37: {};
|
|
262
273
|
type __VLS_Slots = {} & {
|
|
@@ -373,6 +384,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
373
384
|
viewMode?: ViewMode | undefined;
|
|
374
385
|
zoom?: number | undefined;
|
|
375
386
|
shapeDetection?: boolean | undefined;
|
|
387
|
+
scripting?: boolean | undefined;
|
|
376
388
|
}[];
|
|
377
389
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
378
390
|
"update:activeRoleId": (value: string | null) => any;
|
|
@@ -389,6 +401,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
389
401
|
}>, {
|
|
390
402
|
userName: string;
|
|
391
403
|
zoom: number;
|
|
404
|
+
menuItems: ViewerMenuItem[];
|
|
405
|
+
scripting: boolean;
|
|
392
406
|
stamps: StampDefinition[];
|
|
393
407
|
signatureHandlers: SignatureHandlers;
|
|
394
408
|
viewMode: ViewMode;
|
|
@@ -67,8 +67,26 @@ export interface FormFieldsState {
|
|
|
67
67
|
* that calls this re-runs when any signature is signed or its lock
|
|
68
68
|
* rule changes. */
|
|
69
69
|
isFieldLocked: (fieldId: string) => boolean;
|
|
70
|
+
/** Install a sink that is invoked synchronously for every field-value
|
|
71
|
+
* write driven by user interaction. Used by the scripting bridge to
|
|
72
|
+
* forward changes into `pdfDoc.annotationStorage` and dispatch into
|
|
73
|
+
* the JS sandbox. The sink is NOT called for inbound updates from
|
|
74
|
+
* [[applyFieldValueFromExternal]] (those originate from the sandbox
|
|
75
|
+
* itself, so re-firing would create a ping-pong loop). */
|
|
76
|
+
setValueWriteSink: (sink: ValueWriteSink | null) => void;
|
|
77
|
+
/** Apply a value coming from outside Vue's reactive flow (e.g. the
|
|
78
|
+
* PDF scripting sandbox dispatching `updatefromsandbox`). Updates
|
|
79
|
+
* `fieldValues` and triggers rerender. Idempotent: a write that
|
|
80
|
+
* matches the current value is a no-op (prevents redundant renders
|
|
81
|
+
* from the sandbox's own sibling-mirror events). Skips non-parsed
|
|
82
|
+
* fields (placed/detected don't exist in the PDF object model so
|
|
83
|
+
* the sandbox can't legitimately address them). */
|
|
84
|
+
applyFieldValueFromExternal: (fieldId: string, value: string | boolean | string[]) => void;
|
|
70
85
|
/** Clear all values and definitions (for document change) */
|
|
71
86
|
reset: () => void;
|
|
72
87
|
}
|
|
88
|
+
/** Sink signature used by [[setValueWriteSink]]. Receives every user-driven
|
|
89
|
+
* field value write (the primary id AND each mirrored sibling). */
|
|
90
|
+
export type ValueWriteSink = (fieldId: string, value: string | boolean | string[]) => void;
|
|
73
91
|
export declare function provideFormFields(): FormFieldsState;
|
|
74
92
|
export declare function useFormFields(): FormFieldsState;
|
|
@@ -31,6 +31,10 @@ export function provideFormFields() {
|
|
|
31
31
|
}
|
|
32
32
|
return void 0;
|
|
33
33
|
}
|
|
34
|
+
let valueWriteSink = null;
|
|
35
|
+
function setValueWriteSink(sink) {
|
|
36
|
+
valueWriteSink = sink;
|
|
37
|
+
}
|
|
34
38
|
let checkboxStyleMap = null;
|
|
35
39
|
let buttonCaptionMap = null;
|
|
36
40
|
function applyCheckboxStyles(styles) {
|
|
@@ -112,22 +116,37 @@ export function provideFormFields() {
|
|
|
112
116
|
const existing = fieldValues.value.get(fieldId);
|
|
113
117
|
if (!existing) return;
|
|
114
118
|
existing.value = value;
|
|
119
|
+
const sinkWrites = [[fieldId, value]];
|
|
115
120
|
const { fieldName, fieldType } = existing;
|
|
116
121
|
if (fieldName) {
|
|
117
122
|
if (fieldType === "radio" && typeof value === "string" && value !== "") {
|
|
118
123
|
for (const [id, fv] of fieldValues.value.entries()) {
|
|
119
124
|
if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === "radio") {
|
|
120
125
|
fv.value = "";
|
|
126
|
+
sinkWrites.push([id, ""]);
|
|
121
127
|
}
|
|
122
128
|
}
|
|
123
129
|
} else if (fieldType !== "radio") {
|
|
124
130
|
for (const [id, fv] of fieldValues.value.entries()) {
|
|
125
131
|
if (id !== fieldId && fv.fieldName === fieldName && fv.fieldType === fieldType) {
|
|
126
132
|
fv.value = value;
|
|
133
|
+
sinkWrites.push([id, value]);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
}
|
|
138
|
+
if (valueWriteSink) {
|
|
139
|
+
for (const [id, v] of sinkWrites) valueWriteSink(id, v);
|
|
140
|
+
}
|
|
141
|
+
triggerRef(fieldValues);
|
|
142
|
+
}
|
|
143
|
+
function applyFieldValueFromExternal(fieldId, value) {
|
|
144
|
+
const existing = fieldValues.value.get(fieldId);
|
|
145
|
+
if (!existing) return;
|
|
146
|
+
const def = getFieldById(fieldId);
|
|
147
|
+
if (def && def.origin !== "parsed") return;
|
|
148
|
+
if (existing.value === value) return;
|
|
149
|
+
existing.value = value;
|
|
131
150
|
triggerRef(fieldValues);
|
|
132
151
|
}
|
|
133
152
|
function getFieldValue(fieldId) {
|
|
@@ -445,6 +464,7 @@ export function provideFormFields() {
|
|
|
445
464
|
selectedPlacedFieldId.value = null;
|
|
446
465
|
checkboxStyleMap = null;
|
|
447
466
|
buttonCaptionMap = null;
|
|
467
|
+
valueWriteSink = null;
|
|
448
468
|
triggerRef(fieldDefinitions);
|
|
449
469
|
triggerRef(fieldValues);
|
|
450
470
|
}
|
|
@@ -472,6 +492,8 @@ export function provideFormFields() {
|
|
|
472
492
|
isFieldLocked,
|
|
473
493
|
resetValues,
|
|
474
494
|
reset,
|
|
495
|
+
setValueWriteSink,
|
|
496
|
+
applyFieldValueFromExternal,
|
|
475
497
|
activeRoleId,
|
|
476
498
|
setActiveRole,
|
|
477
499
|
roleColors,
|
|
@@ -2,6 +2,10 @@ import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
|
|
|
2
2
|
export interface PageProxyCache {
|
|
3
3
|
setDocument: (doc: PDFDocumentProxy) => void;
|
|
4
4
|
getPage: (pageNumber: number) => Promise<PDFPageProxy>;
|
|
5
|
+
/** Return a cached page proxy synchronously, or undefined if not resolved yet.
|
|
6
|
+
* Used by the scripting viewer-adapter where PDFScriptingManager calls
|
|
7
|
+
* `getPageView(idx).pdfPage` synchronously during page-open dispatch. */
|
|
8
|
+
getPageSync: (pageNumber: number) => PDFPageProxy | undefined;
|
|
5
9
|
clear: () => void;
|
|
6
10
|
}
|
|
7
11
|
export declare function createPageProxyCache(maxSize?: number): PageProxyCache;
|
|
@@ -60,7 +60,10 @@ export function createPageProxyCache(maxSize = DEFAULT_MAX_SIZE) {
|
|
|
60
60
|
inflight.clear();
|
|
61
61
|
doc = null;
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
function getPageSync(pageNumber) {
|
|
64
|
+
return cache.get(pageNumber);
|
|
65
|
+
}
|
|
66
|
+
const cacheInstance = { setDocument, getPage, getPageSync, clear };
|
|
64
67
|
provide(PAGE_PROXY_CACHE_KEY, cacheInstance);
|
|
65
68
|
return cacheInstance;
|
|
66
69
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
|
2
|
+
import type { FormFieldsState } from './useFormFields.js';
|
|
3
|
+
import { type ScriptingManager } from './useScriptingManager.js';
|
|
4
|
+
export interface ScriptingBridge {
|
|
5
|
+
destroy: () => void;
|
|
6
|
+
}
|
|
7
|
+
export interface ScriptingBridgeOptions {
|
|
8
|
+
formFields: FormFieldsState;
|
|
9
|
+
scripting: ScriptingManager;
|
|
10
|
+
pdfDoc: PDFDocumentProxy;
|
|
11
|
+
}
|
|
12
|
+
/** Bridge between kviewer's reactive form-state, the PDF document's
|
|
13
|
+
* AnnotationStorage (which the sandbox reads/writes), and the scripting
|
|
14
|
+
* manager's event bus. Ownership rules:
|
|
15
|
+
*
|
|
16
|
+
* - Outbound (user → sandbox): every `setFieldValue` write hits the
|
|
17
|
+
* `valueWriteSink` we install. We mirror into AnnotationStorage AND
|
|
18
|
+
* dispatch an `'Action'` (and `'Validate'` for text) event into the
|
|
19
|
+
* sandbox so any field-AA script runs.
|
|
20
|
+
*
|
|
21
|
+
* - Inbound (sandbox → UI): we subscribe to `updatefromsandbox` on the
|
|
22
|
+
* event bus, translate the per-field-type payload back into kviewer's
|
|
23
|
+
* primitive shape, and call `applyFieldValueFromExternal` (which is
|
|
24
|
+
* sink-blind, so no ping-pong).
|
|
25
|
+
*
|
|
26
|
+
* We also seed AnnotationStorage from current fieldValues at boot so
|
|
27
|
+
* any pre-bridge user input survives the sandbox handshake.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createScriptingBridge(opts: ScriptingBridgeOptions): ScriptingBridge;
|