@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,88 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "../../canvas/helpers/getCanvasContainerOrBody";
3
+ import { clearHighlightFrame } from "./clearHighlightFrame";
4
+
5
+ vi.mock("../../canvas/helpers/getCanvasContainerOrBody");
6
+
7
+ describe("clearHighlightFrame", () => {
8
+ let container: HTMLElement;
9
+ let highlightFrame: SVGSVGElement;
10
+ let toolsWrapper: HTMLElement;
11
+
12
+ beforeEach(() => {
13
+ container = document.createElement("div");
14
+ document.body.appendChild(container);
15
+
16
+ highlightFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
17
+ highlightFrame.classList.add("highlight-frame-overlay");
18
+ container.appendChild(highlightFrame);
19
+
20
+ toolsWrapper = document.createElement("div");
21
+ toolsWrapper.classList.add("highlight-frame-tools-wrapper");
22
+ container.appendChild(toolsWrapper);
23
+
24
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
25
+ });
26
+
27
+ afterEach(() => {
28
+ if (document.body.contains(container)) {
29
+ document.body.removeChild(container);
30
+ }
31
+ vi.clearAllMocks();
32
+ });
33
+
34
+ it("should remove highlight frame overlay", () => {
35
+ clearHighlightFrame();
36
+
37
+ expect(container.querySelector(".highlight-frame-overlay")).toBeNull();
38
+ });
39
+
40
+ it("should remove tools wrapper", () => {
41
+ clearHighlightFrame();
42
+
43
+ expect(container.querySelector(".highlight-frame-tools-wrapper")).toBeNull();
44
+ });
45
+
46
+ it("should remove both frame and tools wrapper", () => {
47
+ clearHighlightFrame();
48
+
49
+ expect(container.querySelector(".highlight-frame-overlay")).toBeNull();
50
+ expect(container.querySelector(".highlight-frame-tools-wrapper")).toBeNull();
51
+ });
52
+
53
+ it("should not throw when frame does not exist", () => {
54
+ container.removeChild(highlightFrame);
55
+
56
+ expect(() => {
57
+ clearHighlightFrame();
58
+ }).not.toThrow();
59
+ });
60
+
61
+ it("should not throw when tools wrapper does not exist", () => {
62
+ container.removeChild(toolsWrapper);
63
+
64
+ expect(() => {
65
+ clearHighlightFrame();
66
+ }).not.toThrow();
67
+ });
68
+
69
+ it("should not throw when neither exists", () => {
70
+ container.removeChild(highlightFrame);
71
+ container.removeChild(toolsWrapper);
72
+
73
+ expect(() => {
74
+ clearHighlightFrame();
75
+ }).not.toThrow();
76
+ });
77
+
78
+ it("should only remove highlight frame overlay elements", () => {
79
+ const otherElement = document.createElement("div");
80
+ otherElement.classList.add("other-element");
81
+ container.appendChild(otherElement);
82
+
83
+ clearHighlightFrame();
84
+
85
+ expect(container.querySelector(".other-element")).toBe(otherElement);
86
+ expect(container.querySelector(".highlight-frame-overlay")).toBeNull();
87
+ });
88
+ });
@@ -1,8 +1,7 @@
1
- import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
2
2
 
3
3
  export const clearHighlightFrame = (): void => {
4
- const canvasContainer = getCanvasContainer();
5
- const container = canvasContainer || document.body;
4
+ const container = getCanvasContainerOrBody();
6
5
 
7
6
  const frame = container.querySelector(".highlight-frame-overlay");
8
7
  if (frame) {
@@ -0,0 +1,150 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { createCornerHandles } from "./createCornerHandles";
3
+
4
+ describe("createCornerHandles", () => {
5
+ let group: SVGGElement;
6
+
7
+ beforeEach(() => {
8
+ group = document.createElementNS("http://www.w3.org/2000/svg", "g");
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ it("should create four corner handles", () => {
16
+ createCornerHandles(group, 200, 150);
17
+
18
+ const handles = group.querySelectorAll(".highlight-frame-handle");
19
+ expect(handles.length).toBe(4);
20
+ });
21
+
22
+ it("should create top-left handle", () => {
23
+ createCornerHandles(group, 200, 150);
24
+
25
+ const handle = group.querySelector(".handle-top-left");
26
+ expect(handle).not.toBeNull();
27
+ expect(handle?.getAttribute("x")).toBe("-3");
28
+ expect(handle?.getAttribute("y")).toBe("-3");
29
+ });
30
+
31
+ it("should create top-right handle", () => {
32
+ createCornerHandles(group, 200, 150);
33
+
34
+ const handle = group.querySelector(".handle-top-right");
35
+ expect(handle).not.toBeNull();
36
+ expect(handle?.getAttribute("x")).toBe("197");
37
+ expect(handle?.getAttribute("y")).toBe("-3");
38
+ });
39
+
40
+ it("should create bottom-right handle", () => {
41
+ createCornerHandles(group, 200, 150);
42
+
43
+ const handle = group.querySelector(".handle-bottom-right");
44
+ expect(handle).not.toBeNull();
45
+ expect(handle?.getAttribute("x")).toBe("197");
46
+ expect(handle?.getAttribute("y")).toBe("147");
47
+ });
48
+
49
+ it("should create bottom-left handle", () => {
50
+ createCornerHandles(group, 200, 150);
51
+
52
+ const handle = group.querySelector(".handle-bottom-left");
53
+ expect(handle).not.toBeNull();
54
+ expect(handle?.getAttribute("x")).toBe("-3");
55
+ expect(handle?.getAttribute("y")).toBe("147");
56
+ });
57
+
58
+ it("should set handle size to 6", () => {
59
+ createCornerHandles(group, 200, 150);
60
+
61
+ const handles = group.querySelectorAll(".highlight-frame-handle");
62
+ handles.forEach((handle) => {
63
+ expect(handle.getAttribute("width")).toBe("6");
64
+ expect(handle.getAttribute("height")).toBe("6");
65
+ });
66
+ });
67
+
68
+ it("should set vector-effect attribute", () => {
69
+ createCornerHandles(group, 200, 150);
70
+
71
+ const handles = group.querySelectorAll(".highlight-frame-handle");
72
+ handles.forEach((handle) => {
73
+ expect(handle.getAttribute("vector-effect")).toBe("non-scaling-stroke");
74
+ });
75
+ });
76
+
77
+ it("should apply instance color when isInstance is true", () => {
78
+ // Mock getComputedStyle
79
+ const originalGetComputedStyle = window.getComputedStyle;
80
+ window.getComputedStyle = vi.fn().mockReturnValue({
81
+ getPropertyValue: vi.fn((prop: string) => {
82
+ if (prop === "--component-color") return "rgb(255, 0, 0)";
83
+ return "";
84
+ }),
85
+ } as unknown as CSSStyleDeclaration);
86
+
87
+ createCornerHandles(group, 200, 150, true, false);
88
+
89
+ const handles = group.querySelectorAll(".highlight-frame-handle");
90
+ handles.forEach((handle) => {
91
+ expect(handle.getAttribute("stroke")).toBe("rgb(255, 0, 0)");
92
+ });
93
+
94
+ window.getComputedStyle = originalGetComputedStyle;
95
+ });
96
+
97
+ it("should apply text edit color when isTextEdit is true", () => {
98
+ const originalGetComputedStyle = window.getComputedStyle;
99
+ window.getComputedStyle = vi.fn().mockReturnValue({
100
+ getPropertyValue: vi.fn((prop: string) => {
101
+ if (prop === "--text-edit-color") return "rgb(0, 255, 0)";
102
+ return "";
103
+ }),
104
+ } as unknown as CSSStyleDeclaration);
105
+
106
+ createCornerHandles(group, 200, 150, false, true);
107
+
108
+ const handles = group.querySelectorAll(".highlight-frame-handle");
109
+ handles.forEach((handle) => {
110
+ expect(handle.getAttribute("stroke")).toBe("rgb(0, 255, 0)");
111
+ });
112
+
113
+ window.getComputedStyle = originalGetComputedStyle;
114
+ });
115
+
116
+ it("should not set stroke when neither flag is true", () => {
117
+ createCornerHandles(group, 200, 150, false, false);
118
+
119
+ const handles = group.querySelectorAll(".highlight-frame-handle");
120
+ handles.forEach((handle) => {
121
+ expect(handle.getAttribute("stroke")).toBeNull();
122
+ });
123
+ });
124
+
125
+ it("should handle zero dimensions", () => {
126
+ createCornerHandles(group, 0, 0);
127
+
128
+ const topLeft = group.querySelector(".handle-top-left");
129
+ expect(topLeft?.getAttribute("x")).toBe("-3");
130
+ expect(topLeft?.getAttribute("y")).toBe("-3");
131
+ });
132
+
133
+ it("should handle very small dimensions", () => {
134
+ createCornerHandles(group, 1, 1);
135
+
136
+ const topRight = group.querySelector(".handle-top-right");
137
+ expect(topRight?.getAttribute("x")).toBe("-2");
138
+ expect(topRight?.getAttribute("y")).toBe("-3");
139
+ });
140
+
141
+ it("should append handles to group", () => {
142
+ createCornerHandles(group, 200, 150);
143
+
144
+ const handles = group.querySelectorAll(".highlight-frame-handle");
145
+ handles.forEach((handle) => {
146
+ expect(handle.parentNode).toBe(group);
147
+ });
148
+ });
149
+ });
150
+
@@ -0,0 +1,237 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "@/lib/canvas/helpers/getCanvasContainerOrBody";
3
+ import * as getViewportDimensionsModule from "@/lib/helpers/getViewportDimensions";
4
+ import * as getScreenBoundsModule from "./helpers/getScreenBounds";
5
+ import * as createCornerHandlesModule from "./createCornerHandles";
6
+ import { createHighlightFrame } from "./createHighlightFrame";
7
+
8
+ vi.mock("@/lib/canvas/helpers/getCanvasContainerOrBody");
9
+ vi.mock("@/lib/helpers/getViewportDimensions");
10
+ vi.mock("./helpers/getScreenBounds");
11
+ vi.mock("./createCornerHandles");
12
+
13
+ describe("createHighlightFrame", () => {
14
+ let node: HTMLElement;
15
+ let container: HTMLElement;
16
+
17
+ beforeEach(() => {
18
+ node = document.createElement("div");
19
+ node.setAttribute("data-node-id", "test-node");
20
+ document.body.appendChild(node);
21
+
22
+ container = document.createElement("div");
23
+ document.body.appendChild(container);
24
+
25
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
26
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
27
+ width: 1920,
28
+ height: 1080,
29
+ });
30
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
31
+ top: 100,
32
+ left: 200,
33
+ width: 300,
34
+ height: 400,
35
+ });
36
+ vi.mocked(createCornerHandlesModule.createCornerHandles).mockImplementation(() => {});
37
+ });
38
+
39
+ afterEach(() => {
40
+ if (document.body.contains(node)) {
41
+ document.body.removeChild(node);
42
+ }
43
+ if (document.body.contains(container)) {
44
+ document.body.removeChild(container);
45
+ }
46
+ vi.clearAllMocks();
47
+ });
48
+
49
+ it("should create SVG overlay element", () => {
50
+ const result = createHighlightFrame(node);
51
+
52
+ expect(result).toBeInstanceOf(SVGSVGElement);
53
+ expect(result.classList.contains("highlight-frame-overlay")).toBe(true);
54
+ });
55
+
56
+ it("should set SVG attributes", () => {
57
+ const result = createHighlightFrame(node);
58
+
59
+ expect(result.getAttribute("width")).toBe("1920");
60
+ expect(result.getAttribute("height")).toBe("1080");
61
+ });
62
+
63
+ it("should set SVG styles", () => {
64
+ const result = createHighlightFrame(node);
65
+
66
+ expect(result.style.position).toBe("absolute");
67
+ expect(result.style.top).toBe("0px");
68
+ expect(result.style.left).toBe("0px");
69
+ expect(result.style.width).toBe("100vw");
70
+ expect(result.style.height).toBe("100vh");
71
+ expect(result.style.pointerEvents).toBe("none");
72
+ expect(result.style.zIndex).toBe("500");
73
+ });
74
+
75
+ it("should add is-instance class when isInstance is true", () => {
76
+ const result = createHighlightFrame(node, true, false);
77
+
78
+ expect(result.classList.contains("is-instance")).toBe(true);
79
+ });
80
+
81
+ it("should add is-text-edit class when isTextEdit is true", () => {
82
+ const result = createHighlightFrame(node, false, true);
83
+
84
+ expect(result.classList.contains("is-text-edit")).toBe(true);
85
+ });
86
+
87
+ it("should set data-node-id attribute", () => {
88
+ const result = createHighlightFrame(node);
89
+
90
+ expect(result.getAttribute("data-node-id")).toBe("test-node");
91
+ });
92
+
93
+ it("should handle missing data-node-id", () => {
94
+ node.removeAttribute("data-node-id");
95
+ const result = createHighlightFrame(node);
96
+
97
+ expect(result.getAttribute("data-node-id")).toBe("");
98
+ });
99
+
100
+ it("should create group element", () => {
101
+ const result = createHighlightFrame(node);
102
+
103
+ const group = result.querySelector(".highlight-frame-group");
104
+ expect(group).not.toBeNull();
105
+ expect(group?.tagName).toBe("g");
106
+ });
107
+
108
+ it("should set group transform", () => {
109
+ const result = createHighlightFrame(node);
110
+
111
+ const group = result.querySelector(".highlight-frame-group") as SVGGElement;
112
+ expect(group.getAttribute("transform")).toBe("translate(200, 100)");
113
+ });
114
+
115
+ it("should create rect element", () => {
116
+ const result = createHighlightFrame(node);
117
+
118
+ const rect = result.querySelector("rect");
119
+ expect(rect).not.toBeNull();
120
+ expect(rect?.classList.contains("highlight-frame-rect")).toBe(true);
121
+ });
122
+
123
+ it("should set rect dimensions", () => {
124
+ const result = createHighlightFrame(node);
125
+
126
+ const rect = result.querySelector("rect");
127
+ expect(rect?.getAttribute("width")).toBe("300");
128
+ expect(rect?.getAttribute("height")).toBe("400");
129
+ });
130
+
131
+ it("should ensure minimum width of 3", () => {
132
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
133
+ top: 100,
134
+ left: 200,
135
+ width: 1,
136
+ height: 400,
137
+ });
138
+
139
+ const result = createHighlightFrame(node);
140
+
141
+ const rect = result.querySelector("rect");
142
+ expect(rect?.getAttribute("width")).toBe("3");
143
+ });
144
+
145
+ it("should set rect vector-effect", () => {
146
+ const result = createHighlightFrame(node);
147
+
148
+ const rect = result.querySelector("rect");
149
+ expect(rect?.getAttribute("vector-effect")).toBe("non-scaling-stroke");
150
+ });
151
+
152
+ it("should apply instance color when isInstance is true", () => {
153
+ const originalGetComputedStyle = window.getComputedStyle;
154
+ window.getComputedStyle = vi.fn().mockReturnValue({
155
+ getPropertyValue: vi.fn((prop: string) => {
156
+ if (prop === "--component-color") return "rgb(255, 0, 0)";
157
+ return "";
158
+ }),
159
+ } as unknown as CSSStyleDeclaration);
160
+
161
+ const result = createHighlightFrame(node, true, false);
162
+
163
+ const rect = result.querySelector("rect");
164
+ expect(rect?.getAttribute("stroke")).toBe("rgb(255, 0, 0)");
165
+
166
+ window.getComputedStyle = originalGetComputedStyle;
167
+ });
168
+
169
+ it("should apply text edit color when isTextEdit is true", () => {
170
+ const originalGetComputedStyle = window.getComputedStyle;
171
+ window.getComputedStyle = vi.fn().mockReturnValue({
172
+ getPropertyValue: vi.fn((prop: string) => {
173
+ if (prop === "--text-edit-color") return "rgb(0, 255, 0)";
174
+ return "";
175
+ }),
176
+ } as unknown as CSSStyleDeclaration);
177
+
178
+ const result = createHighlightFrame(node, false, true);
179
+
180
+ const rect = result.querySelector("rect");
181
+ expect(rect?.getAttribute("stroke")).toBe("rgb(0, 255, 0)");
182
+
183
+ window.getComputedStyle = originalGetComputedStyle;
184
+ });
185
+
186
+ it("should not set stroke when neither flag is true", () => {
187
+ const result = createHighlightFrame(node, false, false);
188
+
189
+ const rect = result.querySelector("rect");
190
+ expect(rect?.getAttribute("stroke")).toBeNull();
191
+ });
192
+
193
+ it("should create corner handles", () => {
194
+ createHighlightFrame(node);
195
+
196
+ expect(createCornerHandlesModule.createCornerHandles).toHaveBeenCalledWith(
197
+ expect.objectContaining({ tagName: "g" }),
198
+ 300,
199
+ 400,
200
+ false,
201
+ false
202
+ );
203
+ });
204
+
205
+ it("should append SVG to container", () => {
206
+ const result = createHighlightFrame(node);
207
+
208
+ expect(result.parentNode).toBe(container);
209
+ });
210
+
211
+ it("should use viewport dimensions", () => {
212
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
213
+ width: 800,
214
+ height: 600,
215
+ });
216
+
217
+ const result = createHighlightFrame(node);
218
+
219
+ expect(result.getAttribute("width")).toBe("800");
220
+ expect(result.getAttribute("height")).toBe("600");
221
+ });
222
+
223
+ it("should handle zero width with minimum", () => {
224
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
225
+ top: 100,
226
+ left: 200,
227
+ width: 0,
228
+ height: 400,
229
+ });
230
+
231
+ const result = createHighlightFrame(node);
232
+
233
+ const rect = result.querySelector("rect");
234
+ expect(rect?.getAttribute("width")).toBe("3");
235
+ });
236
+ });
237
+
@@ -1,4 +1,5 @@
1
- import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
+ import { getCanvasContainerOrBody } from "@/lib/canvas/helpers/getCanvasContainerOrBody";
2
+ import { getViewportDimensions } from "@/lib/helpers/getViewportDimensions";
2
3
  import { createCornerHandles } from "./createCornerHandles";
3
4
  import { getScreenBounds } from "./helpers/getScreenBounds";
4
5
 
@@ -36,8 +37,7 @@ export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = fa
36
37
  svg.style.pointerEvents = "none";
37
38
  svg.style.zIndex = "500";
38
39
 
39
- const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
40
- const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
40
+ const { width: viewportWidth, height: viewportHeight } = getViewportDimensions();
41
41
  svg.setAttribute("width", viewportWidth.toString());
42
42
  svg.setAttribute("height", viewportHeight.toString());
43
43
 
@@ -65,12 +65,8 @@ export const createHighlightFrame = (node: HTMLElement, isInstance: boolean = fa
65
65
  createCornerHandles(group, minWidth, height, isInstance, isTextEdit);
66
66
 
67
67
  svg.appendChild(group);
68
- const canvasContainer = getCanvasContainer();
69
- if (canvasContainer) {
70
- canvasContainer.appendChild(svg);
71
- } else {
72
- document.body.appendChild(svg);
73
- }
68
+ const container = getCanvasContainerOrBody();
69
+ container.appendChild(svg);
74
70
 
75
71
  return svg as SVGSVGElement;
76
72
  };
@@ -0,0 +1,135 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { createTagLabel } from "./createTagLabel";
3
+
4
+ describe("createTagLabel", () => {
5
+ let node: HTMLElement;
6
+ let nodeTools: HTMLElement;
7
+
8
+ beforeEach(() => {
9
+ node = document.createElement("div");
10
+ nodeTools = document.createElement("div");
11
+ nodeTools.className = "node-tools";
12
+ document.body.appendChild(nodeTools);
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (document.body.contains(nodeTools)) {
17
+ document.body.removeChild(nodeTools);
18
+ }
19
+ });
20
+
21
+ it("should create tag label with div tag name", () => {
22
+ createTagLabel(node, nodeTools);
23
+
24
+ const tagLabel = nodeTools.querySelector(".tag-label");
25
+ expect(tagLabel).not.toBeNull();
26
+ expect(tagLabel?.textContent).toBe("Container");
27
+ });
28
+
29
+ it("should use instance name when available", () => {
30
+ node.setAttribute("data-instance-name", "CustomComponent");
31
+
32
+ createTagLabel(node, nodeTools);
33
+
34
+ const tagLabel = nodeTools.querySelector(".tag-label");
35
+ expect(tagLabel?.textContent).toBe("CustomComponent");
36
+ });
37
+
38
+ it("should capitalize instance name", () => {
39
+ node.setAttribute("data-instance-name", "customComponent");
40
+
41
+ createTagLabel(node, nodeTools);
42
+
43
+ const tagLabel = nodeTools.querySelector(".tag-label");
44
+ expect(tagLabel?.textContent).toBe("CustomComponent");
45
+ });
46
+
47
+ it("should map h1 to Heading 1", () => {
48
+ const h1 = document.createElement("h1");
49
+ createTagLabel(h1, nodeTools);
50
+
51
+ const tagLabel = nodeTools.querySelector(".tag-label");
52
+ expect(tagLabel?.textContent).toBe("Heading 1");
53
+ });
54
+
55
+ it("should map h2 to Heading 2", () => {
56
+ const h2 = document.createElement("h2");
57
+ createTagLabel(h2, nodeTools);
58
+
59
+ const tagLabel = nodeTools.querySelector(".tag-label");
60
+ expect(tagLabel?.textContent).toBe("Heading 2");
61
+ });
62
+
63
+ it("should map p to Text", () => {
64
+ const p = document.createElement("p");
65
+ createTagLabel(p, nodeTools);
66
+
67
+ const tagLabel = nodeTools.querySelector(".tag-label");
68
+ expect(tagLabel?.textContent).toBe("Text");
69
+ });
70
+
71
+ it("should map img to Image", () => {
72
+ const img = document.createElement("img");
73
+ createTagLabel(img, nodeTools);
74
+
75
+ const tagLabel = nodeTools.querySelector(".tag-label");
76
+ expect(tagLabel?.textContent).toBe("Image");
77
+ });
78
+
79
+ it("should map a to Link", () => {
80
+ const a = document.createElement("a");
81
+ createTagLabel(a, nodeTools);
82
+
83
+ const tagLabel = nodeTools.querySelector(".tag-label");
84
+ expect(tagLabel?.textContent).toBe("Link");
85
+ });
86
+
87
+ it("should use tag name for unmapped elements", () => {
88
+ const span = document.createElement("span");
89
+ createTagLabel(span, nodeTools);
90
+
91
+ const tagLabel = nodeTools.querySelector(".tag-label");
92
+ expect(tagLabel?.textContent).toBe("Span");
93
+ });
94
+
95
+ it("should capitalize unmapped tag names", () => {
96
+ const custom = document.createElement("custom-element");
97
+ createTagLabel(custom, nodeTools);
98
+
99
+ const tagLabel = nodeTools.querySelector(".tag-label");
100
+ expect(tagLabel?.textContent).toBe("Custom-element");
101
+ });
102
+
103
+ it("should append label to nodeTools", () => {
104
+ createTagLabel(node, nodeTools);
105
+
106
+ const tagLabel = nodeTools.querySelector(".tag-label");
107
+ expect(tagLabel?.parentNode).toBe(nodeTools);
108
+ });
109
+
110
+ it("should create label with correct class", () => {
111
+ createTagLabel(node, nodeTools);
112
+
113
+ const tagLabel = nodeTools.querySelector(".tag-label");
114
+ expect(tagLabel?.className).toBe("tag-label");
115
+ });
116
+
117
+ it("should prioritize instance name over tag name", () => {
118
+ node.setAttribute("data-instance-name", "MyComponent");
119
+
120
+ createTagLabel(node, nodeTools);
121
+
122
+ const tagLabel = nodeTools.querySelector(".tag-label");
123
+ expect(tagLabel?.textContent).toBe("MyComponent");
124
+ });
125
+
126
+ it("should handle empty instance name", () => {
127
+ node.setAttribute("data-instance-name", "");
128
+
129
+ createTagLabel(node, nodeTools);
130
+
131
+ const tagLabel = nodeTools.querySelector(".tag-label");
132
+ expect(tagLabel?.textContent).toBe("Container");
133
+ });
134
+ });
135
+