@node-edit-utils/core 2.1.7 → 2.1.8

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.
@@ -1 +1 @@
1
- export { withRAF, withRAFThrottle } from "./withRAF";
1
+ export { withRAF, withRAFThrottle, withDoubleRAF } from "./withRAF";
@@ -2,3 +2,6 @@ export declare function withRAF(operation: () => void): () => void;
2
2
  export declare function withRAFThrottle<T extends (...args: any[]) => void>(func: T): T & {
3
3
  cleanup: () => void;
4
4
  };
5
+ export declare function withDoubleRAF<T extends (...args: any[]) => void>(func: T): T & {
6
+ cleanup: () => void;
7
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.1.7
4
+ * @version 2.1.8
5
5
  */
6
6
  'use strict';
7
7
 
@@ -30,6 +30,42 @@ function withRAFThrottle(func) {
30
30
  };
31
31
  return throttled;
32
32
  }
33
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
34
+ function withDoubleRAF(func) {
35
+ let rafId1 = null;
36
+ let rafId2 = null;
37
+ let lastArgs = null;
38
+ const throttled = (...args) => {
39
+ lastArgs = args;
40
+ if (rafId1 === null) {
41
+ rafId1 = requestAnimationFrame(() => {
42
+ // First RAF: let browser complete layout
43
+ rafId2 = requestAnimationFrame(() => {
44
+ // Second RAF: read bounds after layout is complete
45
+ if (lastArgs) {
46
+ const currentArgs = lastArgs;
47
+ rafId1 = null;
48
+ rafId2 = null;
49
+ lastArgs = null;
50
+ func(...currentArgs);
51
+ }
52
+ });
53
+ });
54
+ }
55
+ };
56
+ throttled.cleanup = () => {
57
+ if (rafId1 !== null) {
58
+ cancelAnimationFrame(rafId1);
59
+ rafId1 = null;
60
+ }
61
+ if (rafId2 !== null) {
62
+ cancelAnimationFrame(rafId2);
63
+ rafId2 = null;
64
+ }
65
+ lastArgs = null;
66
+ };
67
+ return throttled;
68
+ }
33
69
 
34
70
  const getCanvasWindowValue = (path) => {
35
71
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
@@ -462,7 +498,7 @@ const createNodeTools = (element) => {
462
498
  let mutationObserver = null;
463
499
  let selectedNode = null;
464
500
  const text = nodeText();
465
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
501
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
466
502
  const handleEscape = () => {
467
503
  if (text.isEditing()) {
468
504
  text.blurEditMode();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.1.7
4
+ * @version 2.1.8
5
5
  */
6
6
  // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
7
7
  function withRAFThrottle(func) {
@@ -28,6 +28,42 @@ function withRAFThrottle(func) {
28
28
  };
29
29
  return throttled;
30
30
  }
31
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
32
+ function withDoubleRAF(func) {
33
+ let rafId1 = null;
34
+ let rafId2 = null;
35
+ let lastArgs = null;
36
+ const throttled = (...args) => {
37
+ lastArgs = args;
38
+ if (rafId1 === null) {
39
+ rafId1 = requestAnimationFrame(() => {
40
+ // First RAF: let browser complete layout
41
+ rafId2 = requestAnimationFrame(() => {
42
+ // Second RAF: read bounds after layout is complete
43
+ if (lastArgs) {
44
+ const currentArgs = lastArgs;
45
+ rafId1 = null;
46
+ rafId2 = null;
47
+ lastArgs = null;
48
+ func(...currentArgs);
49
+ }
50
+ });
51
+ });
52
+ }
53
+ };
54
+ throttled.cleanup = () => {
55
+ if (rafId1 !== null) {
56
+ cancelAnimationFrame(rafId1);
57
+ rafId1 = null;
58
+ }
59
+ if (rafId2 !== null) {
60
+ cancelAnimationFrame(rafId2);
61
+ rafId2 = null;
62
+ }
63
+ lastArgs = null;
64
+ };
65
+ return throttled;
66
+ }
31
67
 
32
68
  const getCanvasWindowValue = (path) => {
33
69
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
@@ -460,7 +496,7 @@ const createNodeTools = (element) => {
460
496
  let mutationObserver = null;
461
497
  let selectedNode = null;
462
498
  const text = nodeText();
463
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
499
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
464
500
  const handleEscape = () => {
465
501
  if (text.isEditing()) {
466
502
  text.blurEditMode();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.1.7
4
+ * @version 2.1.8
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -34,6 +34,42 @@
34
34
  };
35
35
  return throttled;
36
36
  }
37
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
38
+ function withDoubleRAF(func) {
39
+ let rafId1 = null;
40
+ let rafId2 = null;
41
+ let lastArgs = null;
42
+ const throttled = (...args) => {
43
+ lastArgs = args;
44
+ if (rafId1 === null) {
45
+ rafId1 = requestAnimationFrame(() => {
46
+ // First RAF: let browser complete layout
47
+ rafId2 = requestAnimationFrame(() => {
48
+ // Second RAF: read bounds after layout is complete
49
+ if (lastArgs) {
50
+ const currentArgs = lastArgs;
51
+ rafId1 = null;
52
+ rafId2 = null;
53
+ lastArgs = null;
54
+ func(...currentArgs);
55
+ }
56
+ });
57
+ });
58
+ }
59
+ };
60
+ throttled.cleanup = () => {
61
+ if (rafId1 !== null) {
62
+ cancelAnimationFrame(rafId1);
63
+ rafId1 = null;
64
+ }
65
+ if (rafId2 !== null) {
66
+ cancelAnimationFrame(rafId2);
67
+ rafId2 = null;
68
+ }
69
+ lastArgs = null;
70
+ };
71
+ return throttled;
72
+ }
37
73
 
38
74
  const getCanvasWindowValue = (path) => {
39
75
  // biome-ignore lint/suspicious/noExplicitAny: global window extension
@@ -466,7 +502,7 @@
466
502
  let mutationObserver = null;
467
503
  let selectedNode = null;
468
504
  const text = nodeText();
469
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
505
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
470
506
  const handleEscape = () => {
471
507
  if (text.isEditing()) {
472
508
  text.blurEditMode();
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";function t(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const n=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};const o=(e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n};function r(e){return e.querySelector(".highlight-frame")}const s=e=>{if(!e)return;const t=r(e);t&&t.remove()},a=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let i=[],d=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,s=e.metaKey||e.ctrlKey,l=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,r).filter(e=>!a.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&l.includes(t))return t;if(s)return i=[],n=l[0],n;var c,u;u=l,(c=i).length===u.length&&c.every((e,t)=>e===u[t])?d<=l.length&&d++:d=0;return n=l[l.length-1-d],i=l,n},c=(e,t,n,o)=>{const r=e=>{((e,t)=>{if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const o=e.data.data,r=document.querySelector(`[data-node-id="${o}"]`);if(n=r,n?.classList.contains("select-none"))return void t?.(null);r&&t?.(r)}var n})(e,t)},a=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return s(t),void o(null);o(l(e,n))})(n,e,o(),t)},i=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",r),document.addEventListener("click",a),document.addEventListener("keydown",i),()=>{window.removeEventListener("message",r),document.removeEventListener("click",a),document.removeEventListener("keydown",i)}};function u(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,a=o.left-r.left,i=n(["zoom","current"])??1;return{top:parseFloat((s/i).toFixed(5)),left:parseFloat((a/i).toFixed(5)),width:Math.max(4,parseFloat((o.width/i).toFixed(5))),height:parseFloat((o.height/i).toFixed(5))}}const m=(e,t)=>{const n=document.createElement("div");n.className="node-tools",t.appendChild(n),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,n)},p=(e,t)=>{if(!e)return;const o=r(t);o&&o.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:a}=u(e,t),i=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",i.toString()),document.body.style.setProperty("--stroke-width",(2/i).toFixed(3));const d=document.createElement("div");d.classList.add("highlight-frame"),d.style.setProperty("--frame-top",`${o}px`),d.style.setProperty("--frame-left",`${r}px`),d.style.setProperty("--frame-width",`${s}px`),d.style.setProperty("--frame-height",`${a}px`);const l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.classList.add("highlight-frame-svg");const c=document.createElementNS("http://www.w3.org/2000/svg","rect");return c.setAttribute("x","0"),c.setAttribute("y","0"),c.setAttribute("width","100%"),c.setAttribute("height","100%"),c.classList.add("highlight-frame-rect"),l.appendChild(c),d.appendChild(l),d})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),m(e,s),t.appendChild(s)},h=(e,t)=>{const o=r(t),s=n(["zoom","current"])??1;if(!o)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:a,left:i,width:d,height:l}=u(e,t);o.style.setProperty("--frame-top",`${a}px`),o.style.setProperty("--frame-left",`${i}px`),o.style.setProperty("--frame-width",`${d}px`),o.style.setProperty("--frame-height",`${l}px`)},v=(e,t)=>{const n=r(t);if(!n)return;const o=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";n.style.display=o;const s=n.querySelector(".tag-label");s&&(s.style.display=o)},y=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},f=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{h(e,t)});return()=>n.disconnect()},g=()=>{let e=null,t=!1,o=null;const r=()=>{if(t||!e)return;t=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=n(["keyboard","disableTextEditMode"]);e?.()})(),o?.(),e=null,t=!1};return{enableEditMode:(t,s)=>{if(e===t)return;e&&e!==t&&r();const a=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(t);a&&(e=t,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(t),(()=>{const e=n(["keyboard","enableTextEditMode"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=y(e),r=f(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},b=320,w=1680,E=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],x=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(b,Math.min(w,n))})(n,(e.clientX-t)/o);return r},L=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<E.length&&(E[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const o=t(()=>{(()=>{const e=n(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})()}),r=new MutationObserver(()=>{o()});return r.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.cleanup(),r.disconnect()}}},e.createNodeTools=e=>{const n=e;let r=null,a=null,i=null,d=null;const l=g(),u=t(h),m=e=>{if(d!==e){if(l.isEditing()){const t=l.getEditableNode();t&&t!==e&&l.blurEditMode()}var t,s;r?.disconnect(),a?.disconnect(),i?.disconnect(),e&&n&&(l.enableEditMode(e,n),r=o(n,()=>{u(e,n)}),i=new MutationObserver(()=>{u(e,n),v(e,n)}),i.observe(e,{attributes:!0,subtree:!0,childList:!0,characterData:!0}),a=o(e,()=>{u(e,n),v(e,n)})),d=e,t="selectedNodeChanged",s=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:t,data:s,timestamp:Date.now()},"*"),p(e,n),e&&n&&v(e,n)}},y=c(n,m,()=>{l.isEditing()&&l.blurEditMode(),d&&n&&(s(n),d=null,r?.disconnect(),a?.disconnect(),i?.disconnect())},l.getEditableNode),f={selectNode:m,getSelectedNode:()=>d,refreshHighlightFrame:()=>{u(d,n)},clearSelectedNode:()=>{s(n),d=null,r?.disconnect(),a?.disconnect(),i?.disconnect()},getEditableNode:()=>l.getEditableNode(),cleanup:()=>{y(),r?.disconnect(),a?.disconnect(),i?.disconnect(),l.blurEditMode(),u.cleanup()}};var b,w;return b="nodeTools",w=f,"undefined"!=typeof window&&(window[b]=w),f},e.createViewport=e=>{const n=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",E.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,L);let s=!1,a=0,i=0;const d=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=x(t,a,i);L(e,o)}),l=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,a=t.clientX,i=e.offsetWidth},d,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{L(e,t)},cleanup:()=>{s=!1,d?.cleanup(),l(),r.remove()}}}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).MarkupCanvas={})}(this,function(e){"use strict";function t(e){let t=null,n=null;const o=(...o)=>{n=o,null===t&&(t=requestAnimationFrame(()=>{n&&e(...n),t=null,n=null}))};return o.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null,n=null)},o}const n=e=>{const t=window.canvas;return e.reduce((e,t)=>e?.[t],t)};const o=(e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n};function r(e){return e.querySelector(".highlight-frame")}const s=e=>{if(!e)return;const t=r(e);t&&t.remove()},l=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],i=0;const d=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,s=e.metaKey||e.ctrlKey,d=((e,t)=>{const n=document.elementsFromPoint(e,t);return Array.from(n).reduce((e,t)=>e.found?e:"node-provider"===t.getAttribute("data-role")?(e.found=!0,e):(e.elements.push(t),e),{elements:[],found:!1}).elements})(o,r).filter(e=>!l.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&d.includes(t))return t;if(s)return a=[],n=d[0],n;var c,u;u=d,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?i<=d.length&&i++:i=0;return n=d[d.length-1-i],a=d,n},c=(e,t,n,o)=>{const r=e=>{((e,t)=>{if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const o=e.data.data,r=document.querySelector(`[data-node-id="${o}"]`);if(n=r,n?.classList.contains("select-none"))return void t?.(null);r&&t?.(r)}var n})(e,t)},l=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return s(t),void o(null);o(d(e,n))})(n,e,o(),t)},a=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",r),document.addEventListener("click",l),document.addEventListener("keydown",a),()=>{window.removeEventListener("message",r),document.removeEventListener("click",l),document.removeEventListener("keydown",a)}};function u(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,l=o.left-r.left,a=n(["zoom","current"])??1;return{top:parseFloat((s/a).toFixed(5)),left:parseFloat((l/a).toFixed(5)),width:Math.max(4,parseFloat((o.width/a).toFixed(5))),height:parseFloat((o.height/a).toFixed(5))}}const m=(e,t)=>{const n=document.createElement("div");n.className="node-tools",t.appendChild(n),((e,t)=>{const n=document.createElement("div");n.className="tag-label",n.textContent=e.tagName.toLowerCase(),t.appendChild(n)})(e,n)},p=(e,t)=>{if(!e)return;const o=r(t);o&&o.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:l}=u(e,t),a=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",a.toString()),document.body.style.setProperty("--stroke-width",(2/a).toFixed(3));const i=document.createElement("div");i.classList.add("highlight-frame"),i.style.setProperty("--frame-top",`${o}px`),i.style.setProperty("--frame-left",`${r}px`),i.style.setProperty("--frame-width",`${s}px`),i.style.setProperty("--frame-height",`${l}px`);const d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-svg");const c=document.createElementNS("http://www.w3.org/2000/svg","rect");return c.setAttribute("x","0"),c.setAttribute("y","0"),c.setAttribute("width","100%"),c.setAttribute("height","100%"),c.classList.add("highlight-frame-rect"),d.appendChild(c),i.appendChild(d),i})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),m(e,s),t.appendChild(s)},h=(e,t)=>{const o=r(t),s=n(["zoom","current"])??1;if(!o)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:l,left:a,width:i,height:d}=u(e,t);o.style.setProperty("--frame-top",`${l}px`),o.style.setProperty("--frame-left",`${a}px`),o.style.setProperty("--frame-width",`${i}px`),o.style.setProperty("--frame-height",`${d}px`)},v=(e,t)=>{const n=r(t);if(!n)return;const o=e.classList.contains("hidden")||e.classList.contains("select-none")?"none":"";n.style.display=o;const s=n.querySelector(".tag-label");s&&(s.style.display=o)},f=e=>{const t=e=>{"Enter"===e.key&&(e.preventDefault(),e.stopPropagation(),(()=>{const e=window.getSelection();if(e&&e.rangeCount>0){const t=e.getRangeAt(0);t.deleteContents();const n=document.createElement("br");t.insertNode(n),t.setStartAfter(n),t.setEndAfter(n),e.removeAllRanges(),e.addRange(t)}})())};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}},y=(e,t)=>{const n=((e,t)=>{const n=new MutationObserver(e=>{t(e)});return n.observe(e,{subtree:!0,childList:!0,characterData:!0}),n})(e,()=>{h(e,t)});return()=>n.disconnect()},g=()=>{let e=null,t=!1,o=null;const r=()=>{if(t||!e)return;t=!0;var r;(r=e).contentEditable="false",r.classList.remove("is-editable"),r.style.outline="none",(()=>{const e=n(["keyboard","disableTextEditMode"]);e?.()})(),o?.(),e=null,t=!1};return{enableEditMode:(t,s)=>{if(e===t)return;e&&e!==t&&r();const l=(e=>Array.from(e.childNodes).some(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()))(t);l&&(e=t,(e=>{e.contentEditable="true",e.classList.add("is-editable"),e.style.outline="none"})(t),(()=>{const e=n(["keyboard","enableTextEditMode"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=f(e),r=y(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},b=320,w=1680,E=[{name:"Mobile",rawValue:390,value:"320px"},{name:"Tablet Portrait",rawValue:768,value:"768px"},{name:"Tablet Landscape",rawValue:1024,value:"1024px"},{name:"Notebook",rawValue:1280,value:"1280px"},{name:"Desktop",rawValue:1680,value:"1680px"}],x=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(b,Math.min(w,n))})(n,(e.clientX-t)/o);return r},L=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{e.querySelectorAll(".resize-preset-button").forEach((e,n)=>{n<E.length&&(E[n].rawValue===t?e.classList.add("is-active"):e.classList.remove("is-active"))})})(e,t)};e.createCanvasObserver=function(){const e=document.querySelector(".transform-layer");if(!e)return{disconnect:()=>{}};const o=t(()=>{(()=>{const e=n(["zoom","current"]);document.body.style.setProperty("--zoom",e.toFixed(5)),document.body.style.setProperty("--stroke-width",(2/e).toFixed(3)),document.body.dataset.zoom=e.toFixed(5),document.body.dataset.strokeWidth=(2/e).toFixed(3)})()}),r=new MutationObserver(()=>{o()});return r.observe(e,{attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0}),{disconnect:function(){o.cleanup(),r.disconnect()}}},e.createNodeTools=e=>{const t=e;let n=null,r=null,l=null,a=null;const i=g(),d=function(e){let t=null,n=null,o=null;const r=(...r)=>{o=r,null===t&&(t=requestAnimationFrame(()=>{n=requestAnimationFrame(()=>{if(o){const r=o;t=null,n=null,o=null,e(...r)}})}))};return r.cleanup=()=>{null!==t&&(cancelAnimationFrame(t),t=null),null!==n&&(cancelAnimationFrame(n),n=null),o=null},r}(h),u=e=>{if(a!==e){if(i.isEditing()){const t=i.getEditableNode();t&&t!==e&&i.blurEditMode()}var s,c;n?.disconnect(),r?.disconnect(),l?.disconnect(),e&&t&&(i.enableEditMode(e,t),n=o(t,()=>{d(e,t)}),l=new MutationObserver(()=>{d(e,t),v(e,t)}),l.observe(e,{attributes:!0,subtree:!0,childList:!0,characterData:!0}),r=o(e,()=>{d(e,t),v(e,t)})),a=e,s="selectedNodeChanged",c=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:s,data:c,timestamp:Date.now()},"*"),p(e,t),e&&t&&v(e,t)}},m=c(t,u,()=>{i.isEditing()&&i.blurEditMode(),a&&t&&(s(t),a=null,n?.disconnect(),r?.disconnect(),l?.disconnect())},i.getEditableNode),f={selectNode:u,getSelectedNode:()=>a,refreshHighlightFrame:()=>{d(a,t)},clearSelectedNode:()=>{s(t),a=null,n?.disconnect(),r?.disconnect(),l?.disconnect()},getEditableNode:()=>i.getEditableNode(),cleanup:()=>{m(),n?.disconnect(),r?.disconnect(),l?.disconnect(),i.blurEditMode(),d.cleanup()}};var y,b;return y="nodeTools",b=f,"undefined"!=typeof window&&(window[y]=b),f},e.createViewport=e=>{const n=document.querySelector(".canvas-container"),o=e.querySelector(".resize-handle");o&&o.remove();const r=(e=>{const t=document.createElement("div");return t.className="resize-handle",e.appendChild(t),t})(e);e.style.setProperty("--container-width","400px"),((e,t,n)=>{const o=document.createElement("div");o.className="resize-presets",E.forEach(e=>{const r=document.createElement("button");r.textContent=e.name,r.className="resize-preset-button",r.addEventListener("click",()=>{n(t,e.rawValue)}),o.appendChild(r)}),e.appendChild(o)})(r,e,L);let s=!1,l=0,a=0;const i=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=x(t,l,a);L(e,o)}),d=((e,t,n,o,r)=>(e.addEventListener("mousedown",t),document.addEventListener("mousemove",n),document.addEventListener("mouseup",o),window.addEventListener("blur",r),()=>{e.removeEventListener("mousedown",t),document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),window.removeEventListener("blur",r)}))(r,t=>{t.preventDefault(),t.stopPropagation(),s=!0,l=t.clientX,a=e.offsetWidth},i,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{L(e,t)},cleanup:()=>{s=!1,i?.cleanup(),d(),r.remove()}}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
@@ -1 +1 @@
1
- export { withRAF, withRAFThrottle } from "./withRAF";
1
+ export { withRAF, withRAFThrottle, withDoubleRAF } from "./withRAF";
@@ -37,3 +37,44 @@ export function withRAFThrottle<T extends (...args: any[]) => void>(func: T): T
37
37
 
38
38
  return throttled as T & { cleanup: () => void };
39
39
  }
40
+
41
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
42
+ export function withDoubleRAF<T extends (...args: any[]) => void>(func: T): T & { cleanup: () => void } {
43
+ let rafId1: number | null = null;
44
+ let rafId2: number | null = null;
45
+ let lastArgs: Parameters<T> | null = null;
46
+
47
+ const throttled = (...args: Parameters<T>) => {
48
+ lastArgs = args;
49
+
50
+ if (rafId1 === null) {
51
+ rafId1 = requestAnimationFrame(() => {
52
+ // First RAF: let browser complete layout
53
+ rafId2 = requestAnimationFrame(() => {
54
+ // Second RAF: read bounds after layout is complete
55
+ if (lastArgs) {
56
+ const currentArgs = lastArgs;
57
+ rafId1 = null;
58
+ rafId2 = null;
59
+ lastArgs = null;
60
+ func(...currentArgs);
61
+ }
62
+ });
63
+ });
64
+ }
65
+ };
66
+
67
+ throttled.cleanup = () => {
68
+ if (rafId1 !== null) {
69
+ cancelAnimationFrame(rafId1);
70
+ rafId1 = null;
71
+ }
72
+ if (rafId2 !== null) {
73
+ cancelAnimationFrame(rafId2);
74
+ rafId2 = null;
75
+ }
76
+ lastArgs = null;
77
+ };
78
+
79
+ return throttled as T & { cleanup: () => void };
80
+ }
@@ -1,4 +1,4 @@
1
- import { withRAFThrottle } from "../helpers";
1
+ import { withDoubleRAF } from "../helpers";
2
2
  import { connectResizeObserver } from "../helpers/observer/connectResizeObserver";
3
3
  import { sendPostMessage } from "../post-message/sendPostMessage";
4
4
  import { bindToWindow } from "../window/bindToWindow";
@@ -19,7 +19,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
19
19
  let selectedNode: HTMLElement | null = null;
20
20
 
21
21
  const text = nodeText();
22
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
22
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
23
23
 
24
24
  const handleEscape = (): void => {
25
25
  if (text.isEditing()) {