kviewer 0.0.9 → 0.0.11

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 (74) hide show
  1. package/README.md +29 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -0
  4. package/dist/runtime/annotation/checkbox-styles.d.ts +19 -0
  5. package/dist/runtime/annotation/checkbox-styles.js +27 -0
  6. package/dist/runtime/annotation/engine/config.js +44 -0
  7. package/dist/runtime/annotation/engine/editor/editor.d.ts +6 -3
  8. package/dist/runtime/annotation/engine/editor/editor.js +22 -9
  9. package/dist/runtime/annotation/engine/editor/selector.d.ts +9 -1
  10. package/dist/runtime/annotation/engine/editor/selector.js +72 -30
  11. package/dist/runtime/annotation/engine/painter.d.ts +6 -2
  12. package/dist/runtime/annotation/engine/painter.js +42 -5
  13. package/dist/runtime/annotation/engine/tools/form-field.d.ts +24 -0
  14. package/dist/runtime/annotation/engine/tools/form-field.js +117 -0
  15. package/dist/runtime/annotation/engine/types.d.ts +54 -1
  16. package/dist/runtime/annotation/engine/types.js +4 -0
  17. package/dist/runtime/annotation/font-style.d.ts +23 -0
  18. package/dist/runtime/annotation/font-style.js +57 -0
  19. package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +21 -0
  20. package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +75 -0
  21. package/dist/runtime/annotation/parsers/parseFormFields.js +16 -3
  22. package/dist/runtime/annotation/pdf-export/export-form-fields.d.ts +8 -5
  23. package/dist/runtime/annotation/pdf-export/export-form-fields.js +245 -1
  24. package/dist/runtime/annotation/pdf-export/export.d.ts +3 -2
  25. package/dist/runtime/annotation/pdf-export/export.js +9 -2
  26. package/dist/runtime/assets/kviewer.css +1 -1
  27. package/dist/runtime/components/FormFieldLayer.d.vue.ts +5 -0
  28. package/dist/runtime/components/FormFieldLayer.vue +40 -3
  29. package/dist/runtime/components/FormFieldLayer.vue.d.ts +5 -0
  30. package/dist/runtime/components/PdfPage.d.vue.ts +6 -1
  31. package/dist/runtime/components/PdfPage.vue +75 -18
  32. package/dist/runtime/components/PdfPage.vue.d.ts +6 -1
  33. package/dist/runtime/components/ToolButton.d.vue.ts +4 -2
  34. package/dist/runtime/components/ToolButton.vue.d.ts +4 -2
  35. package/dist/runtime/components/Viewer.d.vue.ts +18 -3
  36. package/dist/runtime/components/Viewer.vue +278 -170
  37. package/dist/runtime/components/Viewer.vue.d.ts +18 -3
  38. package/dist/runtime/components/ViewerBar.vue +24 -2
  39. package/dist/runtime/components/form-fields/FormButton.vue +16 -4
  40. package/dist/runtime/components/form-fields/FormCheckbox.vue +31 -15
  41. package/dist/runtime/components/form-fields/FormDropdown.vue +3 -1
  42. package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +12 -1
  43. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +45 -9
  44. package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +12 -1
  45. package/dist/runtime/components/form-fields/FormRadioButton.vue +3 -1
  46. package/dist/runtime/components/form-fields/FormSignatureField.vue +2 -1
  47. package/dist/runtime/components/form-fields/FormTextField.vue +47 -7
  48. package/dist/runtime/components/form-fields/PlacedFieldChrome.d.vue.ts +16 -0
  49. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue +131 -0
  50. package/dist/runtime/components/form-fields/PlacedFieldChrome.vue.d.ts +16 -0
  51. package/dist/runtime/components/modals/FreeTextModal.vue +27 -3
  52. package/dist/runtime/components/modals/SignatureDrawModal.vue +70 -15
  53. package/dist/runtime/components/panels/PlacedFieldSidebar.d.vue.ts +3 -0
  54. package/dist/runtime/components/panels/PlacedFieldSidebar.vue +505 -0
  55. package/dist/runtime/components/panels/PlacedFieldSidebar.vue.d.ts +3 -0
  56. package/dist/runtime/components/tools/ActionTools.vue +4 -5
  57. package/dist/runtime/components/tools/FormFieldTools.d.vue.ts +3 -0
  58. package/dist/runtime/components/tools/FormFieldTools.vue +35 -0
  59. package/dist/runtime/components/tools/FormFieldTools.vue.d.ts +3 -0
  60. package/dist/runtime/composables/useAnnotationEngine.d.ts +2 -0
  61. package/dist/runtime/composables/useAnnotationEngine.js +6 -2
  62. package/dist/runtime/composables/useAnnotationHistory.d.ts +1 -0
  63. package/dist/runtime/composables/useAnnotationHistory.js +10 -3
  64. package/dist/runtime/composables/useFormFields.d.ts +29 -2
  65. package/dist/runtime/composables/useFormFields.js +259 -5
  66. package/dist/runtime/composables/useInertiaPanzoom.d.ts +40 -0
  67. package/dist/runtime/composables/useInertiaPanzoom.js +475 -0
  68. package/dist/runtime/composables/usePageVirtualization.d.ts +9 -0
  69. package/dist/runtime/composables/usePageVirtualization.js +34 -3
  70. package/dist/runtime/composables/useViewerSearch.d.ts +1 -0
  71. package/dist/runtime/composables/useViewerSearch.js +15 -5
  72. package/dist/runtime/composables/useViewerState.d.ts +3 -0
  73. package/dist/runtime/composables/useViewerState.js +22 -1
  74. package/package.json +2 -2
@@ -0,0 +1,475 @@
1
+ import { onBeforeUnmount } from "vue";
2
+ const DECELERATION = 0.998;
3
+ const RUBBER_BAND_C = 0.55;
4
+ const BOUNCE_BACK_RATE = 0.04;
5
+ const BOUNCE_VELOCITY_DECAY = 0.04;
6
+ const MIN_VELOCITY = 0.01;
7
+ export function useInertiaPanzoom(options = {}) {
8
+ const {
9
+ minScale = 0.5,
10
+ maxScale = 4,
11
+ axisLockThreshold = 10,
12
+ axisLockRatio = 2
13
+ } = options;
14
+ let controller = null;
15
+ function attach(target) {
16
+ detach();
17
+ const parent = target.parentElement;
18
+ if (!parent) return;
19
+ let scale = 1;
20
+ let tx = 0;
21
+ let ty = 0;
22
+ let gesturesEnabled = true;
23
+ const pointers = /* @__PURE__ */ new Map();
24
+ let pinchStart = null;
25
+ let panStart = null;
26
+ let lastMoveTime = 0;
27
+ let lastMoveX = 0;
28
+ let lastMoveY = 0;
29
+ let vx = 0;
30
+ let vy = 0;
31
+ let momentumRaf = 0;
32
+ let lastStepTime = 0;
33
+ const subscribers = /* @__PURE__ */ new Set();
34
+ target.style.transformOrigin = "0 0";
35
+ target.style.touchAction = "none";
36
+ parent.style.touchAction = "none";
37
+ target.style.userSelect = "none";
38
+ let willChangeActive = false;
39
+ function setWillChangeActive(active) {
40
+ if (willChangeActive === active) return;
41
+ willChangeActive = active;
42
+ target.style.willChange = active ? "transform" : "";
43
+ }
44
+ const BAR_SIZE = 10;
45
+ const vBar = document.createElement("div");
46
+ vBar.setAttribute("data-inertia-scrollbar", "y");
47
+ vBar.style.cssText = `position:absolute;right:2px;top:0;width:${BAR_SIZE}px;background:rgba(0,0,0,0.35);border-radius:${BAR_SIZE / 2}px;opacity:0;transition:opacity 300ms,background-color 150ms;z-index:10;touch-action:none;cursor:grab;`;
48
+ const hBar = document.createElement("div");
49
+ hBar.setAttribute("data-inertia-scrollbar", "x");
50
+ hBar.style.cssText = `position:absolute;bottom:2px;left:0;height:${BAR_SIZE}px;background:rgba(0,0,0,0.35);border-radius:${BAR_SIZE / 2}px;opacity:0;transition:opacity 300ms,background-color 150ms;z-index:10;touch-action:none;cursor:grab;`;
51
+ parent.appendChild(vBar);
52
+ parent.appendChild(hBar);
53
+ let hideTimer = 0;
54
+ let barDrag = null;
55
+ function updateIndicators() {
56
+ const pw = parent.clientWidth;
57
+ const ph = parent.clientHeight;
58
+ const cw = target.scrollWidth * scale;
59
+ const ch = target.scrollHeight * scale;
60
+ if (ch > ph) {
61
+ const thumbH = Math.max(30, ph / ch * ph);
62
+ const scrollable = ch - ph;
63
+ const st = Math.max(0, Math.min(scrollable, -ty));
64
+ vBar.style.height = `${thumbH}px`;
65
+ vBar.style.top = `${st / scrollable * (ph - thumbH)}px`;
66
+ vBar.style.opacity = "1";
67
+ vBar.style.display = "";
68
+ } else {
69
+ vBar.style.opacity = "0";
70
+ vBar.style.display = "none";
71
+ }
72
+ if (cw > pw) {
73
+ const thumbW = Math.max(30, pw / cw * pw);
74
+ const scrollable = cw - pw;
75
+ const sl = Math.max(0, Math.min(scrollable, -tx));
76
+ hBar.style.width = `${thumbW}px`;
77
+ hBar.style.left = `${sl / scrollable * (pw - thumbW)}px`;
78
+ hBar.style.opacity = "1";
79
+ hBar.style.display = "";
80
+ } else {
81
+ hBar.style.opacity = "0";
82
+ hBar.style.display = "none";
83
+ }
84
+ if (barDrag) return;
85
+ clearTimeout(hideTimer);
86
+ hideTimer = window.setTimeout(() => {
87
+ vBar.style.opacity = "0";
88
+ hBar.style.opacity = "0";
89
+ }, 800);
90
+ }
91
+ function onBarDown(axis) {
92
+ return (e) => {
93
+ e.stopPropagation();
94
+ e.preventDefault();
95
+ stopMomentum();
96
+ const pw = parent.clientWidth;
97
+ const ph = parent.clientHeight;
98
+ const cw = target.scrollWidth * scale;
99
+ const ch = target.scrollHeight * scale;
100
+ if (axis === "y") {
101
+ if (ch <= ph) return;
102
+ const thumbH = Math.max(30, ph / ch * ph);
103
+ const trackH = ph - thumbH;
104
+ const scrollable = ch - ph;
105
+ barDrag = {
106
+ axis: "y",
107
+ startPointer: e.clientY,
108
+ startT: ty,
109
+ ratio: scrollable / trackH,
110
+ pointerId: e.pointerId
111
+ };
112
+ vBar.style.background = "rgba(0,0,0,0.55)";
113
+ } else {
114
+ if (cw <= pw) return;
115
+ const thumbW = Math.max(30, pw / cw * pw);
116
+ const trackW = pw - thumbW;
117
+ const scrollable = cw - pw;
118
+ barDrag = {
119
+ axis: "x",
120
+ startPointer: e.clientX,
121
+ startT: tx,
122
+ ratio: scrollable / trackW,
123
+ pointerId: e.pointerId
124
+ };
125
+ hBar.style.background = "rgba(0,0,0,0.55)";
126
+ }
127
+ e.target.setPointerCapture?.(e.pointerId);
128
+ };
129
+ }
130
+ function onBarMove(e) {
131
+ if (!barDrag || e.pointerId !== barDrag.pointerId) return;
132
+ e.stopPropagation();
133
+ if (barDrag.axis === "y") {
134
+ const dy = e.clientY - barDrag.startPointer;
135
+ const b = getBounds();
136
+ ty = Math.max(b.minY, Math.min(b.maxY, barDrag.startT - dy * barDrag.ratio));
137
+ } else {
138
+ const dx = e.clientX - barDrag.startPointer;
139
+ const b = getBounds();
140
+ tx = Math.max(b.minX, Math.min(b.maxX, barDrag.startT - dx * barDrag.ratio));
141
+ }
142
+ apply();
143
+ }
144
+ function onBarUp(e) {
145
+ if (!barDrag || e.pointerId !== barDrag.pointerId) return;
146
+ e.stopPropagation();
147
+ vBar.style.background = "rgba(0,0,0,0.35)";
148
+ hBar.style.background = "rgba(0,0,0,0.35)";
149
+ barDrag = null;
150
+ updateIndicators();
151
+ }
152
+ vBar.addEventListener("pointerdown", onBarDown("y"));
153
+ hBar.addEventListener("pointerdown", onBarDown("x"));
154
+ vBar.addEventListener("pointermove", onBarMove);
155
+ hBar.addEventListener("pointermove", onBarMove);
156
+ vBar.addEventListener("pointerup", onBarUp);
157
+ hBar.addEventListener("pointerup", onBarUp);
158
+ vBar.addEventListener("pointercancel", onBarUp);
159
+ hBar.addEventListener("pointercancel", onBarUp);
160
+ function apply() {
161
+ target.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
162
+ updateIndicators();
163
+ if (subscribers.size > 0) {
164
+ const t = { scale, tx, ty };
165
+ subscribers.forEach((cb) => cb(t));
166
+ }
167
+ }
168
+ function scrollToElement(elem, opts) {
169
+ const block = opts?.block ?? "start";
170
+ const animate = opts?.animate ?? true;
171
+ const elemRect = elem.getBoundingClientRect();
172
+ const parentRect = parent.getBoundingClientRect();
173
+ const targetOffset = block === "center" ? (parentRect.height - elemRect.height) / 2 : 0;
174
+ const delta = elemRect.top - parentRect.top - targetOffset;
175
+ let newTy = ty - delta;
176
+ const b = getBounds();
177
+ newTy = Math.max(b.minY, Math.min(b.maxY, newTy));
178
+ stopMomentum();
179
+ if (!animate) {
180
+ ty = newTy;
181
+ apply();
182
+ return;
183
+ }
184
+ const startTy = ty;
185
+ const startTime = performance.now();
186
+ const duration = Math.min(600, 200 + Math.abs(newTy - startTy) * 0.5);
187
+ setWillChangeActive(true);
188
+ function frame() {
189
+ const t = Math.min(1, (performance.now() - startTime) / duration);
190
+ const eased = 1 - Math.pow(1 - t, 3);
191
+ ty = startTy + (newTy - startTy) * eased;
192
+ apply();
193
+ if (t < 1) {
194
+ momentumRaf = requestAnimationFrame(frame);
195
+ } else {
196
+ momentumRaf = 0;
197
+ setWillChangeActive(false);
198
+ }
199
+ }
200
+ momentumRaf = requestAnimationFrame(frame);
201
+ }
202
+ function getBounds() {
203
+ const pw = parent.clientWidth;
204
+ const ph = parent.clientHeight;
205
+ const cw = target.scrollWidth * scale;
206
+ const ch = target.scrollHeight * scale;
207
+ let minX, maxX, minY, maxY;
208
+ if (cw <= pw) {
209
+ minX = maxX = (pw - cw) / 2;
210
+ } else {
211
+ minX = pw - cw;
212
+ maxX = 0;
213
+ }
214
+ if (ch <= ph) {
215
+ minY = maxY = (ph - ch) / 2;
216
+ } else {
217
+ minY = ph - ch;
218
+ maxY = 0;
219
+ }
220
+ return { minX, maxX, minY, maxY };
221
+ }
222
+ function rubberBandAxis(v, min, max, dim) {
223
+ if (v < min) return min - (1 - 1 / ((min - v) * RUBBER_BAND_C / dim + 1)) * dim;
224
+ if (v > max) return max + (1 - 1 / ((v - max) * RUBBER_BAND_C / dim + 1)) * dim;
225
+ return v;
226
+ }
227
+ function stopMomentum() {
228
+ if (momentumRaf) {
229
+ cancelAnimationFrame(momentumRaf);
230
+ momentumRaf = 0;
231
+ }
232
+ }
233
+ function startMomentum() {
234
+ stopMomentum();
235
+ lastStepTime = performance.now();
236
+ function step() {
237
+ const now = performance.now();
238
+ const dt = Math.min(32, now - lastStepTime);
239
+ lastStepTime = now;
240
+ const b = getBounds();
241
+ if (tx < b.minX) {
242
+ tx += (b.minX - tx) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
243
+ vx *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
244
+ } else if (tx > b.maxX) {
245
+ tx += (b.maxX - tx) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
246
+ vx *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
247
+ } else {
248
+ vx *= Math.pow(DECELERATION, dt);
249
+ tx += vx * dt;
250
+ }
251
+ if (ty < b.minY) {
252
+ ty += (b.minY - ty) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
253
+ vy *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
254
+ } else if (ty > b.maxY) {
255
+ ty += (b.maxY - ty) * (1 - Math.exp(-BOUNCE_BACK_RATE * dt));
256
+ vy *= Math.exp(-BOUNCE_VELOCITY_DECAY * dt);
257
+ } else {
258
+ vy *= Math.pow(DECELERATION, dt);
259
+ ty += vy * dt;
260
+ }
261
+ apply();
262
+ const stopped = Math.abs(vx) < MIN_VELOCITY && Math.abs(vy) < MIN_VELOCITY;
263
+ const inBounds = tx >= b.minX - 0.5 && tx <= b.maxX + 0.5 && ty >= b.minY - 0.5 && ty <= b.maxY + 0.5;
264
+ if (stopped && inBounds) {
265
+ tx = Math.max(b.minX, Math.min(b.maxX, tx));
266
+ ty = Math.max(b.minY, Math.min(b.maxY, ty));
267
+ vx = vy = 0;
268
+ apply();
269
+ momentumRaf = 0;
270
+ setWillChangeActive(false);
271
+ return;
272
+ }
273
+ momentumRaf = requestAnimationFrame(step);
274
+ }
275
+ momentumRaf = requestAnimationFrame(step);
276
+ }
277
+ function onPenFilter(e) {
278
+ if (e instanceof PointerEvent && e.pointerType === "pen") {
279
+ e.stopPropagation();
280
+ }
281
+ }
282
+ function onDown(e) {
283
+ if (e.pointerType === "pen") return;
284
+ if (!gesturesEnabled) return;
285
+ stopMomentum();
286
+ setWillChangeActive(true);
287
+ pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
288
+ if (pointers.size === 2) {
289
+ const pts = [...pointers.values()];
290
+ pinchStart = {
291
+ dist: Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y),
292
+ midX: (pts[0].x + pts[1].x) / 2,
293
+ midY: (pts[0].y + pts[1].y) / 2,
294
+ s: scale,
295
+ tx,
296
+ ty
297
+ };
298
+ panStart = null;
299
+ } else if (pointers.size === 1) {
300
+ panStart = { x: e.clientX, y: e.clientY, tx, ty, axisLock: "none" };
301
+ lastMoveTime = performance.now();
302
+ lastMoveX = e.clientX;
303
+ lastMoveY = e.clientY;
304
+ vx = vy = 0;
305
+ }
306
+ }
307
+ function onMove(e) {
308
+ if (!pointers.has(e.pointerId)) return;
309
+ pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
310
+ if (pointers.size === 2 && pinchStart) {
311
+ e.preventDefault();
312
+ const pts = [...pointers.values()];
313
+ const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
314
+ const midX = (pts[0].x + pts[1].x) / 2;
315
+ const midY = (pts[0].y + pts[1].y) / 2;
316
+ const newScale = Math.max(minScale, Math.min(maxScale, pinchStart.s * (dist / pinchStart.dist)));
317
+ const rect = parent.getBoundingClientRect();
318
+ const focalX = (pinchStart.midX - rect.left - pinchStart.tx) / pinchStart.s;
319
+ const focalY = (pinchStart.midY - rect.top - pinchStart.ty) / pinchStart.s;
320
+ tx = midX - rect.left - newScale * focalX;
321
+ ty = midY - rect.top - newScale * focalY;
322
+ scale = newScale;
323
+ apply();
324
+ } else if (pointers.size === 1 && panStart) {
325
+ e.preventDefault();
326
+ const dx = e.clientX - panStart.x;
327
+ const dy = e.clientY - panStart.y;
328
+ if (panStart.axisLock === "none") {
329
+ const absDx = Math.abs(dx);
330
+ const absDy = Math.abs(dy);
331
+ if (Math.max(absDx, absDy) >= axisLockThreshold) {
332
+ panStart.axisLock = absDy > absDx * axisLockRatio ? "y" : "free";
333
+ }
334
+ }
335
+ const rawTx = panStart.axisLock === "y" ? panStart.tx : panStart.tx + dx;
336
+ const rawTy = panStart.ty + dy;
337
+ const b = getBounds();
338
+ tx = rubberBandAxis(rawTx, b.minX, b.maxX, parent.clientWidth);
339
+ ty = rubberBandAxis(rawTy, b.minY, b.maxY, parent.clientHeight);
340
+ apply();
341
+ const now = performance.now();
342
+ const dt = Math.max(1, now - lastMoveTime);
343
+ const instVx = (e.clientX - lastMoveX) / dt;
344
+ const instVy = (e.clientY - lastMoveY) / dt;
345
+ const alpha = 0.85;
346
+ vx = vx * (1 - alpha) + instVx * alpha;
347
+ vy = vy * (1 - alpha) + instVy * alpha;
348
+ lastMoveTime = now;
349
+ lastMoveX = e.clientX;
350
+ lastMoveY = e.clientY;
351
+ }
352
+ }
353
+ function onUp(e) {
354
+ pointers.delete(e.pointerId);
355
+ if (pointers.size < 2) pinchStart = null;
356
+ if (pointers.size === 0) {
357
+ const hadPan = panStart !== null;
358
+ const lockedAxis = panStart?.axisLock;
359
+ panStart = null;
360
+ const idleMs = performance.now() - lastMoveTime;
361
+ if (idleMs > 60) {
362
+ vx = 0;
363
+ vy = 0;
364
+ }
365
+ if (hadPan && lockedAxis === "y") vx = 0;
366
+ startMomentum();
367
+ }
368
+ }
369
+ function onWheelZoom(e) {
370
+ if (!e.ctrlKey && !e.metaKey) return;
371
+ e.preventDefault();
372
+ stopMomentum();
373
+ const rect = parent.getBoundingClientRect();
374
+ const mx = e.clientX - rect.left;
375
+ const my = e.clientY - rect.top;
376
+ const delta = e.deltaY < 0 ? 1 : -1;
377
+ const newScale = Math.max(minScale, Math.min(maxScale, scale * Math.exp(delta * 0.1)));
378
+ const focalX = (mx - tx) / scale;
379
+ const focalY = (my - ty) / scale;
380
+ tx = mx - newScale * focalX;
381
+ ty = my - newScale * focalY;
382
+ scale = newScale;
383
+ apply();
384
+ }
385
+ function scrollBy(dx, dy) {
386
+ stopMomentum();
387
+ const b = getBounds();
388
+ tx = Math.max(b.minX, Math.min(b.maxX, tx - dx));
389
+ ty = Math.max(b.minY, Math.min(b.maxY, ty - dy));
390
+ apply();
391
+ }
392
+ parent.addEventListener("pointerdown", onDown);
393
+ parent.addEventListener("pointermove", onMove);
394
+ parent.addEventListener("pointerup", onUp);
395
+ parent.addEventListener("pointercancel", onUp);
396
+ parent.addEventListener("wheel", onWheelZoom, { passive: false });
397
+ target.addEventListener("pointerdown", onPenFilter);
398
+ apply();
399
+ controller = {
400
+ cleanup: () => {
401
+ stopMomentum();
402
+ clearTimeout(hideTimer);
403
+ parent.removeEventListener("pointerdown", onDown);
404
+ parent.removeEventListener("pointermove", onMove);
405
+ parent.removeEventListener("pointerup", onUp);
406
+ parent.removeEventListener("pointercancel", onUp);
407
+ parent.removeEventListener("wheel", onWheelZoom);
408
+ target.removeEventListener("pointerdown", onPenFilter);
409
+ vBar.remove();
410
+ hBar.remove();
411
+ target.style.transform = "";
412
+ target.style.transformOrigin = "";
413
+ target.style.willChange = "";
414
+ target.style.touchAction = "";
415
+ target.style.userSelect = "";
416
+ parent.style.touchAction = "";
417
+ },
418
+ reset: () => {
419
+ stopMomentum();
420
+ scale = 1;
421
+ tx = 0;
422
+ ty = 0;
423
+ vx = vy = 0;
424
+ pointers.clear();
425
+ pinchStart = null;
426
+ panStart = null;
427
+ setWillChangeActive(false);
428
+ const b = getBounds();
429
+ tx = Math.max(b.minX, Math.min(b.maxX, tx));
430
+ ty = Math.max(b.minY, Math.min(b.maxY, ty));
431
+ apply();
432
+ },
433
+ stop: () => {
434
+ stopMomentum();
435
+ setWillChangeActive(false);
436
+ },
437
+ getScale: () => scale,
438
+ getTransform: () => ({ scale, tx, ty }),
439
+ scrollToElement,
440
+ scrollBy,
441
+ setGesturesEnabled: (enabled) => {
442
+ gesturesEnabled = enabled;
443
+ if (!enabled) {
444
+ pointers.clear();
445
+ panStart = null;
446
+ pinchStart = null;
447
+ stopMomentum();
448
+ setWillChangeActive(false);
449
+ }
450
+ },
451
+ onChange: (cb) => {
452
+ subscribers.add(cb);
453
+ return () => subscribers.delete(cb);
454
+ }
455
+ };
456
+ }
457
+ function detach() {
458
+ controller?.cleanup();
459
+ controller = null;
460
+ }
461
+ onBeforeUnmount(detach);
462
+ return {
463
+ attach,
464
+ detach,
465
+ reset: () => controller?.reset(),
466
+ stop: () => controller?.stop(),
467
+ getScale: () => controller?.getScale() ?? 1,
468
+ getTransform: () => controller?.getTransform() ?? { scale: 1, tx: 0, ty: 0 },
469
+ scrollToElement: (elem, opts) => controller?.scrollToElement(elem, opts),
470
+ scrollBy: (dx, dy) => controller?.scrollBy(dx, dy),
471
+ setGesturesEnabled: (enabled) => controller?.setGesturesEnabled(enabled),
472
+ onChange: (cb) => controller?.onChange(cb) ?? (() => {
473
+ })
474
+ };
475
+ }
@@ -4,6 +4,12 @@ export interface PageMeta {
4
4
  pageNumber: number;
5
5
  width: number;
6
6
  height: number;
7
+ /** PDF user-space origin of the page's visible area (`view[0]`, `view[1]`).
8
+ * Almost always (0, 0); non-zero for PDFs whose MediaBox has been
9
+ * shifted. Annotations in `/Rect` are in default user space, so any
10
+ * layer drawn at the canvas top-left must subtract this origin. */
11
+ viewOffsetX: number;
12
+ viewOffsetY: number;
7
13
  }
8
14
  export interface PageVirtualization {
9
15
  pageMetas: ShallowRef<PageMeta[]>;
@@ -13,6 +19,9 @@ export interface PageVirtualization {
13
19
  observePage: (pageNumber: number, element: HTMLElement) => void;
14
20
  unobservePage: (pageNumber: number) => void;
15
21
  scrollToPage: (pageNumber: number) => void;
22
+ getPageElement: (pageNumber: number) => HTMLElement | undefined;
23
+ /** Recompute currentPage using visual bounding rects. Works with CSS transforms. */
24
+ updateCurrentPageByRect: () => void;
16
25
  init: (doc: PDFDocumentProxy, scrollRoot: HTMLElement) => Promise<void>;
17
26
  destroy: () => void;
18
27
  }
@@ -96,13 +96,40 @@ export function createPageVirtualization() {
96
96
  currentPage.value = pageNumber;
97
97
  }
98
98
  }
99
+ function getPageElement(pageNumber) {
100
+ return pageToElement.get(pageNumber);
101
+ }
102
+ function updateCurrentPageByRect() {
103
+ if (!scrollRoot) return;
104
+ const rootRect = scrollRoot.getBoundingClientRect();
105
+ const rootMid = rootRect.top + rootRect.height / 2;
106
+ let bestPage = currentPage.value;
107
+ let bestDist = Infinity;
108
+ for (const [pageNum, el] of pageToElement) {
109
+ const rect = el.getBoundingClientRect();
110
+ const dist = rootMid < rect.top ? rect.top - rootMid : rootMid > rect.bottom ? rootMid - rect.bottom : 0;
111
+ if (dist < bestDist) {
112
+ bestDist = dist;
113
+ bestPage = pageNum;
114
+ }
115
+ }
116
+ if (bestPage !== currentPage.value) {
117
+ currentPage.value = bestPage;
118
+ }
119
+ }
99
120
  async function fetchPageDimensions(doc, pageNumber) {
100
121
  const proxy = await doc.getPage(pageNumber);
101
122
  const view = proxy.view;
123
+ const vx = view[0] ?? 0;
124
+ const vy = view[1] ?? 0;
125
+ const vw = view[2] ?? 0;
126
+ const vh = view[3] ?? 0;
102
127
  return {
103
128
  pageNumber,
104
- width: view[2] - view[0],
105
- height: view[3] - view[1]
129
+ width: vw - vx,
130
+ height: vh - vy,
131
+ viewOffsetX: vx,
132
+ viewOffsetY: vy
106
133
  };
107
134
  }
108
135
  async function init(doc, root) {
@@ -118,7 +145,9 @@ export function createPageVirtualization() {
118
145
  metas.push({
119
146
  pageNumber: i,
120
147
  width: firstMeta.width,
121
- height: firstMeta.height
148
+ height: firstMeta.height,
149
+ viewOffsetX: firstMeta.viewOffsetX,
150
+ viewOffsetY: firstMeta.viewOffsetY
122
151
  });
123
152
  }
124
153
  }
@@ -197,6 +226,8 @@ export function createPageVirtualization() {
197
226
  observePage,
198
227
  unobservePage,
199
228
  scrollToPage,
229
+ getPageElement,
230
+ updateCurrentPageByRect,
200
231
  init,
201
232
  destroy
202
233
  };
@@ -27,6 +27,7 @@ export interface ViewerSearchState {
27
27
  totalPages: number;
28
28
  }) => void;
29
29
  setScrollToPage: (fn: (pageNumber: number) => void) => void;
30
+ setScrollToElement: (fn: (el: HTMLElement) => void) => void;
30
31
  }
31
32
  export declare function provideViewerSearch(): ViewerSearchState;
32
33
  export declare function useViewerSearch(): ViewerSearchState;
@@ -26,12 +26,16 @@ export function provideViewerSearch() {
26
26
  let inputElement = null;
27
27
  let _searchIndex = null;
28
28
  let _scrollToPage = null;
29
+ let _scrollToElement = null;
29
30
  function setSearchIndex(index) {
30
31
  _searchIndex = index;
31
32
  }
32
33
  function setScrollToPage(fn) {
33
34
  _scrollToPage = fn;
34
35
  }
36
+ function setScrollToElement(fn) {
37
+ _scrollToElement = fn;
38
+ }
35
39
  function getSortedPageNumbers() {
36
40
  return [...pageLayers.keys()].sort((a, b) => a - b);
37
41
  }
@@ -186,10 +190,14 @@ export function provideViewerSearch() {
186
190
  }
187
191
  prevEnd = end;
188
192
  if (isSelected && shouldScrollSelected && selectedSpan) {
189
- selectedSpan.scrollIntoView({
190
- block: "start",
191
- inline: "center"
192
- });
193
+ if (_scrollToElement) {
194
+ _scrollToElement(selectedSpan);
195
+ } else {
196
+ selectedSpan.scrollIntoView({
197
+ block: "start",
198
+ inline: "center"
199
+ });
200
+ }
193
201
  }
194
202
  }
195
203
  if (prevEnd) {
@@ -309,6 +317,7 @@ export function provideViewerSearch() {
309
317
  inputElement = null;
310
318
  _searchIndex = null;
311
319
  _scrollToPage = null;
320
+ _scrollToElement = null;
312
321
  }
313
322
  function setQuery(value) {
314
323
  query.value = value;
@@ -404,7 +413,8 @@ export function provideViewerSearch() {
404
413
  setInputElement,
405
414
  focusInput,
406
415
  setSearchIndex,
407
- setScrollToPage
416
+ setScrollToPage,
417
+ setScrollToElement
408
418
  };
409
419
  provide(VIEWER_SEARCH_KEY, state);
410
420
  return state;
@@ -57,6 +57,9 @@ export interface ViewerState {
57
57
  history: AnnotationHistory;
58
58
  readonly: Ref<boolean>;
59
59
  shapeDetection: Ref<boolean>;
60
+ visualZoom: Ref<number>;
61
+ formEditMode: Ref<boolean>;
62
+ setFormEditMode: (enabled: boolean) => void;
60
63
  }
61
64
  export interface ViewerStateProvider {
62
65
  state: ViewerState;
@@ -65,7 +65,22 @@ export function provideViewerState() {
65
65
  }
66
66
  const readonly = ref(false);
67
67
  const shapeDetectionEnabled = ref(false);
68
+ const visualZoom = ref(1);
68
69
  const stylusMode = ref(false);
70
+ const formEditMode = ref(false);
71
+ function isFormFieldTool(t) {
72
+ return typeof t === "number" && (t === AnnotationType.FORM_TEXT || t === AnnotationType.FORM_CHECKBOX || t === AnnotationType.FORM_RADIO || t === AnnotationType.FORM_SIGNATURE);
73
+ }
74
+ function setFormEditMode(enabled) {
75
+ if (formEditMode.value === enabled) return;
76
+ formEditMode.value = enabled;
77
+ const t = activeTool.value;
78
+ if (enabled) {
79
+ if (typeof t === "number" && !isFormFieldTool(t)) selectTool("hand");
80
+ } else {
81
+ if (isFormFieldTool(t)) selectTool("hand");
82
+ }
83
+ }
69
84
  function setStylusMode(enabled) {
70
85
  stylusMode.value = enabled;
71
86
  if (painter.value) {
@@ -95,6 +110,9 @@ export function provideViewerState() {
95
110
  }
96
111
  function selectTool(tool, dataTransfer) {
97
112
  if (readonly.value && tool !== "hand") return;
113
+ if (isFormFieldTool(tool) && !formEditMode.value) {
114
+ formEditMode.value = true;
115
+ }
98
116
  activeTool.value = tool;
99
117
  selectedAnnotation.value = null;
100
118
  selectedAnnotations.value = [];
@@ -181,7 +199,10 @@ export function provideViewerState() {
181
199
  downloadPdf,
182
200
  history,
183
201
  readonly,
184
- shapeDetection: shapeDetectionEnabled
202
+ shapeDetection: shapeDetectionEnabled,
203
+ visualZoom,
204
+ formEditMode,
205
+ setFormEditMode
185
206
  };
186
207
  provide(VIEWER_STATE_KEY, state);
187
208
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "prepack": "nuxt-module-build build",
31
- "dev": "npm run dev:prepare && nuxt dev playground",
31
+ "dev": "npm run dev:prepare && portless kviewer nuxt dev playground",
32
32
  "dev:build": "nuxt build playground",
33
33
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
34
34
  "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",