annotate-image 2.0.0-beta.1 → 2.0.0-beta.2

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 CHANGED
@@ -27,10 +27,11 @@ var AnnotateEdit = class {
27
27
  };
28
28
  }
29
29
  this.area = image.editOverlay.querySelector(".image-annotate-edit-area");
30
- this.area.style.height = this.note.height + "px";
31
- this.area.style.width = this.note.width + "px";
32
- this.area.style.left = this.note.left + "px";
33
- this.area.style.top = this.note.top + "px";
30
+ const rendered = image.toRendered(this.note);
31
+ this.area.style.height = rendered.height + "px";
32
+ this.area.style.width = rendered.width + "px";
33
+ this.area.style.left = rendered.left + "px";
34
+ this.area.style.top = rendered.top + "px";
34
35
  this.form = document.createElement("div");
35
36
  this.form.className = "image-annotate-edit-form";
36
37
  const formEl = document.createElement("form");
@@ -117,13 +118,20 @@ var AnnotateEdit = class {
117
118
  }
118
119
  this.image.notifySave(stripInternals(this.note));
119
120
  this.destroy();
121
+ this.image.flushPendingRescale();
120
122
  };
121
123
  const pos = readInlinePosition(this.area);
122
124
  const size = readInlineSize(this.area);
123
- this.note.top = pos.top;
124
- this.note.left = pos.left;
125
- this.note.width = size.width;
126
- this.note.height = size.height;
125
+ const natural = this.image.toNatural({
126
+ top: pos.top,
127
+ left: pos.left,
128
+ width: size.width,
129
+ height: size.height
130
+ });
131
+ this.note.top = natural.top;
132
+ this.note.left = natural.left;
133
+ this.note.width = natural.width;
134
+ this.note.height = natural.height;
127
135
  this.note.text = text;
128
136
  if (this.image.api.save) {
129
137
  this.busy = true;
@@ -157,6 +165,7 @@ var AnnotateEdit = class {
157
165
  const idx = this.image.notes.indexOf(this.note);
158
166
  if (idx !== -1) this.image.notes.splice(idx, 1);
159
167
  this.image.notifyDelete(stripInternals(this.note));
168
+ this.image.flushPendingRescale();
160
169
  };
161
170
  if (this.image.api.delete) {
162
171
  this.busy = true;
@@ -232,29 +241,29 @@ var AnnotateView = class {
232
241
  });
233
242
  }
234
243
  }
235
- /** Apply the note's position and dimensions to the area element. */
244
+ /** Apply the note's position and dimensions to the area element, scaled to rendered size. */
236
245
  setPosition() {
246
+ const rendered = this.image.toRendered(this.note);
237
247
  const innerDiv = this.area.firstElementChild;
238
- innerDiv.style.height = this.note.height + "px";
239
- innerDiv.style.width = this.note.width + "px";
240
- this.area.style.left = this.note.left + "px";
241
- this.area.style.top = this.note.top + "px";
248
+ innerDiv.style.height = rendered.height + "px";
249
+ innerDiv.style.width = rendered.width + "px";
250
+ this.area.style.left = rendered.left + "px";
251
+ this.area.style.top = rendered.top + "px";
242
252
  }
243
253
  /** Update the view's position, size, and text from the edit area after a save. */
244
254
  resetPosition(editable, text) {
245
255
  this.tooltip.textContent = text;
246
256
  this.tooltip.style.display = "none";
247
- const areaPos = readInlinePosition(editable.area);
248
- const areaSize = readInlineSize(editable.area);
257
+ const rendered = this.image.toRendered(editable.note);
249
258
  const innerDiv = this.area.firstElementChild;
250
- innerDiv.style.height = areaSize.height + "px";
251
- innerDiv.style.width = areaSize.width + "px";
252
- this.area.style.left = areaPos.left + "px";
253
- this.area.style.top = areaPos.top + "px";
254
- this.note.top = areaPos.top;
255
- this.note.left = areaPos.left;
256
- this.note.height = areaSize.height;
257
- this.note.width = areaSize.width;
259
+ innerDiv.style.height = rendered.height + "px";
260
+ innerDiv.style.width = rendered.width + "px";
261
+ this.area.style.left = rendered.left + "px";
262
+ this.area.style.top = rendered.top + "px";
263
+ this.note.top = editable.note.top;
264
+ this.note.left = editable.note.left;
265
+ this.note.height = editable.note.height;
266
+ this.note.width = editable.note.width;
258
267
  this.note.text = text;
259
268
  this.note.id = editable.note.id;
260
269
  this.editable = true;
@@ -524,15 +533,25 @@ var AnnotateImage = class {
524
533
  this._mode = "view";
525
534
  this.activeEdit = null;
526
535
  this.destroyed = false;
536
+ this.pendingRescale = false;
537
+ this.originalParent = null;
538
+ this.originalNextSibling = null;
527
539
  this.options = options;
528
540
  this.handlers = createDefaultHandlers();
529
541
  this.img = img;
530
- const width = img.width;
531
- const height = img.height;
532
- if (width === 0 || height === 0) {
542
+ this.naturalWidth = img.naturalWidth || img.width;
543
+ this.naturalHeight = img.naturalHeight || img.height;
544
+ const rendered = img.getBoundingClientRect();
545
+ const renderedWidth = rendered.width || img.width;
546
+ const renderedHeight = rendered.height || img.height;
547
+ if (this.naturalWidth === 0 || this.naturalHeight === 0) {
533
548
  throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");
534
549
  }
550
+ this.scaleX = renderedWidth / this.naturalWidth;
551
+ this.scaleY = renderedHeight / this.naturalHeight;
535
552
  this.notes = options.notes.map((n) => ({ ...n }));
553
+ this.originalParent = img.parentNode;
554
+ this.originalNextSibling = img.nextSibling;
536
555
  this.canvas = document.createElement("div");
537
556
  this.canvas.className = "image-annotate-canvas";
538
557
  this.viewOverlay = document.createElement("div");
@@ -543,19 +562,13 @@ var AnnotateImage = class {
543
562
  const editArea = document.createElement("div");
544
563
  editArea.className = "image-annotate-edit-area";
545
564
  this.editOverlay.appendChild(editArea);
546
- this.canvas.appendChild(this.viewOverlay);
547
- this.canvas.appendChild(this.editOverlay);
548
565
  if (!img.parentNode) {
549
566
  throw new Error("image-annotate: image must be in the DOM before initialization");
550
567
  }
551
- img.parentNode.insertBefore(this.canvas, img.nextSibling);
552
- this.canvas.style.height = height + "px";
553
- this.canvas.style.width = width + "px";
554
- this.canvas.style.backgroundImage = 'url("' + img.src + '")';
555
- this.viewOverlay.style.height = height + "px";
556
- this.viewOverlay.style.width = width + "px";
557
- this.editOverlay.style.height = height + "px";
558
- this.editOverlay.style.width = width + "px";
568
+ img.parentNode.insertBefore(this.canvas, img);
569
+ this.canvas.appendChild(img);
570
+ this.canvas.appendChild(this.viewOverlay);
571
+ this.canvas.appendChild(this.editOverlay);
559
572
  this.api = this.options.api ? normalizeApi(this.options.api) : {};
560
573
  if (this.api.load) {
561
574
  this.loadFromApi();
@@ -565,7 +578,38 @@ var AnnotateImage = class {
565
578
  if (this.options.editable) {
566
579
  this.createButton();
567
580
  }
568
- img.style.display = "none";
581
+ if (options.autoResize !== false && typeof ResizeObserver !== "undefined") {
582
+ this.resizeObserver = new ResizeObserver((entries) => {
583
+ const entry = entries[0];
584
+ if (!entry) return;
585
+ const { width, height } = entry.contentRect;
586
+ if (width === 0 || height === 0) return;
587
+ this.rescale(width, height);
588
+ });
589
+ this.resizeObserver.observe(this.canvas);
590
+ }
591
+ }
592
+ /** Convert a rect from natural image coordinates to rendered (scaled) coordinates. */
593
+ toRendered(rect) {
594
+ return {
595
+ top: rect.top * this.scaleY,
596
+ left: rect.left * this.scaleX,
597
+ width: rect.width * this.scaleX,
598
+ height: rect.height * this.scaleY
599
+ };
600
+ }
601
+ /** Convert a rect from rendered (scaled) coordinates to natural image coordinates. */
602
+ toNatural(rect) {
603
+ const result = {
604
+ top: rect.top / this.scaleY,
605
+ left: rect.left / this.scaleX,
606
+ width: rect.width / this.scaleX,
607
+ height: rect.height / this.scaleY
608
+ };
609
+ if (!isFinite(result.top) || !isFinite(result.left) || !isFinite(result.width) || !isFinite(result.height)) {
610
+ throw new Error("image-annotate: scale conversion produced non-finite coordinates");
611
+ }
612
+ return result;
569
613
  }
570
614
  /** Current interaction mode — 'view' for browsing, 'edit' when an annotation is being created or modified. */
571
615
  get mode() {
@@ -637,8 +681,15 @@ var AnnotateImage = class {
637
681
  if (this.button) {
638
682
  this.button.remove();
639
683
  }
684
+ if (this.resizeObserver) {
685
+ this.resizeObserver.disconnect();
686
+ this.resizeObserver = void 0;
687
+ }
688
+ if (this.originalParent && this.originalParent.isConnected) {
689
+ const ref = this.originalNextSibling?.parentNode === this.originalParent ? this.originalNextSibling : null;
690
+ this.originalParent.insertBefore(this.img, ref);
691
+ }
640
692
  this.canvas.remove();
641
- this.img.style.display = "";
642
693
  }
643
694
  /** Cancel the active edit (if any) and return to view mode. */
644
695
  cancelEdit() {
@@ -646,6 +697,34 @@ var AnnotateImage = class {
646
697
  this.activeEdit.destroy();
647
698
  this.setMode("view");
648
699
  }
700
+ this.flushPendingRescale();
701
+ }
702
+ /** Recompute scale factors, deferring if an edit is active. */
703
+ rescale(renderedWidth, renderedHeight) {
704
+ if (this.mode === "edit") {
705
+ this.pendingRescale = true;
706
+ return;
707
+ }
708
+ this.applyRescale(renderedWidth, renderedHeight);
709
+ }
710
+ /** Apply new scale factors and re-render all views. */
711
+ applyRescale(renderedWidth, renderedHeight) {
712
+ const newScaleX = renderedWidth / this.naturalWidth;
713
+ const newScaleY = renderedHeight / this.naturalHeight;
714
+ if (newScaleX === this.scaleX && newScaleY === this.scaleY) return;
715
+ this.scaleX = newScaleX;
716
+ this.scaleY = newScaleY;
717
+ this.destroyViews();
718
+ this.createViews();
719
+ }
720
+ /** @internal Flush any deferred rescale after an edit completes. */
721
+ flushPendingRescale() {
722
+ if (!this.pendingRescale) return;
723
+ this.pendingRescale = false;
724
+ const rect = this.canvas.getBoundingClientRect();
725
+ if (rect.width > 0 && rect.height > 0) {
726
+ this.applyRescale(rect.width, rect.height);
727
+ }
649
728
  }
650
729
  /** Replace all annotations with new data. Does not fire lifecycle callbacks. */
651
730
  setNotes(notes) {
@@ -726,6 +805,7 @@ var DEFAULT_LABELS = {
726
805
  var defaults = {
727
806
  editable: true,
728
807
  notes: [],
808
+ autoResize: true,
729
809
  labels: { ...DEFAULT_LABELS }
730
810
  };
731
811
  function annotate(img, options) {
package/dist/core.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";var AnnotateImage=(()=>{var M=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Y=(e,t)=>{for(var n in t)M(e,n,{get:t[n],enumerable:!0})},U=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of V(t))!X.call(e,i)&&i!==n&&M(e,i,{get:()=>t[i],enumerable:!(o=F(t,i))||o.enumerable});return e};var $=e=>U(M({},"__esModule",{value:!0}),e);var at={};Y(at,{AnnotateImage:()=>I,DEFAULT_LABELS:()=>H,annotate:()=>ot});var j=30,K=30,W=30,q=30,D=class{constructor(t,n,o){this.busy=!1;this.image=t,this.handlers=t.handlers,n?this.note=n:this.note={id:"new",top:j,left:K,width:W,height:q,text:"",editable:!0},this.area=t.editOverlay.querySelector(".image-annotate-edit-area"),this.area.style.height=this.note.height+"px",this.area.style.width=this.note.width+"px",this.area.style.left=this.note.left+"px",this.area.style.top=this.note.top+"px",this.form=document.createElement("div"),this.form.className="image-annotate-edit-form";let i=document.createElement("form");this.textarea=document.createElement("textarea"),this.textarea.name="text",this.textarea.rows=3,this.textarea.cols=30,this.textarea.value=this.note.text;let r=this.image.options.labels?.placeholder??"";r&&(this.textarea.placeholder=r),i.appendChild(this.textarea),this.form.appendChild(i),this.area.appendChild(this.form),this.form.addEventListener("pointerdown",s=>s.stopPropagation());let a=this.area,p=s=>{a.style.left=s.left+"px",a.style.top=s.top+"px",a.style.width=s.width+"px",a.style.height=s.height+"px"};this.handlers.makeResizable(a,{containment:t.canvas,onResize:p,onStop:p}),this.handlers.makeDraggable(a,{containment:t.canvas,onDrag:s=>{a.style.left=s.left+"px",a.style.top=s.top+"px"},onStop:s=>{a.style.left=s.left+"px",a.style.top=s.top+"px"}}),this.textarea.focus(),this.form.addEventListener("keydown",s=>{if(s.key==="Escape"){let c=this.form.querySelector(".image-annotate-edit-close");c&&c.click()}});let l=document.createElement("div");l.className="image-annotate-edit-buttons",this.form.appendChild(l),this.addSaveButton(l,o),o&&this.addDeleteButton(l,o),this.addCancelButton(l)}destroy(){this.image.activeEdit=null,this.handlers.destroyResizable(this.area),this.handlers.destroyDraggable(this.area),this.area.style.height="",this.area.style.width="",this.area.style.left="",this.area.style.top="",this.form.remove()}addSaveButton(t,n){let o=document.createElement("button");o.className="image-annotate-edit-ok",o.textContent=this.image.options.labels?.save??"OK",o.type="button",o.addEventListener("click",()=>{if(this.busy)return;let i=this.textarea.value,r=()=>{this.image.setMode("view"),n?n.resetPosition(this,i):(this.note.editable=!0,new T(this.image,this.note).resetPosition(this,i),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy()},a=P(this.area),p=O(this.area);this.note.top=a.top,this.note.left=a.left,this.note.width=p.width,this.note.height=p.height,this.note.text=i,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(l=>{l.annotation_id!=null&&(this.note.id=l.annotation_id),r()}).catch(l=>{this.busy=!1;let s=l instanceof Error?l:new Error(String(l));this.image.reportError({type:"save",error:s,note:this.note})})):r()}),t.appendChild(o)}addDeleteButton(t,n){let o=document.createElement("button");o.className="image-annotate-edit-delete",o.textContent=this.image.options.labels?.delete??"Delete",o.type="button",o.addEventListener("click",()=>{if(this.busy)return;let i=()=>{this.image.setMode("view"),this.destroy(),n.destroy();let r=this.image.notes.indexOf(this.note);r!==-1&&this.image.notes.splice(r,1),this.image.notifyDelete(C(this.note))};this.image.api.delete?(this.busy=!0,this.image.api.delete(C(this.note)).then(()=>{i()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"delete",error:a,note:this.note})})):i()}),t.appendChild(o)}addCancelButton(t){let n=document.createElement("button");n.className="image-annotate-edit-close",n.textContent=this.image.options.labels?.cancel??"Cancel",n.type="button",n.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(n)}};function P(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function O(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var T=class{constructor(t,n){this.image=t,this.note=n,this.editable=!!(n.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let o=document.createElement("div");this.area.appendChild(o),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=n.text,this.tooltip.style.display="none",this.area.appendChild(this.tooltip),this.setPosition(),this.area.addEventListener("mouseenter",()=>this.show()),this.area.addEventListener("mouseleave",()=>this.hide()),this.editable&&(this.area.setAttribute("tabindex","0"),this.area.setAttribute("role","button"),this.area.addEventListener("click",()=>this.edit()),this.area.addEventListener("keydown",i=>{(i.key==="Enter"||i.key===" ")&&(i.preventDefault(),this.edit())}))}setPosition(){let t=this.area.firstElementChild;t.style.height=this.note.height+"px",t.style.width=this.note.width+"px",this.area.style.left=this.note.left+"px",this.area.style.top=this.note.top+"px"}resetPosition(t,n){this.tooltip.textContent=n,this.tooltip.style.display="none";let o=P(t.area),i=O(t.area),r=this.area.firstElementChild;r.style.height=i.height+"px",r.style.width=i.width+"px",this.area.style.left=o.left+"px",this.area.style.top=o.top+"px",this.note.top=o.top,this.note.left=o.left,this.note.height=i.height,this.note.width=i.width,this.note.text=n,this.note.id=t.note.id,this.editable=!0}show(){this.tooltip.style.display="block",this.editable?this.area.classList.add("image-annotate-area-editable-hover"):this.area.classList.add("image-annotate-area-hover")}hide(){this.tooltip.style.display="none",this.area.classList.remove("image-annotate-area-hover"),this.area.classList.remove("image-annotate-area-editable-hover")}destroy(){this.area.remove(),this.tooltip.remove()}edit(){this.image.mode==="view"&&(this.image.setMode("edit"),this.image.activeEdit=new D(this.image,this.note,this))}};var S=new WeakMap,k=new WeakMap;function J(e,t){z(e);function n(o){if(o.button!==0)return;o.preventDefault(),e.setPointerCapture&&e.setPointerCapture(o.pointerId);let i=o.clientX,r=o.clientY,a=parseFloat(e.style.left)||0,p=parseFloat(e.style.top)||0,l=parseFloat(e.style.width)||e.offsetWidth,s=parseFloat(e.style.height)||e.offsetHeight,c=-1/0,L=-1/0,A=1/0,E=1/0;if(t.containment){let m=t.containment.getBoundingClientRect(),u=e.getBoundingClientRect(),y=a-(u.left-m.left),d=p-(u.top-m.top);c=y,L=d,A=m.width-l+y,E=m.height-s+d}function b(m,u,y){return Math.max(u,Math.min(y,m))}function w(m){let u=m.clientX-i,y=m.clientY-r,d=b(a+u,c,A),h=b(p+y,L,E);t.onDrag&&t.onDrag({left:d,top:h})}function x(m){e.releasePointerCapture&&e.releasePointerCapture(m.pointerId),e.removeEventListener("pointermove",w),e.removeEventListener("pointerup",x);let u=m.clientX-i,y=m.clientY-r,d=b(a+u,c,A),h=b(p+y,L,E);t.onStop&&t.onStop({left:d,top:h})}e.addEventListener("pointermove",w),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",n),S.set(e,()=>{e.removeEventListener("pointerdown",n)})}function z(e){let t=S.get(e);t&&(t(),S.delete(e))}var g=10,G=["nw","ne","sw","se"];function R(e,t,n,o,i,r,a){let p=t,l=n,s=o,c=i;return e==="nw"||e==="sw"?(p=t+r,s=o-r):s=o+r,e==="nw"||e==="ne"?(l=n+a,c=i-a):c=i+a,s<g&&((e==="nw"||e==="sw")&&(p=t+o-g),s=g),c<g&&((e==="nw"||e==="ne")&&(l=n+i-g),c=g),{left:p,top:l,width:s,height:c}}function Z(e,t){B(e);let n=[];for(let o of G){let i=document.createElement("div");i.className=`image-annotate-resize-handle image-annotate-resize-handle-${o}`,e.appendChild(i),n.push(i),i.addEventListener("pointerdown",function(a){if(a.button!==0)return;a.preventDefault(),a.stopPropagation(),i.setPointerCapture&&i.setPointerCapture(a.pointerId);let p=a.clientX,l=a.clientY,s=parseFloat(e.style.left)||0,c=parseFloat(e.style.top)||0,L=parseFloat(e.style.width)||0,A=parseFloat(e.style.height)||0,E=1/0,b=1/0,w=-1/0,x=-1/0;if(t.containment){let d=t.containment.getBoundingClientRect(),h=e.getBoundingClientRect(),f=s-(h.left-d.left),v=c-(h.top-d.top);w=f,x=v,E=d.width+f,b=d.height+v}function m(d){let{left:h,top:f,width:v,height:N}=d;return h<w&&(v-=w-h,h=w),f<x&&(N-=x-f,f=x),h+v>E&&(v=E-h),f+N>b&&(N=b-f),v<g&&(v=g),N<g&&(N=g),{left:h,top:f,width:v,height:N}}function u(d){let h=d.clientX-p,f=d.clientY-l,v=m(R(o,s,c,L,A,h,f));t.onResize?.(v)}function y(d){i.releasePointerCapture&&i.releasePointerCapture(d.pointerId),i.removeEventListener("pointermove",u),i.removeEventListener("pointerup",y);let h=d.clientX-p,f=d.clientY-l,v=m(R(o,s,c,L,A,h,f));t.onStop&&t.onStop(v)}i.addEventListener("pointermove",u),i.addEventListener("pointerup",y)})}k.set(e,()=>{for(let o of n)o.remove()})}function B(e){let t=k.get(e);t&&(t(),k.delete(e))}function _(){return{makeDraggable:J,makeResizable:Z,destroyDraggable:z,destroyResizable:B}}function C(e){let{view:t,editable:n,...o}=e;return o}function Q(e){return{load:typeof e.load=="string"?tt(e.load):e.load,save:typeof e.save=="string"?et(e.save):e.save,delete:typeof e.delete=="string"?nt(e.delete):e.delete}}function tt(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function et(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Save failed (HTTP ${n.status})`);return n.json()})}function nt(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Delete failed (HTTP ${n.status})`)})}var I=class{constructor(t,n){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.options=n,this.handlers=_(),this.img=t;let o=t.width,i=t.height;if(o===0||i===0)throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");this.notes=n.notes.map(a=>({...a})),this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",this.viewOverlay=document.createElement("div"),this.viewOverlay.className="image-annotate-view",this.editOverlay=document.createElement("div"),this.editOverlay.className="image-annotate-edit",this.editOverlay.style.display="none";let r=document.createElement("div");if(r.className="image-annotate-edit-area",this.editOverlay.appendChild(r),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t.nextSibling),this.canvas.style.height=i+"px",this.canvas.style.width=o+"px",this.canvas.style.backgroundImage='url("'+t.src+'")',this.viewOverlay.style.height=i+"px",this.viewOverlay.style.width=o+"px",this.editOverlay.style.height=i+"px",this.editOverlay.style.width=o+"px",this.api=this.options.api?Q(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),t.style.display="none"}get mode(){return this._mode}setMode(t){this._mode=t,t==="edit"?(this.canvas.classList.add("image-annotate-editing"),this.editOverlay.style.display="block"):(this.canvas.classList.remove("image-annotate-editing"),this.editOverlay.style.display="none")}getNotes(){return this.notes.map(C)}notifyChange(){this.options.onChange?.(this.getNotes())}notifySave(t){this.options.onSave?.(t),this.notifyChange()}notifyDelete(t){this.options.onDelete?.(t),this.notifyChange()}notifyLoad(){this.options.onLoad?.(this.getNotes()),this.notifyChange()}destroyViews(){this.cancelEdit();for(let t of this.notes)t.view?.destroy()}createViews(){for(let t of this.notes)t.view=new T(this,t)}load(){this.destroyViews(),this.createViews(),this.notifyLoad()}clear(){this.destroyViews(),this.notes=[],this.notifyChange()}destroy(){this.destroyed||(this.destroyed=!0,this.destroyViews(),this.notes=[],this.button&&this.button.remove(),this.canvas.remove(),this.img.style.display="")}cancelEdit(){this.activeEdit&&(this.activeEdit.destroy(),this.setMode("view"))}setNotes(t){this.destroyed||(this.destroyViews(),this.notes=t.map(n=>({...n})),this.createViews())}setEditable(t){this.destroyed||this.options.editable!==t&&(this.options.editable=t,t&&!this.button?this.createButton():!t&&this.button&&(this.button.remove(),this.button=void 0),this.destroyViews(),this.createViews())}createButton(){this.button=document.createElement("button"),this.button.className="image-annotate-add",this.button.title=this.options.labels?.addNote??"Add Note",this.button.type="button",this.button.addEventListener("click",()=>{this.add()}),this.canvas.appendChild(this.button)}reportError(t){this.options.onError?this.options.onError(t):console.error(`image-annotate: ${t.type} failed`,t.error)}loadFromApi(){this.api.load&&this.api.load().then(t=>{this.destroyViews(),this.notes=t,this.createViews(),this.notifyLoad()}).catch(t=>{let n=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:n})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new D(this),!0):!1}};var H={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var it={editable:!0,notes:[],labels:{...H}};function ot(e,t){let n={...it,...t,labels:{...H,...t?.labels}};return new I(e,n)}return $(at);})();
1
+ "use strict";var AnnotateImage=(()=>{var P=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)P(e,n,{get:t[n],enumerable:!0})},W=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Y(t))!_.call(e,o)&&o!==n&&P(e,o,{get:()=>t[o],enumerable:!(i=X(t,o))||i.enumerable});return e};var U=e=>W(P({},"__esModule",{value:!0}),e);var at={};V(at,{AnnotateImage:()=>R,DEFAULT_LABELS:()=>H,annotate:()=>ot});var $=30,j=30,K=30,q=30,D=class{constructor(t,n,i){this.busy=!1;this.image=t,this.handlers=t.handlers,n?this.note=n:this.note={id:"new",top:$,left:j,width:K,height:q,text:"",editable:!0},this.area=t.editOverlay.querySelector(".image-annotate-edit-area");let o=t.toRendered(this.note);this.area.style.height=o.height+"px",this.area.style.width=o.width+"px",this.area.style.left=o.left+"px",this.area.style.top=o.top+"px",this.form=document.createElement("div"),this.form.className="image-annotate-edit-form";let d=document.createElement("form");this.textarea=document.createElement("textarea"),this.textarea.name="text",this.textarea.rows=3,this.textarea.cols=30,this.textarea.value=this.note.text;let l=this.image.options.labels?.placeholder??"";l&&(this.textarea.placeholder=l),d.appendChild(this.textarea),this.form.appendChild(d),this.area.appendChild(this.form),this.form.addEventListener("pointerdown",a=>a.stopPropagation());let s=this.area,c=a=>{s.style.left=a.left+"px",s.style.top=a.top+"px",s.style.width=a.width+"px",s.style.height=a.height+"px"};this.handlers.makeResizable(s,{containment:t.canvas,onResize:c,onStop:c}),this.handlers.makeDraggable(s,{containment:t.canvas,onDrag:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px"},onStop:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px"}}),this.textarea.focus(),this.form.addEventListener("keydown",a=>{if(a.key==="Escape"){let b=this.form.querySelector(".image-annotate-edit-close");b&&b.click()}});let r=document.createElement("div");r.className="image-annotate-edit-buttons",this.form.appendChild(r),this.addSaveButton(r,i),i&&this.addDeleteButton(r,i),this.addCancelButton(r)}destroy(){this.image.activeEdit=null,this.handlers.destroyResizable(this.area),this.handlers.destroyDraggable(this.area),this.area.style.height="",this.area.style.width="",this.area.style.left="",this.area.style.top="",this.form.remove()}addSaveButton(t,n){let i=document.createElement("button");i.className="image-annotate-edit-ok",i.textContent=this.image.options.labels?.save??"OK",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,d=()=>{this.image.setMode("view"),n?n.resetPosition(this,o):(this.note.editable=!0,new T(this.image,this.note).resetPosition(this,o),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},l=O(this.area),s=S(this.area),c=this.image.toNatural({top:l.top,left:l.left,width:s.width,height:s.height});this.note.top=c.top,this.note.left=c.left,this.note.width=c.width,this.note.height=c.height,this.note.text=o,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(r=>{r.annotation_id!=null&&(this.note.id=r.annotation_id),d()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"save",error:a,note:this.note})})):d()}),t.appendChild(i)}addDeleteButton(t,n){let i=document.createElement("button");i.className="image-annotate-edit-delete",i.textContent=this.image.options.labels?.delete??"Delete",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),n.destroy();let d=this.image.notes.indexOf(this.note);d!==-1&&this.image.notes.splice(d,1),this.image.notifyDelete(C(this.note)),this.image.flushPendingRescale()};this.image.api.delete?(this.busy=!0,this.image.api.delete(C(this.note)).then(()=>{o()}).catch(d=>{this.busy=!1;let l=d instanceof Error?d:new Error(String(d));this.image.reportError({type:"delete",error:l,note:this.note})})):o()}),t.appendChild(i)}addCancelButton(t){let n=document.createElement("button");n.className="image-annotate-edit-close",n.textContent=this.image.options.labels?.cancel??"Cancel",n.type="button",n.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(n)}};function O(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function S(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var T=class{constructor(t,n){this.image=t,this.note=n,this.editable=!!(n.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let i=document.createElement("div");this.area.appendChild(i),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=n.text,this.tooltip.style.display="none",this.area.appendChild(this.tooltip),this.setPosition(),this.area.addEventListener("mouseenter",()=>this.show()),this.area.addEventListener("mouseleave",()=>this.hide()),this.editable&&(this.area.setAttribute("tabindex","0"),this.area.setAttribute("role","button"),this.area.addEventListener("click",()=>this.edit()),this.area.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),this.edit())}))}setPosition(){let t=this.image.toRendered(this.note),n=this.area.firstElementChild;n.style.height=t.height+"px",n.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,n){this.tooltip.textContent=n,this.tooltip.style.display="none";let i=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=i.height+"px",o.style.width=i.width+"px",this.area.style.left=i.left+"px",this.area.style.top=i.top+"px",this.note.top=t.note.top,this.note.left=t.note.left,this.note.height=t.note.height,this.note.width=t.note.width,this.note.text=n,this.note.id=t.note.id,this.editable=!0}show(){this.tooltip.style.display="block",this.editable?this.area.classList.add("image-annotate-area-editable-hover"):this.area.classList.add("image-annotate-area-hover")}hide(){this.tooltip.style.display="none",this.area.classList.remove("image-annotate-area-hover"),this.area.classList.remove("image-annotate-area-editable-hover")}destroy(){this.area.remove(),this.tooltip.remove()}edit(){this.image.mode==="view"&&(this.image.setMode("edit"),this.image.activeEdit=new D(this.image,this.note,this))}};var I=new WeakMap,M=new WeakMap;function J(e,t){k(e);function n(i){if(i.button!==0)return;i.preventDefault(),e.setPointerCapture&&e.setPointerCapture(i.pointerId);let o=i.clientX,d=i.clientY,l=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,c=parseFloat(e.style.width)||e.offsetWidth,r=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,b=-1/0,N=1/0,w=1/0;if(t.containment){let m=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=l-(v.left-m.left),h=s-(v.top-m.top);a=g,b=h,N=m.width-c+g,w=m.height-r+h}function E(m,v,g){return Math.max(v,Math.min(g,m))}function L(m){let v=m.clientX-o,g=m.clientY-d,h=E(l+v,a,N),p=E(s+g,b,w);t.onDrag&&t.onDrag({left:h,top:p})}function x(m){e.releasePointerCapture&&e.releasePointerCapture(m.pointerId),e.removeEventListener("pointermove",L),e.removeEventListener("pointerup",x);let v=m.clientX-o,g=m.clientY-d,h=E(l+v,a,N),p=E(s+g,b,w);t.onStop&&t.onStop({left:h,top:p})}e.addEventListener("pointermove",L),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",n),I.set(e,()=>{e.removeEventListener("pointerdown",n)})}function k(e){let t=I.get(e);t&&(t(),I.delete(e))}var y=10,G=["nw","ne","sw","se"];function z(e,t,n,i,o,d,l){let s=t,c=n,r=i,a=o;return e==="nw"||e==="sw"?(s=t+d,r=i-d):r=i+d,e==="nw"||e==="ne"?(c=n+l,a=o-l):a=o+l,r<y&&((e==="nw"||e==="sw")&&(s=t+i-y),r=y),a<y&&((e==="nw"||e==="ne")&&(c=n+o-y),a=y),{left:s,top:c,width:r,height:a}}function Z(e,t){B(e);let n=[];for(let i of G){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${i}`,e.appendChild(o),n.push(o),o.addEventListener("pointerdown",function(l){if(l.button!==0)return;l.preventDefault(),l.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(l.pointerId);let s=l.clientX,c=l.clientY,r=parseFloat(e.style.left)||0,a=parseFloat(e.style.top)||0,b=parseFloat(e.style.width)||0,N=parseFloat(e.style.height)||0,w=1/0,E=1/0,L=-1/0,x=-1/0;if(t.containment){let h=t.containment.getBoundingClientRect(),p=e.getBoundingClientRect(),f=r-(p.left-h.left),u=a-(p.top-h.top);L=f,x=u,w=h.width+f,E=h.height+u}function m(h){let{left:p,top:f,width:u,height:A}=h;return p<L&&(u-=L-p,p=L),f<x&&(A-=x-f,f=x),p+u>w&&(u=w-p),f+A>E&&(A=E-f),u<y&&(u=y),A<y&&(A=y),{left:p,top:f,width:u,height:A}}function v(h){let p=h.clientX-s,f=h.clientY-c,u=m(z(i,r,a,b,N,p,f));t.onResize?.(u)}function g(h){o.releasePointerCapture&&o.releasePointerCapture(h.pointerId),o.removeEventListener("pointermove",v),o.removeEventListener("pointerup",g);let p=h.clientX-s,f=h.clientY-c,u=m(z(i,r,a,b,N,p,f));t.onStop&&t.onStop(u)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}M.set(e,()=>{for(let i of n)i.remove()})}function B(e){let t=M.get(e);t&&(t(),M.delete(e))}function F(){return{makeDraggable:J,makeResizable:Z,destroyDraggable:k,destroyResizable:B}}function C(e){let{view:t,editable:n,...i}=e;return i}function Q(e){return{load:typeof e.load=="string"?tt(e.load):e.load,save:typeof e.save=="string"?et(e.save):e.save,delete:typeof e.delete=="string"?nt(e.delete):e.delete}}function tt(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function et(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Save failed (HTTP ${n.status})`);return n.json()})}function nt(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Delete failed (HTTP ${n.status})`)})}var R=class{constructor(t,n){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=n,this.handlers=F(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let i=t.getBoundingClientRect(),o=i.width||t.width,d=i.height||t.height;if(this.naturalWidth===0||this.naturalHeight===0)throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");this.scaleX=o/this.naturalWidth,this.scaleY=d/this.naturalHeight,this.notes=n.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",this.viewOverlay=document.createElement("div"),this.viewOverlay.className="image-annotate-view",this.editOverlay=document.createElement("div"),this.editOverlay.className="image-annotate-edit",this.editOverlay.style.display="none";let l=document.createElement("div");if(l.className="image-annotate-edit-area",this.editOverlay.appendChild(l),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t),this.canvas.appendChild(t),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),this.api=this.options.api?Q(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),n.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let c=s[0];if(!c)return;let{width:r,height:a}=c.contentRect;r===0||a===0||this.rescale(r,a)}),this.resizeObserver.observe(this.canvas))}toRendered(t){return{top:t.top*this.scaleY,left:t.left*this.scaleX,width:t.width*this.scaleX,height:t.height*this.scaleY}}toNatural(t){let n={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(n.top)||!isFinite(n.left)||!isFinite(n.width)||!isFinite(n.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return n}get mode(){return this._mode}setMode(t){this._mode=t,t==="edit"?(this.canvas.classList.add("image-annotate-editing"),this.editOverlay.style.display="block"):(this.canvas.classList.remove("image-annotate-editing"),this.editOverlay.style.display="none")}getNotes(){return this.notes.map(C)}notifyChange(){this.options.onChange?.(this.getNotes())}notifySave(t){this.options.onSave?.(t),this.notifyChange()}notifyDelete(t){this.options.onDelete?.(t),this.notifyChange()}notifyLoad(){this.options.onLoad?.(this.getNotes()),this.notifyChange()}destroyViews(){this.cancelEdit();for(let t of this.notes)t.view?.destroy()}createViews(){for(let t of this.notes)t.view=new T(this,t)}load(){this.destroyViews(),this.createViews(),this.notifyLoad()}clear(){this.destroyViews(),this.notes=[],this.notifyChange()}destroy(){if(!this.destroyed){if(this.destroyed=!0,this.destroyViews(),this.notes=[],this.button&&this.button.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=void 0),this.originalParent&&this.originalParent.isConnected){let t=this.originalNextSibling?.parentNode===this.originalParent?this.originalNextSibling:null;this.originalParent.insertBefore(this.img,t)}this.canvas.remove()}}cancelEdit(){this.activeEdit&&(this.activeEdit.destroy(),this.setMode("view")),this.flushPendingRescale()}rescale(t,n){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,n)}applyRescale(t,n){let i=t/this.naturalWidth,o=n/this.naturalHeight;i===this.scaleX&&o===this.scaleY||(this.scaleX=i,this.scaleY=o,this.destroyViews(),this.createViews())}flushPendingRescale(){if(!this.pendingRescale)return;this.pendingRescale=!1;let t=this.canvas.getBoundingClientRect();t.width>0&&t.height>0&&this.applyRescale(t.width,t.height)}setNotes(t){this.destroyed||(this.destroyViews(),this.notes=t.map(n=>({...n})),this.createViews())}setEditable(t){this.destroyed||this.options.editable!==t&&(this.options.editable=t,t&&!this.button?this.createButton():!t&&this.button&&(this.button.remove(),this.button=void 0),this.destroyViews(),this.createViews())}createButton(){this.button=document.createElement("button"),this.button.className="image-annotate-add",this.button.title=this.options.labels?.addNote??"Add Note",this.button.type="button",this.button.addEventListener("click",()=>{this.add()}),this.canvas.appendChild(this.button)}reportError(t){this.options.onError?this.options.onError(t):console.error(`image-annotate: ${t.type} failed`,t.error)}loadFromApi(){this.api.load&&this.api.load().then(t=>{this.destroyViews(),this.notes=t,this.createViews(),this.notifyLoad()}).catch(t=>{let n=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:n})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new D(this),!0):!1}};var H={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var it={editable:!0,notes:[],autoResize:!0,labels:{...H}};function ot(e,t){let n={...it,...t,labels:{...H,...t?.labels}};return new R(e,n)}return U(at);})();
@@ -1 +1 @@
1
- .image-annotate-add{--image-annotate-icon-add: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z'/%3E%3C/svg%3E");appearance:none;background-color:#0006;background-image:var(--image-annotate-icon-add);background-repeat:no-repeat;background-position:center;background-size:20px 20px;border:1px solid rgba(255,255,255,.5);border-radius:4px;bottom:8px;color:#fff;cursor:pointer;height:32px;opacity:0;padding:0;position:absolute;right:8px;transition:opacity .2s,background-color .2s;width:32px;z-index:1}.image-annotate-add:hover{background-color:#0009}.image-annotate-add:focus{opacity:1;outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-canvas{--image-annotate-font-family: Verdana, sans-serif;--image-annotate-font-size: 12px;--image-annotate-area-border: #000;--image-annotate-area-inner-border: #fff;--image-annotate-hover-color: yellow;--image-annotate-hover-editable-color: #00ad00;--image-annotate-note-bg: #e7ffe7;--image-annotate-note-border: #397f39;--image-annotate-note-text: #000;--image-annotate-edit-bg: #fffee3;--image-annotate-edit-border: #000;--image-annotate-button-bg: #fff;--image-annotate-button-bg-hover: #eee;--image-annotate-button-border: #ccc;--image-annotate-button-text: #000;--image-annotate-icon-save: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E");--image-annotate-icon-cancel: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z'/%3E%3C/svg%3E");--image-annotate-icon-delete: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z'/%3E%3C/svg%3E");border:solid 1px var(--image-annotate-button-border);max-width:100%;background-position:left top;background-repeat:no-repeat;display:block;margin:0;position:relative}.image-annotate-view{display:none;position:relative}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-add{opacity:1}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-add{opacity:1}}.image-annotate-editing .image-annotate-add{display:none}.image-annotate-area{border:1px solid var(--image-annotate-area-border);position:absolute}.image-annotate-area div{border:1px solid var(--image-annotate-area-inner-border);box-sizing:border-box;display:block}.image-annotate-area-hover div{border-color:var(--image-annotate-hover-color)}.image-annotate-area-editable{cursor:pointer}.image-annotate-area-editable-hover div{border-color:var(--image-annotate-hover-editable-color)}.image-annotate-note{background-color:var(--image-annotate-note-bg);border:solid 1px var(--image-annotate-note-border);color:var(--image-annotate-note-text);display:none;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);left:-1px;max-width:200px;padding:3px 7px;position:absolute;top:calc(100% + 7px)}.image-annotate-note .actions{display:block;font-size:80%}.image-annotate-edit{display:none}.image-annotate-edit-form{background-color:var(--image-annotate-edit-bg);border:1px solid var(--image-annotate-edit-border);box-sizing:border-box;cursor:default;display:flex;flex-direction:column;gap:7px;left:-1px;min-width:250px;padding:7px;position:absolute;top:calc(100% + 7px);width:max-content}.image-annotate-edit-form form{margin:0;padding:0}.image-annotate-edit-form textarea{box-sizing:border-box;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);height:50px;width:100%}.image-annotate-edit-buttons{display:flex;flex-wrap:nowrap;gap:6px}.image-annotate-edit-form button{appearance:none;background-color:var(--image-annotate-button-bg);background-repeat:no-repeat;background-position:3px center;background-size:16px 16px;border:solid 1px var(--image-annotate-button-border);color:var(--image-annotate-button-text);cursor:pointer;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);line-height:18px;min-height:22px;padding:2px 6px 2px 24px;white-space:nowrap}.image-annotate-edit-form button:empty{background-position:center;min-width:24px;padding:2px 4px}.image-annotate-edit-form button:hover{background-color:var(--image-annotate-button-bg-hover)}.image-annotate-edit-area{border:1px solid var(--image-annotate-area-border);cursor:move;display:block;height:60px;left:10px;margin:0;padding:0;position:absolute;top:10px;width:60px}.image-annotate-resize-handle{background-color:var(--image-annotate-area-border);border:1px solid var(--image-annotate-area-inner-border);box-sizing:border-box;height:8px;position:absolute;width:8px}.image-annotate-resize-handle-nw{cursor:nw-resize;left:-4px;top:-4px}.image-annotate-resize-handle-ne{cursor:ne-resize;right:-4px;top:-4px}.image-annotate-resize-handle-sw{cursor:sw-resize;left:-4px;bottom:-4px}.image-annotate-resize-handle-se{cursor:se-resize;right:-4px;bottom:-4px}.image-annotate-edit-ok{background-image:var(--image-annotate-icon-save)}.image-annotate-edit-delete{background-image:var(--image-annotate-icon-delete)}.image-annotate-edit-close{background-image:var(--image-annotate-icon-cancel)}.image-annotate-area-editable:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-edit-form button:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:1px}
1
+ .image-annotate-add{--image-annotate-icon-add: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z'/%3E%3C/svg%3E");appearance:none;background-color:#0006;background-image:var(--image-annotate-icon-add);background-repeat:no-repeat;background-position:center;background-size:20px 20px;border:1px solid rgba(255,255,255,.5);border-radius:4px;bottom:8px;color:#fff;cursor:pointer;height:32px;opacity:0;padding:0;position:absolute;right:8px;transition:opacity .2s,background-color .2s;width:32px;z-index:1}.image-annotate-add:hover{background-color:#0009}.image-annotate-add:focus{opacity:1;outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-canvas{--image-annotate-font-family: Verdana, sans-serif;--image-annotate-font-size: 12px;--image-annotate-area-border: #000;--image-annotate-area-inner-border: #fff;--image-annotate-hover-color: yellow;--image-annotate-hover-editable-color: #00ad00;--image-annotate-note-bg: #e7ffe7;--image-annotate-note-border: #397f39;--image-annotate-note-text: #000;--image-annotate-edit-bg: #fffee3;--image-annotate-edit-border: #000;--image-annotate-button-bg: #fff;--image-annotate-button-bg-hover: #eee;--image-annotate-button-border: #ccc;--image-annotate-button-text: #000;--image-annotate-icon-save: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E");--image-annotate-icon-cancel: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z'/%3E%3C/svg%3E");--image-annotate-icon-delete: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23333'%3E%3Cpath d='M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z'/%3E%3C/svg%3E");border:solid 1px var(--image-annotate-button-border);display:inline-block;margin:0;max-width:100%;position:relative}.image-annotate-canvas>img{display:block;height:auto;max-width:100%}.image-annotate-view{display:none;inset:0;position:absolute}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-add{opacity:1}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-add{opacity:1}}.image-annotate-editing .image-annotate-add{display:none}.image-annotate-area{border:1px solid var(--image-annotate-area-border);position:absolute}.image-annotate-area div{border:1px solid var(--image-annotate-area-inner-border);box-sizing:border-box;display:block}.image-annotate-area-hover div{border-color:var(--image-annotate-hover-color)}.image-annotate-area-editable{cursor:pointer}.image-annotate-area-editable-hover div{border-color:var(--image-annotate-hover-editable-color)}.image-annotate-note{background-color:var(--image-annotate-note-bg);border:solid 1px var(--image-annotate-note-border);color:var(--image-annotate-note-text);display:none;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);left:-1px;max-width:200px;padding:3px 7px;position:absolute;top:calc(100% + 7px)}.image-annotate-note .actions{display:block;font-size:80%}.image-annotate-edit{display:none;inset:0;position:absolute}.image-annotate-edit-form{background-color:var(--image-annotate-edit-bg);border:1px solid var(--image-annotate-edit-border);box-sizing:border-box;cursor:default;display:flex;flex-direction:column;gap:7px;left:-1px;min-width:250px;padding:7px;position:absolute;top:calc(100% + 7px);width:max-content}.image-annotate-edit-form form{margin:0;padding:0}.image-annotate-edit-form textarea{box-sizing:border-box;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);height:50px;width:100%}.image-annotate-edit-buttons{display:flex;flex-wrap:nowrap;gap:6px}.image-annotate-edit-form button{appearance:none;background-color:var(--image-annotate-button-bg);background-repeat:no-repeat;background-position:3px center;background-size:16px 16px;border:solid 1px var(--image-annotate-button-border);color:var(--image-annotate-button-text);cursor:pointer;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);line-height:18px;min-height:22px;padding:2px 6px 2px 24px;white-space:nowrap}.image-annotate-edit-form button:empty{background-position:center;min-width:24px;padding:2px 4px}.image-annotate-edit-form button:hover{background-color:var(--image-annotate-button-bg-hover)}.image-annotate-edit-area{border:1px solid var(--image-annotate-area-border);cursor:move;display:block;height:60px;left:10px;margin:0;padding:0;position:absolute;top:10px;width:60px}.image-annotate-resize-handle{background-color:var(--image-annotate-area-border);border:1px solid var(--image-annotate-area-inner-border);box-sizing:border-box;height:8px;position:absolute;width:8px}.image-annotate-resize-handle-nw{cursor:nw-resize;left:-4px;top:-4px}.image-annotate-resize-handle-ne{cursor:ne-resize;right:-4px;top:-4px}.image-annotate-resize-handle-sw{cursor:sw-resize;left:-4px;bottom:-4px}.image-annotate-resize-handle-se{cursor:se-resize;right:-4px;bottom:-4px}.image-annotate-edit-ok{background-image:var(--image-annotate-icon-save)}.image-annotate-edit-delete{background-image:var(--image-annotate-icon-delete)}.image-annotate-edit-close{background-image:var(--image-annotate-icon-cancel)}.image-annotate-area-editable:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-edit-form button:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:1px}
package/dist/jquery.js CHANGED
@@ -27,10 +27,11 @@ var AnnotateEdit = class {
27
27
  };
28
28
  }
29
29
  this.area = image.editOverlay.querySelector(".image-annotate-edit-area");
30
- this.area.style.height = this.note.height + "px";
31
- this.area.style.width = this.note.width + "px";
32
- this.area.style.left = this.note.left + "px";
33
- this.area.style.top = this.note.top + "px";
30
+ const rendered = image.toRendered(this.note);
31
+ this.area.style.height = rendered.height + "px";
32
+ this.area.style.width = rendered.width + "px";
33
+ this.area.style.left = rendered.left + "px";
34
+ this.area.style.top = rendered.top + "px";
34
35
  this.form = document.createElement("div");
35
36
  this.form.className = "image-annotate-edit-form";
36
37
  const formEl = document.createElement("form");
@@ -117,13 +118,20 @@ var AnnotateEdit = class {
117
118
  }
118
119
  this.image.notifySave(stripInternals(this.note));
119
120
  this.destroy();
121
+ this.image.flushPendingRescale();
120
122
  };
121
123
  const pos = readInlinePosition(this.area);
122
124
  const size = readInlineSize(this.area);
123
- this.note.top = pos.top;
124
- this.note.left = pos.left;
125
- this.note.width = size.width;
126
- this.note.height = size.height;
125
+ const natural = this.image.toNatural({
126
+ top: pos.top,
127
+ left: pos.left,
128
+ width: size.width,
129
+ height: size.height
130
+ });
131
+ this.note.top = natural.top;
132
+ this.note.left = natural.left;
133
+ this.note.width = natural.width;
134
+ this.note.height = natural.height;
127
135
  this.note.text = text;
128
136
  if (this.image.api.save) {
129
137
  this.busy = true;
@@ -157,6 +165,7 @@ var AnnotateEdit = class {
157
165
  const idx = this.image.notes.indexOf(this.note);
158
166
  if (idx !== -1) this.image.notes.splice(idx, 1);
159
167
  this.image.notifyDelete(stripInternals(this.note));
168
+ this.image.flushPendingRescale();
160
169
  };
161
170
  if (this.image.api.delete) {
162
171
  this.busy = true;
@@ -232,29 +241,29 @@ var AnnotateView = class {
232
241
  });
233
242
  }
234
243
  }
235
- /** Apply the note's position and dimensions to the area element. */
244
+ /** Apply the note's position and dimensions to the area element, scaled to rendered size. */
236
245
  setPosition() {
246
+ const rendered = this.image.toRendered(this.note);
237
247
  const innerDiv = this.area.firstElementChild;
238
- innerDiv.style.height = this.note.height + "px";
239
- innerDiv.style.width = this.note.width + "px";
240
- this.area.style.left = this.note.left + "px";
241
- this.area.style.top = this.note.top + "px";
248
+ innerDiv.style.height = rendered.height + "px";
249
+ innerDiv.style.width = rendered.width + "px";
250
+ this.area.style.left = rendered.left + "px";
251
+ this.area.style.top = rendered.top + "px";
242
252
  }
243
253
  /** Update the view's position, size, and text from the edit area after a save. */
244
254
  resetPosition(editable, text) {
245
255
  this.tooltip.textContent = text;
246
256
  this.tooltip.style.display = "none";
247
- const areaPos = readInlinePosition(editable.area);
248
- const areaSize = readInlineSize(editable.area);
257
+ const rendered = this.image.toRendered(editable.note);
249
258
  const innerDiv = this.area.firstElementChild;
250
- innerDiv.style.height = areaSize.height + "px";
251
- innerDiv.style.width = areaSize.width + "px";
252
- this.area.style.left = areaPos.left + "px";
253
- this.area.style.top = areaPos.top + "px";
254
- this.note.top = areaPos.top;
255
- this.note.left = areaPos.left;
256
- this.note.height = areaSize.height;
257
- this.note.width = areaSize.width;
259
+ innerDiv.style.height = rendered.height + "px";
260
+ innerDiv.style.width = rendered.width + "px";
261
+ this.area.style.left = rendered.left + "px";
262
+ this.area.style.top = rendered.top + "px";
263
+ this.note.top = editable.note.top;
264
+ this.note.left = editable.note.left;
265
+ this.note.height = editable.note.height;
266
+ this.note.width = editable.note.width;
258
267
  this.note.text = text;
259
268
  this.note.id = editable.note.id;
260
269
  this.editable = true;
@@ -524,15 +533,25 @@ var AnnotateImage = class {
524
533
  this._mode = "view";
525
534
  this.activeEdit = null;
526
535
  this.destroyed = false;
536
+ this.pendingRescale = false;
537
+ this.originalParent = null;
538
+ this.originalNextSibling = null;
527
539
  this.options = options;
528
540
  this.handlers = createDefaultHandlers();
529
541
  this.img = img;
530
- const width = img.width;
531
- const height = img.height;
532
- if (width === 0 || height === 0) {
542
+ this.naturalWidth = img.naturalWidth || img.width;
543
+ this.naturalHeight = img.naturalHeight || img.height;
544
+ const rendered = img.getBoundingClientRect();
545
+ const renderedWidth = rendered.width || img.width;
546
+ const renderedHeight = rendered.height || img.height;
547
+ if (this.naturalWidth === 0 || this.naturalHeight === 0) {
533
548
  throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");
534
549
  }
550
+ this.scaleX = renderedWidth / this.naturalWidth;
551
+ this.scaleY = renderedHeight / this.naturalHeight;
535
552
  this.notes = options.notes.map((n) => ({ ...n }));
553
+ this.originalParent = img.parentNode;
554
+ this.originalNextSibling = img.nextSibling;
536
555
  this.canvas = document.createElement("div");
537
556
  this.canvas.className = "image-annotate-canvas";
538
557
  this.viewOverlay = document.createElement("div");
@@ -543,19 +562,13 @@ var AnnotateImage = class {
543
562
  const editArea = document.createElement("div");
544
563
  editArea.className = "image-annotate-edit-area";
545
564
  this.editOverlay.appendChild(editArea);
546
- this.canvas.appendChild(this.viewOverlay);
547
- this.canvas.appendChild(this.editOverlay);
548
565
  if (!img.parentNode) {
549
566
  throw new Error("image-annotate: image must be in the DOM before initialization");
550
567
  }
551
- img.parentNode.insertBefore(this.canvas, img.nextSibling);
552
- this.canvas.style.height = height + "px";
553
- this.canvas.style.width = width + "px";
554
- this.canvas.style.backgroundImage = 'url("' + img.src + '")';
555
- this.viewOverlay.style.height = height + "px";
556
- this.viewOverlay.style.width = width + "px";
557
- this.editOverlay.style.height = height + "px";
558
- this.editOverlay.style.width = width + "px";
568
+ img.parentNode.insertBefore(this.canvas, img);
569
+ this.canvas.appendChild(img);
570
+ this.canvas.appendChild(this.viewOverlay);
571
+ this.canvas.appendChild(this.editOverlay);
559
572
  this.api = this.options.api ? normalizeApi(this.options.api) : {};
560
573
  if (this.api.load) {
561
574
  this.loadFromApi();
@@ -565,7 +578,38 @@ var AnnotateImage = class {
565
578
  if (this.options.editable) {
566
579
  this.createButton();
567
580
  }
568
- img.style.display = "none";
581
+ if (options.autoResize !== false && typeof ResizeObserver !== "undefined") {
582
+ this.resizeObserver = new ResizeObserver((entries) => {
583
+ const entry = entries[0];
584
+ if (!entry) return;
585
+ const { width, height } = entry.contentRect;
586
+ if (width === 0 || height === 0) return;
587
+ this.rescale(width, height);
588
+ });
589
+ this.resizeObserver.observe(this.canvas);
590
+ }
591
+ }
592
+ /** Convert a rect from natural image coordinates to rendered (scaled) coordinates. */
593
+ toRendered(rect) {
594
+ return {
595
+ top: rect.top * this.scaleY,
596
+ left: rect.left * this.scaleX,
597
+ width: rect.width * this.scaleX,
598
+ height: rect.height * this.scaleY
599
+ };
600
+ }
601
+ /** Convert a rect from rendered (scaled) coordinates to natural image coordinates. */
602
+ toNatural(rect) {
603
+ const result = {
604
+ top: rect.top / this.scaleY,
605
+ left: rect.left / this.scaleX,
606
+ width: rect.width / this.scaleX,
607
+ height: rect.height / this.scaleY
608
+ };
609
+ if (!isFinite(result.top) || !isFinite(result.left) || !isFinite(result.width) || !isFinite(result.height)) {
610
+ throw new Error("image-annotate: scale conversion produced non-finite coordinates");
611
+ }
612
+ return result;
569
613
  }
570
614
  /** Current interaction mode — 'view' for browsing, 'edit' when an annotation is being created or modified. */
571
615
  get mode() {
@@ -637,8 +681,15 @@ var AnnotateImage = class {
637
681
  if (this.button) {
638
682
  this.button.remove();
639
683
  }
684
+ if (this.resizeObserver) {
685
+ this.resizeObserver.disconnect();
686
+ this.resizeObserver = void 0;
687
+ }
688
+ if (this.originalParent && this.originalParent.isConnected) {
689
+ const ref = this.originalNextSibling?.parentNode === this.originalParent ? this.originalNextSibling : null;
690
+ this.originalParent.insertBefore(this.img, ref);
691
+ }
640
692
  this.canvas.remove();
641
- this.img.style.display = "";
642
693
  }
643
694
  /** Cancel the active edit (if any) and return to view mode. */
644
695
  cancelEdit() {
@@ -646,6 +697,34 @@ var AnnotateImage = class {
646
697
  this.activeEdit.destroy();
647
698
  this.setMode("view");
648
699
  }
700
+ this.flushPendingRescale();
701
+ }
702
+ /** Recompute scale factors, deferring if an edit is active. */
703
+ rescale(renderedWidth, renderedHeight) {
704
+ if (this.mode === "edit") {
705
+ this.pendingRescale = true;
706
+ return;
707
+ }
708
+ this.applyRescale(renderedWidth, renderedHeight);
709
+ }
710
+ /** Apply new scale factors and re-render all views. */
711
+ applyRescale(renderedWidth, renderedHeight) {
712
+ const newScaleX = renderedWidth / this.naturalWidth;
713
+ const newScaleY = renderedHeight / this.naturalHeight;
714
+ if (newScaleX === this.scaleX && newScaleY === this.scaleY) return;
715
+ this.scaleX = newScaleX;
716
+ this.scaleY = newScaleY;
717
+ this.destroyViews();
718
+ this.createViews();
719
+ }
720
+ /** @internal Flush any deferred rescale after an edit completes. */
721
+ flushPendingRescale() {
722
+ if (!this.pendingRescale) return;
723
+ this.pendingRescale = false;
724
+ const rect = this.canvas.getBoundingClientRect();
725
+ if (rect.width > 0 && rect.height > 0) {
726
+ this.applyRescale(rect.width, rect.height);
727
+ }
649
728
  }
650
729
  /** Replace all annotations with new data. Does not fire lifecycle callbacks. */
651
730
  setNotes(notes) {