@node-edit-utils/core 2.1.7 → 2.1.9

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.9
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
@@ -102,15 +138,11 @@ const isLocked = (node) => {
102
138
  };
103
139
 
104
140
  const processPostMessage = (event, onNodeSelected) => {
105
- // if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
106
- // if (event.data.action === "zoom") {
107
- // // Zoom handling can be implemented here if needed
108
- // }
109
- // }
110
141
  if (event.data.source === "application") {
111
142
  if (event.data.action === "selectedNodeChanged") {
112
143
  const nodeId = event.data.data;
113
144
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
145
+ console.log("selectedNode", selectedNode);
114
146
  if (isLocked(selectedNode)) {
115
147
  onNodeSelected?.(null);
116
148
  return;
@@ -294,6 +326,7 @@ const highlightNode = (node, nodeProvider) => {
294
326
  const refreshHighlightFrame = (node, nodeProvider) => {
295
327
  const frame = getHighlightFrameElement(nodeProvider);
296
328
  const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
329
+ console.log("1. refreshHighlightFrame", node);
297
330
  if (!frame)
298
331
  return;
299
332
  if (zoom >= 0.3) {
@@ -307,6 +340,7 @@ const refreshHighlightFrame = (node, nodeProvider) => {
307
340
  frame.style.setProperty("--frame-left", `${left}px`);
308
341
  frame.style.setProperty("--frame-width", `${width}px`);
309
342
  frame.style.setProperty("--frame-height", `${height}px`);
343
+ console.log("2. refreshHighlightFrame", top, left, width, height);
310
344
  };
311
345
 
312
346
  const updateHighlightFrameVisibility = (node, nodeProvider) => {
@@ -462,7 +496,7 @@ const createNodeTools = (element) => {
462
496
  let mutationObserver = null;
463
497
  let selectedNode = null;
464
498
  const text = nodeText();
465
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
499
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
466
500
  const handleEscape = () => {
467
501
  if (text.isEditing()) {
468
502
  text.blurEditMode();
@@ -501,8 +535,6 @@ const createNodeTools = (element) => {
501
535
  });
502
536
  mutationObserver.observe(node, {
503
537
  attributes: true,
504
- subtree: true,
505
- childList: true,
506
538
  characterData: true,
507
539
  });
508
540
  nodeResizeObserver = connectResizeObserver(node, () => {
@@ -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.9
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
@@ -100,15 +136,11 @@ const isLocked = (node) => {
100
136
  };
101
137
 
102
138
  const processPostMessage = (event, onNodeSelected) => {
103
- // if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
104
- // if (event.data.action === "zoom") {
105
- // // Zoom handling can be implemented here if needed
106
- // }
107
- // }
108
139
  if (event.data.source === "application") {
109
140
  if (event.data.action === "selectedNodeChanged") {
110
141
  const nodeId = event.data.data;
111
142
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
143
+ console.log("selectedNode", selectedNode);
112
144
  if (isLocked(selectedNode)) {
113
145
  onNodeSelected?.(null);
114
146
  return;
@@ -292,6 +324,7 @@ const highlightNode = (node, nodeProvider) => {
292
324
  const refreshHighlightFrame = (node, nodeProvider) => {
293
325
  const frame = getHighlightFrameElement(nodeProvider);
294
326
  const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
327
+ console.log("1. refreshHighlightFrame", node);
295
328
  if (!frame)
296
329
  return;
297
330
  if (zoom >= 0.3) {
@@ -305,6 +338,7 @@ const refreshHighlightFrame = (node, nodeProvider) => {
305
338
  frame.style.setProperty("--frame-left", `${left}px`);
306
339
  frame.style.setProperty("--frame-width", `${width}px`);
307
340
  frame.style.setProperty("--frame-height", `${height}px`);
341
+ console.log("2. refreshHighlightFrame", top, left, width, height);
308
342
  };
309
343
 
310
344
  const updateHighlightFrameVisibility = (node, nodeProvider) => {
@@ -460,7 +494,7 @@ const createNodeTools = (element) => {
460
494
  let mutationObserver = null;
461
495
  let selectedNode = null;
462
496
  const text = nodeText();
463
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
497
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
464
498
  const handleEscape = () => {
465
499
  if (text.isEditing()) {
466
500
  text.blurEditMode();
@@ -499,8 +533,6 @@ const createNodeTools = (element) => {
499
533
  });
500
534
  mutationObserver.observe(node, {
501
535
  attributes: true,
502
- subtree: true,
503
- childList: true,
504
536
  characterData: true,
505
537
  });
506
538
  nodeResizeObserver = connectResizeObserver(node, () => {
@@ -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.9
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
@@ -106,15 +142,11 @@
106
142
  };
107
143
 
108
144
  const processPostMessage = (event, onNodeSelected) => {
109
- // if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
110
- // if (event.data.action === "zoom") {
111
- // // Zoom handling can be implemented here if needed
112
- // }
113
- // }
114
145
  if (event.data.source === "application") {
115
146
  if (event.data.action === "selectedNodeChanged") {
116
147
  const nodeId = event.data.data;
117
148
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
149
+ console.log("selectedNode", selectedNode);
118
150
  if (isLocked(selectedNode)) {
119
151
  onNodeSelected?.(null);
120
152
  return;
@@ -298,6 +330,7 @@
298
330
  const refreshHighlightFrame = (node, nodeProvider) => {
299
331
  const frame = getHighlightFrameElement(nodeProvider);
300
332
  const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
333
+ console.log("1. refreshHighlightFrame", node);
301
334
  if (!frame)
302
335
  return;
303
336
  if (zoom >= 0.3) {
@@ -311,6 +344,7 @@
311
344
  frame.style.setProperty("--frame-left", `${left}px`);
312
345
  frame.style.setProperty("--frame-width", `${width}px`);
313
346
  frame.style.setProperty("--frame-height", `${height}px`);
347
+ console.log("2. refreshHighlightFrame", top, left, width, height);
314
348
  };
315
349
 
316
350
  const updateHighlightFrameVisibility = (node, nodeProvider) => {
@@ -466,7 +500,7 @@
466
500
  let mutationObserver = null;
467
501
  let selectedNode = null;
468
502
  const text = nodeText();
469
- const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
503
+ const throttledFrameRefresh = withDoubleRAF(refreshHighlightFrame);
470
504
  const handleEscape = () => {
471
505
  if (text.isEditing()) {
472
506
  text.blurEditMode();
@@ -505,8 +539,6 @@
505
539
  });
506
540
  mutationObserver.observe(node, {
507
541
  attributes: true,
508
- subtree: true,
509
- childList: true,
510
542
  characterData: true,
511
543
  });
512
544
  nodeResizeObserver = connectResizeObserver(node, () => {
@@ -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(console.log("selectedNode",r),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(console.log("1. refreshHighlightFrame",e),!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`),console.log("2. refreshHighlightFrame",l,a,i,d)},f=(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)},v=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=v(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),f(e,t)}),l.observe(e,{attributes:!0,characterData:!0}),r=o(e,()=>{d(e,t),f(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&&f(e,t)}},m=c(t,u,()=>{i.isEditing()&&i.blurEditMode(),a&&t&&(s(t),a=null,n?.disconnect(),r?.disconnect(),l?.disconnect())},i.getEditableNode),v={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=v,"undefined"!=typeof window&&(window[y]=b),v},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.9",
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()) {
@@ -68,8 +68,6 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
68
68
 
69
69
  mutationObserver.observe(node, {
70
70
  attributes: true,
71
- subtree: true,
72
- childList: true,
73
71
  characterData: true,
74
72
  });
75
73
 
@@ -6,6 +6,8 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
6
6
  const frame = getHighlightFrameElement(nodeProvider);
7
7
  const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
8
8
 
9
+ console.log("1. refreshHighlightFrame", node);
10
+
9
11
  if (!frame) return;
10
12
 
11
13
  if (zoom >= 0.3) {
@@ -20,4 +22,6 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
20
22
  frame.style.setProperty("--frame-left", `${left}px`);
21
23
  frame.style.setProperty("--frame-width", `${width}px`);
22
24
  frame.style.setProperty("--frame-height", `${height}px`);
25
+
26
+ console.log("2. refreshHighlightFrame", top, left, width, height);
23
27
  };
@@ -1,17 +1,13 @@
1
1
  import { isLocked } from "../node-tools/select/helpers/isLocked";
2
2
 
3
3
  export const processPostMessage = (event: MessageEvent, onNodeSelected?: (node: HTMLElement | null) => void) => {
4
- // if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
5
- // if (event.data.action === "zoom") {
6
- // // Zoom handling can be implemented here if needed
7
- // }
8
- // }
9
-
10
4
  if (event.data.source === "application") {
11
5
  if (event.data.action === "selectedNodeChanged") {
12
6
  const nodeId = event.data.data;
13
7
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`) as HTMLElement | null;
14
8
 
9
+ console.log("selectedNode", selectedNode);
10
+
15
11
  if (isLocked(selectedNode)) {
16
12
  onNodeSelected?.(null);
17
13
  return;