kviewer 0.3.3 → 0.3.5
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 +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/annotation/engine/editor/selector.d.ts +1 -0
- package/dist/runtime/annotation/engine/editor/selector.js +17 -5
- package/dist/runtime/annotation/engine/painter.d.ts +4 -2
- package/dist/runtime/annotation/engine/painter.js +4 -0
- package/dist/runtime/annotation/engine/tools/form-field.d.ts +5 -5
- package/dist/runtime/annotation/engine/tools/form-field.js +1 -1
- package/dist/runtime/annotation/engine/types.d.ts +18 -9
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +6 -29
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +12 -1
- package/dist/runtime/components/FormFieldLayer.vue +7 -2
- package/dist/runtime/components/PdfPage.vue +24 -1
- package/dist/runtime/components/SelectionToolbarItems.d.vue.ts +8 -0
- package/dist/runtime/components/SelectionToolbarItems.vue +43 -0
- package/dist/runtime/components/SelectionToolbarItems.vue.d.ts +8 -0
- package/dist/runtime/components/Viewer.d.vue.ts +24 -17
- package/dist/runtime/components/Viewer.vue +60 -37
- package/dist/runtime/components/Viewer.vue.d.ts +24 -17
- package/dist/runtime/components/ViewerBar.d.vue.ts +4 -4
- package/dist/runtime/components/ViewerBar.vue +2 -1
- package/dist/runtime/components/ViewerBar.vue.d.ts +4 -4
- package/dist/runtime/components/ViewerTabs.d.vue.ts +16 -17
- package/dist/runtime/components/ViewerTabs.vue +5 -6
- package/dist/runtime/components/ViewerTabs.vue.d.ts +16 -17
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +35 -10
- package/dist/runtime/components/form-fields/FormSignatureField.vue +10 -2
- package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +4 -0
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +35 -18
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +4 -0
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.d.vue.ts +7 -0
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue +51 -0
- package/dist/runtime/components/form-fields/PlacedFieldToolbar.vue.d.ts +7 -0
- package/dist/runtime/composables/useFormFields.d.ts +28 -17
- package/dist/runtime/composables/useFormFields.js +20 -24
- package/dist/runtime/embed/bridge-client.d.ts +4 -2
- package/dist/runtime/embed/bridge-client.js +2 -1
- package/dist/runtime/embed/bridge-host.d.ts +2 -0
- package/dist/runtime/embed/bridge-host.js +16 -0
- package/dist/runtime/embed/protocol.d.ts +12 -0
- package/dist/runtime/public-types.d.ts +2 -1
- package/dist/runtime/selection-items.d.ts +61 -0
- package/dist/runtime/selection-items.js +1 -0
- package/dist/runtime/toolbar-tools.d.ts +4 -1
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
|
@@ -4,14 +4,19 @@
|
|
|
4
4
|
:class="{
|
|
5
5
|
'kviewer-form-field--editable': showChrome,
|
|
6
6
|
'kviewer-form-field--placed': isPlaced,
|
|
7
|
-
'kviewer-form-field--selected': props.selected
|
|
7
|
+
'kviewer-form-field--selected': props.selected,
|
|
8
|
+
'kviewer-form-field--readonly': isReadonlyOutsideAuthoring
|
|
8
9
|
}"
|
|
9
|
-
:style="[positionStyle, {
|
|
10
|
+
:style="[positionStyle, {
|
|
11
|
+
'--kvw-field-color': chromeColor,
|
|
12
|
+
'--kvw-handle-zoom': handleZoom
|
|
13
|
+
}]"
|
|
10
14
|
:data-element-id="props.field.id"
|
|
15
|
+
@contextmenu="onContextMenu"
|
|
11
16
|
>
|
|
12
17
|
<div
|
|
13
18
|
class="kviewer-form-field__body"
|
|
14
|
-
:class="{ 'kviewer-form-field__body--locked':
|
|
19
|
+
:class="{ 'kviewer-form-field__body--locked': chromeMode === 'authoring' }"
|
|
15
20
|
>
|
|
16
21
|
<FormTextField v-if="props.field.fieldType === 'text'" :field="props.field" />
|
|
17
22
|
<FormCheckbox v-else-if="props.field.fieldType === 'checkbox'" :field="props.field" />
|
|
@@ -27,8 +32,14 @@
|
|
|
27
32
|
:selected="props.selected"
|
|
28
33
|
:scale="props.scale"
|
|
29
34
|
:page-height="props.pageTransform.height"
|
|
35
|
+
:mode="chromeMode ?? 'authoring'"
|
|
30
36
|
@select="emit('select')"
|
|
31
37
|
/>
|
|
38
|
+
|
|
39
|
+
<PlacedFieldToolbar
|
|
40
|
+
v-if="showChrome && props.selected && !state.readonly.value"
|
|
41
|
+
:field="props.field"
|
|
42
|
+
/>
|
|
32
43
|
</div>
|
|
33
44
|
</template>
|
|
34
45
|
|
|
@@ -44,6 +55,7 @@ import FormDropdown from "./FormDropdown.vue";
|
|
|
44
55
|
import FormSignatureField from "./FormSignatureField.vue";
|
|
45
56
|
import FormButton from "./FormButton.vue";
|
|
46
57
|
import PlacedFieldChrome from "./PlacedFieldChrome.vue";
|
|
58
|
+
import PlacedFieldToolbar from "./PlacedFieldToolbar.vue";
|
|
47
59
|
const props = defineProps({
|
|
48
60
|
field: { type: Object, required: true },
|
|
49
61
|
pageTransform: { type: Object, required: true },
|
|
@@ -55,14 +67,27 @@ const state = useViewerState();
|
|
|
55
67
|
const formFields = useFormFields();
|
|
56
68
|
const isPlaced = computed(() => props.field.origin === "placed");
|
|
57
69
|
const isFormEditMode = computed(() => state.formEditMode.value);
|
|
58
|
-
const
|
|
59
|
-
(
|
|
70
|
+
const chromeMode = computed(() => {
|
|
71
|
+
if (isFormEditMode.value) return "authoring";
|
|
72
|
+
if (formFields.editablePlacedFields.value && isPlaced.value && !props.field.readOnly) {
|
|
73
|
+
return "live";
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
});
|
|
77
|
+
const showChrome = computed(() => chromeMode.value !== null);
|
|
78
|
+
const isReadonlyOutsideAuthoring = computed(
|
|
79
|
+
() => props.field.readOnly && !isFormEditMode.value
|
|
60
80
|
);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
function onContextMenu(e) {
|
|
82
|
+
if (chromeMode.value !== "live") return;
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
emit("select");
|
|
85
|
+
}
|
|
86
|
+
const handleZoom = computed(() => {
|
|
87
|
+
const totalZoom = (props.scale || 1) * (state.visualZoom.value || 1);
|
|
88
|
+
return (1 / totalZoom).toFixed(3);
|
|
65
89
|
});
|
|
90
|
+
const chromeColor = computed(() => props.field.fieldColor ?? "#1677ff");
|
|
66
91
|
const positionStyle = computed(() => {
|
|
67
92
|
const rect = props.field.rect;
|
|
68
93
|
const r = convertPdfRectToCanvas(props.pageTransform, [rect[0], rect[1], rect[2], rect[3]]);
|
|
@@ -77,5 +102,5 @@ const positionStyle = computed(() => {
|
|
|
77
102
|
</script>
|
|
78
103
|
|
|
79
104
|
<style>
|
|
80
|
-
.kviewer-form-field__body{height:100%;width:100%}.kviewer-form-field__body--locked{pointer-events:none}
|
|
105
|
+
.kviewer-form-field__body{height:100%;width:100%}.kviewer-form-field__body--locked{pointer-events:none}
|
|
81
106
|
</style>
|
|
@@ -55,10 +55,18 @@ const signatureUrl = computed(() => {
|
|
|
55
55
|
});
|
|
56
56
|
const hasSigned = computed(() => !!signatureUrl.value);
|
|
57
57
|
const clickToSignEnabled = computed(() => state.clickToSign.value);
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const liveEditing = computed(
|
|
59
|
+
() => formFields.editablePlacedFields.value && props.field.origin === "placed" && !state.formEditMode.value
|
|
60
|
+
);
|
|
61
|
+
async function onClickSign(e) {
|
|
60
62
|
if (props.field.readOnly || state.readonly.value) return;
|
|
61
63
|
if (formFields.isFieldLocked(props.field.id)) return;
|
|
64
|
+
if (liveEditing.value && (hasSigned.value || !clickToSignEnabled.value)) {
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
formFields.selectPlacedField(props.field.id);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!clickToSignEnabled.value) return;
|
|
62
70
|
if (state.signatures.value.length > 0) {
|
|
63
71
|
const sig = state.signatures.value[0];
|
|
64
72
|
if (sig) {
|
|
@@ -6,6 +6,10 @@ type __VLS_Props = {
|
|
|
6
6
|
scale: number;
|
|
7
7
|
/** PDF-space page height (kept for parity; unused here). */
|
|
8
8
|
pageHeight: number;
|
|
9
|
+
/** 'authoring' = form-edit mode (chrome is the whole interactive
|
|
10
|
+
* surface). 'live' = editablePlacedFields outside form-edit mode
|
|
11
|
+
* (chrome is transparent except grip and handles). */
|
|
12
|
+
mode: 'authoring' | 'live';
|
|
9
13
|
};
|
|
10
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
11
15
|
select: () => any;
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!--
|
|
3
|
-
click to select, drag anywhere to move
|
|
4
|
-
|
|
2
|
+
<!-- Two interaction modes. Authoring (form-edit mode): the whole chrome
|
|
3
|
+
is the interactive surface — click to select, drag anywhere to move.
|
|
4
|
+
Live (editablePlacedFields): the surface is transparent so the field
|
|
5
|
+
body stays usable (click-to-sign, text input); management happens on
|
|
6
|
+
the always-visible grip ("handle = manage, body = use"). -->
|
|
5
7
|
<div
|
|
6
8
|
ref="rootEl"
|
|
7
9
|
class="kviewer-placed-chrome"
|
|
8
|
-
:class="{
|
|
9
|
-
|
|
10
|
+
:class="{
|
|
11
|
+
'kviewer-placed-chrome--selected': selected,
|
|
12
|
+
'kviewer-placed-chrome--live': mode === 'live'
|
|
13
|
+
}"
|
|
10
14
|
@pointerdown.stop="onChromePointerDown"
|
|
11
15
|
@click.stop
|
|
12
16
|
>
|
|
13
17
|
<div
|
|
14
|
-
|
|
18
|
+
class="kviewer-placed-chrome__outline"
|
|
19
|
+
:class="{
|
|
20
|
+
'kviewer-placed-chrome__outline--placed': field.origin === 'placed',
|
|
21
|
+
'kviewer-placed-chrome__outline--selected': selected
|
|
22
|
+
}"
|
|
23
|
+
/>
|
|
24
|
+
<div
|
|
25
|
+
v-if="selected || mode === 'live'"
|
|
15
26
|
class="kviewer-placed-chrome__grip"
|
|
16
27
|
:title="field.fieldName"
|
|
28
|
+
@pointerdown.stop="onChromePointerDown"
|
|
29
|
+
@click.stop
|
|
17
30
|
>
|
|
18
31
|
<UIcon name="i-lucide-grip-vertical" class="kviewer-placed-chrome__grip-dots" />
|
|
19
32
|
<span class="kviewer-placed-chrome__grip-label">{{ field.fieldName }}</span>
|
|
@@ -33,20 +46,15 @@
|
|
|
33
46
|
</template>
|
|
34
47
|
|
|
35
48
|
<script setup>
|
|
36
|
-
import {
|
|
49
|
+
import { ref } from "vue";
|
|
37
50
|
import { useFormFields } from "../../composables/useFormFields";
|
|
38
|
-
import { useViewerState } from "../../composables/useViewerState";
|
|
39
51
|
const rootEl = ref(null);
|
|
40
|
-
const viewerState = useViewerState();
|
|
41
|
-
const handleZoom = computed(() => {
|
|
42
|
-
const totalZoom = (props.scale || 1) * (viewerState.visualZoom.value || 1);
|
|
43
|
-
return Math.max(0.4, 1 / totalZoom).toFixed(3);
|
|
44
|
-
});
|
|
45
52
|
const props = defineProps({
|
|
46
53
|
field: { type: Object, required: true },
|
|
47
54
|
selected: { type: Boolean, required: true },
|
|
48
55
|
scale: { type: Number, required: true },
|
|
49
|
-
pageHeight: { type: Number, required: true }
|
|
56
|
+
pageHeight: { type: Number, required: true },
|
|
57
|
+
mode: { type: String, required: true }
|
|
50
58
|
});
|
|
51
59
|
const emit = defineEmits(["select"]);
|
|
52
60
|
const formFields = useFormFields();
|
|
@@ -91,12 +99,16 @@ function onChromePointerDown(e) {
|
|
|
91
99
|
startRect[2] + dx,
|
|
92
100
|
startRect[3] + pdfDy
|
|
93
101
|
];
|
|
94
|
-
formFields.updatePlacedField(props.field.id, { rect: next });
|
|
102
|
+
formFields.updatePlacedField(props.field.id, { rect: next }, { silent: true });
|
|
95
103
|
};
|
|
96
104
|
const onUp = () => {
|
|
97
105
|
window.removeEventListener("pointermove", onMove);
|
|
98
106
|
window.removeEventListener("pointerup", onUp);
|
|
99
|
-
if (!dragging)
|
|
107
|
+
if (!dragging) {
|
|
108
|
+
emit("select");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
formFields.updatePlacedField(props.field.id, { rect: [...props.field.rect] });
|
|
100
112
|
};
|
|
101
113
|
window.addEventListener("pointermove", onMove);
|
|
102
114
|
window.addEventListener("pointerup", onUp);
|
|
@@ -106,6 +118,7 @@ function onHandlePointerDown(e, name) {
|
|
|
106
118
|
const startY = e.clientY;
|
|
107
119
|
const [sx1, sy1, sx2, sy2] = props.field.rect;
|
|
108
120
|
const s = effectiveScale();
|
|
121
|
+
let resized = false;
|
|
109
122
|
const onMove = (ev) => {
|
|
110
123
|
const dx = (ev.clientX - startX) / s;
|
|
111
124
|
const dy = (ev.clientY - startY) / s;
|
|
@@ -115,11 +128,15 @@ function onHandlePointerDown(e, name) {
|
|
|
115
128
|
if (name.includes("s")) y1 = Math.min(y2 - 4, sy1 + pdfDy);
|
|
116
129
|
if (name.includes("e")) x2 = Math.max(x1 + 4, sx2 + dx);
|
|
117
130
|
if (name.includes("w")) x1 = Math.min(x2 - 4, sx1 + dx);
|
|
118
|
-
|
|
131
|
+
resized = true;
|
|
132
|
+
formFields.updatePlacedField(props.field.id, { rect: [x1, y1, x2, y2] }, { silent: true });
|
|
119
133
|
};
|
|
120
134
|
const onUp = () => {
|
|
121
135
|
window.removeEventListener("pointermove", onMove);
|
|
122
136
|
window.removeEventListener("pointerup", onUp);
|
|
137
|
+
if (resized) {
|
|
138
|
+
formFields.updatePlacedField(props.field.id, { rect: [...props.field.rect] });
|
|
139
|
+
}
|
|
123
140
|
};
|
|
124
141
|
window.addEventListener("pointermove", onMove);
|
|
125
142
|
window.addEventListener("pointerup", onUp);
|
|
@@ -127,5 +144,5 @@ function onHandlePointerDown(e, name) {
|
|
|
127
144
|
</script>
|
|
128
145
|
|
|
129
146
|
<style>
|
|
130
|
-
.kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome--
|
|
147
|
+
.kviewer-placed-chrome{cursor:move;inset:0;pointer-events:auto;position:absolute;touch-action:none}.kviewer-placed-chrome--live{cursor:default;pointer-events:none}.kviewer-placed-chrome__outline{border:1px dashed color-mix(in srgb,var(--kvw-field-color,#1677ff) 45%,transparent);box-sizing:border-box;height:calc(100%/var(--kvw-handle-zoom, 1));left:0;pointer-events:none;position:absolute;top:0;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 0;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__outline--placed{border-color:color-mix(in srgb,var(--kvw-field-color,#1677ff) 75%,transparent)}.kviewer-placed-chrome__outline--selected{border:1.5px dashed var(--kvw-field-color,#1677ff)}.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:4px;bottom:calc(100% + 4px*var(--kvw-handle-zoom, 1));box-sizing:border-box;color:var(--kvw-field-color,#1677ff);cursor:move;display:flex;font-size:12px;gap:4px;height:22px;left:0;line-height:1;overflow:hidden;padding:0 8px 0 3px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));transform-origin:0 100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:calc(100%/var(--kvw-handle-zoom, 1))}.kviewer-placed-chrome__grip-dots{color:var(--kvw-field-color,#1677ff);flex:0 0 auto;height:13px;width:13px}.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:8px;pointer-events:auto;position:absolute;touch-action:none;transform:scale(var(--kvw-handle-zoom,1));width:8px}.kviewer-placed-chrome__handle:before{content:"";inset:-8px;position:absolute}.kviewer-placed-chrome__handle--n{left:50%;top:-4px;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--s{bottom:-4px;left:50%;transform:translateX(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--e{right:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--w{left:-4px;top:50%;transform:translateY(-50%) scale(var(--kvw-handle-zoom,1))}.kviewer-placed-chrome__handle--ne{right:-4px;top:-4px}.kviewer-placed-chrome__handle--nw{left:-4px;top:-4px}.kviewer-placed-chrome__handle--se{bottom:-4px;right:-4px}.kviewer-placed-chrome__handle--sw{bottom:-4px;left:-4px}
|
|
131
148
|
</style>
|
|
@@ -6,6 +6,10 @@ type __VLS_Props = {
|
|
|
6
6
|
scale: number;
|
|
7
7
|
/** PDF-space page height (kept for parity; unused here). */
|
|
8
8
|
pageHeight: number;
|
|
9
|
+
/** 'authoring' = form-edit mode (chrome is the whole interactive
|
|
10
|
+
* surface). 'live' = editablePlacedFields outside form-edit mode
|
|
11
|
+
* (chrome is transparent except grip and handles). */
|
|
12
|
+
mode: 'authoring' | 'live';
|
|
9
13
|
};
|
|
10
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
11
15
|
select: () => any;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FormFieldDefinition } from '../../annotation/engine/types.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
field: FormFieldDefinition;
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Same pill as AnnotationToolbar, but anchored in the page DOM below
|
|
3
|
+
the selected field so it follows pan/zoom for free (the Konva
|
|
4
|
+
selection popover is viewport-fixed because the stage has no DOM
|
|
5
|
+
anchor to attach to). Stops pointer events so a click here never
|
|
6
|
+
reaches page-level deselect handlers. -->
|
|
7
|
+
<div
|
|
8
|
+
class="kviewer-placed-toolbar"
|
|
9
|
+
@pointerdown.stop
|
|
10
|
+
@click.stop
|
|
11
|
+
>
|
|
12
|
+
<div class="flex items-center gap-0.5 bg-elevated rounded-lg shadow-lg border border-default p-1">
|
|
13
|
+
<SelectionToolbarItems :items="customItems" :ctx="selectionCtx" />
|
|
14
|
+
|
|
15
|
+
<button
|
|
16
|
+
class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
|
|
17
|
+
:title="t('deleteField')"
|
|
18
|
+
data-testid="placed-field-delete"
|
|
19
|
+
@click="onDelete"
|
|
20
|
+
>
|
|
21
|
+
<UIcon name="i-lucide-trash-2" class="text-sm text-error" />
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { computed, inject } from "vue";
|
|
29
|
+
import { useFormFields } from "../../composables/useFormFields";
|
|
30
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
31
|
+
import { SELECTION_ITEMS_KEY } from "../../selection-items";
|
|
32
|
+
import SelectionToolbarItems from "../SelectionToolbarItems.vue";
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
field: { type: Object, required: true }
|
|
35
|
+
});
|
|
36
|
+
const formFields = useFormFields();
|
|
37
|
+
const { t } = useKviewerI18n();
|
|
38
|
+
const customItems = inject(SELECTION_ITEMS_KEY, void 0);
|
|
39
|
+
const selectionCtx = computed(() => ({
|
|
40
|
+
kind: "field",
|
|
41
|
+
annotations: [],
|
|
42
|
+
field: props.field
|
|
43
|
+
}));
|
|
44
|
+
function onDelete() {
|
|
45
|
+
formFields.removePlacedField(props.field.id);
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style>
|
|
50
|
+
.kviewer-placed-toolbar{left:50%;pointer-events:auto;position:absolute;top:calc(100% + 8px*var(--kvw-handle-zoom, 1));transform:translateX(-50%) scale(var(--kvw-handle-zoom,1));transform-origin:50% 0;width:-moz-max-content;width:max-content;z-index:1}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FormFieldDefinition } from '../../annotation/engine/types.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
field: FormFieldDefinition;
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -27,10 +27,22 @@ export interface FormFieldsState {
|
|
|
27
27
|
* `addPlacedField` with full control over the field's properties.
|
|
28
28
|
* Returns the created definition. */
|
|
29
29
|
addFormField: (payload: AddFormFieldPayload) => FormFieldDefinition;
|
|
30
|
-
/** Update a form field's rect, name, or flags.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
/** Update a form field's rect, name, or flags. Notifies the field-event
|
|
31
|
+
* sink unless `opts.silent` — pass silent from the public API (the host
|
|
32
|
+
* knows about its own calls) and from high-frequency gesture updates
|
|
33
|
+
* (commit one non-silent update on gesture end instead). */
|
|
34
|
+
updatePlacedField: (id: string, patch: Partial<FormFieldDefinition>, opts?: {
|
|
35
|
+
silent?: boolean;
|
|
36
|
+
}) => void;
|
|
37
|
+
/** Remove a form field and its value. Same `silent` contract as
|
|
38
|
+
* `updatePlacedField`. */
|
|
39
|
+
removePlacedField: (id: string, opts?: {
|
|
40
|
+
silent?: boolean;
|
|
41
|
+
}) => void;
|
|
42
|
+
/** Install the sink that receives user-driven field mutations. Viewer.vue
|
|
43
|
+
* owns it and fans out to the `field-updated` / `field-removed` emits and
|
|
44
|
+
* the embed bridge. Survives document changes (unlike the value sink). */
|
|
45
|
+
setFieldEventSink: (sink: FieldEventSink | null) => void;
|
|
34
46
|
/** All form-field definitions, flattened across pages. */
|
|
35
47
|
getAllFields: () => FormFieldDefinition[];
|
|
36
48
|
/** All user-placed field definitions (used by export). */
|
|
@@ -45,19 +57,6 @@ export interface FormFieldsState {
|
|
|
45
57
|
selectedPlacedFieldId: Ref<string | null>;
|
|
46
58
|
/** Select or deselect a placed field. Pass null to deselect. */
|
|
47
59
|
selectPlacedField: (id: string | null) => void;
|
|
48
|
-
/** Opaque role id new placements are auto-tagged with. The host owns
|
|
49
|
-
* what roles exist; the editor only carries the active id forward to
|
|
50
|
-
* newly placed fields and looks up its color via roleColors. */
|
|
51
|
-
activeRoleId: Ref<string | null>;
|
|
52
|
-
/** Set or clear the active role for upcoming placements. */
|
|
53
|
-
setActiveRole: (id: string | null) => void;
|
|
54
|
-
/** Map from roleId → CSS color, supplied by the host via the KViewer
|
|
55
|
-
* `roleColors` prop. Reading this drives the chrome and placement
|
|
56
|
-
* preview color. The editor never mutates role definitions itself. */
|
|
57
|
-
roleColors: ShallowRef<Record<string, string>>;
|
|
58
|
-
/** Replace the host-provided color map. Called by KViewer when the
|
|
59
|
-
* prop changes. */
|
|
60
|
-
setRoleColors: (colors: Record<string, string>) => void;
|
|
61
60
|
/** Per-field-type presets merged into every tool placement. Host-owned
|
|
62
61
|
* via the KViewer `fieldDefaults` prop or the bridge; programmatic
|
|
63
62
|
* `addFormField` calls are NOT affected (the payload is explicit). */
|
|
@@ -110,5 +109,17 @@ export interface FormFieldsState {
|
|
|
110
109
|
/** Sink signature used by [[setValueWriteSink]]. Receives every user-driven
|
|
111
110
|
* field value write (the primary id AND each mirrored sibling). */
|
|
112
111
|
export type ValueWriteSink = (fieldId: string, value: string | boolean | string[]) => void;
|
|
112
|
+
/** User-driven field mutation, delivered to the sink installed via
|
|
113
|
+
* [[setFieldEventSink]]. `field` is the post-mutation definition for
|
|
114
|
+
* updates and the last-known definition for removals. */
|
|
115
|
+
export type FieldEvent = {
|
|
116
|
+
type: 'updated';
|
|
117
|
+
field: FormFieldDefinition;
|
|
118
|
+
patch: Partial<FormFieldDefinition>;
|
|
119
|
+
} | {
|
|
120
|
+
type: 'removed';
|
|
121
|
+
field: FormFieldDefinition;
|
|
122
|
+
};
|
|
123
|
+
export type FieldEventSink = (event: FieldEvent) => void;
|
|
113
124
|
export declare function provideFormFields(): FormFieldsState;
|
|
114
125
|
export declare function useFormFields(): FormFieldsState;
|
|
@@ -12,16 +12,8 @@ export function provideFormFields() {
|
|
|
12
12
|
const fieldDefinitions = shallowRef(/* @__PURE__ */ new Map());
|
|
13
13
|
const fieldValues = shallowRef(/* @__PURE__ */ new Map());
|
|
14
14
|
const selectedPlacedFieldId = ref(null);
|
|
15
|
-
const activeRoleId = ref(null);
|
|
16
|
-
const roleColors = shallowRef({});
|
|
17
15
|
const placedFieldDefaults = shallowRef({});
|
|
18
16
|
const editablePlacedFields = ref(false);
|
|
19
|
-
function setActiveRole(id) {
|
|
20
|
-
activeRoleId.value = id;
|
|
21
|
-
}
|
|
22
|
-
function setRoleColors(colors) {
|
|
23
|
-
roleColors.value = colors;
|
|
24
|
-
}
|
|
25
17
|
function setPlacedFieldDefaults(defaults) {
|
|
26
18
|
placedFieldDefaults.value = defaults;
|
|
27
19
|
}
|
|
@@ -43,6 +35,10 @@ export function provideFormFields() {
|
|
|
43
35
|
function setValueWriteSink(sink) {
|
|
44
36
|
valueWriteSink = sink;
|
|
45
37
|
}
|
|
38
|
+
let fieldEventSink = null;
|
|
39
|
+
function setFieldEventSink(sink) {
|
|
40
|
+
fieldEventSink = sink;
|
|
41
|
+
}
|
|
46
42
|
let checkboxStyleMap = null;
|
|
47
43
|
let buttonCaptionMap = null;
|
|
48
44
|
let signedSignatureNames = null;
|
|
@@ -297,9 +293,7 @@ export function provideFormFields() {
|
|
|
297
293
|
)
|
|
298
294
|
);
|
|
299
295
|
}
|
|
300
|
-
if (
|
|
301
|
-
def.roleId = activeRoleId.value;
|
|
302
|
-
}
|
|
296
|
+
if (def.meta) def.meta = { ...def.meta };
|
|
303
297
|
if (payload.fieldType === "radio") {
|
|
304
298
|
def.buttonValue = nextRadioOptionValue(fieldName);
|
|
305
299
|
}
|
|
@@ -340,7 +334,8 @@ export function provideFormFields() {
|
|
|
340
334
|
required: payload.required ?? false,
|
|
341
335
|
origin: "placed"
|
|
342
336
|
};
|
|
343
|
-
if (payload.
|
|
337
|
+
if (payload.fieldColor !== void 0) def.fieldColor = payload.fieldColor;
|
|
338
|
+
if (payload.meta !== void 0) def.meta = { ...payload.meta };
|
|
344
339
|
if (payload.defaultValue !== void 0) def.defaultValue = payload.defaultValue;
|
|
345
340
|
if (payload.fontSize !== void 0) def.fontSize = payload.fontSize;
|
|
346
341
|
if (payload.fontName !== void 0) def.fontName = payload.fontName;
|
|
@@ -401,7 +396,7 @@ export function provideFormFields() {
|
|
|
401
396
|
while (used.has(`Option_${n}`)) n += 1;
|
|
402
397
|
return `Option_${n}`;
|
|
403
398
|
}
|
|
404
|
-
function updatePlacedField(id, patch) {
|
|
399
|
+
function updatePlacedField(id, patch, opts) {
|
|
405
400
|
let found;
|
|
406
401
|
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
407
402
|
const idx = defs.findIndex((d) => d.id === id);
|
|
@@ -422,12 +417,16 @@ export function provideFormFields() {
|
|
|
422
417
|
fv.fieldType = found.fieldType;
|
|
423
418
|
triggerRef(fieldValues);
|
|
424
419
|
}
|
|
420
|
+
if (!opts?.silent && fieldEventSink) {
|
|
421
|
+
fieldEventSink({ type: "updated", field: found, patch });
|
|
422
|
+
}
|
|
425
423
|
}
|
|
426
|
-
function removePlacedField(id) {
|
|
427
|
-
let removed
|
|
424
|
+
function removePlacedField(id, opts) {
|
|
425
|
+
let removed;
|
|
428
426
|
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
429
427
|
const idx = defs.findIndex((d) => d.id === id);
|
|
430
428
|
if (idx === -1) continue;
|
|
429
|
+
removed = defs[idx];
|
|
431
430
|
const nextDefs = defs.slice();
|
|
432
431
|
nextDefs.splice(idx, 1);
|
|
433
432
|
if (nextDefs.length === 0) {
|
|
@@ -435,7 +434,6 @@ export function provideFormFields() {
|
|
|
435
434
|
} else {
|
|
436
435
|
fieldDefinitions.value.set(page, nextDefs);
|
|
437
436
|
}
|
|
438
|
-
removed = true;
|
|
439
437
|
break;
|
|
440
438
|
}
|
|
441
439
|
if (!removed) return;
|
|
@@ -446,6 +444,9 @@ export function provideFormFields() {
|
|
|
446
444
|
if (selectedPlacedFieldId.value === id) {
|
|
447
445
|
selectedPlacedFieldId.value = null;
|
|
448
446
|
}
|
|
447
|
+
if (!opts?.silent && fieldEventSink) {
|
|
448
|
+
fieldEventSink({ type: "removed", field: removed });
|
|
449
|
+
}
|
|
449
450
|
}
|
|
450
451
|
function getPlacedFields() {
|
|
451
452
|
const out = [];
|
|
@@ -477,14 +478,12 @@ export function provideFormFields() {
|
|
|
477
478
|
if (d.fieldType !== "signature") continue;
|
|
478
479
|
const fv = fieldValues.value.get(d.id);
|
|
479
480
|
const signed = typeof fv?.value === "string" && fv.value !== "" || d.signedInSource === true;
|
|
480
|
-
|
|
481
|
+
out.push({
|
|
481
482
|
id: d.id,
|
|
482
483
|
fieldName: d.fieldName,
|
|
483
484
|
pageNumber: d.pageNumber,
|
|
484
485
|
signed
|
|
485
|
-
};
|
|
486
|
-
if (d.roleId) status.roleId = d.roleId;
|
|
487
|
-
out.push(status);
|
|
486
|
+
});
|
|
488
487
|
}
|
|
489
488
|
}
|
|
490
489
|
return out;
|
|
@@ -556,11 +555,8 @@ export function provideFormFields() {
|
|
|
556
555
|
resetValues,
|
|
557
556
|
reset,
|
|
558
557
|
setValueWriteSink,
|
|
558
|
+
setFieldEventSink,
|
|
559
559
|
applyFieldValueFromExternal,
|
|
560
|
-
activeRoleId,
|
|
561
|
-
setActiveRole,
|
|
562
|
-
roleColors,
|
|
563
|
-
setRoleColors,
|
|
564
560
|
placedFieldDefaults,
|
|
565
561
|
setPlacedFieldDefaults,
|
|
566
562
|
editablePlacedFields,
|
|
@@ -21,7 +21,8 @@ type EventHandler<E extends EventName> = (payload: EventPayloads[E]) => void;
|
|
|
21
21
|
* an iframe. Wraps `postMessage` into a Promise-based RPC mirroring the
|
|
22
22
|
* viewer's exposed methods, and surfaces iframe-emitted events
|
|
23
23
|
* (`ready`, `formEditMode-changed`, `viewedPages-changed`, `all-pages-read`,
|
|
24
|
-
* `field-placed`, `signedStatus-changed`)
|
|
24
|
+
* `field-placed`, `field-updated`, `field-removed`, `signedStatus-changed`)
|
|
25
|
+
* via `on()`.
|
|
25
26
|
*/
|
|
26
27
|
export declare class KViewerEmbedClient {
|
|
27
28
|
private readonly iframe;
|
|
@@ -68,7 +69,8 @@ export declare class KViewerEmbedClient {
|
|
|
68
69
|
* a push-style signal subscribe via `on('signedStatus-changed', …)`. */
|
|
69
70
|
getSignedStatus(): Promise<boolean>;
|
|
70
71
|
/** Per-widget signed-state snapshot (id, fieldName, pageNumber,
|
|
71
|
-
* signed
|
|
72
|
+
* signed). Correlate to host data by `id` (see the `meta` bag on
|
|
73
|
+
* `getFormFields()` for grouping, e.g. by signer). */
|
|
72
74
|
getSignatureFieldStatus(): Promise<SignatureFieldStatus[]>;
|
|
73
75
|
getFormEditMode(): Promise<boolean>;
|
|
74
76
|
setFormEditMode(enabled: boolean): Promise<void>;
|
|
@@ -104,7 +104,8 @@ export class KViewerEmbedClient {
|
|
|
104
104
|
return this.call("getSignedStatus");
|
|
105
105
|
}
|
|
106
106
|
/** Per-widget signed-state snapshot (id, fieldName, pageNumber,
|
|
107
|
-
* signed
|
|
107
|
+
* signed). Correlate to host data by `id` (see the `meta` bag on
|
|
108
|
+
* `getFormFields()` for grouping, e.g. by signer). */
|
|
108
109
|
getSignatureFieldStatus() {
|
|
109
110
|
return this.call("getSignatureFieldStatus");
|
|
110
111
|
}
|
|
@@ -27,6 +27,8 @@ export interface KViewerApi {
|
|
|
27
27
|
setFieldDefaults: (defaults: PlacedFieldDefaults) => void;
|
|
28
28
|
setClickToSign: (enabled: boolean) => void;
|
|
29
29
|
onFieldPlaced: (cb: (field: FormFieldDefinition) => void) => () => void;
|
|
30
|
+
onFieldUpdated: (cb: (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => void) => () => void;
|
|
31
|
+
onFieldRemoved: (cb: (field: FormFieldDefinition) => void) => () => void;
|
|
30
32
|
getSignedStatus: () => boolean;
|
|
31
33
|
getSignatureFieldStatus: () => SignatureFieldStatus[];
|
|
32
34
|
onSignedStatusChanged: (cb: (payload: {
|
|
@@ -202,6 +202,22 @@ export function useKViewerEmbedBridge(viewerRef, options) {
|
|
|
202
202
|
)
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
|
+
if (typeof viewer.onFieldUpdated === "function") {
|
|
206
|
+
events.push("field-updated");
|
|
207
|
+
stopViewerStateWatches.push(
|
|
208
|
+
viewer.onFieldUpdated(
|
|
209
|
+
(field, patch) => postEvent("field-updated", { field, patch })
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (typeof viewer.onFieldRemoved === "function") {
|
|
214
|
+
events.push("field-removed");
|
|
215
|
+
stopViewerStateWatches.push(
|
|
216
|
+
viewer.onFieldRemoved(
|
|
217
|
+
(field) => postEvent("field-removed", { field })
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
205
221
|
if (typeof viewer.onSignedStatusChanged === "function") {
|
|
206
222
|
events.push("signedStatus-changed");
|
|
207
223
|
stopViewerStateWatches.push(
|
|
@@ -150,6 +150,18 @@ export interface EventPayloads {
|
|
|
150
150
|
'field-placed': {
|
|
151
151
|
field: FormFieldDefinition;
|
|
152
152
|
};
|
|
153
|
+
/** The user changed a field through the viewer UI (moved/resized it or
|
|
154
|
+
* edited its properties). Not fired for `updateFormField()` calls.
|
|
155
|
+
* Continuous edits may fire repeatedly — treat as an upsert. */
|
|
156
|
+
'field-updated': {
|
|
157
|
+
field: FormFieldDefinition;
|
|
158
|
+
patch: Partial<FormFieldDefinition>;
|
|
159
|
+
};
|
|
160
|
+
/** The user removed a field through the viewer UI. Not fired for
|
|
161
|
+
* `removeFormField()` calls. `field` is the last-known definition. */
|
|
162
|
+
'field-removed': {
|
|
163
|
+
field: FormFieldDefinition;
|
|
164
|
+
};
|
|
153
165
|
/** A signature field's signed state changed (signed, cleared, added,
|
|
154
166
|
* removed, or renamed). Push-style counterpart to `getSignedStatus()`. */
|
|
155
167
|
'signedStatus-changed': {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { ExportPdfOptions } from './annotation/pdf-export/export.js';
|
|
2
2
|
export type { ViewerTabItem, AddTabOptions } from './components/ViewerTabs.vue.js';
|
|
3
|
-
export type { AddFormFieldPayload, CheckboxStyle, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, PlacedFieldDefaultProps, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, } from './annotation/engine/types.js';
|
|
3
|
+
export type { AddFormFieldPayload, CheckboxStyle, FormFieldDefinition, FormFieldOrigin, FormFieldType, FormFieldValue, IAnnotationStore, PlacedFieldDefaultProps, PlacedFieldDefaults, SignatureData, SignatureFieldStatus, SignatureHandlers, } from './annotation/engine/types.js';
|
|
4
4
|
export type { ViewMode } from './composables/viewMode.js';
|
|
5
5
|
export type { ViewerState, ActiveTool } from './composables/useViewerState.js';
|
|
6
6
|
export type { KViewerApi, KViewerHostApi, KViewerEmbedBridgeOptions, } from './embed/bridge-host.js';
|
|
@@ -10,4 +10,5 @@ export { KViewerEmbedClient } from './embed/bridge-client.js';
|
|
|
10
10
|
export type { EventName as KViewerEmbedEventName, EventPayloads as KViewerEmbedEventPayloads, MethodName as KViewerEmbedMethodName, } from './embed/protocol.js';
|
|
11
11
|
export { KVIEWER_EMBED_PROTOCOL_VERSION, UNSUPPORTED_METHOD_ERROR_NAME as KVIEWER_EMBED_UNSUPPORTED_METHOD_ERROR_NAME, } from './embed/protocol.js';
|
|
12
12
|
export type { ViewerMenuItem, ViewerMenuButtonItem, ViewerMenuCheckboxItem, ViewerMenuSeparatorItem, } from './menu-items.js';
|
|
13
|
+
export type { ViewerSelectionItem, ViewerSelectionButtonItem, ViewerSelectionSelectItem, ViewerSelectionContext, } from './selection-items.js';
|
|
13
14
|
export type { ViewerToolId, ViewerTopToolId, ViewerBottomToolId, ViewerLayoutToolId, ViewerToolEntry, ViewerToolSlot, } from './toolbar-tools.js';
|