@node-edit-utils/core 2.0.5 → 2.0.7

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 disableCanvasTextMode: () => void;
@@ -0,0 +1 @@
1
+ export declare const enableCanvasTextMode: () => void;
@@ -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.7
5
5
  */
6
6
  'use strict';
7
7
 
@@ -100,17 +100,14 @@ const bindToWindow = (key, value) => {
100
100
  const processPostMessage = (event, onNodeSelected) => {
101
101
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
102
102
  if (event.data.action === "zoom") {
103
- const zoom = event.data.data;
104
- console.log("zoom", zoom);
103
+ event.data.data;
105
104
  }
106
105
  }
107
106
  if (event.data.source === "application") {
108
107
  if (event.data.action === "selectedNodeChanged") {
109
108
  const nodeId = event.data.data;
110
- console.log("selectedNodeChanged in node-edit-utils", nodeId);
111
109
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
112
110
  if (selectedNode) {
113
- console.log("selectedNode", selectedNode);
114
111
  onNodeSelected?.(selectedNode);
115
112
  }
116
113
  }
@@ -156,7 +153,7 @@ const selectNode = (event, editableNode) => {
156
153
  const clickX = event.clientX;
157
154
  const clickY = event.clientY;
158
155
  const clickThrough = event.metaKey || event.ctrlKey;
159
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
156
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
160
157
  if (editableNode && candidates.includes(editableNode)) {
161
158
  return editableNode;
162
159
  }
@@ -197,9 +194,8 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, get
197
194
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
198
195
  };
199
196
  const documentKeydownHandler = (event) => {
197
+ console.log("Esacpe Handler", event);
200
198
  if (event.key === "Escape") {
201
- event.preventDefault();
202
- event.stopPropagation();
203
199
  onEscapePressed?.();
204
200
  }
205
201
  };
@@ -304,14 +300,22 @@ const refreshHighlightFrame = (node, nodeProvider) => {
304
300
  frame.style.setProperty("--frame-height", `${height}px`);
305
301
  };
306
302
 
307
- const disableCanvasKeyboard = () => {
308
- const disable = getCanvasWindowValue(["keyboard", "disable"]);
309
- disable?.();
303
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
304
+ const frame = getHighlightFrameElement(nodeProvider);
305
+ if (!frame)
306
+ return;
307
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
308
+ frame.style.display = hasHiddenClass ? "none" : "";
309
+ };
310
+
311
+ const disableCanvasTextMode = () => {
312
+ const disableTextEditMode = getCanvasWindowValue(["keyboard", "disableTextEditMode"]);
313
+ disableTextEditMode?.();
310
314
  };
311
315
 
312
- const enableCanvasKeyboard = () => {
313
- const enable = getCanvasWindowValue(["keyboard", "enable"]);
314
- enable?.();
316
+ const enableCanvasTextMode = () => {
317
+ const enableTextEditMode = getCanvasWindowValue(["keyboard", "enableTextEditMode"]);
318
+ enableTextEditMode?.();
315
319
  };
316
320
 
317
321
  const insertLineBreak = () => {
@@ -407,7 +411,7 @@ const nodeText = () => {
407
411
  if (editable) {
408
412
  editableNode = node;
409
413
  makeNodeEditable(node);
410
- disableCanvasKeyboard();
414
+ enableCanvasTextMode();
411
415
  cleanup = setupNodeListeners(node, nodeProvider, blurEditMode);
412
416
  }
413
417
  };
@@ -424,7 +428,7 @@ const nodeText = () => {
424
428
  blurInProgress = true;
425
429
  const nodeToCleanup = editableNode;
426
430
  makeNodeNonEditable(nodeToCleanup);
427
- enableCanvasKeyboard();
431
+ disableCanvasTextMode();
428
432
  cleanup?.();
429
433
  editableNode = null;
430
434
  blurInProgress = false;
@@ -440,6 +444,7 @@ const nodeText = () => {
440
444
  const createNodeTools = (element) => {
441
445
  const nodeProvider = element;
442
446
  let resizeObserver = null;
447
+ let mutationObserver = null;
443
448
  let selectedNode = null;
444
449
  const text = nodeText();
445
450
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -452,10 +457,14 @@ const createNodeTools = (element) => {
452
457
  clearHighlightFrame(nodeProvider);
453
458
  selectedNode = null;
454
459
  resizeObserver?.disconnect();
460
+ mutationObserver?.disconnect();
455
461
  }
456
462
  }
457
463
  };
458
464
  const selectNode = (node) => {
465
+ if (selectedNode === node) {
466
+ return;
467
+ }
459
468
  if (text.isEditing()) {
460
469
  const currentEditable = text.getEditableNode();
461
470
  if (currentEditable && currentEditable !== node) {
@@ -463,21 +472,34 @@ const createNodeTools = (element) => {
463
472
  }
464
473
  }
465
474
  resizeObserver?.disconnect();
475
+ mutationObserver?.disconnect();
466
476
  if (node && nodeProvider) {
467
477
  text.enableEditMode(node, nodeProvider);
468
478
  resizeObserver = connectResizeObserver(nodeProvider, () => {
469
479
  throttledFrameRefresh(node, nodeProvider);
470
480
  });
481
+ mutationObserver = new MutationObserver(() => {
482
+ throttledFrameRefresh(node, nodeProvider);
483
+ updateHighlightFrameVisibility(node, nodeProvider);
484
+ });
485
+ mutationObserver.observe(node, {
486
+ attributes: true,
487
+ attributeFilter: ["class"],
488
+ });
471
489
  }
472
490
  selectedNode = node;
473
491
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
474
492
  highlightNode(node, nodeProvider) ?? null;
493
+ if (node && nodeProvider) {
494
+ updateHighlightFrameVisibility(node, nodeProvider);
495
+ }
475
496
  };
476
497
  // Setup event listener
477
498
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
478
499
  const cleanup = () => {
479
500
  removeListeners();
480
501
  resizeObserver?.disconnect();
502
+ mutationObserver?.disconnect();
481
503
  text.blurEditMode();
482
504
  throttledFrameRefresh.cleanup();
483
505
  };
@@ -491,6 +513,7 @@ const createNodeTools = (element) => {
491
513
  clearHighlightFrame(nodeProvider);
492
514
  selectedNode = null;
493
515
  resizeObserver?.disconnect();
516
+ mutationObserver?.disconnect();
494
517
  },
495
518
  getEditableNode: () => text.getEditableNode(),
496
519
  cleanup,
@@ -585,9 +608,7 @@ const calcWidth = (event, startX, startWidth) => {
585
608
  };
586
609
 
587
610
  const updateActivePreset = (container, width) => {
588
- console.log("updateActivePreset", width);
589
611
  const presetButtons = container.querySelectorAll(".resize-preset-button");
590
- console.log("presetButtons", presetButtons);
591
612
  presetButtons.forEach((button, index) => {
592
613
  if (index < RESIZE_PRESETS.length) {
593
614
  const preset = RESIZE_PRESETS[index];
@@ -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.7
5
5
  */
6
6
  // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
7
7
  function withRAFThrottle(func) {
@@ -98,17 +98,14 @@ const bindToWindow = (key, value) => {
98
98
  const processPostMessage = (event, onNodeSelected) => {
99
99
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
100
100
  if (event.data.action === "zoom") {
101
- const zoom = event.data.data;
102
- console.log("zoom", zoom);
101
+ event.data.data;
103
102
  }
104
103
  }
105
104
  if (event.data.source === "application") {
106
105
  if (event.data.action === "selectedNodeChanged") {
107
106
  const nodeId = event.data.data;
108
- console.log("selectedNodeChanged in node-edit-utils", nodeId);
109
107
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
110
108
  if (selectedNode) {
111
- console.log("selectedNode", selectedNode);
112
109
  onNodeSelected?.(selectedNode);
113
110
  }
114
111
  }
@@ -154,7 +151,7 @@ const selectNode = (event, editableNode) => {
154
151
  const clickX = event.clientX;
155
152
  const clickY = event.clientY;
156
153
  const clickThrough = event.metaKey || event.ctrlKey;
157
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
154
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
158
155
  if (editableNode && candidates.includes(editableNode)) {
159
156
  return editableNode;
160
157
  }
@@ -195,9 +192,8 @@ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, get
195
192
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
196
193
  };
197
194
  const documentKeydownHandler = (event) => {
195
+ console.log("Esacpe Handler", event);
198
196
  if (event.key === "Escape") {
199
- event.preventDefault();
200
- event.stopPropagation();
201
197
  onEscapePressed?.();
202
198
  }
203
199
  };
@@ -302,14 +298,22 @@ const refreshHighlightFrame = (node, nodeProvider) => {
302
298
  frame.style.setProperty("--frame-height", `${height}px`);
303
299
  };
304
300
 
305
- const disableCanvasKeyboard = () => {
306
- const disable = getCanvasWindowValue(["keyboard", "disable"]);
307
- disable?.();
301
+ const updateHighlightFrameVisibility = (node, nodeProvider) => {
302
+ const frame = getHighlightFrameElement(nodeProvider);
303
+ if (!frame)
304
+ return;
305
+ const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
306
+ frame.style.display = hasHiddenClass ? "none" : "";
307
+ };
308
+
309
+ const disableCanvasTextMode = () => {
310
+ const disableTextEditMode = getCanvasWindowValue(["keyboard", "disableTextEditMode"]);
311
+ disableTextEditMode?.();
308
312
  };
309
313
 
310
- const enableCanvasKeyboard = () => {
311
- const enable = getCanvasWindowValue(["keyboard", "enable"]);
312
- enable?.();
314
+ const enableCanvasTextMode = () => {
315
+ const enableTextEditMode = getCanvasWindowValue(["keyboard", "enableTextEditMode"]);
316
+ enableTextEditMode?.();
313
317
  };
314
318
 
315
319
  const insertLineBreak = () => {
@@ -405,7 +409,7 @@ const nodeText = () => {
405
409
  if (editable) {
406
410
  editableNode = node;
407
411
  makeNodeEditable(node);
408
- disableCanvasKeyboard();
412
+ enableCanvasTextMode();
409
413
  cleanup = setupNodeListeners(node, nodeProvider, blurEditMode);
410
414
  }
411
415
  };
@@ -422,7 +426,7 @@ const nodeText = () => {
422
426
  blurInProgress = true;
423
427
  const nodeToCleanup = editableNode;
424
428
  makeNodeNonEditable(nodeToCleanup);
425
- enableCanvasKeyboard();
429
+ disableCanvasTextMode();
426
430
  cleanup?.();
427
431
  editableNode = null;
428
432
  blurInProgress = false;
@@ -438,6 +442,7 @@ const nodeText = () => {
438
442
  const createNodeTools = (element) => {
439
443
  const nodeProvider = element;
440
444
  let resizeObserver = null;
445
+ let mutationObserver = null;
441
446
  let selectedNode = null;
442
447
  const text = nodeText();
443
448
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -450,10 +455,14 @@ const createNodeTools = (element) => {
450
455
  clearHighlightFrame(nodeProvider);
451
456
  selectedNode = null;
452
457
  resizeObserver?.disconnect();
458
+ mutationObserver?.disconnect();
453
459
  }
454
460
  }
455
461
  };
456
462
  const selectNode = (node) => {
463
+ if (selectedNode === node) {
464
+ return;
465
+ }
457
466
  if (text.isEditing()) {
458
467
  const currentEditable = text.getEditableNode();
459
468
  if (currentEditable && currentEditable !== node) {
@@ -461,21 +470,34 @@ const createNodeTools = (element) => {
461
470
  }
462
471
  }
463
472
  resizeObserver?.disconnect();
473
+ mutationObserver?.disconnect();
464
474
  if (node && nodeProvider) {
465
475
  text.enableEditMode(node, nodeProvider);
466
476
  resizeObserver = connectResizeObserver(nodeProvider, () => {
467
477
  throttledFrameRefresh(node, nodeProvider);
468
478
  });
479
+ mutationObserver = new MutationObserver(() => {
480
+ throttledFrameRefresh(node, nodeProvider);
481
+ updateHighlightFrameVisibility(node, nodeProvider);
482
+ });
483
+ mutationObserver.observe(node, {
484
+ attributes: true,
485
+ attributeFilter: ["class"],
486
+ });
469
487
  }
470
488
  selectedNode = node;
471
489
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
472
490
  highlightNode(node, nodeProvider) ?? null;
491
+ if (node && nodeProvider) {
492
+ updateHighlightFrameVisibility(node, nodeProvider);
493
+ }
473
494
  };
474
495
  // Setup event listener
475
496
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
476
497
  const cleanup = () => {
477
498
  removeListeners();
478
499
  resizeObserver?.disconnect();
500
+ mutationObserver?.disconnect();
479
501
  text.blurEditMode();
480
502
  throttledFrameRefresh.cleanup();
481
503
  };
@@ -489,6 +511,7 @@ const createNodeTools = (element) => {
489
511
  clearHighlightFrame(nodeProvider);
490
512
  selectedNode = null;
491
513
  resizeObserver?.disconnect();
514
+ mutationObserver?.disconnect();
492
515
  },
493
516
  getEditableNode: () => text.getEditableNode(),
494
517
  cleanup,
@@ -583,9 +606,7 @@ const calcWidth = (event, startX, startWidth) => {
583
606
  };
584
607
 
585
608
  const updateActivePreset = (container, width) => {
586
- console.log("updateActivePreset", width);
587
609
  const presetButtons = container.querySelectorAll(".resize-preset-button");
588
- console.log("presetButtons", presetButtons);
589
610
  presetButtons.forEach((button, index) => {
590
611
  if (index < RESIZE_PRESETS.length) {
591
612
  const preset = RESIZE_PRESETS[index];
@@ -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.7
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
@@ -104,17 +104,14 @@
104
104
  const processPostMessage = (event, onNodeSelected) => {
105
105
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
106
106
  if (event.data.action === "zoom") {
107
- const zoom = event.data.data;
108
- console.log("zoom", zoom);
107
+ event.data.data;
109
108
  }
110
109
  }
111
110
  if (event.data.source === "application") {
112
111
  if (event.data.action === "selectedNodeChanged") {
113
112
  const nodeId = event.data.data;
114
- console.log("selectedNodeChanged in node-edit-utils", nodeId);
115
113
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
116
114
  if (selectedNode) {
117
- console.log("selectedNode", selectedNode);
118
115
  onNodeSelected?.(selectedNode);
119
116
  }
120
117
  }
@@ -160,7 +157,7 @@
160
157
  const clickX = event.clientX;
161
158
  const clickY = event.clientY;
162
159
  const clickThrough = event.metaKey || event.ctrlKey;
163
- const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
160
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()) && !element.classList.contains("select-none"));
164
161
  if (editableNode && candidates.includes(editableNode)) {
165
162
  return editableNode;
166
163
  }
@@ -201,9 +198,8 @@
201
198
  handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
202
199
  };
203
200
  const documentKeydownHandler = (event) => {
201
+ console.log("Esacpe Handler", event);
204
202
  if (event.key === "Escape") {
205
- event.preventDefault();
206
- event.stopPropagation();
207
203
  onEscapePressed?.();
208
204
  }
209
205
  };
@@ -308,14 +304,22 @@
308
304
  frame.style.setProperty("--frame-height", `${height}px`);
309
305
  };
310
306
 
311
- const disableCanvasKeyboard = () => {
312
- const disable = getCanvasWindowValue(["keyboard", "disable"]);
313
- disable?.();
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
+
315
+ const disableCanvasTextMode = () => {
316
+ const disableTextEditMode = getCanvasWindowValue(["keyboard", "disableTextEditMode"]);
317
+ disableTextEditMode?.();
314
318
  };
315
319
 
316
- const enableCanvasKeyboard = () => {
317
- const enable = getCanvasWindowValue(["keyboard", "enable"]);
318
- enable?.();
320
+ const enableCanvasTextMode = () => {
321
+ const enableTextEditMode = getCanvasWindowValue(["keyboard", "enableTextEditMode"]);
322
+ enableTextEditMode?.();
319
323
  };
320
324
 
321
325
  const insertLineBreak = () => {
@@ -411,7 +415,7 @@
411
415
  if (editable) {
412
416
  editableNode = node;
413
417
  makeNodeEditable(node);
414
- disableCanvasKeyboard();
418
+ enableCanvasTextMode();
415
419
  cleanup = setupNodeListeners(node, nodeProvider, blurEditMode);
416
420
  }
417
421
  };
@@ -428,7 +432,7 @@
428
432
  blurInProgress = true;
429
433
  const nodeToCleanup = editableNode;
430
434
  makeNodeNonEditable(nodeToCleanup);
431
- enableCanvasKeyboard();
435
+ disableCanvasTextMode();
432
436
  cleanup?.();
433
437
  editableNode = null;
434
438
  blurInProgress = false;
@@ -444,6 +448,7 @@
444
448
  const createNodeTools = (element) => {
445
449
  const nodeProvider = element;
446
450
  let resizeObserver = null;
451
+ let mutationObserver = null;
447
452
  let selectedNode = null;
448
453
  const text = nodeText();
449
454
  const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
@@ -456,10 +461,14 @@
456
461
  clearHighlightFrame(nodeProvider);
457
462
  selectedNode = null;
458
463
  resizeObserver?.disconnect();
464
+ mutationObserver?.disconnect();
459
465
  }
460
466
  }
461
467
  };
462
468
  const selectNode = (node) => {
469
+ if (selectedNode === node) {
470
+ return;
471
+ }
463
472
  if (text.isEditing()) {
464
473
  const currentEditable = text.getEditableNode();
465
474
  if (currentEditable && currentEditable !== node) {
@@ -467,21 +476,34 @@
467
476
  }
468
477
  }
469
478
  resizeObserver?.disconnect();
479
+ mutationObserver?.disconnect();
470
480
  if (node && nodeProvider) {
471
481
  text.enableEditMode(node, nodeProvider);
472
482
  resizeObserver = connectResizeObserver(nodeProvider, () => {
473
483
  throttledFrameRefresh(node, nodeProvider);
474
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
+ });
475
493
  }
476
494
  selectedNode = node;
477
495
  sendPostMessage("selectedNodeChanged", node?.getAttribute("data-node-id") ?? null);
478
496
  highlightNode(node, nodeProvider) ?? null;
497
+ if (node && nodeProvider) {
498
+ updateHighlightFrameVisibility(node, nodeProvider);
499
+ }
479
500
  };
480
501
  // Setup event listener
481
502
  const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
482
503
  const cleanup = () => {
483
504
  removeListeners();
484
505
  resizeObserver?.disconnect();
506
+ mutationObserver?.disconnect();
485
507
  text.blurEditMode();
486
508
  throttledFrameRefresh.cleanup();
487
509
  };
@@ -495,6 +517,7 @@
495
517
  clearHighlightFrame(nodeProvider);
496
518
  selectedNode = null;
497
519
  resizeObserver?.disconnect();
520
+ mutationObserver?.disconnect();
498
521
  },
499
522
  getEditableNode: () => text.getEditableNode(),
500
523
  cleanup,
@@ -589,9 +612,7 @@
589
612
  };
590
613
 
591
614
  const updateActivePreset = (container, width) => {
592
- console.log("updateActivePreset", width);
593
615
  const presetButtons = container.querySelectorAll(".resize-preset-button");
594
- console.log("presetButtons", presetButtons);
595
616
  presetButtons.forEach((button, index) => {
596
617
  if (index < RESIZE_PRESETS.length) {
597
618
  const preset = RESIZE_PRESETS[index];
@@ -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 d=(e,t)=>{let n=null;const o=e.clientX,r=e.clientY,d=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=>!s.includes(e.tagName.toLowerCase())&&!e.classList.contains("select-none"));if(t&&l.includes(t))return t;if(d)return a=[],n=l[0],n;var c,u;u=l,(c=a).length===u.length&&c.every((e,t)=>e===u[t])?i<=l.length&&i++:i=0;return n=l[l.length-1-i],a=l,n},l=(e,t,n,o)=>{const s=e=>{((e,t)=>{if("markup-canvas"===e.data.source&&"canvas"===e.data.canvasName&&"zoom"===e.data.action&&e.data.data,"application"===e.data.source&&"selectedNodeChanged"===e.data.action){const n=e.data.data,o=document.querySelector(`[data-node-id="${n}"]`);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(d(e,n))})(n,e,o(),t)},i=e=>{console.log("Esacpe Handler",e),"Escape"===e.key&&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 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 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"),l.appendChild(u),d.appendChild(l),d})(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:d,height:l}=c(e,t);r.style.setProperty("--frame-top",`${a}px`),r.style.setProperty("--frame-left",`${i}px`),r.style.setProperty("--frame-width",`${d}px`),r.style.setProperty("--frame-height",`${l}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)}},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,()=>{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","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=h(e),r=y(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)=>{e.querySelectorAll(".resize-preset-button").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=f(),d=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,()=>{d(e,n)}),s=new MutationObserver(()=>{d(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=l(n,c,()=>{i.isEditing()&&i.blurEditMode(),a&&n&&(r(n),a=null,o?.disconnect(),s?.disconnect())},i.getEditableNode),h={selectNode:c,getSelectedNode:()=>a,refreshHighlightFrame:()=>{d(a,n)},clearSelectedNode:()=>{r(n),a=null,o?.disconnect(),s?.disconnect()},getEditableNode:()=>i.getEditableNode(),cleanup:()=>{u(),o?.disconnect(),s?.disconnect(),i.blurEditMode(),d.cleanup()}};var y,g;return y="nodeTools",g=h,"undefined"!=typeof window&&(window[y]=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 d=t(t=>{if(!s)return;n&&(n.style.cursor="ew-resize");const o=E(t,a,i);x(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=>{x(e,t)},cleanup:()=>{s=!1,d?.cleanup(),l(),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
+ :root{--primary-color:oklch(0.6235 0.22 294);--uncode-color:oklch(45.7% 0.24 277.023);--handle-color:oklch(55.2% 0.016 285.938);--handle-color-transparent:oklch(55.2% 0.016 285.938/0.7);--primary-color-selection:oklch(0.59 0.18 294/0.3);--text-color-white:#fff;--spacing-xs:0.25rem;--spacing-sm:0.375rem;--spacing-md:0.5rem;--spacing-lg:1.25rem;--transition-fast:0.1s ease-in-out;--transition-medium:0.2s ease-in-out;--z-index-high:10000;--z-index-medium:1000;--letter-spacing:0.02em;--font-family-primary:"Manrope",sans-serif}.node-provider{::selection{background:transparent}::-moz-selection{background:transparent}::-webkit-selection{background:transparent}}.node-tools{bottom:0;display:flex;gap:var(--spacing-xs);justify-content:center;left:0;opacity:var(--tool-opacity);padding-top:var(--spacing-xs);position:absolute;top:0;transform:scale(calc(1/var(--zoom))) translate3d(0,100%,0);transform-origin:bottom left;transition:opacity var(--transition-fast);z-index:var(--z-index-high)}.highlight-frame{font-family:var(--font-family-primary);height:var(--frame-height);left:var(--frame-left);letter-spacing:var(--letter-spacing);pointer-events:none;position:absolute;top:var(--frame-top);width:var(--frame-width);z-index:var(--z-index-medium)}.highlight-frame-svg{height:100%;left:0;overflow:visible;position:absolute;top:0;width:100%}.highlight-frame-rect{fill:none;stroke:var(--primary-color);stroke-width:var(--stroke-width)}.tag-label{align-items:center;background-color:var(--primary-color);border-radius:var(--spacing-sm);color:var(--text-color-white);display:flex;font-size:.575rem;font-weight:500;height:1rem;justify-content:center;line-height:1;padding:.5625rem var(--spacing-sm);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:var(--spacing-lg);transform:scale(calc(1/var(--zoom)));transform-origin:top left;transition:opacity var(--transition-fast),visibility var(--transition-fast);visibility:hidden;width:12px;z-index:var(--z-index-high)}.viewport:hover .resize-handle{opacity:1;visibility:visible}.resize-handle:before{background:var(--handle-color);border-radius:4px;content:"";height:32px;position:relative;right:0;top:0;transition:transform var(--transition-medium);width:3px}.resize-handle:hover:before{transform:scaleY(1.3)}.resize-presets{display:flex;flex-direction:column;font-family:var(--font-family-primary);gap:var(--spacing-xs);left:0;letter-spacing:var(--letter-spacing);opacity:0;padding-left:var(--spacing-lg);position:absolute;top:0;transition:visibility var(--transition-fast),opacity var(--transition-fast);visibility:hidden}.resize-handle:hover .resize-presets{opacity:1;visibility:visible}.resize-preset-button{text-wrap:nowrap;backdrop-filter:blur(8px);background:var(--handle-color-transparent);border:none;border-radius:var(--spacing-md);color:var(--text-color-white);cursor:pointer;font-size:.625rem;padding:var(--spacing-xs) var(--spacing-md);text-align:left;transition:background var(--transition-fast);width:fit-content;&:hover{background:var(--handle-color)}&.is-active{background:var(--uncode-color)}}.is-editable{outline:none;user-select:text;&::selection{background:var(--primary-color-selection)}&::-moz-selection{background:var(--primary-color-selection)}&::-webkit-selection{background:var(--primary-color-selection)}}
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.7",
4
4
  "description": "Utilities for editing nodes in a dom tree.",
5
5
  "type": "module",
6
6
  "main": "dist/node-edit-utils.cjs.js",
@@ -0,0 +1,7 @@
1
+ import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
2
+
3
+ export const disableCanvasTextMode = () => {
4
+ const disableTextEditMode = getCanvasWindowValue(["keyboard", "disableTextEditMode"]);
5
+
6
+ disableTextEditMode?.();
7
+ };
@@ -0,0 +1,7 @@
1
+ import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
2
+
3
+ export const enableCanvasTextMode = () => {
4
+ const enableTextEditMode = getCanvasWindowValue(["keyboard", "enableTextEditMode"]);
5
+
6
+ enableTextEditMode?.();
7
+ };
@@ -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,
@@ -16,9 +16,9 @@ export const setupEventListener = (
16
16
  };
17
17
 
18
18
  const documentKeydownHandler = (event: KeyboardEvent) => {
19
+ console.log("Esacpe Handler", event);
20
+
19
21
  if (event.key === "Escape") {
20
- event.preventDefault();
21
- event.stopPropagation();
22
22
  onEscapePressed?.();
23
23
  }
24
24
  };
@@ -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)) {
@@ -1,5 +1,5 @@
1
- import { disableCanvasKeyboard } from "../../canvas/disableCanvasKeyboard";
2
- import { enableCanvasKeyboard } from "../../canvas/enableCanvasKeyboard";
1
+ import { disableCanvasTextMode } from "../../canvas/disableCanvasTextMode";
2
+ import { enableCanvasTextMode } from "../../canvas/enableCanvasTextMode";
3
3
  import { setupNodeListeners } from "./events/setupNodeListeners";
4
4
  import { hasTextContent } from "./helpers/hasTextContent";
5
5
  import { makeNodeEditable } from "./helpers/makeNodeEditable";
@@ -26,7 +26,7 @@ export const nodeText = (): NodeText => {
26
26
  editableNode = node;
27
27
 
28
28
  makeNodeEditable(node);
29
- disableCanvasKeyboard();
29
+ enableCanvasTextMode();
30
30
 
31
31
  cleanup = setupNodeListeners(node, nodeProvider, blurEditMode);
32
32
  }
@@ -50,8 +50,7 @@ export const nodeText = (): NodeText => {
50
50
  const nodeToCleanup = editableNode;
51
51
 
52
52
  makeNodeNonEditable(nodeToCleanup);
53
- enableCanvasKeyboard();
54
-
53
+ disableCanvasTextMode();
55
54
  cleanup?.();
56
55
 
57
56
  editableNode = null;
@@ -2,18 +2,15 @@ export const processPostMessage = (event: MessageEvent, onNodeSelected?: (node:
2
2
  if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
3
3
  if (event.data.action === "zoom") {
4
4
  const zoom = event.data.data;
5
- console.log("zoom", zoom);
6
5
  }
7
6
  }
8
7
 
9
8
  if (event.data.source === "application") {
10
9
  if (event.data.action === "selectedNodeChanged") {
11
10
  const nodeId = event.data.data;
12
- console.log("selectedNodeChanged in node-edit-utils", nodeId);
13
11
  const selectedNode = document.querySelector(`[data-node-id="${nodeId}"]`);
14
12
 
15
13
  if (selectedNode) {
16
- console.log("selectedNode", selectedNode);
17
14
  onNodeSelected?.(selectedNode as HTMLElement);
18
15
  }
19
16
  }
@@ -1,3 +1,28 @@
1
+ :root {
2
+ --primary-color: oklch(0.6235 0.22 294);
3
+ --uncode-color: oklch(45.7% 0.24 277.023);
4
+
5
+ --handle-color: oklch(55.2% 0.016 285.938);
6
+ --handle-color-transparent: oklch(55.2% 0.016 285.938 / 0.7);
7
+ --primary-color-selection: oklch(0.59 0.18 294 / 0.3);
8
+
9
+ --text-color-white: white;
10
+
11
+ --spacing-xs: 0.25rem;
12
+ --spacing-sm: 0.375rem;
13
+ --spacing-md: 0.5rem;
14
+ --spacing-lg: 1.25rem;
15
+
16
+ --transition-fast: 0.1s ease-in-out;
17
+ --transition-medium: 0.2s ease-in-out;
18
+
19
+ --z-index-high: 10000;
20
+ --z-index-medium: 1000;
21
+
22
+ --letter-spacing: 0.02em;
23
+ --font-family-primary: "Manrope", sans-serif;
24
+ }
25
+
1
26
  .node-provider {
2
27
  ::selection {
3
28
  background: transparent;
@@ -16,13 +41,14 @@
16
41
  position: absolute;
17
42
  left: 0;
18
43
  bottom: 0;
19
- gap: 0.25rem;
20
- z-index: 10000;
44
+ top: 0;
45
+ gap: var(--spacing-xs);
46
+ z-index: var(--z-index-high);
21
47
  transform: scale(calc(1 / var(--zoom))) translate3d(0, 100%, 0);
22
48
  transform-origin: bottom left;
23
- transition: opacity 0.1s ease;
49
+ transition: opacity var(--transition-fast);
24
50
  opacity: var(--tool-opacity);
25
- padding-top: 0.25rem;
51
+ padding-top: var(--spacing-xs);
26
52
  display: flex;
27
53
  justify-content: center;
28
54
  }
@@ -33,9 +59,10 @@
33
59
  left: var(--frame-left);
34
60
  width: var(--frame-width);
35
61
  height: var(--frame-height);
36
- z-index: 1000;
62
+ z-index: var(--z-index-medium);
37
63
  pointer-events: none;
38
- /* outline: calc(0.125em / var(--zoom)) solid oklch(62.7% 0.265 303.9); */
64
+ font-family: var(--font-family-primary);
65
+ letter-spacing: var(--letter-spacing);
39
66
  }
40
67
 
41
68
  .highlight-frame-svg {
@@ -49,20 +76,20 @@
49
76
 
50
77
  .highlight-frame-rect {
51
78
  fill: none;
52
- stroke: oklch(62.7% 0.265 303.9);
79
+ stroke: var(--primary-color);
53
80
  stroke-width: var(--stroke-width);
54
81
  }
55
82
 
56
83
  .tag-label {
57
- background-color: oklch(62.7% 0.265 303.9);
58
- color: white;
84
+ background-color: var(--primary-color);
85
+ color: var(--text-color-white);
59
86
  font-size: 0.575rem;
60
87
  font-weight: 500;
61
88
  text-transform: uppercase;
62
- height: 1.25rem;
63
- padding: 0 0.375rem;
89
+ height: 1rem;
90
+ padding: 0.5625rem var(--spacing-sm);
64
91
  line-height: 1;
65
- border-radius: 0.375rem;
92
+ border-radius: var(--spacing-sm);
66
93
  display: flex;
67
94
  align-items: center;
68
95
  justify-content: center;
@@ -75,12 +102,12 @@
75
102
 
76
103
  .resize-handle {
77
104
  position: absolute;
78
- top: 1.25rem;
105
+ top: var(--spacing-lg);
79
106
  right: -12px;
80
107
  width: 12px;
81
108
  height: 40px;
82
109
  cursor: ew-resize;
83
- z-index: 10000;
110
+ z-index: var(--z-index-high);
84
111
  display: flex;
85
112
  opacity: 0;
86
113
  visibility: hidden;
@@ -90,8 +117,8 @@
90
117
  transform: scale(calc(1 / var(--zoom)));
91
118
  transform-origin: top left;
92
119
  transition:
93
- opacity 0.1s ease-in-out,
94
- visibility 0.1s ease-in-out;
120
+ opacity var(--transition-fast),
121
+ visibility var(--transition-fast);
95
122
  }
96
123
 
97
124
  .viewport:hover .resize-handle {
@@ -106,9 +133,9 @@
106
133
  right: 0;
107
134
  width: 3px;
108
135
  height: 32px;
109
- background: oklch(55.2% 0.016 285.938);
136
+ background: var(--handle-color);
110
137
  border-radius: 4px;
111
- transition: transform 0.2s ease-in-out;
138
+ transition: transform var(--transition-medium);
112
139
  }
113
140
 
114
141
  .resize-handle:hover::before {
@@ -119,15 +146,17 @@
119
146
  position: absolute;
120
147
  top: 0;
121
148
  left: 0;
122
- padding-left: 1.25rem;
149
+ padding-left: var(--spacing-lg);
123
150
  display: flex;
124
151
  flex-direction: column;
125
- gap: 0.25rem;
152
+ gap: var(--spacing-xs);
126
153
  visibility: hidden;
127
154
  opacity: 0;
155
+ font-family: var(--font-family-primary);
156
+ letter-spacing: var(--letter-spacing);
128
157
  transition:
129
- visibility 0.1s ease-in-out,
130
- opacity 0.1s ease-in-out;
158
+ visibility var(--transition-fast),
159
+ opacity var(--transition-fast);
131
160
  }
132
161
 
133
162
  .resize-handle:hover .resize-presets {
@@ -136,25 +165,25 @@
136
165
  }
137
166
 
138
167
  .resize-preset-button {
139
- background: oklch(55.2% 0.016 285.938 / 0.7);
168
+ background: var(--handle-color-transparent);
140
169
  border: none;
141
170
  cursor: pointer;
142
- padding: 0.25rem 0.5rem;
143
- border-radius: 0.5rem;
144
- font-size: 0.6875rem;
171
+ padding: var(--spacing-xs) var(--spacing-md);
172
+ border-radius: var(--spacing-md);
173
+ font-size: 0.625rem;
145
174
  text-wrap: nowrap;
146
175
  text-align: left;
147
176
  backdrop-filter: blur(8px);
148
- transition: background 0.1s ease-in-out;
149
- color: white;
177
+ transition: background var(--transition-fast);
178
+ color: var(--text-color-white);
150
179
  width: fit-content;
151
180
 
152
181
  &:hover {
153
- background: oklch(55.2% 0.016 285.938 / 1);
182
+ background: var(--handle-color);
154
183
  }
155
184
 
156
185
  &.is-active {
157
- background: oklch(45.7% 0.24 277.023);
186
+ background: var(--uncode-color);
158
187
  }
159
188
  }
160
189
 
@@ -163,14 +192,14 @@
163
192
  user-select: text;
164
193
 
165
194
  &::selection {
166
- background: oklch(62.7% 0.265 303.9 / 0.3);
195
+ background: var(--primary-color-selection);
167
196
  }
168
197
 
169
198
  &::-moz-selection {
170
- background: oklch(62.7% 0.265 303.9 / 0.3);
199
+ background: var(--primary-color-selection);
171
200
  }
172
201
 
173
202
  &::-webkit-selection {
174
- background: oklch(62.7% 0.265 303.9 / 0.3);
203
+ background: var(--primary-color-selection);
175
204
  }
176
205
  }
@@ -1,9 +1,7 @@
1
1
  import { RESIZE_PRESETS } from "../constants";
2
2
 
3
3
  export const updateActivePreset = (container: HTMLElement, width: number): void => {
4
- console.log("updateActivePreset", width);
5
4
  const presetButtons = container.querySelectorAll<HTMLButtonElement>(".resize-preset-button");
6
- console.log("presetButtons", presetButtons);
7
5
  presetButtons.forEach((button, index) => {
8
6
  if (index < RESIZE_PRESETS.length) {
9
7
  const preset = RESIZE_PRESETS[index];