annotate-image 2.0.0-beta.1 → 2.0.0-beta.3
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/core.js +173 -41
- package/dist/core.min.js +1 -1
- package/dist/css/annotate.min.css +1 -1
- package/dist/jquery.js +172 -41
- package/dist/jquery.min.js +1 -1
- package/dist/react.js +174 -42
- package/dist/types/annotate-edit.d.ts +2 -0
- package/dist/types/annotate-image.d.ts +42 -0
- package/dist/types/annotate-view.d.ts +1 -1
- package/dist/types/positioning.d.ts +31 -0
- package/dist/types/react.d.ts +2 -0
- package/dist/types/types.d.ts +4 -0
- package/dist/types/vue.d.ts +11 -0
- package/dist/vue.js +176 -42
- package/package.json +12 -12
- package/readme.md +111 -2
package/dist/react.js
CHANGED
|
@@ -7,6 +7,33 @@ import {
|
|
|
7
7
|
useRef
|
|
8
8
|
} from "react";
|
|
9
9
|
|
|
10
|
+
// src/positioning.ts
|
|
11
|
+
function clampNote(note, naturalWidth, naturalHeight) {
|
|
12
|
+
const width = Math.min(note.width, naturalWidth);
|
|
13
|
+
const height = Math.min(note.height, naturalHeight);
|
|
14
|
+
const left = Math.max(0, Math.min(note.left, naturalWidth - width));
|
|
15
|
+
const top = Math.max(0, Math.min(note.top, naturalHeight - height));
|
|
16
|
+
return { top, left, width, height };
|
|
17
|
+
}
|
|
18
|
+
function clampNotes(notes, naturalWidth, naturalHeight) {
|
|
19
|
+
for (const note of notes) {
|
|
20
|
+
Object.assign(note, clampNote(note, naturalWidth, naturalHeight));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function computeNoteLeft(noteWidth, areaLeftInViewport, areaWidth, viewportWidth) {
|
|
24
|
+
let left = (areaWidth - noteWidth) / 2;
|
|
25
|
+
const noteLeftInViewport = areaLeftInViewport + left;
|
|
26
|
+
const noteRightInViewport = noteLeftInViewport + noteWidth;
|
|
27
|
+
if (noteRightInViewport > viewportWidth) {
|
|
28
|
+
left -= noteRightInViewport - viewportWidth;
|
|
29
|
+
}
|
|
30
|
+
const adjustedNoteLeft = areaLeftInViewport + left;
|
|
31
|
+
if (adjustedNoteLeft < 0) {
|
|
32
|
+
left -= adjustedNoteLeft;
|
|
33
|
+
}
|
|
34
|
+
return left;
|
|
35
|
+
}
|
|
36
|
+
|
|
10
37
|
// src/annotate-edit.ts
|
|
11
38
|
var DEFAULT_NOTE_TOP = 30;
|
|
12
39
|
var DEFAULT_NOTE_LEFT = 30;
|
|
@@ -36,10 +63,11 @@ var AnnotateEdit = class {
|
|
|
36
63
|
};
|
|
37
64
|
}
|
|
38
65
|
this.area = image.editOverlay.querySelector(".image-annotate-edit-area");
|
|
39
|
-
|
|
40
|
-
this.area.style.
|
|
41
|
-
this.area.style.
|
|
42
|
-
this.area.style.
|
|
66
|
+
const rendered = image.toRendered(this.note);
|
|
67
|
+
this.area.style.height = rendered.height + "px";
|
|
68
|
+
this.area.style.width = rendered.width + "px";
|
|
69
|
+
this.area.style.left = rendered.left + "px";
|
|
70
|
+
this.area.style.top = rendered.top + "px";
|
|
43
71
|
this.form = document.createElement("div");
|
|
44
72
|
this.form.className = "image-annotate-edit-form";
|
|
45
73
|
const formEl = document.createElement("form");
|
|
@@ -55,6 +83,8 @@ var AnnotateEdit = class {
|
|
|
55
83
|
formEl.appendChild(this.textarea);
|
|
56
84
|
this.form.appendChild(formEl);
|
|
57
85
|
this.area.appendChild(this.form);
|
|
86
|
+
this.positionForm();
|
|
87
|
+
this.textarea.focus();
|
|
58
88
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
59
89
|
const area = this.area;
|
|
60
90
|
const applyRect = (rect) => {
|
|
@@ -66,7 +96,10 @@ var AnnotateEdit = class {
|
|
|
66
96
|
this.handlers.makeResizable(area, {
|
|
67
97
|
containment: image.canvas,
|
|
68
98
|
onResize: applyRect,
|
|
69
|
-
onStop:
|
|
99
|
+
onStop: (rect) => {
|
|
100
|
+
applyRect(rect);
|
|
101
|
+
this.positionForm();
|
|
102
|
+
}
|
|
70
103
|
});
|
|
71
104
|
this.handlers.makeDraggable(area, {
|
|
72
105
|
containment: image.canvas,
|
|
@@ -77,9 +110,9 @@ var AnnotateEdit = class {
|
|
|
77
110
|
onStop: (pos) => {
|
|
78
111
|
area.style.left = pos.left + "px";
|
|
79
112
|
area.style.top = pos.top + "px";
|
|
113
|
+
this.positionForm();
|
|
80
114
|
}
|
|
81
115
|
});
|
|
82
|
-
this.textarea.focus();
|
|
83
116
|
this.form.addEventListener("keydown", (e) => {
|
|
84
117
|
if (e.key === "Escape") {
|
|
85
118
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -95,6 +128,13 @@ var AnnotateEdit = class {
|
|
|
95
128
|
}
|
|
96
129
|
this.addCancelButton(buttonRow);
|
|
97
130
|
}
|
|
131
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
132
|
+
positionForm() {
|
|
133
|
+
const formRect = this.form.getBoundingClientRect();
|
|
134
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
135
|
+
const left = computeNoteLeft(formRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
136
|
+
this.form.style.left = left + "px";
|
|
137
|
+
}
|
|
98
138
|
/** Tear down the edit form and interaction handlers. */
|
|
99
139
|
destroy() {
|
|
100
140
|
this.image.activeEdit = null;
|
|
@@ -126,13 +166,20 @@ var AnnotateEdit = class {
|
|
|
126
166
|
}
|
|
127
167
|
this.image.notifySave(stripInternals(this.note));
|
|
128
168
|
this.destroy();
|
|
169
|
+
this.image.flushPendingRescale();
|
|
129
170
|
};
|
|
130
171
|
const pos = readInlinePosition(this.area);
|
|
131
172
|
const size = readInlineSize(this.area);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
const natural = this.image.toNatural({
|
|
174
|
+
top: pos.top,
|
|
175
|
+
left: pos.left,
|
|
176
|
+
width: size.width,
|
|
177
|
+
height: size.height
|
|
178
|
+
});
|
|
179
|
+
this.note.top = natural.top;
|
|
180
|
+
this.note.left = natural.left;
|
|
181
|
+
this.note.width = natural.width;
|
|
182
|
+
this.note.height = natural.height;
|
|
136
183
|
this.note.text = text;
|
|
137
184
|
if (this.image.api.save) {
|
|
138
185
|
this.busy = true;
|
|
@@ -166,6 +213,7 @@ var AnnotateEdit = class {
|
|
|
166
213
|
const idx = this.image.notes.indexOf(this.note);
|
|
167
214
|
if (idx !== -1) this.image.notes.splice(idx, 1);
|
|
168
215
|
this.image.notifyDelete(stripInternals(this.note));
|
|
216
|
+
this.image.flushPendingRescale();
|
|
169
217
|
};
|
|
170
218
|
if (this.image.api.delete) {
|
|
171
219
|
this.busy = true;
|
|
@@ -241,36 +289,42 @@ var AnnotateView = class {
|
|
|
241
289
|
});
|
|
242
290
|
}
|
|
243
291
|
}
|
|
244
|
-
/** Apply the note's position and dimensions to the area element. */
|
|
292
|
+
/** Apply the note's position and dimensions to the area element, scaled to rendered size. */
|
|
245
293
|
setPosition() {
|
|
294
|
+
const rendered = this.image.toRendered(this.note);
|
|
246
295
|
const innerDiv = this.area.firstElementChild;
|
|
247
|
-
innerDiv.style.height =
|
|
248
|
-
innerDiv.style.width =
|
|
249
|
-
this.area.style.left =
|
|
250
|
-
this.area.style.top =
|
|
296
|
+
innerDiv.style.height = rendered.height + "px";
|
|
297
|
+
innerDiv.style.width = rendered.width + "px";
|
|
298
|
+
this.area.style.left = rendered.left + "px";
|
|
299
|
+
this.area.style.top = rendered.top + "px";
|
|
251
300
|
}
|
|
252
301
|
/** Update the view's position, size, and text from the edit area after a save. */
|
|
253
302
|
resetPosition(editable, text) {
|
|
254
303
|
this.tooltip.textContent = text;
|
|
255
304
|
this.tooltip.style.display = "none";
|
|
256
|
-
const
|
|
257
|
-
const areaSize = readInlineSize(editable.area);
|
|
305
|
+
const rendered = this.image.toRendered(editable.note);
|
|
258
306
|
const innerDiv = this.area.firstElementChild;
|
|
259
|
-
innerDiv.style.height =
|
|
260
|
-
innerDiv.style.width =
|
|
261
|
-
this.area.style.left =
|
|
262
|
-
this.area.style.top =
|
|
263
|
-
this.note.top =
|
|
264
|
-
this.note.left =
|
|
265
|
-
this.note.height =
|
|
266
|
-
this.note.width =
|
|
307
|
+
innerDiv.style.height = rendered.height + "px";
|
|
308
|
+
innerDiv.style.width = rendered.width + "px";
|
|
309
|
+
this.area.style.left = rendered.left + "px";
|
|
310
|
+
this.area.style.top = rendered.top + "px";
|
|
311
|
+
this.note.top = editable.note.top;
|
|
312
|
+
this.note.left = editable.note.left;
|
|
313
|
+
this.note.height = editable.note.height;
|
|
314
|
+
this.note.width = editable.note.width;
|
|
267
315
|
this.note.text = text;
|
|
268
316
|
this.note.id = editable.note.id;
|
|
269
317
|
this.editable = true;
|
|
270
318
|
}
|
|
271
319
|
/** Show the tooltip and apply hover styling. */
|
|
272
320
|
show() {
|
|
321
|
+
this.tooltip.style.visibility = "hidden";
|
|
273
322
|
this.tooltip.style.display = "block";
|
|
323
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
324
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
325
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
326
|
+
this.tooltip.style.left = left + "px";
|
|
327
|
+
this.tooltip.style.visibility = "";
|
|
274
328
|
if (!this.editable) {
|
|
275
329
|
this.area.classList.add("image-annotate-area-hover");
|
|
276
330
|
} else {
|
|
@@ -368,17 +422,19 @@ function destroyDraggable(el) {
|
|
|
368
422
|
var MIN_SIZE = 10;
|
|
369
423
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
370
424
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
371
|
-
let left
|
|
425
|
+
let left, top, width, height;
|
|
372
426
|
if (corner === "nw" || corner === "sw") {
|
|
373
427
|
left = startLeft + dx;
|
|
374
428
|
width = startWidth - dx;
|
|
375
429
|
} else {
|
|
430
|
+
left = startLeft;
|
|
376
431
|
width = startWidth + dx;
|
|
377
432
|
}
|
|
378
433
|
if (corner === "nw" || corner === "ne") {
|
|
379
434
|
top = startTop + dy;
|
|
380
435
|
height = startHeight - dy;
|
|
381
436
|
} else {
|
|
437
|
+
top = startTop;
|
|
382
438
|
height = startHeight + dy;
|
|
383
439
|
}
|
|
384
440
|
if (width < MIN_SIZE) {
|
|
@@ -533,17 +589,30 @@ var AnnotateImage = class {
|
|
|
533
589
|
this._mode = "view";
|
|
534
590
|
this.activeEdit = null;
|
|
535
591
|
this.destroyed = false;
|
|
592
|
+
this.pendingRescale = false;
|
|
593
|
+
this.originalParent = null;
|
|
594
|
+
this.originalNextSibling = null;
|
|
536
595
|
this.options = options;
|
|
537
596
|
this.handlers = createDefaultHandlers();
|
|
538
597
|
this.img = img;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
598
|
+
this.naturalWidth = img.naturalWidth || img.width;
|
|
599
|
+
this.naturalHeight = img.naturalHeight || img.height;
|
|
600
|
+
const rendered = img.getBoundingClientRect();
|
|
601
|
+
const renderedWidth = rendered.width || img.width;
|
|
602
|
+
const renderedHeight = rendered.height || img.height;
|
|
603
|
+
if (this.naturalWidth === 0 || this.naturalHeight === 0) {
|
|
542
604
|
throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");
|
|
543
605
|
}
|
|
606
|
+
this.scaleX = renderedWidth / this.naturalWidth;
|
|
607
|
+
this.scaleY = renderedHeight / this.naturalHeight;
|
|
544
608
|
this.notes = options.notes.map((n) => ({ ...n }));
|
|
609
|
+
this.originalParent = img.parentNode;
|
|
610
|
+
this.originalNextSibling = img.nextSibling;
|
|
545
611
|
this.canvas = document.createElement("div");
|
|
546
612
|
this.canvas.className = "image-annotate-canvas";
|
|
613
|
+
if (options.theme) {
|
|
614
|
+
this.canvas.dataset.theme = options.theme;
|
|
615
|
+
}
|
|
547
616
|
this.viewOverlay = document.createElement("div");
|
|
548
617
|
this.viewOverlay.className = "image-annotate-view";
|
|
549
618
|
this.editOverlay = document.createElement("div");
|
|
@@ -552,19 +621,13 @@ var AnnotateImage = class {
|
|
|
552
621
|
const editArea = document.createElement("div");
|
|
553
622
|
editArea.className = "image-annotate-edit-area";
|
|
554
623
|
this.editOverlay.appendChild(editArea);
|
|
555
|
-
this.canvas.appendChild(this.viewOverlay);
|
|
556
|
-
this.canvas.appendChild(this.editOverlay);
|
|
557
624
|
if (!img.parentNode) {
|
|
558
625
|
throw new Error("image-annotate: image must be in the DOM before initialization");
|
|
559
626
|
}
|
|
560
|
-
img.parentNode.insertBefore(this.canvas, img
|
|
561
|
-
this.canvas.
|
|
562
|
-
this.canvas.
|
|
563
|
-
this.canvas.
|
|
564
|
-
this.viewOverlay.style.height = height + "px";
|
|
565
|
-
this.viewOverlay.style.width = width + "px";
|
|
566
|
-
this.editOverlay.style.height = height + "px";
|
|
567
|
-
this.editOverlay.style.width = width + "px";
|
|
627
|
+
img.parentNode.insertBefore(this.canvas, img);
|
|
628
|
+
this.canvas.appendChild(img);
|
|
629
|
+
this.canvas.appendChild(this.viewOverlay);
|
|
630
|
+
this.canvas.appendChild(this.editOverlay);
|
|
568
631
|
this.api = this.options.api ? normalizeApi(this.options.api) : {};
|
|
569
632
|
if (this.api.load) {
|
|
570
633
|
this.loadFromApi();
|
|
@@ -574,7 +637,38 @@ var AnnotateImage = class {
|
|
|
574
637
|
if (this.options.editable) {
|
|
575
638
|
this.createButton();
|
|
576
639
|
}
|
|
577
|
-
|
|
640
|
+
if (options.autoResize !== false && typeof ResizeObserver !== "undefined") {
|
|
641
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
642
|
+
const entry = entries[0];
|
|
643
|
+
if (!entry) return;
|
|
644
|
+
const { width, height } = entry.contentRect;
|
|
645
|
+
if (width === 0 || height === 0) return;
|
|
646
|
+
this.rescale(width, height);
|
|
647
|
+
});
|
|
648
|
+
this.resizeObserver.observe(this.canvas);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** Convert a rect from natural image coordinates to rendered (scaled) coordinates. */
|
|
652
|
+
toRendered(rect) {
|
|
653
|
+
return {
|
|
654
|
+
top: rect.top * this.scaleY,
|
|
655
|
+
left: rect.left * this.scaleX,
|
|
656
|
+
width: rect.width * this.scaleX,
|
|
657
|
+
height: rect.height * this.scaleY
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/** Convert a rect from rendered (scaled) coordinates to natural image coordinates. */
|
|
661
|
+
toNatural(rect) {
|
|
662
|
+
const result = {
|
|
663
|
+
top: rect.top / this.scaleY,
|
|
664
|
+
left: rect.left / this.scaleX,
|
|
665
|
+
width: rect.width / this.scaleX,
|
|
666
|
+
height: rect.height / this.scaleY
|
|
667
|
+
};
|
|
668
|
+
if (!isFinite(result.top) || !isFinite(result.left) || !isFinite(result.width) || !isFinite(result.height)) {
|
|
669
|
+
throw new Error("image-annotate: scale conversion produced non-finite coordinates");
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
578
672
|
}
|
|
579
673
|
/** Current interaction mode — 'view' for browsing, 'edit' when an annotation is being created or modified. */
|
|
580
674
|
get mode() {
|
|
@@ -628,6 +722,7 @@ var AnnotateImage = class {
|
|
|
628
722
|
/** Rebuild annotation views from the current notes array. */
|
|
629
723
|
load() {
|
|
630
724
|
this.destroyViews();
|
|
725
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
631
726
|
this.createViews();
|
|
632
727
|
this.notifyLoad();
|
|
633
728
|
}
|
|
@@ -646,8 +741,15 @@ var AnnotateImage = class {
|
|
|
646
741
|
if (this.button) {
|
|
647
742
|
this.button.remove();
|
|
648
743
|
}
|
|
744
|
+
if (this.resizeObserver) {
|
|
745
|
+
this.resizeObserver.disconnect();
|
|
746
|
+
this.resizeObserver = void 0;
|
|
747
|
+
}
|
|
748
|
+
if (this.originalParent && this.originalParent.isConnected) {
|
|
749
|
+
const ref = this.originalNextSibling?.parentNode === this.originalParent ? this.originalNextSibling : null;
|
|
750
|
+
this.originalParent.insertBefore(this.img, ref);
|
|
751
|
+
}
|
|
649
752
|
this.canvas.remove();
|
|
650
|
-
this.img.style.display = "";
|
|
651
753
|
}
|
|
652
754
|
/** Cancel the active edit (if any) and return to view mode. */
|
|
653
755
|
cancelEdit() {
|
|
@@ -655,6 +757,34 @@ var AnnotateImage = class {
|
|
|
655
757
|
this.activeEdit.destroy();
|
|
656
758
|
this.setMode("view");
|
|
657
759
|
}
|
|
760
|
+
this.flushPendingRescale();
|
|
761
|
+
}
|
|
762
|
+
/** Recompute scale factors, deferring if an edit is active. */
|
|
763
|
+
rescale(renderedWidth, renderedHeight) {
|
|
764
|
+
if (this.mode === "edit") {
|
|
765
|
+
this.pendingRescale = true;
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
this.applyRescale(renderedWidth, renderedHeight);
|
|
769
|
+
}
|
|
770
|
+
/** Apply new scale factors and re-render all views. */
|
|
771
|
+
applyRescale(renderedWidth, renderedHeight) {
|
|
772
|
+
const newScaleX = renderedWidth / this.naturalWidth;
|
|
773
|
+
const newScaleY = renderedHeight / this.naturalHeight;
|
|
774
|
+
if (newScaleX === this.scaleX && newScaleY === this.scaleY) return;
|
|
775
|
+
this.scaleX = newScaleX;
|
|
776
|
+
this.scaleY = newScaleY;
|
|
777
|
+
this.destroyViews();
|
|
778
|
+
this.createViews();
|
|
779
|
+
}
|
|
780
|
+
/** @internal Flush any deferred rescale after an edit completes. */
|
|
781
|
+
flushPendingRescale() {
|
|
782
|
+
if (!this.pendingRescale) return;
|
|
783
|
+
this.pendingRescale = false;
|
|
784
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
785
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
786
|
+
this.applyRescale(rect.width, rect.height);
|
|
787
|
+
}
|
|
658
788
|
}
|
|
659
789
|
/** Replace all annotations with new data. Does not fire lifecycle callbacks. */
|
|
660
790
|
setNotes(notes) {
|
|
@@ -701,6 +831,7 @@ var AnnotateImage = class {
|
|
|
701
831
|
this.api.load().then((notes) => {
|
|
702
832
|
this.destroyViews();
|
|
703
833
|
this.notes = notes;
|
|
834
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
704
835
|
this.createViews();
|
|
705
836
|
this.notifyLoad();
|
|
706
837
|
}).catch((err) => {
|
|
@@ -746,6 +877,7 @@ var AnnotateImage2 = forwardRef(
|
|
|
746
877
|
try {
|
|
747
878
|
const instance = new AnnotateImage(imgRef.current, {
|
|
748
879
|
editable: props.editable ?? true,
|
|
880
|
+
autoResize: props.autoResize ?? true,
|
|
749
881
|
notes: props.notes ? props.notes.slice() : [],
|
|
750
882
|
onChange: (notes) => onChangeRef.current?.(notes),
|
|
751
883
|
onSave: (note) => onSaveRef.current?.(note),
|
|
@@ -786,7 +918,7 @@ var AnnotateImage2 = forwardRef(
|
|
|
786
918
|
return instanceRef.current?.getNotes() ?? [];
|
|
787
919
|
}
|
|
788
920
|
}));
|
|
789
|
-
return /* @__PURE__ */ jsx("img", { ref: imgRef, src: props.src, width: props.width, height: props.height, alt: props.alt });
|
|
921
|
+
return /* @__PURE__ */ jsx("span", { style: { display: "contents" }, children: /* @__PURE__ */ jsx("img", { ref: imgRef, src: props.src, width: props.width, height: props.height, alt: props.alt }) });
|
|
790
922
|
}
|
|
791
923
|
);
|
|
792
924
|
export {
|
|
@@ -21,6 +21,8 @@ export declare class AnnotateEdit {
|
|
|
21
21
|
* @param existingView - The view being edited (for updates); omit for new annotations.
|
|
22
22
|
*/
|
|
23
23
|
constructor(image: AnnotateImage, note?: AnnotationNote, existingView?: AnnotateView);
|
|
24
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
25
|
+
private positionForm;
|
|
24
26
|
/** Tear down the edit form and interaction handlers. */
|
|
25
27
|
destroy(): void;
|
|
26
28
|
private addSaveButton;
|
|
@@ -24,6 +24,42 @@ export declare class AnnotateImage {
|
|
|
24
24
|
handlers: InteractionHandlers;
|
|
25
25
|
activeEdit: AnnotateEdit | null;
|
|
26
26
|
private destroyed;
|
|
27
|
+
private pendingRescale;
|
|
28
|
+
private resizeObserver?;
|
|
29
|
+
private originalParent;
|
|
30
|
+
private originalNextSibling;
|
|
31
|
+
/** Natural (intrinsic) image width. */
|
|
32
|
+
readonly naturalWidth: number;
|
|
33
|
+
/** Natural (intrinsic) image height. */
|
|
34
|
+
readonly naturalHeight: number;
|
|
35
|
+
/** Current horizontal scale factor (rendered / natural). */
|
|
36
|
+
scaleX: number;
|
|
37
|
+
/** Current vertical scale factor (rendered / natural). */
|
|
38
|
+
scaleY: number;
|
|
39
|
+
/** Convert a rect from natural image coordinates to rendered (scaled) coordinates. */
|
|
40
|
+
toRendered(rect: {
|
|
41
|
+
top: number;
|
|
42
|
+
left: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
}): {
|
|
46
|
+
top: number;
|
|
47
|
+
left: number;
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
};
|
|
51
|
+
/** Convert a rect from rendered (scaled) coordinates to natural image coordinates. */
|
|
52
|
+
toNatural(rect: {
|
|
53
|
+
top: number;
|
|
54
|
+
left: number;
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}): {
|
|
58
|
+
top: number;
|
|
59
|
+
left: number;
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
};
|
|
27
63
|
/**
|
|
28
64
|
* @param img - Image element to annotate. Must be in the DOM with non-zero dimensions.
|
|
29
65
|
* @param options - Plugin configuration.
|
|
@@ -53,6 +89,12 @@ export declare class AnnotateImage {
|
|
|
53
89
|
destroy(): void;
|
|
54
90
|
/** Cancel the active edit (if any) and return to view mode. */
|
|
55
91
|
cancelEdit(): void;
|
|
92
|
+
/** Recompute scale factors, deferring if an edit is active. */
|
|
93
|
+
private rescale;
|
|
94
|
+
/** Apply new scale factors and re-render all views. */
|
|
95
|
+
private applyRescale;
|
|
96
|
+
/** @internal Flush any deferred rescale after an edit completes. */
|
|
97
|
+
flushPendingRescale(): void;
|
|
56
98
|
/** Replace all annotations with new data. Does not fire lifecycle callbacks. */
|
|
57
99
|
setNotes(notes: AnnotationNote[]): void;
|
|
58
100
|
/** Toggle editing mode. Creates or removes Add Note button and rebuilds views. Does not fire lifecycle callbacks. */
|
|
@@ -25,7 +25,7 @@ export declare class AnnotateView {
|
|
|
25
25
|
* @param note - Annotation data to display.
|
|
26
26
|
*/
|
|
27
27
|
constructor(image: AnnotateImage, note: AnnotationNote);
|
|
28
|
-
/** Apply the note's position and dimensions to the area element. */
|
|
28
|
+
/** Apply the note's position and dimensions to the area element, scaled to rendered size. */
|
|
29
29
|
setPosition(): void;
|
|
30
30
|
/** Update the view's position, size, and text from the edit area after a save. */
|
|
31
31
|
resetPosition(editable: {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Clamp a note's size and position to fit within the given natural image dimensions. */
|
|
2
|
+
export declare function clampNote(note: {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}, naturalWidth: number, naturalHeight: number): {
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
/** Clamp all notes in the array in place, constraining geometry to image bounds. */
|
|
14
|
+
export declare function clampNotes(notes: {
|
|
15
|
+
top: number;
|
|
16
|
+
left: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}[], naturalWidth: number, naturalHeight: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the horizontal `left` value for a note/form element
|
|
22
|
+
* relative to its parent annotation area, centering it under the area
|
|
23
|
+
* and clamping to viewport edges.
|
|
24
|
+
*
|
|
25
|
+
* @param noteWidth - Measured width of the note/form element
|
|
26
|
+
* @param areaLeftInViewport - The area's left edge in viewport coordinates
|
|
27
|
+
* @param areaWidth - Width of the annotation area
|
|
28
|
+
* @param viewportWidth - Browser viewport width (window.innerWidth)
|
|
29
|
+
* @returns CSS `left` value in pixels, relative to the area element
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeNoteLeft(noteWidth: number, areaLeftInViewport: number, areaWidth: number, viewportWidth: number): number;
|
package/dist/types/react.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface AnnotateImageProps {
|
|
|
23
23
|
onLoad?: (notes: NoteData[]) => void;
|
|
24
24
|
/** Called when an operation fails. */
|
|
25
25
|
onError?: (context: AnnotateErrorContext) => void;
|
|
26
|
+
/** Enable automatic re-scaling when the container resizes. Default: true. */
|
|
27
|
+
autoResize?: boolean;
|
|
26
28
|
}
|
|
27
29
|
/** Imperative methods exposed via ref. */
|
|
28
30
|
export interface AnnotateImageRef {
|
package/dist/types/types.d.ts
CHANGED
|
@@ -84,6 +84,10 @@ export interface AnnotateImageOptions {
|
|
|
84
84
|
onLoad?: (notes: NoteData[]) => void;
|
|
85
85
|
/** UI label overrides. Missing fields use built-in defaults. */
|
|
86
86
|
labels?: Labels;
|
|
87
|
+
/** Attach a ResizeObserver to rescale annotations when the image resizes. Default: true. */
|
|
88
|
+
autoResize?: boolean;
|
|
89
|
+
/** CSS theme name. When set, adds `data-theme="<value>"` to the canvas element for CSS variable scoping. */
|
|
90
|
+
theme?: string;
|
|
87
91
|
}
|
|
88
92
|
export interface DragCallbacks {
|
|
89
93
|
containment?: HTMLElement;
|
package/dist/types/vue.d.ts
CHANGED
|
@@ -33,6 +33,11 @@ export declare const AnnotateImage: import("vue").DefineComponent<import("vue").
|
|
|
33
33
|
type: BooleanConstructor;
|
|
34
34
|
default: boolean;
|
|
35
35
|
};
|
|
36
|
+
/** Enable automatic re-scaling when the container resizes. Default: true. */
|
|
37
|
+
autoResize: {
|
|
38
|
+
type: BooleanConstructor;
|
|
39
|
+
default: boolean;
|
|
40
|
+
};
|
|
36
41
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
37
42
|
[key: string]: any;
|
|
38
43
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -68,6 +73,11 @@ export declare const AnnotateImage: import("vue").DefineComponent<import("vue").
|
|
|
68
73
|
type: BooleanConstructor;
|
|
69
74
|
default: boolean;
|
|
70
75
|
};
|
|
76
|
+
/** Enable automatic re-scaling when the container resizes. Default: true. */
|
|
77
|
+
autoResize: {
|
|
78
|
+
type: BooleanConstructor;
|
|
79
|
+
default: boolean;
|
|
80
|
+
};
|
|
71
81
|
}>> & Readonly<{
|
|
72
82
|
onChange?: ((_notes: NoteData[]) => any) | undefined;
|
|
73
83
|
onError?: ((_context: AnnotateErrorContext) => any) | undefined;
|
|
@@ -76,4 +86,5 @@ export declare const AnnotateImage: import("vue").DefineComponent<import("vue").
|
|
|
76
86
|
onDelete?: ((_note: NoteData) => any) | undefined;
|
|
77
87
|
}>, {
|
|
78
88
|
editable: boolean;
|
|
89
|
+
autoResize: boolean;
|
|
79
90
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|