@node-edit-utils/core 2.2.6 → 2.2.8

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 (34) hide show
  1. package/dist/lib/node-tools/events/click/handleNodeClick.d.ts +2 -1
  2. package/dist/lib/node-tools/events/setupEventListener.d.ts +2 -1
  3. package/dist/lib/node-tools/highlight/createCornerHandles.d.ts +1 -1
  4. package/dist/lib/node-tools/highlight/createHighlightFrame.d.ts +1 -1
  5. package/dist/lib/node-tools/highlight/createToolsContainer.d.ts +1 -1
  6. package/dist/lib/node-tools/select/selectNode.d.ts +2 -1
  7. package/dist/lib/node-tools/text/helpers/enterTextEditMode.d.ts +2 -0
  8. package/dist/lib/node-tools/text/helpers/handleTextChange.d.ts +1 -0
  9. package/dist/lib/node-tools/text/helpers/shouldEnterTextEditMode.d.ts +1 -0
  10. package/dist/node-edit-utils.cjs.js +227 -90
  11. package/dist/node-edit-utils.esm.js +227 -90
  12. package/dist/node-edit-utils.umd.js +227 -90
  13. package/dist/node-edit-utils.umd.min.js +1 -1
  14. package/dist/styles.css +1 -1
  15. package/package.json +4 -2
  16. package/src/index.ts +0 -2
  17. package/src/lib/node-tools/createNodeTools.ts +3 -16
  18. package/src/lib/node-tools/events/click/handleNodeClick.ts +3 -2
  19. package/src/lib/node-tools/events/setupEventListener.ts +3 -2
  20. package/src/lib/node-tools/highlight/clearHighlightFrame.ts +7 -2
  21. package/src/lib/node-tools/highlight/createCornerHandles.ts +12 -6
  22. package/src/lib/node-tools/highlight/createHighlightFrame.ts +25 -7
  23. package/src/lib/node-tools/highlight/createTagLabel.ts +25 -1
  24. package/src/lib/node-tools/highlight/createToolsContainer.ts +4 -1
  25. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.ts +5 -1
  26. package/src/lib/node-tools/highlight/highlightNode.ts +21 -11
  27. package/src/lib/node-tools/highlight/refreshHighlightFrame.ts +40 -7
  28. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.ts +6 -1
  29. package/src/lib/node-tools/select/selectNode.ts +24 -5
  30. package/src/lib/node-tools/text/events/setupMutationObserver.ts +17 -3
  31. package/src/lib/node-tools/text/helpers/enterTextEditMode.ts +9 -0
  32. package/src/lib/node-tools/text/helpers/handleTextChange.ts +27 -0
  33. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.ts +9 -0
  34. package/src/lib/styles/styles.css +28 -8
@@ -1,15 +1,20 @@
1
+ import { getCanvasContainer } from "@/lib/canvas/helpers/getCanvasContainer";
1
2
  import { getHighlightFrameElement } from "./helpers/getHighlightFrameElement";
2
3
 
3
4
  export const updateHighlightFrameVisibility = (node: HTMLElement): void => {
4
5
  const frame = getHighlightFrameElement();
5
6
  if (!frame) return;
6
7
 
8
+ // Batch DOM reads
7
9
  const hasHiddenClass = node.classList.contains("hidden") || node.classList.contains("select-none");
8
10
  const displayValue = hasHiddenClass ? "none" : "";
9
11
 
12
+ // Batch DOM writes
10
13
  frame.style.display = displayValue;
11
14
 
12
- const toolsWrapper = document.body.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
15
+ const canvasContainer = getCanvasContainer();
16
+ const container = canvasContainer || document.body;
17
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper") as HTMLElement | null;
13
18
  if (toolsWrapper) {
14
19
  toolsWrapper.style.display = displayValue;
15
20
  }
@@ -1,3 +1,5 @@
1
+ import { enterTextEditMode } from "../text/helpers/enterTextEditMode";
2
+ import type { NodeText } from "../text/types";
1
3
  import { IGNORED_DOM_ELEMENTS } from "./constants";
2
4
  import { getElementsFromPoint } from "./helpers/getElementsFromPoint";
3
5
  import { isInsideComponent } from "./helpers/isInsideComponent";
@@ -6,7 +8,9 @@ import { targetSameCandidates } from "./helpers/targetSameCandidates";
6
8
  let candidateCache: Element[] = [];
7
9
  let attempt = 0;
8
10
 
9
- export const selectNode = (event: MouseEvent, editableNode: HTMLElement | null): HTMLElement | null => {
11
+ let lastSelectedNode: HTMLElement | null = null;
12
+
13
+ export const selectNode = (event: MouseEvent, nodeProvider: HTMLElement | null, text: NodeText): HTMLElement | null => {
10
14
  let selectedNode: HTMLElement | null = null;
11
15
 
12
16
  const clickX = event.clientX;
@@ -21,19 +25,29 @@ export const selectNode = (event: MouseEvent, editableNode: HTMLElement | null):
21
25
  !isInsideComponent(element)
22
26
  );
23
27
 
28
+ const editableNode = text.getEditableNode();
24
29
  if (editableNode && candidates.includes(editableNode)) {
25
- return editableNode;
30
+ selectedNode = editableNode;
31
+ lastSelectedNode = selectedNode;
32
+
33
+ return selectedNode;
26
34
  }
27
35
 
28
36
  if (clickThrough) {
29
37
  candidateCache = [];
30
-
31
38
  selectedNode = candidates[0] as HTMLElement;
39
+
40
+ if (lastSelectedNode && lastSelectedNode === selectedNode) {
41
+ enterTextEditMode(selectedNode, nodeProvider, text);
42
+ }
43
+
44
+ lastSelectedNode = selectedNode;
45
+
32
46
  return selectedNode;
33
47
  }
34
48
 
35
49
  if (targetSameCandidates(candidateCache, candidates)) {
36
- attempt <= candidates.length && attempt++;
50
+ attempt <= candidates.length - 2 && attempt++;
37
51
  } else {
38
52
  attempt = 0;
39
53
  }
@@ -41,8 +55,13 @@ export const selectNode = (event: MouseEvent, editableNode: HTMLElement | null):
41
55
  const nodeIndex = candidates.length - 1 - attempt;
42
56
 
43
57
  selectedNode = candidates[nodeIndex] as HTMLElement;
44
-
45
58
  candidateCache = candidates;
46
59
 
60
+ if (lastSelectedNode && lastSelectedNode === selectedNode) {
61
+ enterTextEditMode(selectedNode, nodeProvider, text);
62
+ }
63
+
64
+ lastSelectedNode = selectedNode;
65
+
47
66
  return selectedNode;
48
67
  };
@@ -1,10 +1,24 @@
1
1
  import { connectMutationObserver } from "../../../helpers/observer/connectMutationObserver";
2
+ import { withRAFThrottle } from "../../../helpers/withRAF";
2
3
  import { refreshHighlightFrame } from "../../highlight/refreshHighlightFrame";
4
+ import { handleTextChange } from "../helpers/handleTextChange";
3
5
 
4
- export const setupMutationObserver = (node: HTMLElement, nodeProvider: HTMLElement, canvasName: string = "canvas"): (() => void) | undefined => {
5
- const mutationObserver = connectMutationObserver(node, () => {
6
+ export const setupMutationObserver = (
7
+ node: HTMLElement,
8
+ nodeProvider: HTMLElement,
9
+ canvasName: string = "canvas"
10
+ ): (() => void) | undefined => {
11
+ const throttledHandleTextChange = withRAFThrottle((mutations: MutationRecord[]) => {
12
+ handleTextChange(node, mutations);
13
+ });
14
+
15
+ const mutationObserver = connectMutationObserver(node, (mutations) => {
16
+ throttledHandleTextChange(mutations);
6
17
  refreshHighlightFrame(node, nodeProvider, canvasName);
7
18
  });
8
19
 
9
- return () => mutationObserver.disconnect();
20
+ return () => {
21
+ mutationObserver.disconnect();
22
+ throttledHandleTextChange.cleanup();
23
+ };
10
24
  };
@@ -0,0 +1,9 @@
1
+ import type { NodeText } from "../types";
2
+
3
+ export const enterTextEditMode = (node: HTMLElement, nodeProvider: HTMLElement | null, text: NodeText): void => {
4
+ if (!node || !nodeProvider) {
5
+ return;
6
+ }
7
+
8
+ text.enableEditMode(node, nodeProvider);
9
+ };
@@ -0,0 +1,27 @@
1
+ import { sendPostMessage } from "@/lib/post-message/sendPostMessage";
2
+
3
+ export const handleTextChange = (node: HTMLElement, mutations: MutationRecord[]): void => {
4
+ // Check if any mutation is a text content change
5
+ const hasTextChange = mutations.some((mutation) => {
6
+ return (
7
+ mutation.type === "characterData" ||
8
+ (mutation.type === "childList" && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0))
9
+ );
10
+ });
11
+
12
+ if (!hasTextChange) {
13
+ return;
14
+ }
15
+
16
+ // Get the text content of the node
17
+ const textContent = node.textContent ?? "";
18
+
19
+ // Get the node ID
20
+ const nodeId = node.getAttribute("data-node-id");
21
+
22
+ // Send postMessage with the text change
23
+ sendPostMessage("textContentChanged", {
24
+ nodeId,
25
+ textContent,
26
+ });
27
+ };
@@ -0,0 +1,9 @@
1
+ import { hasTextContent } from "./hasTextContent";
2
+
3
+ export const shouldEnterTextEditMode = (node: HTMLElement | null): boolean => {
4
+ if (!node) {
5
+ return false;
6
+ }
7
+
8
+ return hasTextContent(node);
9
+ };
@@ -2,6 +2,8 @@
2
2
  --primary-color: oklch(0.6235 0.22 294);
3
3
  --uncode-color: oklch(45.7% 0.24 277.023);
4
4
  --component-color: oklch(65.6% 0.241 354.308);
5
+ --text-edit-color: oklch(62.3% 0.214 259.815);
6
+ --text-edit-selection-color: oklch(62.3% 0.214 259.815 / 0.2);
5
7
 
6
8
  --handle-color: oklch(55.2% 0.016 285.938);
7
9
  --handle-color-transparent: oklch(55.2% 0.016 285.938 / 0.7);
@@ -19,7 +21,7 @@
19
21
  --transition-medium: 0.2s ease-in-out;
20
22
 
21
23
  --z-index-high: 10000;
22
- --z-index-highlight: 5000;
24
+ --z-index-highlight: 500;
23
25
  --z-index-medium: 1000;
24
26
 
25
27
  --letter-spacing: 0.02em;
@@ -54,16 +56,19 @@
54
56
  padding-top: var(--spacing-2xs);
55
57
  display: flex;
56
58
  justify-content: center;
59
+ contain: layout style;
57
60
  }
58
61
 
59
62
  .highlight-frame-overlay {
60
- position: fixed;
63
+ position: absolute;
61
64
  inset: 0;
62
65
  width: 100vw;
63
66
  height: 100vh;
64
67
  pointer-events: none;
65
68
  z-index: var(--z-index-highlight);
66
69
  overflow: visible;
70
+ contain: layout style paint;
71
+ will-change: transform;
67
72
  }
68
73
 
69
74
  .highlight-frame-rect {
@@ -76,6 +81,10 @@
76
81
  stroke: var(--component-color);
77
82
  }
78
83
 
84
+ .highlight-frame-overlay.is-text-edit .highlight-frame-rect {
85
+ stroke: var(--text-edit-color);
86
+ }
87
+
79
88
  .highlight-frame-handle {
80
89
  fill: white;
81
90
  stroke: var(--primary-color);
@@ -88,6 +97,10 @@
88
97
  stroke: var(--component-color);
89
98
  }
90
99
 
100
+ .highlight-frame-overlay.is-text-edit .highlight-frame-handle {
101
+ stroke: var(--text-edit-color);
102
+ }
103
+
91
104
  .highlight-frame-handle.handle-top-left {
92
105
  cursor: nwse-resize;
93
106
  }
@@ -105,7 +118,9 @@
105
118
  }
106
119
 
107
120
  .highlight-frame-tools-wrapper {
108
- position: fixed;
121
+ position: absolute;
122
+ left: 0;
123
+ top: 0;
109
124
  pointer-events: none;
110
125
  z-index: var(--z-index-highlight);
111
126
  contain: layout style;
@@ -116,14 +131,14 @@
116
131
  color: var(--text-color-white);
117
132
  font-size: 0.575rem;
118
133
  font-weight: 500;
119
- text-transform: uppercase;
120
134
  height: 1rem;
121
135
  padding: 0.5625rem var(--spacing-sm);
122
- line-height: 1;
136
+ line-height: 1.4;
123
137
  border-radius: var(--spacing-sm);
124
138
  display: flex;
125
139
  align-items: center;
126
140
  justify-content: center;
141
+ white-space: nowrap;
127
142
  }
128
143
 
129
144
  .highlight-frame-tools-wrapper.is-instance .tag-label,
@@ -131,6 +146,11 @@
131
146
  background-color: var(--component-color);
132
147
  }
133
148
 
149
+ .highlight-frame-tools-wrapper.is-text-edit .tag-label,
150
+ .node-tools.is-text-edit .tag-label {
151
+ background-color: var(--text-edit-color);
152
+ }
153
+
134
154
  .viewport {
135
155
  position: relative;
136
156
  width: var(--container-width);
@@ -228,14 +248,14 @@
228
248
  user-select: text;
229
249
 
230
250
  &::selection {
231
- background: var(--primary-color-selection);
251
+ background: var(--text-edit-selection-color);
232
252
  }
233
253
 
234
254
  &::-moz-selection {
235
- background: var(--primary-color-selection);
255
+ background: var(--text-edit-selection-color);
236
256
  }
237
257
 
238
258
  &::-webkit-selection {
239
- background: var(--primary-color-selection);
259
+ background: var(--text-edit-selection-color);
240
260
  }
241
261
  }