kviewer 0.1.0 → 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.
Files changed (33) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +16 -1
  4. package/dist/runtime/components/Viewer.d.vue.ts +16 -0
  5. package/dist/runtime/components/Viewer.vue +43 -2
  6. package/dist/runtime/components/Viewer.vue.d.ts +16 -0
  7. package/dist/runtime/components/ViewerBar.d.vue.ts +7 -1
  8. package/dist/runtime/components/ViewerBar.vue +36 -0
  9. package/dist/runtime/components/ViewerBar.vue.d.ts +7 -1
  10. package/dist/runtime/components/ViewerTabs.d.vue.ts +14 -0
  11. package/dist/runtime/components/ViewerTabs.vue +5 -1
  12. package/dist/runtime/components/ViewerTabs.vue.d.ts +14 -0
  13. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +1 -0
  14. package/dist/runtime/composables/useFormFields.d.ts +18 -0
  15. package/dist/runtime/composables/useFormFields.js +22 -0
  16. package/dist/runtime/composables/usePageProxyCache.d.ts +4 -0
  17. package/dist/runtime/composables/usePageProxyCache.js +4 -1
  18. package/dist/runtime/composables/useScriptingBridge.d.ts +29 -0
  19. package/dist/runtime/composables/useScriptingBridge.js +74 -0
  20. package/dist/runtime/composables/useScriptingManager.d.ts +65 -0
  21. package/dist/runtime/composables/useScriptingManager.js +123 -0
  22. package/dist/runtime/embed/bridge-client.d.ts +58 -0
  23. package/dist/runtime/embed/bridge-client.js +143 -0
  24. package/dist/runtime/embed/bridge-host.d.ts +59 -0
  25. package/dist/runtime/embed/bridge-host.js +136 -0
  26. package/dist/runtime/embed/protocol.d.ts +105 -0
  27. package/dist/runtime/embed/protocol.js +13 -0
  28. package/dist/runtime/menu-items.d.ts +34 -0
  29. package/dist/runtime/menu-items.js +0 -0
  30. package/dist/runtime/public-types.d.ts +7 -0
  31. package/dist/runtime/public-types.js +3 -0
  32. package/dist/types.d.mts +1 -1
  33. package/package.json +1 -1
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kviewer",
3
3
  "configKey": "kviewer",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
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, 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: {
@@ -27,6 +28,11 @@ const module$1 = defineNuxtModule({
27
28
  prefix: options.prefix,
28
29
  ignore: ["color-mode/**", "content/**", "prose/**"]
29
30
  });
31
+ addImports([
32
+ { name: "useKViewerEmbedBridge", from: resolve("./runtime/embed/bridge-host") },
33
+ { name: "KViewerEmbedClient", from: resolve("./runtime/embed/bridge-client") },
34
+ { name: "KVIEWER_EMBED_PROTOCOL_VERSION", from: resolve("./runtime/embed/protocol") }
35
+ ]);
30
36
  const require_ = createRequire(import.meta.url);
31
37
  const pdfjsDir = dirname(require_.resolve("pdfjs-dist/package.json"));
32
38
  nuxt.hook("nitro:config", (nitroConfig) => {
@@ -41,6 +47,15 @@ const module$1 = defineNuxtModule({
41
47
  dir: join(pdfjsDir, "cmaps"),
42
48
  baseURL: "/_kviewer/cmaps",
43
49
  maxAge: 60 * 60 * 24 * 365
50
+ },
51
+ // PDF.js scripting sandbox bundle. Required when the viewer's
52
+ // `scripting` prop is enabled — the sandbox is a QuickJS WASM
53
+ // module that executes embedded PDF JavaScript (field AA scripts,
54
+ // calculate/format/validate, document/page-open actions).
55
+ {
56
+ dir: join(pdfjsDir, "legacy/build"),
57
+ baseURL: "/_kviewer/pdfjs",
58
+ maxAge: 60 * 60 * 24 * 365
44
59
  }
45
60
  );
46
61
  });
@@ -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
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
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
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
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;
@@ -7,6 +7,7 @@
7
7
  'kviewer-form-field--selected': props.selected
8
8
  }"
9
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"
@@ -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
- const cacheInstance = { setDocument, getPage, clear };
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;