@base44/vite-plugin 0.2.28 → 0.2.30

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 (81) hide show
  1. package/dist/capabilities/inline-edit/controller.d.ts.map +1 -1
  2. package/dist/capabilities/inline-edit/controller.js +3 -0
  3. package/dist/capabilities/inline-edit/controller.js.map +1 -1
  4. package/dist/capabilities/inline-edit/index.d.ts +1 -1
  5. package/dist/capabilities/inline-edit/index.d.ts.map +1 -1
  6. package/dist/capabilities/inline-edit/types.d.ts +4 -0
  7. package/dist/capabilities/inline-edit/types.d.ts.map +1 -1
  8. package/dist/consts.d.ts +11 -0
  9. package/dist/consts.d.ts.map +1 -0
  10. package/dist/consts.js +11 -0
  11. package/dist/consts.js.map +1 -0
  12. package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -1
  13. package/dist/injections/layer-dropdown/dropdown-ui.js +3 -0
  14. package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -1
  15. package/dist/injections/utils.d.ts +31 -0
  16. package/dist/injections/utils.d.ts.map +1 -1
  17. package/dist/injections/utils.js +97 -0
  18. package/dist/injections/utils.js.map +1 -1
  19. package/dist/injections/visual-edit-agent.d.ts.map +1 -1
  20. package/dist/injections/visual-edit-agent.js +58 -51
  21. package/dist/injections/visual-edit-agent.js.map +1 -1
  22. package/dist/jsx-processor.d.ts +3 -1
  23. package/dist/jsx-processor.d.ts.map +1 -1
  24. package/dist/jsx-processor.js +29 -6
  25. package/dist/jsx-processor.js.map +1 -1
  26. package/dist/jsx-utils.d.ts +9 -0
  27. package/dist/jsx-utils.d.ts.map +1 -1
  28. package/dist/jsx-utils.js +86 -0
  29. package/dist/jsx-utils.js.map +1 -1
  30. package/dist/processors/collection-id-processor.d.ts +20 -0
  31. package/dist/processors/collection-id-processor.d.ts.map +1 -0
  32. package/dist/processors/collection-id-processor.js +182 -0
  33. package/dist/processors/collection-id-processor.js.map +1 -0
  34. package/dist/processors/collection-item-field-processor.d.ts +39 -0
  35. package/dist/processors/collection-item-field-processor.d.ts.map +1 -0
  36. package/dist/processors/collection-item-field-processor.js +281 -0
  37. package/dist/processors/collection-item-field-processor.js.map +1 -0
  38. package/dist/processors/collection-item-id-processor.d.ts +12 -0
  39. package/dist/processors/collection-item-id-processor.d.ts.map +1 -0
  40. package/dist/processors/collection-item-id-processor.js +50 -0
  41. package/dist/processors/collection-item-id-processor.js.map +1 -0
  42. package/dist/processors/static-array-processor.d.ts +0 -3
  43. package/dist/processors/static-array-processor.d.ts.map +1 -1
  44. package/dist/processors/static-array-processor.js +2 -4
  45. package/dist/processors/static-array-processor.js.map +1 -1
  46. package/dist/processors/utils/collection-tracing-utils.d.ts +36 -0
  47. package/dist/processors/utils/collection-tracing-utils.d.ts.map +1 -0
  48. package/dist/processors/utils/collection-tracing-utils.js +390 -0
  49. package/dist/processors/utils/collection-tracing-utils.js.map +1 -0
  50. package/dist/processors/utils/shared-utils.d.ts +96 -0
  51. package/dist/processors/utils/shared-utils.d.ts.map +1 -0
  52. package/dist/processors/utils/shared-utils.js +600 -0
  53. package/dist/processors/utils/shared-utils.js.map +1 -0
  54. package/dist/statics/index.mjs +12 -2
  55. package/dist/statics/index.mjs.map +1 -1
  56. package/dist/visual-edit-plugin.d.ts +0 -1
  57. package/dist/visual-edit-plugin.d.ts.map +1 -1
  58. package/dist/visual-edit-plugin.js +29 -178
  59. package/dist/visual-edit-plugin.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/capabilities/inline-edit/controller.ts +3 -0
  62. package/src/capabilities/inline-edit/index.ts +1 -1
  63. package/src/capabilities/inline-edit/types.ts +5 -0
  64. package/src/consts.ts +11 -0
  65. package/src/injections/layer-dropdown/dropdown-ui.ts +3 -0
  66. package/src/injections/utils.ts +105 -0
  67. package/src/injections/visual-edit-agent.ts +56 -64
  68. package/src/jsx-processor.ts +36 -14
  69. package/src/jsx-utils.ts +116 -0
  70. package/src/processors/collection-id-processor.ts +261 -0
  71. package/src/processors/collection-item-field-processor.ts +433 -0
  72. package/src/processors/collection-item-id-processor.ts +69 -0
  73. package/src/processors/static-array-processor.ts +7 -4
  74. package/src/processors/utils/collection-tracing-utils.ts +507 -0
  75. package/src/processors/utils/shared-utils.ts +785 -0
  76. package/src/visual-edit-plugin.ts +34 -215
  77. package/dist/processors/shared-utils.d.ts +0 -19
  78. package/dist/processors/shared-utils.d.ts.map +0 -1
  79. package/dist/processors/shared-utils.js +0 -77
  80. package/dist/processors/shared-utils.js.map +0 -1
  81. package/src/processors/shared-utils.ts +0 -116
@@ -2,175 +2,27 @@ import { parse } from "@babel/parser";
2
2
  import { default as traverse } from "@babel/traverse";
3
3
  import { default as generate } from "@babel/generator";
4
4
  import * as t from "@babel/types";
5
- import { StaticArrayProcessor } from "./processors/static-array-processor.js";
5
+ import { JSXProcessor } from "./jsx-processor.js";
6
6
  import { JSXUtils } from "./jsx-utils.js";
7
- // Helper function to check if JSX element contains dynamic content
8
- export function checkIfElementHasDynamicContent(jsxElement) {
9
- let hasDynamicContent = false;
10
- // Helper function to check if any node contains dynamic patterns
11
- function checkNodeForDynamicContent(node) {
12
- // JSX expressions like {variable}, {func()}, {obj.prop}
13
- if (t.isJSXExpressionContainer(node)) {
14
- const expression = node.expression;
15
- // Skip empty expressions {}
16
- if (t.isJSXEmptyExpression(expression)) {
17
- return false;
18
- }
19
- // Any non-literal expression is considered dynamic
20
- if (!t.isLiteral(expression)) {
21
- return true;
22
- }
23
- }
24
- // Template literals with expressions `Hello ${name}`
25
- if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
26
- return true;
27
- }
28
- // Member expressions like props.title, state.value
29
- if (t.isMemberExpression(node)) {
30
- return true;
31
- }
32
- // Function calls like getData(), format()
33
- if (t.isCallExpression(node)) {
34
- return true;
35
- }
36
- // Conditional expressions like condition ? "yes" : "no"
37
- if (t.isConditionalExpression(node)) {
38
- return true;
39
- }
40
- // Identifier references (could be props, state, variables)
41
- if (t.isIdentifier(node)) {
42
- // Common dynamic identifiers
43
- const dynamicNames = [
44
- "props",
45
- "state",
46
- "data",
47
- "item",
48
- "value",
49
- "text",
50
- "content",
51
- ];
52
- if (dynamicNames.some((name) => node.name.includes(name))) {
53
- return true;
54
- }
55
- }
56
- return false;
57
- }
58
- // Recursively traverse all child nodes
59
- function traverseNode(node) {
60
- if (checkNodeForDynamicContent(node)) {
61
- hasDynamicContent = true;
62
- return;
63
- }
64
- // Recursively check child nodes
65
- Object.keys(node).forEach((key) => {
66
- const value = node[key];
67
- if (Array.isArray(value)) {
68
- value.forEach((child) => {
69
- if (child && typeof child === "object" && child.type) {
70
- traverseNode(child);
71
- }
72
- });
73
- }
74
- else if (value && typeof value === "object" && value.type) {
75
- traverseNode(value);
76
- }
77
- });
78
- }
79
- // Check the element's own attributes for dynamic content
80
- const attributes = jsxElement.openingElement?.attributes || [];
81
- attributes.forEach((attr) => {
82
- if (hasDynamicContent)
83
- return; // Early exit if already found dynamic content
84
- // Spread attributes like {...props} are always dynamic
85
- if (t.isJSXSpreadAttribute(attr)) {
86
- hasDynamicContent = true;
87
- return;
88
- }
89
- // Check attribute values for dynamic expressions
90
- if (t.isJSXAttribute(attr) && attr.value) {
91
- traverseNode(attr.value);
92
- }
93
- });
94
- // Check all children of the JSX element
95
- jsxElement.children.forEach((child) => {
96
- if (hasDynamicContent)
97
- return; // Early exit if already found dynamic content
98
- traverseNode(child);
99
- });
100
- return hasDynamicContent;
101
- }
102
7
  export function visualEditPlugin() {
103
8
  return {
104
9
  name: "visual-edit-transform",
105
10
  apply: (config) => config.mode === "development",
106
11
  enforce: "pre",
107
12
  order: "pre",
108
- // Inject Tailwind CDN for visual editing capabilities
109
13
  transformIndexHtml(html) {
110
- // Inject the Tailwind CSS CDN script right before the closing </head> tag
111
14
  const tailwindScript = ` <!-- Tailwind CSS CDN for visual editing -->\n <script src="https://cdn.tailwindcss.com"></script>\n `;
112
15
  return html.replace("</head>", tailwindScript + "</head>");
113
16
  },
114
17
  transform(code, id) {
115
- // Skip node_modules and visual-edit-agent itself
116
18
  if (id.includes("node_modules") || id.includes("visual-edit-agent")) {
117
19
  return null;
118
20
  }
119
- // Process JS/JSX/TS/TSX files
120
21
  if (!id.match(/\.(jsx?|tsx?)$/)) {
121
22
  return null;
122
23
  }
123
- // Extract filename from path, preserving pages/ or components/ structure
124
- const pathParts = id.split("/");
125
- let filename;
126
- // Check if this is a pages or components file
127
- if (id.includes("/pages/")) {
128
- const pagesIndex = pathParts.findIndex((part) => part === "pages");
129
- if (pagesIndex >= 0 && pagesIndex < pathParts.length - 1) {
130
- // Get all parts from 'pages' to the file, preserving nested structure
131
- const relevantParts = pathParts.slice(pagesIndex, pathParts.length);
132
- const lastPart = relevantParts[relevantParts.length - 1];
133
- // Remove file extension from the last part
134
- relevantParts[relevantParts.length - 1] = lastPart.includes(".")
135
- ? lastPart.split(".")[0]
136
- : lastPart;
137
- filename = relevantParts.join("/");
138
- }
139
- else {
140
- filename = pathParts[pathParts.length - 1];
141
- if (filename.includes(".")) {
142
- filename = filename.split(".")[0];
143
- }
144
- }
145
- }
146
- else if (id.includes("/components/")) {
147
- const componentsIndex = pathParts.findIndex((part) => part === "components");
148
- if (componentsIndex >= 0 && componentsIndex < pathParts.length - 1) {
149
- // Get all parts from 'components' to the file, preserving nested structure
150
- const relevantParts = pathParts.slice(componentsIndex, pathParts.length);
151
- const lastPart = relevantParts[relevantParts.length - 1];
152
- // Remove file extension from the last part
153
- relevantParts[relevantParts.length - 1] = lastPart.includes(".")
154
- ? lastPart.split(".")[0]
155
- : lastPart;
156
- filename = relevantParts.join("/");
157
- }
158
- else {
159
- filename = pathParts[pathParts.length - 1];
160
- if (filename.includes(".")) {
161
- filename = filename.split(".")[0];
162
- }
163
- }
164
- }
165
- else {
166
- // For other files (like layout), just use the filename
167
- filename = pathParts[pathParts.length - 1];
168
- if (filename.includes(".")) {
169
- filename = filename.split(".")[0];
170
- }
171
- }
24
+ const filename = extractFilename(id);
172
25
  try {
173
- // Parse the code into an AST
174
26
  const ast = parse(code, {
175
27
  sourceType: "module",
176
28
  plugins: [
@@ -191,41 +43,16 @@ export function visualEditPlugin() {
191
43
  "throwExpressions",
192
44
  ],
193
45
  });
194
- // Traverse the AST and add source location and dynamic content attributes to JSX elements
195
46
  JSXUtils.init(t);
196
- const staticArrayProcessor = new StaticArrayProcessor(t);
197
- let elementsProcessed = 0;
47
+ const processor = new JSXProcessor(t, filename);
198
48
  traverse.default(ast, {
199
49
  JSXElement(path) {
200
50
  const jsxElement = path.node;
201
- const openingElement = jsxElement.openingElement;
202
- // Skip fragments
203
51
  if (t.isJSXFragment(jsxElement))
204
52
  return;
205
- // Skip if already has source location attribute
206
- const hasSourceLocation = openingElement.attributes.some((attr) => t.isJSXAttribute(attr) &&
207
- t.isJSXIdentifier(attr.name) &&
208
- attr.name.name === "data-source-location");
209
- if (hasSourceLocation)
210
- return;
211
- // Get line and column from AST node location
212
- const { line, column } = openingElement.loc?.start || {
213
- line: 1,
214
- column: 0,
215
- };
216
- // Create the source location attribute
217
- const sourceLocationAttr = t.jsxAttribute(t.jsxIdentifier("data-source-location"), t.stringLiteral(`${filename}:${line}:${column}`));
218
- // Check if element has dynamic content
219
- const isDynamic = checkIfElementHasDynamicContent(jsxElement);
220
- // Create the dynamic content attribute
221
- const dynamicContentAttr = t.jsxAttribute(t.jsxIdentifier("data-dynamic-content"), t.stringLiteral(isDynamic ? "true" : "false"));
222
- // Add both attributes to the beginning of the attributes array
223
- openingElement.attributes.unshift(sourceLocationAttr, dynamicContentAttr);
224
- staticArrayProcessor.process(path.get("openingElement"));
225
- elementsProcessed++;
53
+ processor.processJSXElement(path.get("openingElement"));
226
54
  },
227
55
  });
228
- // Generate the code back from the AST
229
56
  const result = generate.default(ast, {
230
57
  compact: false,
231
58
  concise: false,
@@ -239,11 +66,35 @@ export function visualEditPlugin() {
239
66
  catch (error) {
240
67
  console.error("Failed to add source location to JSX:", error);
241
68
  return {
242
- code: code, // Return original code on failure
69
+ code: code,
243
70
  map: null,
244
71
  };
245
72
  }
246
73
  },
247
74
  };
248
75
  }
76
+ function extractFilename(id) {
77
+ const pathParts = id.split("/");
78
+ const segmentIndex = findSegmentIndex(pathParts, ["pages", "components"]);
79
+ if (segmentIndex >= 0 && segmentIndex < pathParts.length - 1) {
80
+ const relevantParts = pathParts.slice(segmentIndex);
81
+ const last = relevantParts[relevantParts.length - 1];
82
+ relevantParts[relevantParts.length - 1] = stripExtension(last ?? "");
83
+ return relevantParts.join("/");
84
+ }
85
+ const lastPart = pathParts[pathParts.length - 1] ?? "";
86
+ return stripExtension(lastPart);
87
+ }
88
+ function findSegmentIndex(parts, segments) {
89
+ for (const segment of segments) {
90
+ const idx = parts.findIndex((part) => part === segment);
91
+ if (idx >= 0)
92
+ return idx;
93
+ }
94
+ return -1;
95
+ }
96
+ function stripExtension(filename) {
97
+ const dotIndex = filename.indexOf(".");
98
+ return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
99
+ }
249
100
  //# sourceMappingURL=visual-edit-plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/vite-plugin",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "The Vite plugin for base44 based applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,6 +6,7 @@ import {
6
6
  shouldEnterInlineEditingMode,
7
7
  isStaticArrayTextElement,
8
8
  } from "./dom-utils.js";
9
+ import { PLUGIN_ELEMENT_ATTR } from "../../injections/utils.js";
9
10
 
10
11
  const DEBOUNCE_MS = 500;
11
12
 
@@ -98,6 +99,7 @@ export function createInlineEditController(
98
99
  element.dataset.originalTextContent = element.textContent || "";
99
100
  element.dataset.originalCursor = element.style.cursor;
100
101
  element.contentEditable = "true";
102
+ element.setAttribute(PLUGIN_ELEMENT_ATTR, "true");
101
103
 
102
104
  const abortController = new AbortController();
103
105
  listenerAbortControllers.set(element, abortController);
@@ -125,6 +127,7 @@ export function createInlineEditController(
125
127
 
126
128
  removeFocusOutlineCSS();
127
129
  element.contentEditable = "false";
130
+ element.removeAttribute(PLUGIN_ELEMENT_ATTR);
128
131
  delete element.dataset.originalTextContent;
129
132
 
130
133
  if (element.dataset.originalCursor !== undefined) {
@@ -1,2 +1,2 @@
1
1
  export { createInlineEditController } from "./controller.js";
2
- export type { InlineEditController, InlineEditHost } from "./types.js";
2
+ export type { CollectionInfo, InlineEditController, InlineEditHost } from "./types.js";
@@ -1,3 +1,8 @@
1
+ export interface CollectionInfo {
2
+ id: string;
3
+ references: string[];
4
+ }
5
+
1
6
  export interface InlineEditHost {
2
7
  findElementsById(id: string | null): Element[];
3
8
  getSelectedElementId(): string | null;
package/src/consts.ts ADDED
@@ -0,0 +1,11 @@
1
+ export const DATA_COLLECTION_ID = "data-collection-id";
2
+ export const DATA_COLLECTION_ITEM_ID = "data-collection-item-id";
3
+ export const DATA_COLLECTION_ITEM_FIELD = "data-collection-item-field";
4
+ export const DATA_COLLECTION_REFERENCE = "data-collection-reference";
5
+ export const DATA_ARR_INDEX = "data-arr-index";
6
+ export const DATA_ARR_VARIABLE_NAME = "data-arr-variable-name";
7
+ export const DATA_ARR_FIELD = "data-arr-field";
8
+
9
+ export const ALLOWED_CUSTOM_COMPONENTS = ["Image", "Link"];
10
+ export const MAX_JSX_DEPTH = 10;
11
+ export const EXCLUDED_FIELDS = ["children", "length"];
@@ -15,6 +15,7 @@ import {
15
15
  } from "./consts.js";
16
16
  import { applyStyles, getLayerDisplayName } from "./utils.js";
17
17
  import type { LayerInfo, DropdownCallbacks } from "./types.js";
18
+ import { PLUGIN_ELEMENT_ATTR } from "../utils.js";
18
19
 
19
20
  let activeDropdown: HTMLDivElement | null = null;
20
21
  let activeLabel: HTMLDivElement | null = null;
@@ -69,6 +70,7 @@ export function createDropdownElement(
69
70
  ): HTMLDivElement {
70
71
  const container = document.createElement("div");
71
72
  container.setAttribute(LAYER_DROPDOWN_ATTR, "true");
73
+ container.setAttribute(PLUGIN_ELEMENT_ATTR, "true");
72
74
  applyStyles(container, DROPDOWN_CONTAINER_STYLES);
73
75
 
74
76
  layers.forEach((layer) => {
@@ -90,6 +92,7 @@ export function enhanceLabelWithChevron(label: HTMLDivElement): void {
90
92
  label.style.whiteSpace = "nowrap";
91
93
  label.style.pointerEvents = "auto";
92
94
  label.setAttribute(LAYER_DROPDOWN_ATTR, "true");
95
+ label.setAttribute(PLUGIN_ELEMENT_ATTR, "true");
93
96
  }
94
97
 
95
98
  function setupKeyboardNavigation(
@@ -18,6 +18,8 @@ export function getElementSelectorId(element: Element): string | null {
18
18
 
19
19
  export const ALLOWED_ATTRIBUTES: string[] = ["src"];
20
20
 
21
+ export const PLUGIN_ELEMENT_ATTR = "data-vite-plugin-element";
22
+
21
23
  /** Find elements by ID - first try data-source-location, fallback to data-visual-selector-id */
22
24
  export function findElementsById(id: string | null): Element[] {
23
25
  if (!id) return [];
@@ -64,3 +66,106 @@ export function collectAllowedAttributes(element: Element, allowedAttributes: st
64
66
  }
65
67
  return attributes;
66
68
  }
69
+
70
+ /**
71
+ * Freeze all CSS animations and transitions on the page by injecting
72
+ * scoped styles under `[data-visual-edit-active]` and programmatically
73
+ * finishing (or pausing) every running animation.
74
+ *
75
+ * Plugin-owned elements (`[data-vite-plugin-element]`) are excluded so
76
+ * the plugin UI stays animated.
77
+ */
78
+ export function stopAnimations(): void {
79
+ if (document.getElementById('freeze-animations')) return;
80
+
81
+ document.documentElement.setAttribute('data-visual-edit-active', '');
82
+
83
+ const animStyle = document.createElement('style');
84
+ animStyle.id = 'freeze-animations';
85
+ animStyle.textContent = `
86
+ [data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *),
87
+ [data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *)::before,
88
+ [data-visual-edit-active] *:not([${PLUGIN_ELEMENT_ATTR}]):not([${PLUGIN_ELEMENT_ATTR}] *)::after {
89
+ animation-play-state: paused !important;
90
+ transition: none !important;
91
+ }
92
+ `;
93
+
94
+ const pointerStyle = document.createElement('style');
95
+ pointerStyle.id = 'freeze-pointer-events';
96
+ pointerStyle.textContent = `
97
+ [data-visual-edit-active] * { pointer-events: none !important; }
98
+ [${PLUGIN_ELEMENT_ATTR}], [${PLUGIN_ELEMENT_ATTR}] * { pointer-events: auto !important; }
99
+ `;
100
+
101
+ const target = document.head || document.documentElement;
102
+ target.appendChild(animStyle);
103
+ target.appendChild(pointerStyle);
104
+
105
+ document.getAnimations().forEach((a) => {
106
+ // Skip animations on plugin UI elements
107
+ const animTarget = (a.effect as KeyframeEffect)?.target;
108
+ if (animTarget instanceof Element && animTarget.closest(`[${PLUGIN_ELEMENT_ATTR}]`)) return;
109
+
110
+ try {
111
+ a.finish(); // fast-forward to end state
112
+ } catch {
113
+ a.pause(); // finish() throws on infinite animations — pause instead
114
+ }
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Resume all previously frozen animations and remove the injected
120
+ * freeze styles. Cleans up the `data-visual-edit-active` attribute
121
+ * from `<html>` so scoped selectors no longer match.
122
+ */
123
+ export function resumeAnimations(): void {
124
+ const animStyle = document.getElementById('freeze-animations');
125
+ if (!animStyle) return;
126
+
127
+ animStyle.remove();
128
+ document.getElementById('freeze-pointer-events')?.remove();
129
+ document.documentElement.removeAttribute('data-visual-edit-active');
130
+
131
+ document.getAnimations().forEach((a) => {
132
+ if (a.playState === 'paused') {
133
+ try { a.play(); } catch { /* animation target may have been removed */ }
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Hit-test the page at (`x`, `y`) and walk up the DOM to find the
140
+ * nearest ancestor that carries instrumentation attributes
141
+ * (`data-source-location` or `data-visual-selector-id`).
142
+ *
143
+ * Temporarily disables the pointer-events freeze sheet so the
144
+ * browser's native `elementFromPoint` can reach the real target.
145
+ */
146
+ export function findInstrumentedElement(x: number, y: number): Element | null {
147
+ const pointerStyle = document.getElementById('freeze-pointer-events') as HTMLStyleElement | null;
148
+ if (pointerStyle) pointerStyle.disabled = true;
149
+
150
+ const el = document.elementFromPoint(x, y);
151
+
152
+ if (pointerStyle) pointerStyle.disabled = false;
153
+
154
+ return el?.closest('[data-source-location], [data-visual-selector-id]') ?? null;
155
+ }
156
+
157
+ /**
158
+ * Resolve which element should be hovered at (`x`, `y`), skipping the
159
+ * currently selected element. Returns the selector ID of the hovered
160
+ * element, or `null` if the point is empty or hits the selected element.
161
+ */
162
+ export function resolveHoverTarget(x: number, y: number, selectedElementId: string | null): string | null {
163
+ const element = findInstrumentedElement(x, y);
164
+ if (!element) return null;
165
+
166
+ const selectorId = getElementSelectorId(element);
167
+
168
+ if (selectorId === selectedElementId) return null;
169
+
170
+ return selectorId;
171
+ }