kviewer 0.3.4 → 0.3.6
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.json +1 -1
- package/dist/runtime/annotation/engine/painter.d.ts +2 -2
- 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/assets/kviewer.css +1 -1
- package/dist/runtime/components/Viewer.d.vue.ts +21 -17
- package/dist/runtime/components/Viewer.vue +62 -40
- package/dist/runtime/components/Viewer.vue.d.ts +21 -17
- package/dist/runtime/components/ViewerTabs.d.vue.ts +12 -17
- package/dist/runtime/components/ViewerTabs.vue +3 -6
- package/dist/runtime/components/ViewerTabs.vue.d.ts +12 -17
- package/dist/runtime/components/form-fields/FormFieldWrapper.vue +21 -9
- 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 +27 -10
- package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +4 -0
- package/dist/runtime/composables/useFormFields.d.ts +28 -17
- package/dist/runtime/composables/useFormFields.js +20 -24
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +8 -2
- package/dist/runtime/composables/useInertiaPanzoom.js +36 -12
- 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 +4 -1
- package/dist/runtime/embed/bridge-host.js +24 -0
- package/dist/runtime/embed/protocol.d.ts +19 -0
- package/dist/runtime/public-types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -63,13 +63,12 @@
|
|
|
63
63
|
:menu-items="menuItems"
|
|
64
64
|
:selection-items="selectionItems"
|
|
65
65
|
:tools="tools"
|
|
66
|
-
:role-colors="roleColors"
|
|
67
|
-
:active-role-id="activeRoleId"
|
|
68
66
|
:click-to-sign="clickToSign"
|
|
69
67
|
:editable-placed-fields="editablePlacedFields"
|
|
70
68
|
:field-defaults="fieldDefaults"
|
|
71
|
-
@update:active-role-id="(v) => emit('update:activeRoleId', v)"
|
|
72
69
|
@field-placed="(f) => emit('field-placed', f)"
|
|
70
|
+
@field-updated="(f, p) => emit('field-updated', f, p)"
|
|
71
|
+
@field-removed="(f) => emit('field-removed', f)"
|
|
73
72
|
@signed-status-changed="(p) => emit('signed-status-changed', p)"
|
|
74
73
|
>
|
|
75
74
|
<template v-if="$slots.header" #header>
|
|
@@ -109,8 +108,6 @@ const props = defineProps({
|
|
|
109
108
|
viewMode: { type: String, required: false, default: "fit-width" },
|
|
110
109
|
zoom: { type: Number, required: false, default: 1 },
|
|
111
110
|
minTabs: { type: Number, required: false, default: 0 },
|
|
112
|
-
roleColors: { type: Object, required: false, default: void 0 },
|
|
113
|
-
activeRoleId: { type: [String, null], required: false, default: void 0 },
|
|
114
111
|
scripting: { type: Boolean, required: false, default: false },
|
|
115
112
|
menuItems: { type: Array, required: false, default: void 0 },
|
|
116
113
|
selectionItems: { type: Array, required: false, default: void 0 },
|
|
@@ -123,7 +120,7 @@ const slots = useSlots();
|
|
|
123
120
|
const toolSlotNames = computed(
|
|
124
121
|
() => Object.keys(slots).filter((name) => name.startsWith("tool-"))
|
|
125
122
|
);
|
|
126
|
-
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "
|
|
123
|
+
const emit = defineEmits(["update:activeTab", "tab-added", "tab-close", "tab-removed", "field-placed", "field-updated", "field-removed", "signed-status-changed"]);
|
|
127
124
|
let idCounter = 0;
|
|
128
125
|
function generateId() {
|
|
129
126
|
return `tab-${Date.now()}-${++idCounter}`;
|
|
@@ -54,11 +54,6 @@ type __VLS_Props = {
|
|
|
54
54
|
zoom?: number;
|
|
55
55
|
/** Minimum number of tabs -- prevents closing below this count. */
|
|
56
56
|
minTabs?: number;
|
|
57
|
-
/** Forwarded to every Viewer instance — see KViewer's `roleColors`. */
|
|
58
|
-
roleColors?: Record<string, string>;
|
|
59
|
-
/** Forwarded to every Viewer instance — see KViewer's `activeRoleId`.
|
|
60
|
-
* Supports v-model via `v-model:active-role-id`. */
|
|
61
|
-
activeRoleId?: string | null;
|
|
62
57
|
/** Forwarded to every Viewer instance — see KViewer's `scripting`.
|
|
63
58
|
* Per-tab override via `ViewerTabItem.scripting` wins when set. */
|
|
64
59
|
scripting?: boolean;
|
|
@@ -82,7 +77,7 @@ declare function addTab(item: Omit<ViewerTabItem, 'id'> & {
|
|
|
82
77
|
declare function removeTab(id: string): boolean;
|
|
83
78
|
declare function activateTab(id: string): void;
|
|
84
79
|
declare function getViewer(id: string): ComponentPublicInstance | null;
|
|
85
|
-
declare var __VLS_7: {}, __VLS_21: {},
|
|
80
|
+
declare var __VLS_7: {}, __VLS_21: {}, __VLS_35: {
|
|
86
81
|
tab: {
|
|
87
82
|
id: string;
|
|
88
83
|
label: string;
|
|
@@ -182,7 +177,7 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
|
|
|
182
177
|
shapeDetection?: boolean | undefined;
|
|
183
178
|
scripting?: boolean | undefined;
|
|
184
179
|
};
|
|
185
|
-
},
|
|
180
|
+
}, __VLS_38: {
|
|
186
181
|
tab: {
|
|
187
182
|
id: string;
|
|
188
183
|
label: string;
|
|
@@ -282,21 +277,21 @@ declare var __VLS_7: {}, __VLS_21: {}, __VLS_34: {
|
|
|
282
277
|
shapeDetection?: boolean | undefined;
|
|
283
278
|
scripting?: boolean | undefined;
|
|
284
279
|
};
|
|
285
|
-
},
|
|
280
|
+
}, __VLS_42: string, __VLS_43: {
|
|
286
281
|
state: import("../composables/useViewerState.js").ViewerState;
|
|
287
|
-
},
|
|
282
|
+
}, __VLS_45: {};
|
|
288
283
|
type __VLS_Slots = {} & {
|
|
289
|
-
[K in NonNullable<typeof
|
|
284
|
+
[K in NonNullable<typeof __VLS_42>]?: (props: typeof __VLS_43) => any;
|
|
290
285
|
} & {
|
|
291
286
|
'tabs-leading'?: (props: typeof __VLS_7) => any;
|
|
292
287
|
} & {
|
|
293
288
|
'tabs-trailing'?: (props: typeof __VLS_21) => any;
|
|
294
289
|
} & {
|
|
295
|
-
header?: (props: typeof
|
|
290
|
+
header?: (props: typeof __VLS_35) => any;
|
|
296
291
|
} & {
|
|
297
|
-
footer?: (props: typeof
|
|
292
|
+
footer?: (props: typeof __VLS_38) => any;
|
|
298
293
|
} & {
|
|
299
|
-
empty?: (props: typeof
|
|
294
|
+
empty?: (props: typeof __VLS_45) => any;
|
|
300
295
|
};
|
|
301
296
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
302
297
|
addTab: typeof addTab;
|
|
@@ -405,7 +400,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
405
400
|
}[];
|
|
406
401
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
407
402
|
"field-placed": (field: FormFieldDefinition) => any;
|
|
408
|
-
"
|
|
403
|
+
"field-updated": (field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any;
|
|
404
|
+
"field-removed": (field: FormFieldDefinition) => any;
|
|
409
405
|
"signed-status-changed": (payload: {
|
|
410
406
|
allSigned: boolean;
|
|
411
407
|
fields: SignatureFieldStatus[];
|
|
@@ -416,7 +412,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
416
412
|
"tab-removed": (id: string) => any;
|
|
417
413
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
418
414
|
"onField-placed"?: ((field: FormFieldDefinition) => any) | undefined;
|
|
419
|
-
"
|
|
415
|
+
"onField-updated"?: ((field: FormFieldDefinition, patch: Partial<FormFieldDefinition>) => any) | undefined;
|
|
416
|
+
"onField-removed"?: ((field: FormFieldDefinition) => any) | undefined;
|
|
420
417
|
"onSigned-status-changed"?: ((payload: {
|
|
421
418
|
allSigned: boolean;
|
|
422
419
|
fields: SignatureFieldStatus[];
|
|
@@ -435,8 +432,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
435
432
|
stamps: StampDefinition[];
|
|
436
433
|
signatureHandlers: SignatureHandlers;
|
|
437
434
|
viewMode: ViewMode;
|
|
438
|
-
roleColors: Record<string, string>;
|
|
439
|
-
activeRoleId: string | null;
|
|
440
435
|
editablePlacedFields: boolean;
|
|
441
436
|
fieldDefaults: PlacedFieldDefaults;
|
|
442
437
|
selectionItems: ViewerSelectionItem[];
|
|
@@ -4,17 +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
10
|
:style="[positionStyle, {
|
|
10
11
|
'--kvw-field-color': chromeColor,
|
|
11
12
|
'--kvw-handle-zoom': handleZoom
|
|
12
13
|
}]"
|
|
13
14
|
:data-element-id="props.field.id"
|
|
15
|
+
@contextmenu="onContextMenu"
|
|
14
16
|
>
|
|
15
17
|
<div
|
|
16
18
|
class="kviewer-form-field__body"
|
|
17
|
-
:class="{ 'kviewer-form-field__body--locked':
|
|
19
|
+
:class="{ 'kviewer-form-field__body--locked': chromeMode === 'authoring' }"
|
|
18
20
|
>
|
|
19
21
|
<FormTextField v-if="props.field.fieldType === 'text'" :field="props.field" />
|
|
20
22
|
<FormCheckbox v-else-if="props.field.fieldType === 'checkbox'" :field="props.field" />
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
:selected="props.selected"
|
|
31
33
|
:scale="props.scale"
|
|
32
34
|
:page-height="props.pageTransform.height"
|
|
35
|
+
:mode="chromeMode ?? 'authoring'"
|
|
33
36
|
@select="emit('select')"
|
|
34
37
|
/>
|
|
35
38
|
|
|
@@ -64,18 +67,27 @@ const state = useViewerState();
|
|
|
64
67
|
const formFields = useFormFields();
|
|
65
68
|
const isPlaced = computed(() => props.field.origin === "placed");
|
|
66
69
|
const isFormEditMode = computed(() => state.formEditMode.value);
|
|
67
|
-
const
|
|
68
|
-
(
|
|
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
|
|
69
80
|
);
|
|
81
|
+
function onContextMenu(e) {
|
|
82
|
+
if (chromeMode.value !== "live") return;
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
emit("select");
|
|
85
|
+
}
|
|
70
86
|
const handleZoom = computed(() => {
|
|
71
87
|
const totalZoom = (props.scale || 1) * (state.visualZoom.value || 1);
|
|
72
88
|
return (1 / totalZoom).toFixed(3);
|
|
73
89
|
});
|
|
74
|
-
const chromeColor = computed(() =>
|
|
75
|
-
const id = props.field.roleId;
|
|
76
|
-
if (!id) return "#1677ff";
|
|
77
|
-
return formFields.roleColors.value[id] ?? "#1677ff";
|
|
78
|
-
});
|
|
90
|
+
const chromeColor = computed(() => props.field.fieldColor ?? "#1677ff");
|
|
79
91
|
const positionStyle = computed(() => {
|
|
80
92
|
const rect = props.field.rect;
|
|
81
93
|
const r = convertPdfRectToCanvas(props.pageTransform, [rect[0], rect[1], rect[2], rect[3]]);
|
|
@@ -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,11 +1,16 @@
|
|
|
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="{
|
|
10
|
+
:class="{
|
|
11
|
+
'kviewer-placed-chrome--selected': selected,
|
|
12
|
+
'kviewer-placed-chrome--live': mode === 'live'
|
|
13
|
+
}"
|
|
9
14
|
@pointerdown.stop="onChromePointerDown"
|
|
10
15
|
@click.stop
|
|
11
16
|
>
|
|
@@ -17,9 +22,11 @@
|
|
|
17
22
|
}"
|
|
18
23
|
/>
|
|
19
24
|
<div
|
|
20
|
-
v-if="selected"
|
|
25
|
+
v-if="selected || mode === 'live'"
|
|
21
26
|
class="kviewer-placed-chrome__grip"
|
|
22
27
|
:title="field.fieldName"
|
|
28
|
+
@pointerdown.stop="onChromePointerDown"
|
|
29
|
+
@click.stop
|
|
23
30
|
>
|
|
24
31
|
<UIcon name="i-lucide-grip-vertical" class="kviewer-placed-chrome__grip-dots" />
|
|
25
32
|
<span class="kviewer-placed-chrome__grip-label">{{ field.fieldName }}</span>
|
|
@@ -46,7 +53,8 @@ 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__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}
|
|
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;
|
|
@@ -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,
|
|
@@ -17,6 +17,11 @@ export interface Transform {
|
|
|
17
17
|
tx: number;
|
|
18
18
|
ty: number;
|
|
19
19
|
}
|
|
20
|
+
export interface ScrollDelta {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function getUnconsumedScrollDelta(requested: ScrollDelta, before: Transform, after: Transform): ScrollDelta;
|
|
20
25
|
export interface InertiaPanzoomHandle {
|
|
21
26
|
/** Bind gesture handlers to the given element (its parent becomes the viewport). */
|
|
22
27
|
attach: (target: HTMLElement) => void;
|
|
@@ -30,8 +35,9 @@ export interface InertiaPanzoomHandle {
|
|
|
30
35
|
getTransform: () => Transform;
|
|
31
36
|
/** Animate the view so `elem` is at the top (or center) of the viewport. */
|
|
32
37
|
scrollToElement: (elem: HTMLElement, opts?: ScrollToElementOptions) => void;
|
|
33
|
-
/** Pan by a delta in viewport pixels
|
|
34
|
-
|
|
38
|
+
/** Pan by a delta in viewport pixels and return the portion that could not
|
|
39
|
+
* be consumed because the viewport reached a boundary. */
|
|
40
|
+
scrollBy: (dx: number, dy: number) => ScrollDelta;
|
|
35
41
|
/** Enable/disable pan+pinch gestures (wheel/scrollbar/keyboard still work). */
|
|
36
42
|
setGesturesEnabled: (enabled: boolean) => void;
|
|
37
43
|
/** Subscribe to transform updates. Returns an unsubscribe function. */
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { onBeforeUnmount } from "vue";
|
|
2
|
+
export function getUnconsumedScrollDelta(requested, before, after) {
|
|
3
|
+
return {
|
|
4
|
+
x: requested.x - (before.tx - after.tx),
|
|
5
|
+
y: requested.y - (before.ty - after.ty)
|
|
6
|
+
};
|
|
7
|
+
}
|
|
2
8
|
const DECELERATION = 0.998;
|
|
3
9
|
const RUBBER_BAND_C = 0.55;
|
|
4
10
|
const BOUNCE_BACK_RATE = 0.04;
|
|
5
11
|
const BOUNCE_VELOCITY_DECAY = 0.04;
|
|
6
12
|
const MIN_VELOCITY = 0.01;
|
|
7
13
|
export function useInertiaPanzoom(options = {}) {
|
|
8
|
-
const {
|
|
9
|
-
axisLockThreshold = 10,
|
|
10
|
-
axisLockRatio = 2
|
|
11
|
-
} = options;
|
|
14
|
+
const { axisLockThreshold = 10, axisLockRatio = 2 } = options;
|
|
12
15
|
let minScale = options.minScale ?? 0.5;
|
|
13
16
|
let maxScale = options.maxScale ?? 4;
|
|
14
17
|
const subscribers = /* @__PURE__ */ new Set();
|
|
@@ -139,11 +142,17 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
139
142
|
if (barDrag.axis === "y") {
|
|
140
143
|
const dy = e.clientY - barDrag.startPointer;
|
|
141
144
|
const b = getBounds();
|
|
142
|
-
ty = Math.max(
|
|
145
|
+
ty = Math.max(
|
|
146
|
+
b.minY,
|
|
147
|
+
Math.min(b.maxY, barDrag.startT - dy * barDrag.ratio)
|
|
148
|
+
);
|
|
143
149
|
} else {
|
|
144
150
|
const dx = e.clientX - barDrag.startPointer;
|
|
145
151
|
const b = getBounds();
|
|
146
|
-
tx = Math.max(
|
|
152
|
+
tx = Math.max(
|
|
153
|
+
b.minX,
|
|
154
|
+
Math.min(b.maxX, barDrag.startT - dx * barDrag.ratio)
|
|
155
|
+
);
|
|
147
156
|
}
|
|
148
157
|
apply();
|
|
149
158
|
}
|
|
@@ -226,8 +235,10 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
226
235
|
return { minX, maxX, minY, maxY };
|
|
227
236
|
}
|
|
228
237
|
function rubberBandAxis(v, min, max, dim) {
|
|
229
|
-
if (v < min)
|
|
230
|
-
|
|
238
|
+
if (v < min)
|
|
239
|
+
return min - (1 - 1 / ((min - v) * RUBBER_BAND_C / dim + 1)) * dim;
|
|
240
|
+
if (v > max)
|
|
241
|
+
return max + (1 - 1 / ((v - max) * RUBBER_BAND_C / dim + 1)) * dim;
|
|
231
242
|
return v;
|
|
232
243
|
}
|
|
233
244
|
function stopMomentum() {
|
|
@@ -320,7 +331,10 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
320
331
|
const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
321
332
|
const midX = (pts[0].x + pts[1].x) / 2;
|
|
322
333
|
const midY = (pts[0].y + pts[1].y) / 2;
|
|
323
|
-
const newScale = Math.max(
|
|
334
|
+
const newScale = Math.max(
|
|
335
|
+
minScale,
|
|
336
|
+
Math.min(maxScale, pinchStart.s * (dist / pinchStart.dist))
|
|
337
|
+
);
|
|
324
338
|
const rect = parent.getBoundingClientRect();
|
|
325
339
|
const focalX = (pinchStart.midX - rect.left - pinchStart.tx) / pinchStart.s;
|
|
326
340
|
const focalY = (pinchStart.midY - rect.top - pinchStart.ty) / pinchStart.s;
|
|
@@ -386,7 +400,10 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
386
400
|
const mx = e.clientX - rect.left;
|
|
387
401
|
const my = e.clientY - rect.top;
|
|
388
402
|
const delta = e.deltaY < 0 ? 1 : -1;
|
|
389
|
-
const newScale = Math.max(
|
|
403
|
+
const newScale = Math.max(
|
|
404
|
+
minScale,
|
|
405
|
+
Math.min(maxScale, scale * Math.exp(delta * 0.1))
|
|
406
|
+
);
|
|
390
407
|
const focalX = (mx - tx) / scale;
|
|
391
408
|
const focalY = (my - ty) / scale;
|
|
392
409
|
tx = mx - newScale * focalX;
|
|
@@ -398,7 +415,8 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
398
415
|
}
|
|
399
416
|
function setTransform(t) {
|
|
400
417
|
stopMomentum();
|
|
401
|
-
if (t.scale !== void 0)
|
|
418
|
+
if (t.scale !== void 0)
|
|
419
|
+
scale = Math.max(minScale, Math.min(maxScale, t.scale));
|
|
402
420
|
if (t.tx !== void 0) tx = t.tx;
|
|
403
421
|
if (t.ty !== void 0) ty = t.ty;
|
|
404
422
|
const b = getBounds();
|
|
@@ -424,9 +442,15 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
424
442
|
function scrollBy(dx, dy) {
|
|
425
443
|
stopMomentum();
|
|
426
444
|
const b = getBounds();
|
|
445
|
+
const before = { scale, tx, ty };
|
|
427
446
|
tx = Math.max(b.minX, Math.min(b.maxX, tx - dx));
|
|
428
447
|
ty = Math.max(b.minY, Math.min(b.maxY, ty - dy));
|
|
429
448
|
apply();
|
|
449
|
+
return getUnconsumedScrollDelta({ x: dx, y: dy }, before, {
|
|
450
|
+
scale,
|
|
451
|
+
tx,
|
|
452
|
+
ty
|
|
453
|
+
});
|
|
430
454
|
}
|
|
431
455
|
parent.addEventListener("pointerdown", onDown);
|
|
432
456
|
parent.addEventListener("pointermove", onMove);
|
|
@@ -504,7 +528,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
504
528
|
getScale: () => controller?.getScale() ?? 1,
|
|
505
529
|
getTransform: () => controller?.getTransform() ?? { scale: 1, tx: 0, ty: 0 },
|
|
506
530
|
scrollToElement: (elem, opts) => controller?.scrollToElement(elem, opts),
|
|
507
|
-
scrollBy: (dx, dy) => controller?.scrollBy(dx, dy),
|
|
531
|
+
scrollBy: (dx, dy) => controller?.scrollBy(dx, dy) ?? { x: dx, y: dy },
|
|
508
532
|
setGesturesEnabled: (enabled) => controller?.setGesturesEnabled(enabled),
|
|
509
533
|
setTransform: (t) => controller?.setTransform(t),
|
|
510
534
|
setScaleLimits: (min, max) => {
|