@node-edit-utils/core 2.0.5 → 2.0.6

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.
@@ -0,0 +1 @@
1
+ export declare const updateHighlightFrameVisibility: (node: HTMLElement, nodeProvider: HTMLElement) => void;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.5
4
+ * @version 2.0.6
5
5
  */
6
6
  'use strict';
7
7
 
@@ -156,7 +156,7 @@ const selectNode = (event, editableNode) => {
156
156
  const clickX = event.clientX;
157
157
  const clickY = event.clientY;
158
158
  const clickThrough = event.metaKey || event.ctrlKey;
159
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
159
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
160
160
  if (editableNode && candidates.includes(editableNode)) {
161
161
  return editableNode;
162
162
  }
@@ -304,6 +304,14 @@ const refreshHighlightFrame = (node, nodeProvider) => {
304
304
  frame.style.setProperty("--frame-height", `${height}px`);
305
305
  };
306
306
 
307
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
308
+ const frame = getHighlightFrameElement(nodeProvider);
309
+ if (!frame)
310
+ return;
311
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
312
+ frame.style.display = hasHiddenClass ? "none" : "";
313
+ };
314
+
307
315
  const disableCanvasKeyboard = () => {
308
316
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
309
317
  disable?.();
@@ -440,6 +448,7 @@ const nodeText = () => {
440
448
  const createNodeTools = (element) => {
441
449
  const nodeProvider = element;
442
450
  let resizeObserver = null;
451
+ let mutationObserver = null;
443
452
  let selectedNode = null;
444
453
  const text = nodeText();
445
454
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -452,10 +461,14 @@ const createNodeTools = (element) => {
452
461
  clearHighlightFrame(nodeProvider);
453
462
  selectedNode = null;
454
463
  resizeObserver?.disconnect();
464
+ mutationObserver?.disconnect();
455
465
  }
456
466
  }
457
467
  };
458
468
  const selectNode = (node) => {
469
+ if (selectedNode === node) {
470
+ return;
471
+ }
459
472
  if (text.isEditing()) {
460
473
  const currentEditable = text.getEditableNode();
461
474
  if (currentEditable && currentEditable !== node) {
@@ -463,21 +476,34 @@ const createNodeTools = (element) => {
463
476
  }
464
477
  }
465
478
  resizeObserver?.disconnect();
479
+ mutationObserver?.disconnect();
466
480
  if (node && nodeProvider) {
467
481
  text.enableEditMode(node, nodeProvider);
468
482
  resizeObserver = connectResizeObserver(nodeProvider, () => {
469
483
  throttledFrameRefresh(node, nodeProvider);
470
484
  });
485
+ mutationObserver = new MutationObserver(() => {
486
+ throttledFrameRefresh(node, nodeProvider);
487
+ updateHighlightFrameVisibility(node, nodeProvider);
488
+ });
489
+ mutationObserver.observe(node, {
490
+ attributes: true,
491
+ attributeFilter: ["class"],
492
+ });
471
493
  }
472
494
  selectedNode = node;
473
495
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
474
496
  highlightNode(node, nodeProvider) ?? null;
497
+ if (node && nodeProvider) {
498
+ updateHighlightFrameVisibility(node, nodeProvider);
499
+ }
475
500
  };
476
501
  // Setup event listener
477
502
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
478
503
  const cleanup = () => {
479
504
  removeListeners();
480
505
  resizeObserver?.disconnect();
506
+ mutationObserver?.disconnect();
481
507
  text.blurEditMode();
482
508
  throttledFrameRefresh.cleanup();
483
509
  };
@@ -491,6 +517,7 @@ const createNodeTools = (element) => {
491
517
  clearHighlightFrame(nodeProvider);
492
518
  selectedNode = null;
493
519
  resizeObserver?.disconnect();
520
+ mutationObserver?.disconnect();
494
521
  },
495
522
  getEditableNode: () => text.getEditableNode(),
496
523
  cleanup,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.5
4
+ * @version 2.0.6
5
5
  */
6
6
  // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
7
7
  function withRAFThrottle(func) {
@@ -154,7 +154,7 @@ const selectNode = (event, editableNode) => {
154
154
  const clickX = event.clientX;
155
155
  const clickY = event.clientY;
156
156
  const clickThrough = event.metaKey || event.ctrlKey;
157
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
157
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
158
158
  if (editableNode && candidates.includes(editableNode)) {
159
159
  return editableNode;
160
160
  }
@@ -302,6 +302,14 @@ const refreshHighlightFrame = (node, nodeProvider) => {
302
302
  frame.style.setProperty("--frame-height", `${height}px`);
303
303
  };
304
304
 
305
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
306
+ const frame = getHighlightFrameElement(nodeProvider);
307
+ if (!frame)
308
+ return;
309
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
310
+ frame.style.display = hasHiddenClass ? "none" : "";
311
+ };
312
+
305
313
  const disableCanvasKeyboard = () => {
306
314
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
307
315
  disable?.();
@@ -438,6 +446,7 @@ const nodeText = () => {
438
446
  const createNodeTools = (element) => {
439
447
  const nodeProvider = element;
440
448
  let resizeObserver = null;
449
+ let mutationObserver = null;
441
450
  let selectedNode = null;
442
451
  const text = nodeText();
443
452
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -450,10 +459,14 @@ const createNodeTools = (element) => {
450
459
  clearHighlightFrame(nodeProvider);
451
460
  selectedNode = null;
452
461
  resizeObserver?.disconnect();
462
+ mutationObserver?.disconnect();
453
463
  }
454
464
  }
455
465
  };
456
466
  const selectNode = (node) => {
467
+ if (selectedNode === node) {
468
+ return;
469
+ }
457
470
  if (text.isEditing()) {
458
471
  const currentEditable = text.getEditableNode();
459
472
  if (currentEditable && currentEditable !== node) {
@@ -461,21 +474,34 @@ const createNodeTools = (element) => {
461
474
  }
462
475
  }
463
476
  resizeObserver?.disconnect();
477
+ mutationObserver?.disconnect();
464
478
  if (node && nodeProvider) {
465
479
  text.enableEditMode(node, nodeProvider);
466
480
  resizeObserver = connectResizeObserver(nodeProvider, () => {
467
481
  throttledFrameRefresh(node, nodeProvider);
468
482
  });
483
+ mutationObserver = new MutationObserver(() => {
484
+ throttledFrameRefresh(node, nodeProvider);
485
+ updateHighlightFrameVisibility(node, nodeProvider);
486
+ });
487
+ mutationObserver.observe(node, {
488
+ attributes: true,
489
+ attributeFilter: ["class"],
490
+ });
469
491
  }
470
492
  selectedNode = node;
471
493
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
472
494
  highlightNode(node, nodeProvider) ?? null;
495
+ if (node && nodeProvider) {
496
+ updateHighlightFrameVisibility(node, nodeProvider);
497
+ }
473
498
  };
474
499
  // Setup event listener
475
500
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
476
501
  const cleanup = () => {
477
502
  removeListeners();
478
503
  resizeObserver?.disconnect();
504
+ mutationObserver?.disconnect();
479
505
  text.blurEditMode();
480
506
  throttledFrameRefresh.cleanup();
481
507
  };
@@ -489,6 +515,7 @@ const createNodeTools = (element) => {
489
515
  clearHighlightFrame(nodeProvider);
490
516
  selectedNode = null;
491
517
  resizeObserver?.disconnect();
518
+ mutationObserver?.disconnect();
492
519
  },
493
520
  getEditableNode: () => text.getEditableNode(),
494
521
  cleanup,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Markup Canvas
3
3
  * High-performance markup canvas with zoom and pan capabilities
4
- * @version 2.0.5
4
+ * @version 2.0.6
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -160,7 +160,7 @@
160
160
  const clickX = event.clientX;
161
161
  const clickY = event.clientY;
162
162
  const clickThrough = event.metaKey || event.ctrlKey;
163
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
163
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
164
164
  if (editableNode && candidates.includes(editableNode)) {
165
165
  return editableNode;
166
166
  }
@@ -308,6 +308,14 @@
308
308
  frame.style.setProperty("--frame-height", `${height}px`);
309
309
  };
310
310
 
311
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
312
+ const frame = getHighlightFrameElement(nodeProvider);
313
+ if (!frame)
314
+ return;
315
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
316
+ frame.style.display = hasHiddenClass ? "none" : "";
317
+ };
318
+
311
319
  const disableCanvasKeyboard = () => {
312
320
  const disable = getCanvasWindowValue(["keyboard", "disable"]);
313
321
  disable?.();
@@ -444,6 +452,7 @@
444
452
  const createNodeTools = (element) => {
445
453
  const nodeProvider = element;
446
454
  let resizeObserver = null;
455
+ let mutationObserver = null;
447
456
  let selectedNode = null;
448
457
  const text = nodeText();
449
458
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -456,10 +465,14 @@
456
465
  clearHighlightFrame(nodeProvider);
457
466
  selectedNode = null;
458
467
  resizeObserver?.disconnect();
468
+ mutationObserver?.disconnect();
459
469
  }
460
470
  }
461
471
  };
462
472
  const selectNode = (node) => {
473
+ if (selectedNode === node) {
474
+ return;
475
+ }
463
476
  if (text.isEditing()) {
464
477
  const currentEditable = text.getEditableNode();
465
478
  if (currentEditable && currentEditable !== node) {
@@ -467,21 +480,34 @@
467
480
  }
468
481
  }
469
482
  resizeObserver?.disconnect();
483
+ mutationObserver?.disconnect();
470
484
  if (node && nodeProvider) {
471
485
  text.enableEditMode(node, nodeProvider);
472
486
  resizeObserver = connectResizeObserver(nodeProvider, () => {
473
487
  throttledFrameRefresh(node, nodeProvider);
474
488
  });
489
+ mutationObserver = new MutationObserver(() => {
490
+ throttledFrameRefresh(node, nodeProvider);
491
+ updateHighlightFrameVisibility(node, nodeProvider);
492
+ });
493
+ mutationObserver.observe(node, {
494
+ attributes: true,
495
+ attributeFilter: ["class"],
496
+ });
475
497
  }
476
498
  selectedNode = node;
477
499
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
478
500
  highlightNode(node, nodeProvider) ?? null;
501
+ if (node && nodeProvider) {
502
+ updateHighlightFrameVisibility(node, nodeProvider);
503
+ }
479
504
  };
480
505
  // Setup event listener
481
506
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
482
507
  const cleanup = () => {
483
508
  removeListeners();
484
509
  resizeObserver?.disconnect();
510
+ mutationObserver?.disconnect();
485
511
  text.blurEditMode();
486
512
  throttledFrameRefresh.cleanup();
487
513
  };
@@ -495,6 +521,7 @@
495
521
  clearHighlightFrame(nodeProvider);
496
522
  selectedNode = null;
497
523
  resizeObserver?.disconnect();
524
+ mutationObserver?.disconnect();
498
525
  },
499
526
  getEditableNode: () => text.getEditableNode(),
500
527
  cleanup,
@@ -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)};function o(e){return e.querySelector(".highlight-frame")}const r=e=>{if(!e)return;const t=o(e);t&&t.remove()},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],d=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,l=e.metaKey||e.ctrlKey,i=((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=>!s.includes(e.tagName.toLowerCase()));if(t&&i.includes(t))return t;if(l)return a=[],n=i[0],n;var c,u;u=i,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?d<=i.length&&d++:d=0;return n=i[i.length-1-d],a=i,n},i=(e,t,n,o)=>{const s=e=>{((e,t)=>{if("markup-canvas"===e.data.source&&"canvas"===e.data.canvasName&&"zoom"===e.data.action){const t=e.data.data;console.log("zoom",t)}if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const n=e.data.data;console.log("selectedNodeChanged in node-edit-utils",n);const o=document.querySelector(`[data-node-id="${n}"]`);o&&(console.log("selectedNode",o),t?.(o))}})(e,t)},a=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(t),void o(null);o(l(e,n))})(n,e,o(),t)},d=e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),n?.())};return window.addEventListener("message",s),document.addEventListener("click",a),document.addEventListener("keydown",d),()=>{window.removeEventListener("message",s),document.removeEventListener("click",a),document.removeEventListener("keydown",d)}};function c(e,t){const o=e.getBoundingClientRect(),r=t.getBoundingClientRect(),s=o.top-r.top,a=o.left-r.left,d=n(["zoom","current"])??1;return{top:parseFloat((s/d).toFixed(5)),left:parseFloat((a/d).toFixed(5)),width:Math.max(4,parseFloat((o.width/d).toFixed(5))),height:parseFloat((o.height/d).toFixed(5))}}const u=(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)},m=(e,t)=>{if(!e)return;const r=o(t);r&&r.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:a}=c(e,t),d=n(["zoom","current"])??1;document.body.style.setProperty("--zoom",d.toString()),document.body.style.setProperty("--stroke-width",(2/d).toFixed(3));const l=document.createElement("div");l.classList.add("highlight-frame"),l.style.setProperty("--frame-top",`${o}px`),l.style.setProperty("--frame-left",`${r}px`),l.style.setProperty("--frame-width",`${s}px`),l.style.setProperty("--frame-height",`${a}px`);const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.classList.add("highlight-frame-svg");const u=document.createElementNS("http://www.w3.org/2000/svg","rect");return u.setAttribute("x","0"),u.setAttribute("y","0"),u.setAttribute("width","100%"),u.setAttribute("height","100%"),u.classList.add("highlight-frame-rect"),i.appendChild(u),l.appendChild(i),l})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),u(e,s),t.appendChild(s)},p=(e,t)=>{const r=o(t),s=n(["zoom","current"])??1;if(!r)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:a,left:d,width:l,height:i}=c(e,t);r.style.setProperty("--frame-top",`${a}px`),r.style.setProperty("--frame-left",`${d}px`),r.style.setProperty("--frame-width",`${l}px`),r.style.setProperty("--frame-height",`${i}px`)},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)}},h=(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,()=>{p(e,t)});return()=>n.disconnect()},f=()=>{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","enable"]);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","disable"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=v(e),r=h(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},y=320,g=1680,b=[{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"}],w=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(y,Math.min(g,n))})(n,(e.clientX-t)/o);return r},E=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{console.log("updateActivePreset",t);const n=e.querySelectorAll(".resize-preset-button");console.log("presetButtons",n),n.forEach((e,n)=>{n<b.length&&(b[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 o=null,s=null;const a=f(),d=t(p),l=e=>{if(a.isEditing()){const t=a.getEditableNode();t&&t!==e&&a.blurEditMode()}var t,r;o?.disconnect(),e&&n&&(a.enableEditMode(e,n),o=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(n,()=>{d(e,n)})),s=e,t="selectedNodeChanged",r=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:t,data:r,timestamp:Date.now()},"*"),m(e,n)},c=i(n,l,()=>{a.isEditing()&&a.blurEditMode(),s&&n&&(r(n),s=null,o?.disconnect())},a.getEditableNode),u={selectNode:l,getSelectedNode:()=>s,refreshHighlightFrame:()=>{d(s,n)},clearSelectedNode:()=>{r(n),s=null,o?.disconnect()},getEditableNode:()=>a.getEditableNode(),cleanup:()=>{c(),o?.disconnect(),a.blurEditMode(),d.cleanup()}};var v,h;return v="nodeTools",h=u,"undefined"!=typeof window&&(window[v]=h),u},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",b.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,E);let s=!1,a=0,d=0;const l=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=w(t,a,d);E(e,o)}),i=((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,d=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{E(e,t)},cleanup:()=>{s=!1,l?.cleanup(),i(),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)};function o(e){return e.querySelector(".highlight-frame")}const r=e=>{if(!e)return;const t=o(e);t&&t.remove()},s=["path","rect","circle","ellipse","polygon","line","polyline","text","text-noci"];let a=[],i=0;const l=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,l=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=>!s.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&d.includes(t))return t;if(l)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},d=(e,t,n,o)=>{const s=e=>{((e,t)=>{if("markup-canvas"===e.data.source&&"canvas"===e.data.canvasName&&"zoom"===e.data.action){const t=e.data.data;console.log("zoom",t)}if("application"===e.data.source&&"selectedNodeChanged"===e.data.action){const n=e.data.data;console.log("selectedNodeChanged in node-edit-utils",n);const o=document.querySelector(`[data-node-id="${n}"]`);o&&(console.log("selectedNode",o),t?.(o))}})(e,t)},a=n=>{((e,t,n,o)=>{if(e.preventDefault(),e.stopPropagation(),t&&!t.contains(e.target))return r(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",s),document.addEventListener("click",a),document.addEventListener("keydown",i),()=>{window.removeEventListener("message",s),document.removeEventListener("click",a),document.removeEventListener("keydown",i)}};function c(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 u=(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)},m=(e,t)=>{if(!e)return;const r=o(t);r&&r.remove();const s=((e,t)=>{const{top:o,left:r,width:s,height:a}=c(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 l=document.createElement("div");l.classList.add("highlight-frame"),l.style.setProperty("--frame-top",`${o}px`),l.style.setProperty("--frame-left",`${r}px`),l.style.setProperty("--frame-width",`${s}px`),l.style.setProperty("--frame-height",`${a}px`);const d=document.createElementNS("http://www.w3.org/2000/svg","svg");d.classList.add("highlight-frame-svg");const u=document.createElementNS("http://www.w3.org/2000/svg","rect");return u.setAttribute("x","0"),u.setAttribute("y","0"),u.setAttribute("width","100%"),u.setAttribute("height","100%"),u.classList.add("highlight-frame-rect"),d.appendChild(u),l.appendChild(d),l})(e,t);"true"===e.contentEditable&&s.classList.add("is-editable"),u(e,s),t.appendChild(s)},p=(e,t)=>{const r=o(t),s=n(["zoom","current"])??1;if(!r)return;s>=.3?t.style.setProperty("--tool-opacity","1"):t.style.setProperty("--tool-opacity","0");const{top:a,left:i,width:l,height:d}=c(e,t);r.style.setProperty("--frame-top",`${a}px`),r.style.setProperty("--frame-left",`${i}px`),r.style.setProperty("--frame-width",`${l}px`),r.style.setProperty("--frame-height",`${d}px`)},v=(e,t)=>{const n=o(t);if(!n)return;const r=e.classList.contains("hidden")||e.classList.contains("select-none");n.style.display=r?"none":""},h=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,()=>{p(e,t)});return()=>n.disconnect()},y=()=>{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","enable"]);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","disable"]);e?.()})(),o=((e,t,n)=>{if(!t)return()=>{};e.addEventListener("blur",n);const o=h(e),r=f(e,t);return()=>{e.removeEventListener("blur",n),o(),r?.()}})(t,s,r))},blurEditMode:r,getEditableNode:()=>e,isEditing:()=>null!==e}},g=320,b=1680,w=[{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"}],E=(e,t,n)=>{const o=parseFloat(document.body.dataset.zoom||"1"),r=((e,t)=>{const n=e+Math.round(t);return Math.max(g,Math.min(b,n))})(n,(e.clientX-t)/o);return r},x=(e,t)=>{e.style.setProperty("--container-width",`${t}px`),((e,t)=>{console.log("updateActivePreset",t);const n=e.querySelectorAll(".resize-preset-button");console.log("presetButtons",n),n.forEach((e,n)=>{n<w.length&&(w[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 o=null,s=null,a=null;const i=y(),l=t(p),c=e=>{if(a!==e){if(i.isEditing()){const t=i.getEditableNode();t&&t!==e&&i.blurEditMode()}var t,r;o?.disconnect(),s?.disconnect(),e&&n&&(i.enableEditMode(e,n),o=((e,t)=>{const n=new ResizeObserver(e=>{t(e)});return n.observe(e),n})(n,()=>{l(e,n)}),s=new MutationObserver(()=>{l(e,n),v(e,n)}),s.observe(e,{attributes:!0,attributeFilter:["class"]})),a=e,t="selectedNodeChanged",r=e?.getAttribute("data-node-id")??null,window.parent.postMessage({source:"node-edit-utils",action:t,data:r,timestamp:Date.now()},"*"),m(e,n),e&&n&&v(e,n)}},u=d(n,c,()=>{i.isEditing()&&i.blurEditMode(),a&&n&&(r(n),a=null,o?.disconnect(),s?.disconnect())},i.getEditableNode),h={selectNode:c,getSelectedNode:()=>a,refreshHighlightFrame:()=>{l(a,n)},clearSelectedNode:()=>{r(n),a=null,o?.disconnect(),s?.disconnect()},getEditableNode:()=>i.getEditableNode(),cleanup:()=>{u(),o?.disconnect(),s?.disconnect(),i.blurEditMode(),l.cleanup()}};var f,g;return f="nodeTools",g=h,"undefined"!=typeof window&&(window[f]=g),h},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",w.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,x);let s=!1,a=0,i=0;const l=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=E(t,a,i);x(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,a=t.clientX,i=e.offsetWidth},l,e=>{e.preventDefault(),e.stopPropagation(),n&&(n.style.cursor="default"),s=!1},()=>{s=!1});return{setWidth:t=>{x(e,t)},cleanup:()=>{s=!1,l?.cleanup(),d(),r.remove()}}}});
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;display:flex;gap:.25rem;justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:.25rem;position:absolute;transform:scale(calc(1/var(--zoom))) translate3d(0,100%,0);transform-origin:bottom left;transition:opacity .1s ease;z-index:10000}.highlight-frame{height:var(--frame-height);left:var(--frame-left);pointer-events:none;position:absolute;top:var(--frame-top);width:var(--frame-width);z-index:1000}.highlight-frame-svg{height:100%;left:0;overflow:visible;position:absolute;top:0;width:100%}.highlight-frame-rect{fill:none;stroke:oklch(62.7% .265 303.9);stroke-width:var(--stroke-width)}.tag-label{align-items:center;background-color:oklch(62.7% .265 303.9);border-radius:.375rem;color:#fff;display:flex;font-size:.575rem;font-weight:500;height:1.25rem;justify-content:center;line-height:1;padding:0 .375rem;text-transform:uppercase}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:1.25rem;transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity .1s ease-in-out,visibility .1s ease-in-out;visibility:hidden;width:12px;z-index:10000}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:oklch(55.2% .016 285.938);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform .2s ease-in-out;width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;gap:.25rem;left:0;opacity:0;padding-left:1.25rem;position:absolute;top:0;transition:visibility .1s ease-in-out,opacity .1s ease-in-out;visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:oklch(55.2% .016 285.938/.7);border:none;border-radius:.5rem;color:#fff;cursor:pointer;font-size:.6875rem;padding:.25rem .5rem;text-align:left;transition:background .1s ease-in-out;width:fit-content;&:hover{background:oklch(55.2% .016 285.938/1)}&.is-active{background:oklch(45.7% .24 277.023)}}.is-editable{outline:none;user-select:text;&::selection{background:oklch(62.7% .265 303.9/.3)}&::-moz-selection{background:oklch(62.7% .265 303.9/.3)}&::-webkit-selection{background:oklch(62.7% .265 303.9/.3)}}
1
+ .node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;display:flex;gap:.25rem;justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:.25rem;position:absolute;top:0;transform:scale(calc(1/var(--zoom))) translate3d(0,100%,0);transform-origin:bottom left;transition:opacity .1s ease;z-index:10000}.highlight-frame{font-family:Manrope,sans-serif;height:var(--frame-height);left:var(--frame-left);letter-spacing:.02em;pointer-events:none;position:absolute;top:var(--frame-top);width:var(--frame-width);z-index:1000}.highlight-frame-svg{height:100%;left:0;overflow:visible;position:absolute;top:0;width:100%}.highlight-frame-rect{fill:none;stroke:oklch(45.7% .24 277.023);stroke-width:var(--stroke-width)}.tag-label{align-items:center;background-color:oklch(45.7% .24 277.023);border-radius:.375rem;color:#fff;display:flex;font-size:.575rem;font-weight:500;height:1.25rem;justify-content:center;line-height:1;padding:0 .375rem;text-transform:uppercase}.viewport{position:relative;width:var(--container-width)}.resize-handle{align-items:center;cursor:ew-resize;display:flex;height:40px;justify-content:center;opacity:0;pointer-events:auto;position:absolute;right:-12px;top:1.25rem;transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity .1s ease-in-out,visibility .1s ease-in-out;visibility:hidden;width:12px;z-index:10000}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:oklch(55.2% .016 285.938);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform .2s ease-in-out;width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;font-family:Manrope,sans-serif;gap:.25rem;left:0;letter-spacing:.02em;opacity:0;padding-left:1.25rem;position:absolute;top:0;transition:visibility .1s ease-in-out,opacity .1s ease-in-out;visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:oklch(55.2% .016 285.938/.7);border:none;border-radius:.5rem;color:#fff;cursor:pointer;font-size:.6875rem;padding:.25rem .5rem;text-align:left;transition:background .1s ease-in-out;width:fit-content;&:hover{background:oklch(55.2% .016 285.938/1)}&.is-active{background:oklch(45.7% .24 277.023)}}.is-editable{outline:none;user-select:text;&::selection{background:oklch(62.7% .265 303.9/.3)}&::-moz-selection{background:oklch(62.7% .265 303.9/.3)}&::-webkit-selection{background:oklch(62.7% .265 303.9/.3)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-edit-utils/core",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
@@ -6,6 +6,7 @@ import { setupEventListener } from "./events/setupEventListener";
6
6
  import { clearHighlightFrame } from "./highlight/clearHighlightFrame";
7
7
  import { highlightNode } from "./highlight/highlightNode";
8
8
  import { refreshHighlightFrame } from "./highlight/refreshHighlightFrame";
9
+ import { updateHighlightFrameVisibility } from "./highlight/updateHighlightFrameVisibility";
9
10
  import { nodeText } from "./text/nodeText";
10
11
  import type { NodeTools } from "./types";
11
12
 
@@ -13,6 +14,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
13
14
  const nodeProvider = element;
14
15
 
15
16
  let resizeObserver: ResizeObserver | null = null;
17
+ let mutationObserver: MutationObserver | null = null;
16
18
  let selectedNode: HTMLElement | null = null;
17
19
 
18
20
  const text = nodeText();
@@ -29,11 +31,16 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
29
31
  selectedNode = null;
30
32
 
31
33
  resizeObserver?.disconnect();
34
+ mutationObserver?.disconnect();
32
35
  }
33
36
  }
34
37
  };
35
38
 
36
39
  const selectNode = (node: HTMLElement | null): void => {
40
+ if (selectedNode === node) {
41
+ return;
42
+ }
43
+
37
44
  if (text.isEditing()) {
38
45
  const currentEditable = text.getEditableNode();
39
46
  if (currentEditable && currentEditable !== node) {
@@ -42,6 +49,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
42
49
  }
43
50
 
44
51
  resizeObserver?.disconnect();
52
+ mutationObserver?.disconnect();
45
53
 
46
54
  if (node && nodeProvider) {
47
55
  text.enableEditMode(node, nodeProvider);
@@ -49,11 +57,25 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
49
57
  resizeObserver = connectResizeObserver(nodeProvider, () => {
50
58
  throttledFrameRefresh(node, nodeProvider);
51
59
  });
60
+
61
+ mutationObserver = new MutationObserver(() => {
62
+ throttledFrameRefresh(node, nodeProvider);
63
+ updateHighlightFrameVisibility(node, nodeProvider);
64
+ });
65
+
66
+ mutationObserver.observe(node, {
67
+ attributes: true,
68
+ attributeFilter: ["class"],
69
+ });
52
70
  }
53
71
 
54
72
  selectedNode = node;
55
73
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
56
74
  highlightNode(node, nodeProvider as HTMLElement) ?? null;
75
+
76
+ if (node && nodeProvider) {
77
+ updateHighlightFrameVisibility(node, nodeProvider);
78
+ }
57
79
  };
58
80
 
59
81
  // Setup event listener
@@ -62,6 +84,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
62
84
  const cleanup = (): void => {
63
85
  removeListeners();
64
86
  resizeObserver?.disconnect();
87
+ mutationObserver?.disconnect();
65
88
 
66
89
  text.blurEditMode();
67
90
  throttledFrameRefresh.cleanup();
@@ -77,6 +100,7 @@ export const createNodeTools = (element: HTMLElement | null): NodeTools => {
77
100
  clearHighlightFrame(nodeProvider);
78
101
  selectedNode = null;
79
102
  resizeObserver?.disconnect();
103
+ mutationObserver?.disconnect();
80
104
  },
81
105
  getEditableNode: () => text.getEditableNode(),
82
106
  cleanup,
@@ -0,0 +1,10 @@
1
+ import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
2
+
3
+ export const updateHighlightFrameVisibility = (node: HTMLElement, nodeProvider: HTMLElement): void => {
4
+ const frame = getHighlightFrameElement(nodeProvider);
5
+ if (!frame) return;
6
+
7
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
8
+ frame.style.display = hasHiddenClass ? "none" : "";
9
+ };
10
+
@@ -14,7 +14,7 @@ export const selectNode = (event: MouseEvent, editableNode: HTMLElement | null):
14
14
  const clickThrough = event.metaKey || event.ctrlKey;
15
15
 
16
16
  const candidates = getElementsFromPoint(clickX, clickY).filter(
17
- (element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase())
17
+ (element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none")
18
18
  );
19
19
 
20
20
  if (editableNode && candidates.includes(editableNode)) {
@@ -16,6 +16,7 @@
16
16
  position: absolute;
17
17
  left: 0;
18
18
  bottom: 0;
19
+ top: 0;
19
20
  gap: 0.25rem;
20
21
  z-index: 10000;
21
22
  transform: scale(calc(1 / var(--zoom))) translate3d(0, 100%, 0);
@@ -35,6 +36,8 @@
35
36
  height: var(--frame-height);
36
37
  z-index: 1000;
37
38
  pointer-events: none;
39
+ font-family: "Manrope", sans-serif;
40
+ letter-spacing: 0.02em;
38
41
  /* outline: calc(0.125em / var(--zoom)) solid oklch(62.7% 0.265 303.9); */
39
42
  }
40
43
 
@@ -49,12 +52,12 @@
49
52
 
50
53
  .highlight-frame-rect {
51
54
  fill: none;
52
- stroke: oklch(62.7% 0.265 303.9);
55
+ stroke: oklch(45.7% 0.24 277.023);
53
56
  stroke-width: var(--stroke-width);
54
57
  }
55
58
 
56
59
  .tag-label {
57
- background-color: oklch(62.7% 0.265 303.9);
60
+ background-color: oklch(45.7% 0.24 277.023);
58
61
  color: white;
59
62
  font-size: 0.575rem;
60
63
  font-weight: 500;
@@ -125,6 +128,8 @@
125
128
  gap: 0.25rem;
126
129
  visibility: hidden;
127
130
  opacity: 0;
131
+ font-family: "Manrope", sans-serif;
132
+ letter-spacing: 0.02em;
128
133
  transition:
129
134
  visibility 0.1s ease-in-out,
130
135
  opacity 0.1s ease-in-out;