@node-edit-utils/core 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +268 -0
  2. package/dist/index.d.ts +6 -0
  3. package/dist/lib/canvas/createCanvasObserver.d.ts +2 -0
  4. package/dist/lib/canvas/disableCanvasKeyboard.d.ts +1 -0
  5. package/dist/lib/canvas/enableCanvasKeyboard.d.ts +1 -0
  6. package/dist/lib/canvas/helpers/applyCanvasState.d.ts +1 -0
  7. package/dist/lib/canvas/helpers/getCanvasContainer.d.ts +1 -0
  8. package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -0
  9. package/dist/lib/helpers/index.d.ts +1 -0
  10. package/dist/lib/helpers/observer/connectMutationObserver.d.ts +1 -0
  11. package/dist/lib/helpers/observer/connectResizeObserver.d.ts +1 -0
  12. package/dist/lib/helpers/withRAF.d.ts +4 -0
  13. package/dist/lib/node-tools/createNodeTools.d.ts +2 -0
  14. package/dist/lib/node-tools/events/click/handleNodeClick.d.ts +1 -0
  15. package/dist/lib/node-tools/events/setupEventListener.d.ts +1 -0
  16. package/dist/lib/node-tools/highlight/clearHighlightFrame.d.ts +1 -0
  17. package/dist/lib/node-tools/highlight/createHighlightFrame.d.ts +1 -0
  18. package/dist/lib/node-tools/highlight/createTagLabel.d.ts +1 -0
  19. package/dist/lib/node-tools/highlight/createToolsContainer.d.ts +1 -0
  20. package/dist/lib/node-tools/highlight/helpers/getElementBounds.d.ts +6 -0
  21. package/dist/lib/node-tools/highlight/helpers/getHighlightFrameElement.d.ts +1 -0
  22. package/dist/lib/node-tools/highlight/highlightNode.d.ts +1 -0
  23. package/dist/lib/node-tools/highlight/refreshHighlightFrame.d.ts +1 -0
  24. package/dist/lib/node-tools/select/constants.d.ts +1 -0
  25. package/dist/lib/node-tools/select/helpers/getElementsFromPoint.d.ts +1 -0
  26. package/dist/lib/node-tools/select/helpers/targetSameCandidates.d.ts +1 -0
  27. package/dist/lib/node-tools/select/selectNode.d.ts +1 -0
  28. package/dist/lib/node-tools/text/events/setupKeydownHandler.d.ts +1 -0
  29. package/dist/lib/node-tools/text/events/setupMutationObserver.d.ts +1 -0
  30. package/dist/lib/node-tools/text/events/setupNodeListeners.d.ts +1 -0
  31. package/dist/lib/node-tools/text/helpers/hasTextContent.d.ts +1 -0
  32. package/dist/lib/node-tools/text/helpers/insertLineBreak.d.ts +1 -0
  33. package/dist/lib/node-tools/text/helpers/makeNodeEditable.d.ts +1 -0
  34. package/dist/lib/node-tools/text/helpers/makeNodeNonEditable.d.ts +1 -0
  35. package/dist/lib/node-tools/text/nodeText.d.ts +2 -0
  36. package/dist/lib/post-message/handlePostMessage.d.ts +1 -0
  37. package/dist/lib/post-message/sendPostMessage.d.ts +1 -0
  38. package/dist/lib/viewport/constants.d.ts +5 -0
  39. package/dist/lib/viewport/createViewport.d.ts +2 -0
  40. package/dist/lib/viewport/events/setupEventListener.d.ts +1 -0
  41. package/dist/lib/viewport/resize/createResizeHandle.d.ts +1 -0
  42. package/dist/lib/viewport/width/calcConstrainedWidth.d.ts +1 -0
  43. package/dist/lib/viewport/width/calcWidth.d.ts +1 -0
  44. package/dist/lib/viewport/width/updateWidth.d.ts +1 -0
  45. package/dist/lib/window/bindToWindow.d.ts +1 -0
  46. package/dist/node-edit-utils.cjs.js +588 -0
  47. package/dist/node-edit-utils.esm.js +584 -0
  48. package/dist/node-edit-utils.umd.js +594 -0
  49. package/dist/node-edit-utils.umd.min.js +1 -0
  50. package/dist/styles.css +1 -0
  51. package/dist/umd.d.ts +1 -0
  52. package/package.json +65 -0
  53. package/src/index.ts +9 -0
  54. package/src/lib/canvas/createCanvasObserver.ts +37 -0
  55. package/src/lib/canvas/disableCanvasKeyboard.ts +7 -0
  56. package/src/lib/canvas/enableCanvasKeyboard.ts +7 -0
  57. package/src/lib/canvas/helpers/applyCanvasState.ts +11 -0
  58. package/src/lib/canvas/helpers/getCanvasContainer.ts +3 -0
  59. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +5 -0
  60. package/src/lib/canvas/types.d.ts +3 -0
  61. package/src/lib/helpers/index.ts +1 -0
  62. package/src/lib/helpers/observer/connectMutationObserver.ts +12 -0
  63. package/src/lib/helpers/observer/connectResizeObserver.ts +8 -0
  64. package/src/lib/helpers/withRAF.ts +39 -0
  65. package/src/lib/node-tools/createNodeTools.ts +88 -0
  66. package/src/lib/node-tools/events/click/handleNodeClick.ts +21 -0
  67. package/src/lib/node-tools/events/setupEventListener.ts +35 -0
  68. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +12 -0
  69. package/src/lib/node-tools/highlight/createHighlightFrame.ts +37 -0
  70. package/src/lib/node-tools/highlight/createTagLabel.ts +7 -0
  71. package/src/lib/node-tools/highlight/createToolsContainer.ts +10 -0
  72. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +31 -0
  73. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +5 -0
  74. package/src/lib/node-tools/highlight/highlightNode.ts +23 -0
  75. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +23 -0
  76. package/src/lib/node-tools/select/constants.ts +1 -0
  77. package/src/lib/node-tools/select/helpers/getElementsFromPoint.ts +16 -0
  78. package/src/lib/node-tools/select/helpers/targetSameCandidates.ts +2 -0
  79. package/src/lib/node-tools/select/selectNode.ts +44 -0
  80. package/src/lib/node-tools/text/events/setupKeydownHandler.ts +18 -0
  81. package/src/lib/node-tools/text/events/setupMutationObserver.ts +10 -0
  82. package/src/lib/node-tools/text/events/setupNodeListeners.ts +20 -0
  83. package/src/lib/node-tools/text/helpers/hasTextContent.ts +3 -0
  84. package/src/lib/node-tools/text/helpers/insertLineBreak.ts +17 -0
  85. package/src/lib/node-tools/text/helpers/makeNodeEditable.ts +5 -0
  86. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.ts +5 -0
  87. package/src/lib/node-tools/text/nodeText.ts +67 -0
  88. package/src/lib/node-tools/text/types.d.ts +6 -0
  89. package/src/lib/node-tools/types.d.ts +12 -0
  90. package/src/lib/post-message/handlePostMessage.ts +8 -0
  91. package/src/lib/post-message/sendPostMessage.ts +11 -0
  92. package/src/lib/styles/styles.css +133 -0
  93. package/src/lib/viewport/constants.ts +6 -0
  94. package/src/lib/viewport/createViewport.ts +70 -0
  95. package/src/lib/viewport/events/setupEventListener.ts +20 -0
  96. package/src/lib/viewport/resize/createResizeHandle.ts +9 -0
  97. package/src/lib/viewport/types.d.ts +8 -0
  98. package/src/lib/viewport/width/calcConstrainedWidth.ts +6 -0
  99. package/src/lib/viewport/width/calcWidth.ts +9 -0
  100. package/src/lib/viewport/width/updateWidth.ts +3 -0
  101. package/src/lib/window/bindToWindow.ts +6 -0
  102. package/src/umd.ts +1 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Markup Canvas
3
+ * High-performance markup canvas with zoom and pan capabilities
4
+ * @version 1.2.2
5
+ */
6
+ 'use strict';
7
+
8
+ // biome-ignore lint/suspicious/noExplicitAny: generic constraint requires flexibility
9
+ function withRAFThrottle(func) {
10
+ let rafId = null;
11
+ let lastArgs = null;
12
+ const throttled = (...args) => {
13
+ lastArgs = args;
14
+ if (rafId === null) {
15
+ rafId = requestAnimationFrame(() => {
16
+ if (lastArgs) {
17
+ func(...lastArgs);
18
+ }
19
+ rafId = null;
20
+ lastArgs = null;
21
+ });
22
+ }
23
+ };
24
+ throttled.cleanup = () => {
25
+ if (rafId !== null) {
26
+ cancelAnimationFrame(rafId);
27
+ rafId = null;
28
+ lastArgs = null;
29
+ }
30
+ };
31
+ return throttled;
32
+ }
33
+
34
+ const getCanvasWindowValue = (path) => {
35
+ // biome-ignore lint/suspicious/noExplicitAny: global window extension
36
+ const canvas = window.canvas;
37
+ return path.reduce((obj, prop) => obj?.[prop], canvas);
38
+ };
39
+
40
+ const applyCanvasState = () => {
41
+ const zoom = getCanvasWindowValue(["zoom", "current"]);
42
+ document.body.style.setProperty("--zoom", zoom.toFixed(5));
43
+ document.body.style.setProperty("--stroke-width", (2 / zoom).toFixed(3));
44
+ document.body.dataset.zoom = zoom.toFixed(5);
45
+ document.body.dataset.strokeWidth = (2 / zoom).toFixed(3);
46
+ };
47
+
48
+ function createCanvasObserver() {
49
+ const transformLayer = document.querySelector(".transform-layer");
50
+ if (!transformLayer) {
51
+ return {
52
+ disconnect: () => { },
53
+ };
54
+ }
55
+ const throttledUpdate = withRAFThrottle(() => {
56
+ applyCanvasState();
57
+ });
58
+ const observer = new MutationObserver(() => {
59
+ throttledUpdate();
60
+ });
61
+ observer.observe(transformLayer, {
62
+ attributes: true,
63
+ attributeOldValue: true,
64
+ subtree: true,
65
+ childList: true,
66
+ });
67
+ function disconnect() {
68
+ throttledUpdate.cleanup();
69
+ observer.disconnect();
70
+ }
71
+ return {
72
+ disconnect,
73
+ };
74
+ }
75
+
76
+ const connectResizeObserver = (element, handler) => {
77
+ const resizeObserver = new ResizeObserver((entries) => {
78
+ handler(entries);
79
+ });
80
+ resizeObserver.observe(element);
81
+ return resizeObserver;
82
+ };
83
+
84
+ function sendPostMessage(action, data) {
85
+ window.parent.postMessage({
86
+ source: "node-edit-utils",
87
+ action,
88
+ data,
89
+ timestamp: Date.now(),
90
+ }, "*");
91
+ }
92
+
93
+ const bindToWindow = (key, value) => {
94
+ if (typeof window !== "undefined") {
95
+ // biome-ignore lint/suspicious/noExplicitAny: global window extension requires flexibility
96
+ window[key] = value;
97
+ }
98
+ };
99
+
100
+ const handlePostMessage = (event) => {
101
+ if (event.data.source === "markup-canvas" && event.data.canvasName === "canvas") {
102
+ if (event.data.action === "zoom") {
103
+ const zoom = event.data.data;
104
+ console.log("zoom", zoom);
105
+ }
106
+ }
107
+ };
108
+
109
+ function getHighlightFrameElement(nodeProvider) {
110
+ return nodeProvider.querySelector(".highlight-frame");
111
+ }
112
+
113
+ const clearHighlightFrame = (nodeProvider) => {
114
+ if (!nodeProvider) {
115
+ return;
116
+ }
117
+ const highlightFrame = getHighlightFrameElement(nodeProvider);
118
+ if (highlightFrame) {
119
+ highlightFrame.remove();
120
+ }
121
+ };
122
+
123
+ const IGNORED_DOM_ELEMENTS = ["path", "rect", "circle", "ellipse", "polygon", "line", "polyline", "text", "text-noci"];
124
+
125
+ const getElementsFromPoint = (clickX, clickY) => {
126
+ const elements = document.elementsFromPoint(clickX, clickY);
127
+ return Array.from(elements).reduce((acc, el) => {
128
+ if (acc.found)
129
+ return acc;
130
+ if (el.getAttribute("data-role") === "node-provider") {
131
+ acc.found = true;
132
+ return acc;
133
+ }
134
+ acc.elements.push(el);
135
+ return acc;
136
+ }, { elements: [], found: false }).elements;
137
+ };
138
+
139
+ const targetSameCandidates = (cache, current) => cache.length === current.length && cache.every((el, i) => el === current[i]);
140
+
141
+ let candidateCache = [];
142
+ let attempt = 0;
143
+ const selectNode = (event, editableNode) => {
144
+ let selectedNode = null;
145
+ const clickX = event.clientX;
146
+ const clickY = event.clientY;
147
+ const clickThrough = event.metaKey || event.ctrlKey;
148
+ const candidates = getElementsFromPoint(clickX, clickY).filter((element) => !IGNORED_DOM_ELEMENTS.includes(element.tagName.toLowerCase()));
149
+ if (editableNode && candidates.includes(editableNode)) {
150
+ return editableNode;
151
+ }
152
+ if (clickThrough) {
153
+ candidateCache = [];
154
+ selectedNode = candidates[0];
155
+ return selectedNode;
156
+ }
157
+ if (targetSameCandidates(candidateCache, candidates)) {
158
+ attempt <= candidates.length && attempt++;
159
+ }
160
+ else {
161
+ attempt = 0;
162
+ }
163
+ const nodeIndex = candidates.length - 1 - attempt;
164
+ selectedNode = candidates[nodeIndex];
165
+ candidateCache = candidates;
166
+ return selectedNode;
167
+ };
168
+
169
+ const handleNodeClick = (event, nodeProvider, editableNode, onNodeSelected) => {
170
+ event.preventDefault();
171
+ event.stopPropagation();
172
+ if (nodeProvider && !nodeProvider.contains(event.target)) {
173
+ clearHighlightFrame(nodeProvider);
174
+ onNodeSelected(null);
175
+ return;
176
+ }
177
+ const selectedNode = selectNode(event, editableNode);
178
+ onNodeSelected(selectedNode);
179
+ };
180
+
181
+ const setupEventListener$1 = (nodeProvider, onNodeSelected, onEscapePressed, getEditableNode) => {
182
+ const messageHandler = (event) => {
183
+ handlePostMessage(event);
184
+ };
185
+ const documentClickHandler = (event) => {
186
+ handleNodeClick(event, nodeProvider, getEditableNode(), onNodeSelected);
187
+ };
188
+ const documentKeydownHandler = (event) => {
189
+ if (event.key === "Escape") {
190
+ event.preventDefault();
191
+ event.stopPropagation();
192
+ onEscapePressed?.();
193
+ }
194
+ };
195
+ window.addEventListener("message", messageHandler);
196
+ document.addEventListener("click", documentClickHandler);
197
+ document.addEventListener("keydown", documentKeydownHandler);
198
+ return () => {
199
+ window.removeEventListener("message", messageHandler);
200
+ document.removeEventListener("click", documentClickHandler);
201
+ document.removeEventListener("keydown", documentKeydownHandler);
202
+ };
203
+ };
204
+
205
+ function getElementBounds(element, nodeProvider) {
206
+ const elementRect = element.getBoundingClientRect();
207
+ const componentRootRect = nodeProvider.getBoundingClientRect();
208
+ const relativeTop = elementRect.top - componentRootRect.top;
209
+ const relativeLeft = elementRect.left - componentRootRect.left;
210
+ const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
211
+ const top = parseFloat((relativeTop / zoom).toFixed(5));
212
+ const left = parseFloat((relativeLeft / zoom).toFixed(5));
213
+ const width = Math.max(4, parseFloat((elementRect.width / zoom).toFixed(5)));
214
+ const height = parseFloat((elementRect.height / zoom).toFixed(5));
215
+ return {
216
+ top,
217
+ left,
218
+ width,
219
+ height,
220
+ };
221
+ }
222
+
223
+ const createHighlightFrame = (node, nodeProvider) => {
224
+ const { top, left, width, height } = getElementBounds(node, nodeProvider);
225
+ const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
226
+ document.body.style.setProperty("--zoom", zoom.toString());
227
+ document.body.style.setProperty("--stroke-width", (2 / zoom).toFixed(3));
228
+ const frame = document.createElement("div");
229
+ frame.classList.add("highlight-frame");
230
+ frame.style.setProperty("--frame-top", `${top}px`);
231
+ frame.style.setProperty("--frame-left", `${left}px`);
232
+ frame.style.setProperty("--frame-width", `${width}px`);
233
+ frame.style.setProperty("--frame-height", `${height}px`);
234
+ // Create SVG overlay for outline
235
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
236
+ svg.classList.add("highlight-frame-svg");
237
+ const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
238
+ rect.setAttribute("x", "0");
239
+ rect.setAttribute("y", "0");
240
+ rect.setAttribute("width", "100%");
241
+ rect.setAttribute("height", "100%");
242
+ rect.classList.add("highlight-frame-rect");
243
+ svg.appendChild(rect);
244
+ frame.appendChild(svg);
245
+ const highlightFrame = frame;
246
+ return highlightFrame;
247
+ };
248
+
249
+ const createTagLabel = (node, nodeTools) => {
250
+ const tagLabel = document.createElement("div");
251
+ tagLabel.className = "tag-label";
252
+ tagLabel.textContent = node.tagName.toLowerCase();
253
+ nodeTools.appendChild(tagLabel);
254
+ };
255
+
256
+ const createToolsContainer = (node, highlightFrame) => {
257
+ const nodeTools = document.createElement("div");
258
+ nodeTools.className = "node-tools";
259
+ highlightFrame.appendChild(nodeTools);
260
+ createTagLabel(node, nodeTools);
261
+ };
262
+
263
+ const highlightNode = (node, nodeProvider) => {
264
+ if (!node)
265
+ return;
266
+ const existingHighlightFrame = getHighlightFrameElement(nodeProvider);
267
+ if (existingHighlightFrame) {
268
+ existingHighlightFrame.remove();
269
+ }
270
+ const highlightFrame = createHighlightFrame(node, nodeProvider);
271
+ if (node.contentEditable === "true") {
272
+ highlightFrame.classList.add("is-editable");
273
+ }
274
+ createToolsContainer(node, highlightFrame);
275
+ nodeProvider.appendChild(highlightFrame);
276
+ };
277
+
278
+ const refreshHighlightFrame = (node, nodeProvider) => {
279
+ const frame = getHighlightFrameElement(nodeProvider);
280
+ const zoom = getCanvasWindowValue(["zoom", "current"]) ?? 1;
281
+ if (!frame)
282
+ return;
283
+ if (zoom >= 0.3) {
284
+ nodeProvider.style.setProperty("--tool-opacity", `1`);
285
+ }
286
+ else {
287
+ nodeProvider.style.setProperty("--tool-opacity", `0`);
288
+ }
289
+ const { top, left, width, height } = getElementBounds(node, nodeProvider);
290
+ frame.style.setProperty("--frame-top", `${top}px`);
291
+ frame.style.setProperty("--frame-left", `${left}px`);
292
+ frame.style.setProperty("--frame-width", `${width}px`);
293
+ frame.style.setProperty("--frame-height", `${height}px`);
294
+ };
295
+
296
+ const disableCanvasKeyboard = () => {
297
+ const disable = getCanvasWindowValue(["keyboard", "disable"]);
298
+ disable?.();
299
+ };
300
+
301
+ const enableCanvasKeyboard = () => {
302
+ const enable = getCanvasWindowValue(["keyboard", "enable"]);
303
+ enable?.();
304
+ };
305
+
306
+ const insertLineBreak = () => {
307
+ const selection = window.getSelection();
308
+ if (selection && selection.rangeCount > 0) {
309
+ const range = selection.getRangeAt(0);
310
+ range.deleteContents();
311
+ const br = document.createElement("br");
312
+ range.insertNode(br);
313
+ // Move cursor after the br
314
+ range.setStartAfter(br);
315
+ range.setEndAfter(br);
316
+ selection.removeAllRanges();
317
+ selection.addRange(range);
318
+ }
319
+ };
320
+
321
+ const setupKeydownHandler = (node) => {
322
+ const keydownHandler = (event) => {
323
+ if (event.key === "Enter") {
324
+ event.preventDefault();
325
+ event.stopPropagation();
326
+ insertLineBreak();
327
+ }
328
+ };
329
+ node.addEventListener("keydown", keydownHandler);
330
+ return () => {
331
+ node.removeEventListener("keydown", keydownHandler);
332
+ };
333
+ };
334
+
335
+ const connectMutationObserver = (element, handler) => {
336
+ const mutationObserver = new MutationObserver((mutations) => {
337
+ handler(mutations);
338
+ });
339
+ mutationObserver.observe(element, {
340
+ subtree: true,
341
+ childList: true,
342
+ characterData: true,
343
+ });
344
+ return mutationObserver;
345
+ };
346
+
347
+ const setupMutationObserver = (node, nodeProvider) => {
348
+ const mutationObserver = connectMutationObserver(node, () => {
349
+ refreshHighlightFrame(node, nodeProvider);
350
+ });
351
+ return mutationObserver.disconnect;
352
+ };
353
+
354
+ const setupNodeListeners = (node, nodeProvider, blur) => {
355
+ if (!nodeProvider) {
356
+ return () => { };
357
+ }
358
+ node.addEventListener("blur", blur);
359
+ const keydownCleanup = setupKeydownHandler(node);
360
+ const mutationCleanup = setupMutationObserver(node, nodeProvider);
361
+ return () => {
362
+ node.removeEventListener("blur", blur);
363
+ keydownCleanup();
364
+ mutationCleanup?.();
365
+ };
366
+ };
367
+
368
+ const hasTextContent = (node) => {
369
+ return Array.from(node.childNodes).some((child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim());
370
+ };
371
+
372
+ const makeNodeEditable = (node) => {
373
+ node.contentEditable = "true";
374
+ node.classList.add("is-editable");
375
+ node.style.outline = "none";
376
+ };
377
+
378
+ const makeNodeNonEditable = (node) => {
379
+ node.contentEditable = "false";
380
+ node.classList.remove("is-editable");
381
+ node.style.outline = "none";
382
+ };
383
+
384
+ const nodeText = () => {
385
+ let editableNode = null;
386
+ let blurInProgress = false;
387
+ let cleanup = null;
388
+ const enableEditMode = (node, nodeProvider) => {
389
+ if (editableNode === node) {
390
+ return;
391
+ }
392
+ if (editableNode && editableNode !== node) {
393
+ blurEditMode();
394
+ }
395
+ const editable = hasTextContent(node);
396
+ if (editable) {
397
+ editableNode = node;
398
+ makeNodeEditable(node);
399
+ disableCanvasKeyboard();
400
+ cleanup = setupNodeListeners(node, nodeProvider, blurEditMode);
401
+ }
402
+ };
403
+ const getEditableNode = () => {
404
+ return editableNode;
405
+ };
406
+ const isEditing = () => {
407
+ return editableNode !== null;
408
+ };
409
+ const blurEditMode = () => {
410
+ if (blurInProgress || !editableNode) {
411
+ return;
412
+ }
413
+ blurInProgress = true;
414
+ const nodeToCleanup = editableNode;
415
+ makeNodeNonEditable(nodeToCleanup);
416
+ enableCanvasKeyboard();
417
+ cleanup?.();
418
+ editableNode = null;
419
+ blurInProgress = false;
420
+ };
421
+ return {
422
+ enableEditMode,
423
+ blurEditMode,
424
+ getEditableNode,
425
+ isEditing,
426
+ };
427
+ };
428
+
429
+ const createNodeTools = (element) => {
430
+ const nodeProvider = element;
431
+ let resizeObserver = null;
432
+ let selectedNode = null;
433
+ const text = nodeText();
434
+ const throttledFrameRefresh = withRAFThrottle(refreshHighlightFrame);
435
+ const handleEscape = () => {
436
+ if (text.isEditing()) {
437
+ text.blurEditMode();
438
+ }
439
+ if (selectedNode) {
440
+ if (nodeProvider) {
441
+ clearHighlightFrame(nodeProvider);
442
+ selectedNode = null;
443
+ resizeObserver?.disconnect();
444
+ }
445
+ }
446
+ };
447
+ const selectNode = (node) => {
448
+ if (text.isEditing()) {
449
+ const currentEditable = text.getEditableNode();
450
+ if (currentEditable && currentEditable !== node) {
451
+ text.blurEditMode();
452
+ }
453
+ }
454
+ resizeObserver?.disconnect();
455
+ if (node && nodeProvider) {
456
+ text.enableEditMode(node, nodeProvider);
457
+ resizeObserver = connectResizeObserver(nodeProvider, () => {
458
+ throttledFrameRefresh(node, nodeProvider);
459
+ });
460
+ }
461
+ selectedNode = node;
462
+ sendPostMessage("selectedNodeChanged", node?.getAttribute("data-layer-id") ?? null);
463
+ highlightNode(node, nodeProvider) ?? null;
464
+ };
465
+ // Setup event listener
466
+ const removeListeners = setupEventListener$1(nodeProvider, selectNode, handleEscape, text.getEditableNode);
467
+ const cleanup = () => {
468
+ removeListeners();
469
+ resizeObserver?.disconnect();
470
+ text.blurEditMode();
471
+ throttledFrameRefresh.cleanup();
472
+ };
473
+ const nodeTools = {
474
+ selectNode,
475
+ getSelectedNode: () => selectedNode,
476
+ refreshHighlightFrame: () => {
477
+ throttledFrameRefresh(selectedNode, nodeProvider);
478
+ },
479
+ clearSelectedNode: () => {
480
+ clearHighlightFrame(nodeProvider);
481
+ selectedNode = null;
482
+ resizeObserver?.disconnect();
483
+ },
484
+ getEditableNode: () => text.getEditableNode(),
485
+ cleanup,
486
+ };
487
+ bindToWindow("nodeTools", nodeTools);
488
+ return nodeTools;
489
+ };
490
+
491
+ const getCanvasContainer = () => {
492
+ return document.querySelector(".canvas-container");
493
+ };
494
+
495
+ const DEFAULT_WIDTH = 400;
496
+ const RESIZE_CONFIG = {
497
+ minWidth: 320,
498
+ maxWidth: 1680,
499
+ };
500
+
501
+ const setupEventListener = (resizeHandle, startResize, handleResize, stopResize, blurResize) => {
502
+ resizeHandle.addEventListener("mousedown", startResize);
503
+ document.addEventListener("mousemove", handleResize);
504
+ document.addEventListener("mouseup", stopResize);
505
+ window.addEventListener("blur", blurResize);
506
+ return () => {
507
+ resizeHandle.removeEventListener("mousedown", startResize);
508
+ document.removeEventListener("mousemove", handleResize);
509
+ document.removeEventListener("mouseup", stopResize);
510
+ window.removeEventListener("blur", blurResize);
511
+ };
512
+ };
513
+
514
+ const createResizeHandle = (container) => {
515
+ const handle = document.createElement("div");
516
+ handle.className = "component-resize-handle resize-handle";
517
+ container.appendChild(handle);
518
+ return handle;
519
+ };
520
+
521
+ const calcConstrainedWidth = (startWidth, deltaX) => {
522
+ const newWidth = startWidth + Math.round(deltaX);
523
+ return Math.max(RESIZE_CONFIG.minWidth, Math.min(RESIZE_CONFIG.maxWidth, newWidth));
524
+ };
525
+
526
+ const calcWidth = (event, startX, startWidth) => {
527
+ const zoom = parseFloat(document.body.dataset.zoom || "1");
528
+ const deltaX = (event.clientX - startX) / zoom;
529
+ const width = calcConstrainedWidth(startWidth, deltaX);
530
+ return width;
531
+ };
532
+
533
+ const updateWidth = (container, width) => {
534
+ container.style.setProperty("--container-width", `${width}px`);
535
+ };
536
+
537
+ const createViewport = (container) => {
538
+ const canvas = getCanvasContainer();
539
+ const resizeHandle = createResizeHandle(container);
540
+ container.style.setProperty("--container-width", `${DEFAULT_WIDTH}px`);
541
+ let isDragging = false;
542
+ let startX = 0;
543
+ let startWidth = 0;
544
+ const startResize = (event) => {
545
+ event.preventDefault();
546
+ event.stopPropagation();
547
+ isDragging = true;
548
+ startX = event.clientX;
549
+ startWidth = container.offsetWidth;
550
+ };
551
+ const handleResize = (event) => {
552
+ if (!isDragging)
553
+ return;
554
+ if (canvas) {
555
+ canvas.style.cursor = "ew-resize";
556
+ }
557
+ const width = calcWidth(event, startX, startWidth);
558
+ updateWidth(container, width);
559
+ };
560
+ const throttledHandleResize = withRAFThrottle(handleResize);
561
+ const stopResize = (event) => {
562
+ event.preventDefault();
563
+ event.stopPropagation();
564
+ if (canvas) {
565
+ canvas.style.cursor = "default";
566
+ }
567
+ isDragging = false;
568
+ };
569
+ const blurResize = () => {
570
+ isDragging = false;
571
+ };
572
+ const removeListeners = setupEventListener(resizeHandle, startResize, throttledHandleResize, stopResize, blurResize);
573
+ const cleanup = () => {
574
+ isDragging = false;
575
+ throttledHandleResize?.cleanup();
576
+ removeListeners();
577
+ };
578
+ return {
579
+ setWidth: (width) => {
580
+ updateWidth(container, width);
581
+ },
582
+ cleanup,
583
+ };
584
+ };
585
+
586
+ exports.createCanvasObserver = createCanvasObserver;
587
+ exports.createNodeTools = createNodeTools;
588
+ exports.createViewport = createViewport;