@node-edit-utils/core 2.3.2 → 2.3.4

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 (136) hide show
  1. package/dist/lib/canvas/helpers/getCanvasContainerOrBody.d.ts +1 -0
  2. package/dist/lib/canvas/helpers/getCanvasWindowValue.d.ts +1 -1
  3. package/dist/lib/helpers/adjustForZoom.d.ts +1 -0
  4. package/dist/lib/helpers/createDragHandler.d.ts +69 -0
  5. package/dist/lib/helpers/getNodeProvider.d.ts +1 -0
  6. package/dist/lib/helpers/getNodeTools.d.ts +2 -0
  7. package/dist/lib/helpers/getViewportDimensions.d.ts +4 -0
  8. package/dist/lib/helpers/index.d.ts +9 -1
  9. package/dist/lib/helpers/parseTransform.d.ts +8 -0
  10. package/dist/lib/helpers/toggleClass.d.ts +1 -0
  11. package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
  12. package/dist/lib/viewport/label/helpers/selectFirstViewportNode.d.ts +5 -0
  13. package/dist/lib/viewport/label/index.d.ts +5 -3
  14. package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
  15. package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
  16. package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
  17. package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
  18. package/dist/node-edit-utils.cjs.js +342 -280
  19. package/dist/node-edit-utils.esm.js +342 -280
  20. package/dist/node-edit-utils.umd.js +342 -280
  21. package/dist/node-edit-utils.umd.min.js +1 -1
  22. package/dist/styles.css +1 -1
  23. package/package.json +7 -2
  24. package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
  25. package/src/lib/canvas/createCanvasObserver.ts +2 -2
  26. package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
  27. package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
  28. package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
  29. package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
  30. package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
  31. package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
  32. package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
  33. package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
  34. package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
  35. package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
  36. package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
  37. package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
  38. package/src/lib/canvas/helpers/getCanvasContainerOrBody.ts +6 -0
  39. package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
  40. package/src/lib/canvas/helpers/getCanvasWindowValue.ts +2 -3
  41. package/src/lib/helpers/adjustForZoom.test.ts +65 -0
  42. package/src/lib/helpers/adjustForZoom.ts +4 -0
  43. package/src/lib/helpers/createDragHandler.test.ts +325 -0
  44. package/src/lib/helpers/createDragHandler.ts +171 -0
  45. package/src/lib/helpers/getNodeProvider.test.ts +71 -0
  46. package/src/lib/helpers/getNodeProvider.ts +4 -0
  47. package/src/lib/helpers/getNodeTools.test.ts +50 -0
  48. package/src/lib/helpers/getNodeTools.ts +6 -0
  49. package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
  50. package/src/lib/helpers/getViewportDimensions.ts +7 -0
  51. package/src/lib/helpers/index.ts +9 -1
  52. package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
  53. package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
  54. package/src/lib/helpers/parseTransform.test.ts +117 -0
  55. package/src/lib/helpers/parseTransform.ts +9 -0
  56. package/src/lib/helpers/toggleClass.test.ts +71 -0
  57. package/src/lib/helpers/toggleClass.ts +9 -0
  58. package/src/lib/helpers/withRAF.test.ts +439 -0
  59. package/src/lib/node-tools/createNodeTools.test.ts +373 -0
  60. package/src/lib/node-tools/createNodeTools.ts +0 -1
  61. package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
  62. package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
  63. package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
  64. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +2 -3
  65. package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
  66. package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
  67. package/src/lib/node-tools/highlight/createHighlightFrame.ts +5 -9
  68. package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
  69. package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
  70. package/src/lib/node-tools/highlight/createToolsContainer.ts +3 -6
  71. package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
  72. package/src/lib/node-tools/highlight/helpers/getElementBounds.ts +6 -5
  73. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
  74. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +2 -3
  75. package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
  76. package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
  77. package/src/lib/node-tools/highlight/highlightNode.ts +7 -15
  78. package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
  79. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +12 -42
  80. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
  81. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +2 -3
  82. package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
  83. package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
  84. package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
  85. package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
  86. package/src/lib/node-tools/select/selectNode.test.ts +238 -0
  87. package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
  88. package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
  89. package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
  90. package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
  91. package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
  92. package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
  93. package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
  94. package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
  95. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
  96. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
  97. package/src/lib/node-tools/text/nodeText.test.ts +233 -0
  98. package/src/lib/post-message/processPostMessage.test.ts +218 -0
  99. package/src/lib/post-message/sendPostMessage.test.ts +120 -0
  100. package/src/lib/styles/styles.css +3 -3
  101. package/src/lib/viewport/createViewport.test.ts +267 -0
  102. package/src/lib/viewport/createViewport.ts +51 -51
  103. package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
  104. package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
  105. package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +6 -6
  106. package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
  107. package/src/lib/viewport/label/helpers/getLabelPosition.ts +3 -5
  108. package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
  109. package/src/lib/viewport/label/helpers/getTransformValues.ts +3 -5
  110. package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
  111. package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
  112. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +26 -0
  113. package/src/lib/viewport/label/index.ts +5 -3
  114. package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
  115. package/src/lib/viewport/label/isViewportDragging.ts +9 -0
  116. package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
  117. package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
  118. package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
  119. package/src/lib/viewport/label/refreshViewportLabels.ts +19 -52
  120. package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
  121. package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
  122. package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
  123. package/src/lib/viewport/label/setupViewportDrag.ts +70 -0
  124. package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
  125. package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
  126. package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
  127. package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
  128. package/src/lib/viewport/width/calcWidth.test.ts +68 -0
  129. package/src/lib/viewport/width/updateWidth.test.ts +78 -0
  130. package/src/lib/window/bindToWindow.test.ts +166 -0
  131. package/src/lib/window/bindToWindow.ts +1 -2
  132. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
  133. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
  134. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
  135. package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
  136. package/src/lib/viewport/label/setupViewportLabelDrag.ts +0 -98
@@ -0,0 +1,213 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "@/lib/canvas/helpers/getCanvasContainerOrBody";
3
+ import * as toggleClassModule from "@/lib/helpers/toggleClass";
4
+ import * as isComponentInstanceModule from "../select/helpers/isComponentInstance";
5
+ import * as createHighlightFrameModule from "./createHighlightFrame";
6
+ import * as createToolsContainerModule from "./createToolsContainer";
7
+ import * as getHighlightFrameElementModule from "./helpers/getHighlightFrameElement";
8
+ import * as getScreenBoundsModule from "./helpers/getScreenBounds";
9
+ import { highlightNode } from "./highlightNode";
10
+
11
+ vi.mock("@/lib/canvas/helpers/getCanvasContainerOrBody");
12
+ vi.mock("@/lib/helpers/toggleClass");
13
+ vi.mock("../select/helpers/isComponentInstance");
14
+ vi.mock("./createHighlightFrame");
15
+ vi.mock("./createToolsContainer");
16
+ vi.mock("./helpers/getHighlightFrameElement");
17
+ vi.mock("./helpers/getScreenBounds");
18
+
19
+ describe("highlightNode", () => {
20
+ let node: HTMLElement;
21
+ let container: HTMLElement;
22
+ let existingFrame: SVGSVGElement;
23
+ let existingToolsWrapper: HTMLElement;
24
+ let newFrame: SVGSVGElement;
25
+
26
+ beforeEach(() => {
27
+ node = document.createElement("div");
28
+ node.setAttribute("data-node-id", "test-node");
29
+ document.body.appendChild(node);
30
+
31
+ container = document.createElement("div");
32
+ document.body.appendChild(container);
33
+
34
+ existingFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
35
+ existingFrame.classList.add("highlight-frame-overlay");
36
+ container.appendChild(existingFrame);
37
+
38
+ existingToolsWrapper = document.createElement("div");
39
+ existingToolsWrapper.classList.add("highlight-frame-tools-wrapper");
40
+ container.appendChild(existingToolsWrapper);
41
+
42
+ newFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
43
+ newFrame.classList.add("highlight-frame-overlay");
44
+
45
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
46
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(existingFrame);
47
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(false);
48
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
49
+ top: 100,
50
+ left: 200,
51
+ width: 300,
52
+ height: 400,
53
+ });
54
+ vi.mocked(createHighlightFrameModule.createHighlightFrame).mockReturnValue(newFrame);
55
+ vi.mocked(toggleClassModule.toggleClass).mockImplementation(() => {});
56
+ vi.mocked(createToolsContainerModule.createToolsContainer).mockImplementation(() => {});
57
+ });
58
+
59
+ afterEach(() => {
60
+ if (document.body.contains(node)) {
61
+ document.body.removeChild(node);
62
+ }
63
+ if (document.body.contains(container)) {
64
+ document.body.removeChild(container);
65
+ }
66
+ vi.clearAllMocks();
67
+ });
68
+
69
+ it("should do nothing when node is null", () => {
70
+ highlightNode(null);
71
+
72
+ expect(createHighlightFrameModule.createHighlightFrame).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it("should remove existing highlight frame", () => {
76
+ highlightNode(node);
77
+
78
+ expect(existingFrame.parentNode).toBeNull();
79
+ });
80
+
81
+ it("should remove existing tools wrapper", () => {
82
+ highlightNode(node);
83
+
84
+ expect(existingToolsWrapper.parentNode).toBeNull();
85
+ });
86
+
87
+ it("should create new highlight frame", () => {
88
+ highlightNode(node);
89
+
90
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, false, false);
91
+ });
92
+
93
+ it("should create highlight frame with instance flag", () => {
94
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
95
+
96
+ highlightNode(node);
97
+
98
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, true, false);
99
+ });
100
+
101
+ it("should create highlight frame with text edit flag", () => {
102
+ node.contentEditable = "true";
103
+
104
+ highlightNode(node);
105
+
106
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, false, true);
107
+ });
108
+
109
+ it("should add is-editable class when node is contentEditable", () => {
110
+ node.contentEditable = "true";
111
+
112
+ highlightNode(node);
113
+
114
+ expect(newFrame.classList.contains("is-editable")).toBe(true);
115
+ });
116
+
117
+ it("should create tools wrapper with correct styles", () => {
118
+ highlightNode(node);
119
+
120
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
121
+ expect(toolsWrapper).not.toBeNull();
122
+ expect((toolsWrapper as HTMLElement).style.position).toBe("absolute");
123
+ expect((toolsWrapper as HTMLElement).style.transform).toBe("translate(200px, 500px)");
124
+ expect((toolsWrapper as HTMLElement).style.transformOrigin).toBe("left center");
125
+ expect((toolsWrapper as HTMLElement).style.pointerEvents).toBe("none");
126
+ expect((toolsWrapper as HTMLElement).style.zIndex).toBe("500");
127
+ });
128
+
129
+ it("should toggle classes on tools wrapper", () => {
130
+ highlightNode(node);
131
+
132
+ expect(toggleClassModule.toggleClass).toHaveBeenCalled();
133
+ });
134
+
135
+ it("should create tools container", () => {
136
+ highlightNode(node);
137
+
138
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
139
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
140
+ node,
141
+ toolsWrapper as HTMLElement,
142
+ false,
143
+ false
144
+ );
145
+ });
146
+
147
+ it("should append tools wrapper to container", () => {
148
+ highlightNode(node);
149
+
150
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
151
+ expect(toolsWrapper?.parentNode).toBe(container);
152
+ });
153
+
154
+ it("should handle instance node", () => {
155
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
156
+
157
+ highlightNode(node);
158
+
159
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
160
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
161
+ node,
162
+ toolsWrapper as HTMLElement,
163
+ true,
164
+ false
165
+ );
166
+ });
167
+
168
+ it("should handle text edit node", () => {
169
+ node.contentEditable = "true";
170
+
171
+ highlightNode(node);
172
+
173
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
174
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
175
+ node,
176
+ toolsWrapper as HTMLElement,
177
+ false,
178
+ true
179
+ );
180
+ });
181
+
182
+ it("should calculate bottomY correctly", () => {
183
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
184
+ top: 50,
185
+ left: 100,
186
+ width: 200,
187
+ height: 150,
188
+ });
189
+
190
+ highlightNode(node);
191
+
192
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
193
+ expect((toolsWrapper as HTMLElement).style.transform).toBe("translate(100px, 200px)");
194
+ });
195
+
196
+ it("should work when no existing frame exists", () => {
197
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(null);
198
+ container.removeChild(existingFrame);
199
+
200
+ expect(() => {
201
+ highlightNode(node);
202
+ }).not.toThrow();
203
+ });
204
+
205
+ it("should work when no existing tools wrapper exists", () => {
206
+ container.removeChild(existingToolsWrapper);
207
+
208
+ expect(() => {
209
+ highlightNode(node);
210
+ }).not.toThrow();
211
+ });
212
+ });
213
+
@@ -1,4 +1,5 @@
1
- import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
2
+ import { toggleClass } from "@/lib/helpers/toggleClass";
2
3
  import { isComponentInstance } from "../select/helpers/isComponentInstance";
3
4
  import { createHighlightFrame } from "./createHighlightFrame";
4
5
  import { createToolsContainer } from "./createToolsContainer";
@@ -9,9 +10,8 @@ export const highlightNode = (node: HTMLElement | null): void => {
9
10
  if (!node) return;
10
11
 
11
12
  const existingHighlightFrame = getHighlightFrameElement();
12
- const canvasContainer = getCanvasContainer();
13
- const existingToolsWrapper =
14
- canvasContainer?.querySelector(".highlight-frame-tools-wrapper") || document.body.querySelector(".highlight-frame-tools-wrapper");
13
+ const container = getCanvasContainerOrBody();
14
+ const existingToolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
15
15
 
16
16
  if (existingHighlightFrame) {
17
17
  existingHighlightFrame.remove();
@@ -35,12 +35,8 @@ export const highlightNode = (node: HTMLElement | null): void => {
35
35
  // Create tools wrapper using CSS transform (GPU-accelerated)
36
36
  const toolsWrapper = document.createElement("div");
37
37
  toolsWrapper.classList.add("highlight-frame-tools-wrapper");
38
- if (isInstance) {
39
- toolsWrapper.classList.add("is-instance");
40
- }
41
- if (isTextEdit) {
42
- toolsWrapper.classList.add("is-text-edit");
43
- }
38
+ toggleClass(toolsWrapper, "is-instance", isInstance);
39
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
44
40
  toolsWrapper.style.position = "absolute";
45
41
  toolsWrapper.style.transform = `translate(${left}px, ${bottomY}px)`;
46
42
  toolsWrapper.style.transformOrigin = "left center";
@@ -48,9 +44,5 @@ export const highlightNode = (node: HTMLElement | null): void => {
48
44
  toolsWrapper.style.zIndex = "500";
49
45
 
50
46
  createToolsContainer(node, toolsWrapper, isInstance, isTextEdit);
51
- if (canvasContainer) {
52
- canvasContainer.appendChild(toolsWrapper);
53
- } else {
54
- document.body.appendChild(toolsWrapper);
55
- }
47
+ container.appendChild(toolsWrapper);
56
48
  };
@@ -0,0 +1,323 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "@/lib/canvas/helpers/getCanvasContainerOrBody";
3
+ import * as getCanvasWindowValueModule from "@/lib/canvas/helpers/getCanvasWindowValue";
4
+ import * as getViewportDimensionsModule from "@/lib/helpers/getViewportDimensions";
5
+ import * as toggleClassModule from "@/lib/helpers/toggleClass";
6
+ import * as isComponentInstanceModule from "../select/helpers/isComponentInstance";
7
+ import * as getHighlightFrameElementModule from "./helpers/getHighlightFrameElement";
8
+ import * as getScreenBoundsModule from "./helpers/getScreenBounds";
9
+ import { refreshHighlightFrame } from "./refreshHighlightFrame";
10
+
11
+ vi.mock("@/lib/canvas/helpers/getCanvasContainerOrBody");
12
+ vi.mock("@/lib/canvas/helpers/getCanvasWindowValue");
13
+ vi.mock("@/lib/helpers/getViewportDimensions");
14
+ vi.mock("@/lib/helpers/toggleClass");
15
+ vi.mock("../select/helpers/isComponentInstance");
16
+ vi.mock("./helpers/getHighlightFrameElement");
17
+ vi.mock("./helpers/getScreenBounds");
18
+
19
+ describe("refreshHighlightFrame", () => {
20
+ let node: HTMLElement;
21
+ let nodeProvider: HTMLElement;
22
+ let container: HTMLElement;
23
+ let frame: SVGSVGElement;
24
+ let group: SVGGElement;
25
+ let rect: SVGRectElement;
26
+ let toolsWrapper: HTMLElement;
27
+ let nodeTools: HTMLElement;
28
+
29
+ beforeEach(() => {
30
+ node = document.createElement("div");
31
+ node.setAttribute("data-node-id", "test-node");
32
+ document.body.appendChild(node);
33
+
34
+ nodeProvider = document.createElement("div");
35
+ document.body.appendChild(nodeProvider);
36
+
37
+ container = document.createElement("div");
38
+ document.body.appendChild(container);
39
+
40
+ frame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
41
+ frame.classList.add("highlight-frame-overlay");
42
+ container.appendChild(frame);
43
+
44
+ group = document.createElementNS("http://www.w3.org/2000/svg", "g");
45
+ group.classList.add("highlight-frame-group");
46
+ frame.appendChild(group);
47
+
48
+ rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
49
+ group.appendChild(rect);
50
+
51
+ toolsWrapper = document.createElement("div");
52
+ toolsWrapper.classList.add("highlight-frame-tools-wrapper");
53
+ container.appendChild(toolsWrapper);
54
+
55
+ nodeTools = document.createElement("div");
56
+ nodeTools.classList.add("node-tools");
57
+ toolsWrapper.appendChild(nodeTools);
58
+
59
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
60
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(frame);
61
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
62
+ width: 1920,
63
+ height: 1080,
64
+ });
65
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
66
+ top: 100,
67
+ left: 200,
68
+ width: 300,
69
+ height: 400,
70
+ });
71
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(1);
72
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(false);
73
+ vi.mocked(toggleClassModule.toggleClass).mockImplementation(() => {});
74
+ });
75
+
76
+ afterEach(() => {
77
+ if (document.body.contains(node)) {
78
+ document.body.removeChild(node);
79
+ }
80
+ if (document.body.contains(nodeProvider)) {
81
+ document.body.removeChild(nodeProvider);
82
+ }
83
+ if (document.body.contains(container)) {
84
+ document.body.removeChild(container);
85
+ }
86
+ vi.clearAllMocks();
87
+ });
88
+
89
+ it("should do nothing when frame does not exist", () => {
90
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(null);
91
+
92
+ expect(() => {
93
+ refreshHighlightFrame(node, nodeProvider);
94
+ }).not.toThrow();
95
+ });
96
+
97
+ it("should update SVG dimensions", () => {
98
+ refreshHighlightFrame(node, nodeProvider);
99
+
100
+ expect(frame.getAttribute("width")).toBe("1920");
101
+ expect(frame.getAttribute("height")).toBe("1080");
102
+ });
103
+
104
+ it("should toggle is-instance class", () => {
105
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
106
+
107
+ refreshHighlightFrame(node, nodeProvider);
108
+
109
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(frame, "is-instance", true);
110
+ });
111
+
112
+ it("should toggle is-text-edit class", () => {
113
+ node.contentEditable = "true";
114
+
115
+ refreshHighlightFrame(node, nodeProvider);
116
+
117
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(frame, "is-text-edit", true);
118
+ });
119
+
120
+ it("should update group transform", () => {
121
+ refreshHighlightFrame(node, nodeProvider);
122
+
123
+ expect(group.getAttribute("transform")).toBe("translate(200, 100)");
124
+ });
125
+
126
+ it("should update rect dimensions", () => {
127
+ refreshHighlightFrame(node, nodeProvider);
128
+
129
+ expect(rect.getAttribute("width")).toBe("300");
130
+ expect(rect.getAttribute("height")).toBe("400");
131
+ });
132
+
133
+ it("should ensure minimum width of 3", () => {
134
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
135
+ top: 100,
136
+ left: 200,
137
+ width: 1,
138
+ height: 400,
139
+ });
140
+
141
+ refreshHighlightFrame(node, nodeProvider);
142
+
143
+ expect(rect.getAttribute("width")).toBe("3");
144
+ });
145
+
146
+ it("should apply instance color when isInstance is true", () => {
147
+ const originalGetComputedStyle = window.getComputedStyle;
148
+ window.getComputedStyle = vi.fn().mockReturnValue({
149
+ getPropertyValue: vi.fn((prop: string) => {
150
+ if (prop === "--component-color") return "rgb(255, 0, 0)";
151
+ return "";
152
+ }),
153
+ } as unknown as CSSStyleDeclaration);
154
+
155
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
156
+
157
+ refreshHighlightFrame(node, nodeProvider);
158
+
159
+ expect(rect.getAttribute("stroke")).toBe("rgb(255, 0, 0)");
160
+
161
+ window.getComputedStyle = originalGetComputedStyle;
162
+ });
163
+
164
+ it("should apply text edit color when isTextEdit is true", () => {
165
+ const originalGetComputedStyle = window.getComputedStyle;
166
+ window.getComputedStyle = vi.fn().mockReturnValue({
167
+ getPropertyValue: vi.fn((prop: string) => {
168
+ if (prop === "--text-edit-color") return "rgb(0, 255, 0)";
169
+ return "";
170
+ }),
171
+ } as unknown as CSSStyleDeclaration);
172
+
173
+ node.contentEditable = "true";
174
+
175
+ refreshHighlightFrame(node, nodeProvider);
176
+
177
+ expect(rect.getAttribute("stroke")).toBe("rgb(0, 255, 0)");
178
+
179
+ window.getComputedStyle = originalGetComputedStyle;
180
+ });
181
+
182
+ it("should remove stroke when neither flag is true", () => {
183
+ rect.setAttribute("stroke", "rgb(255, 0, 0)");
184
+
185
+ refreshHighlightFrame(node, nodeProvider);
186
+
187
+ expect(rect.getAttribute("stroke")).toBeNull();
188
+ });
189
+
190
+ it("should update tools wrapper transform", () => {
191
+ refreshHighlightFrame(node, nodeProvider);
192
+
193
+ expect(toolsWrapper.style.transform).toBe("translate(200px, 500px)");
194
+ });
195
+
196
+ it("should toggle classes on tools wrapper", () => {
197
+ refreshHighlightFrame(node, nodeProvider);
198
+
199
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(toolsWrapper, "is-instance", false);
200
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(toolsWrapper, "is-text-edit", false);
201
+ });
202
+
203
+ it("should toggle classes on node tools", () => {
204
+ refreshHighlightFrame(node, nodeProvider);
205
+
206
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(nodeTools, "is-instance", false);
207
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(nodeTools, "is-text-edit", false);
208
+ });
209
+
210
+ it("should update tool opacity when zoom <= 10", () => {
211
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(5);
212
+
213
+ refreshHighlightFrame(node, nodeProvider);
214
+
215
+ expect(nodeProvider.style.getPropertyValue("--tool-opacity")).toBe("1");
216
+ });
217
+
218
+ it("should set tool opacity to 0 when zoom > 10", () => {
219
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(15);
220
+
221
+ refreshHighlightFrame(node, nodeProvider);
222
+
223
+ expect(nodeProvider.style.getPropertyValue("--tool-opacity")).toBe("0");
224
+ });
225
+
226
+ it("should use default zoom of 1 when zoom is undefined", () => {
227
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(undefined);
228
+
229
+ refreshHighlightFrame(node, nodeProvider);
230
+
231
+ expect(nodeProvider.style.getPropertyValue("--tool-opacity")).toBe("1");
232
+ });
233
+
234
+ it("should use custom canvas name", () => {
235
+ refreshHighlightFrame(node, nodeProvider, "custom-canvas");
236
+
237
+ expect(getCanvasWindowValueModule.getCanvasWindowValue).toHaveBeenCalledWith(["zoom", "current"], "custom-canvas");
238
+ });
239
+
240
+ it("should update corner handle positions", () => {
241
+ const topLeft = document.createElementNS("http://www.w3.org/2000/svg", "rect");
242
+ topLeft.classList.add("handle-top-left");
243
+ group.appendChild(topLeft);
244
+
245
+ refreshHighlightFrame(node, nodeProvider);
246
+
247
+ expect(topLeft.getAttribute("x")).toBe("-3");
248
+ expect(topLeft.getAttribute("y")).toBe("-3");
249
+ });
250
+
251
+ it("should update all corner handles", () => {
252
+ const topLeft = document.createElementNS("http://www.w3.org/2000/svg", "rect");
253
+ topLeft.classList.add("handle-top-left");
254
+ const topRight = document.createElementNS("http://www.w3.org/2000/svg", "rect");
255
+ topRight.classList.add("handle-top-right");
256
+ const bottomRight = document.createElementNS("http://www.w3.org/2000/svg", "rect");
257
+ bottomRight.classList.add("handle-bottom-right");
258
+ const bottomLeft = document.createElementNS("http://www.w3.org/2000/svg", "rect");
259
+ bottomLeft.classList.add("handle-bottom-left");
260
+ group.appendChild(topLeft);
261
+ group.appendChild(topRight);
262
+ group.appendChild(bottomRight);
263
+ group.appendChild(bottomLeft);
264
+
265
+ refreshHighlightFrame(node, nodeProvider);
266
+
267
+ expect(topLeft.getAttribute("x")).toBe("-3");
268
+ expect(topLeft.getAttribute("y")).toBe("-3");
269
+ expect(topRight.getAttribute("x")).toBe("297");
270
+ expect(topRight.getAttribute("y")).toBe("-3");
271
+ expect(bottomRight.getAttribute("x")).toBe("297");
272
+ expect(bottomRight.getAttribute("y")).toBe("397");
273
+ expect(bottomLeft.getAttribute("x")).toBe("-3");
274
+ expect(bottomLeft.getAttribute("y")).toBe("397");
275
+ });
276
+
277
+ it("should update handle colors for instance", () => {
278
+ const originalGetComputedStyle = window.getComputedStyle;
279
+ window.getComputedStyle = vi.fn().mockReturnValue({
280
+ getPropertyValue: vi.fn((prop: string) => {
281
+ if (prop === "--component-color") return "rgb(255, 0, 0)";
282
+ return "";
283
+ }),
284
+ } as unknown as CSSStyleDeclaration);
285
+
286
+ const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
287
+ handle.classList.add("handle-top-left");
288
+ group.appendChild(handle);
289
+
290
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
291
+
292
+ refreshHighlightFrame(node, nodeProvider);
293
+
294
+ expect(handle.getAttribute("stroke")).toBe("rgb(255, 0, 0)");
295
+
296
+ window.getComputedStyle = originalGetComputedStyle;
297
+ });
298
+
299
+ it("should not update when group does not exist", () => {
300
+ group.remove();
301
+
302
+ expect(() => {
303
+ refreshHighlightFrame(node, nodeProvider);
304
+ }).not.toThrow();
305
+ });
306
+
307
+ it("should not update when rect does not exist", () => {
308
+ rect.remove();
309
+
310
+ expect(() => {
311
+ refreshHighlightFrame(node, nodeProvider);
312
+ }).not.toThrow();
313
+ });
314
+
315
+ it("should not update tools wrapper when it does not exist", () => {
316
+ container.removeChild(toolsWrapper);
317
+
318
+ expect(() => {
319
+ refreshHighlightFrame(node, nodeProvider);
320
+ }).not.toThrow();
321
+ });
322
+ });
323
+
@@ -1,5 +1,7 @@
1
- import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
2
2
  import { getCanvasWindowValue } from "@/lib/canvas/helpers/getCanvasWindowValue";
3
+ import { getViewportDimensions } from "@/lib/helpers/getViewportDimensions";
4
+ import { toggleClass } from "@/lib/helpers/toggleClass";
3
5
  import { isComponentInstance } from "../select/helpers/isComponentInstance";
4
6
  import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
5
7
  import { getScreenBounds } from "./helpers/getScreenBounds";
@@ -22,24 +24,15 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
22
24
 
23
25
  // Update SVG dimensions to match current viewport (handles window resize and ensures coordinate system is correct)
24
26
  // Use clientWidth/Height to match getBoundingClientRect() coordinate system (excludes scrollbars)
25
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
26
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
27
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
27
28
  frame.setAttribute("width", viewportWidth.toString());
28
29
  frame.setAttribute("height", viewportHeight.toString());
29
30
 
30
31
  // Update instance class
31
- if (isInstance) {
32
- frame.classList.add("is-instance");
33
- } else {
34
- frame.classList.remove("is-instance");
35
- }
32
+ toggleClass(frame, "is-instance", isInstance);
36
33
 
37
34
  // Update text edit class
38
- if (isTextEdit) {
39
- frame.classList.add("is-text-edit");
40
- } else {
41
- frame.classList.remove("is-text-edit");
42
- }
35
+ toggleClass(frame, "is-text-edit", isTextEdit);
43
36
 
44
37
  const group = frame.querySelector(".highlight-frame-group") as SVGGElement | null;
45
38
  if (!group) return;
@@ -56,12 +49,11 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
56
49
  rect.removeAttribute("stroke"); // Use CSS default
57
50
  }
58
51
 
59
- const canvasContainer = getCanvasContainer();
60
- const container = canvasContainer || document.body;
52
+ const container = getCanvasContainerOrBody();
61
53
  const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
62
54
  const nodeTools = toolsWrapper?.querySelector(".node-tools") as HTMLElement | null;
63
55
 
64
- const zoom = getCanvasWindowValue(["zoom", "current"], canvasName) ?? 1;
56
+ const zoom = (getCanvasWindowValue(["zoom", "current"], canvasName) as number | undefined) ?? 1;
65
57
  const bounds = getScreenBounds(node);
66
58
 
67
59
  // Calculate all values before any DOM writes
@@ -71,32 +63,10 @@ export const refreshHighlightFrame = (node: HTMLElement, nodeProvider: HTMLEleme
71
63
  const bottomY = top + height;
72
64
 
73
65
  // Update instance classes on tools wrapper and node tools
74
- if (toolsWrapper) {
75
- if (isInstance) {
76
- toolsWrapper.classList.add("is-instance");
77
- } else {
78
- toolsWrapper.classList.remove("is-instance");
79
- }
80
- // Update text edit class
81
- if (isTextEdit) {
82
- toolsWrapper.classList.add("is-text-edit");
83
- } else {
84
- toolsWrapper.classList.remove("is-text-edit");
85
- }
86
- }
87
- if (nodeTools) {
88
- if (isInstance) {
89
- nodeTools.classList.add("is-instance");
90
- } else {
91
- nodeTools.classList.remove("is-instance");
92
- }
93
- // Update text edit class
94
- if (isTextEdit) {
95
- nodeTools.classList.add("is-text-edit");
96
- } else {
97
- nodeTools.classList.remove("is-text-edit");
98
- }
99
- }
66
+ toggleClass(toolsWrapper, "is-instance", isInstance);
67
+ toggleClass(toolsWrapper, "is-text-edit", isTextEdit);
68
+ toggleClass(nodeTools, "is-instance", isInstance);
69
+ toggleClass(nodeTools, "is-text-edit", isTextEdit);
100
70
 
101
71
  // Batch all DOM writes (single paint pass)
102
72
  // Update group transform to move entire group (rect + handles) at once