@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.
- package/dist/capabilities/inline-edit/controller.d.ts.map +1 -1
- package/dist/capabilities/inline-edit/controller.js +3 -0
- package/dist/capabilities/inline-edit/controller.js.map +1 -1
- package/dist/capabilities/inline-edit/index.d.ts +1 -1
- package/dist/capabilities/inline-edit/index.d.ts.map +1 -1
- package/dist/capabilities/inline-edit/types.d.ts +4 -0
- package/dist/capabilities/inline-edit/types.d.ts.map +1 -1
- package/dist/consts.d.ts +11 -0
- package/dist/consts.d.ts.map +1 -0
- package/dist/consts.js +11 -0
- package/dist/consts.js.map +1 -0
- package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -1
- package/dist/injections/layer-dropdown/dropdown-ui.js +3 -0
- package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -1
- package/dist/injections/utils.d.ts +31 -0
- package/dist/injections/utils.d.ts.map +1 -1
- package/dist/injections/utils.js +97 -0
- package/dist/injections/utils.js.map +1 -1
- package/dist/injections/visual-edit-agent.d.ts.map +1 -1
- package/dist/injections/visual-edit-agent.js +58 -51
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/jsx-processor.d.ts +3 -1
- package/dist/jsx-processor.d.ts.map +1 -1
- package/dist/jsx-processor.js +29 -6
- package/dist/jsx-processor.js.map +1 -1
- package/dist/jsx-utils.d.ts +9 -0
- package/dist/jsx-utils.d.ts.map +1 -1
- package/dist/jsx-utils.js +86 -0
- package/dist/jsx-utils.js.map +1 -1
- package/dist/processors/collection-id-processor.d.ts +20 -0
- package/dist/processors/collection-id-processor.d.ts.map +1 -0
- package/dist/processors/collection-id-processor.js +182 -0
- package/dist/processors/collection-id-processor.js.map +1 -0
- package/dist/processors/collection-item-field-processor.d.ts +39 -0
- package/dist/processors/collection-item-field-processor.d.ts.map +1 -0
- package/dist/processors/collection-item-field-processor.js +281 -0
- package/dist/processors/collection-item-field-processor.js.map +1 -0
- package/dist/processors/collection-item-id-processor.d.ts +12 -0
- package/dist/processors/collection-item-id-processor.d.ts.map +1 -0
- package/dist/processors/collection-item-id-processor.js +50 -0
- package/dist/processors/collection-item-id-processor.js.map +1 -0
- package/dist/processors/static-array-processor.d.ts +0 -3
- package/dist/processors/static-array-processor.d.ts.map +1 -1
- package/dist/processors/static-array-processor.js +2 -4
- package/dist/processors/static-array-processor.js.map +1 -1
- package/dist/processors/utils/collection-tracing-utils.d.ts +36 -0
- package/dist/processors/utils/collection-tracing-utils.d.ts.map +1 -0
- package/dist/processors/utils/collection-tracing-utils.js +390 -0
- package/dist/processors/utils/collection-tracing-utils.js.map +1 -0
- package/dist/processors/utils/shared-utils.d.ts +96 -0
- package/dist/processors/utils/shared-utils.d.ts.map +1 -0
- package/dist/processors/utils/shared-utils.js +600 -0
- package/dist/processors/utils/shared-utils.js.map +1 -0
- package/dist/statics/index.mjs +12 -2
- package/dist/statics/index.mjs.map +1 -1
- package/dist/visual-edit-plugin.d.ts +0 -1
- package/dist/visual-edit-plugin.d.ts.map +1 -1
- package/dist/visual-edit-plugin.js +29 -178
- package/dist/visual-edit-plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/inline-edit/controller.ts +3 -0
- package/src/capabilities/inline-edit/index.ts +1 -1
- package/src/capabilities/inline-edit/types.ts +5 -0
- package/src/consts.ts +11 -0
- package/src/injections/layer-dropdown/dropdown-ui.ts +3 -0
- package/src/injections/utils.ts +105 -0
- package/src/injections/visual-edit-agent.ts +56 -64
- package/src/jsx-processor.ts +36 -14
- package/src/jsx-utils.ts +116 -0
- package/src/processors/collection-id-processor.ts +261 -0
- package/src/processors/collection-item-field-processor.ts +433 -0
- package/src/processors/collection-item-id-processor.ts +69 -0
- package/src/processors/static-array-processor.ts +7 -4
- package/src/processors/utils/collection-tracing-utils.ts +507 -0
- package/src/processors/utils/shared-utils.ts +785 -0
- package/src/visual-edit-plugin.ts +34 -215
- package/dist/processors/shared-utils.d.ts +0 -19
- package/dist/processors/shared-utils.d.ts.map +0 -1
- package/dist/processors/shared-utils.js +0 -77
- package/dist/processors/shared-utils.js.map +0 -1
- package/src/processors/shared-utils.ts +0 -116
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId } from "./utils.js";
|
|
1
|
+
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget } from "./utils.js";
|
|
2
2
|
import { createLayerController } from "./layer-dropdown/controller.js";
|
|
3
3
|
import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
|
|
4
4
|
import { createInlineEditController } from "../capabilities/inline-edit/index.js";
|
|
@@ -191,53 +191,19 @@ export function setupVisualEditAgent() {
|
|
|
191
191
|
window.parent.postMessage({ type: "unselect-element" }, "*");
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const target = e.target as Element;
|
|
200
|
-
|
|
201
|
-
// Prevent hover effects when a dropdown is open
|
|
202
|
-
if (isDropdownOpen) {
|
|
203
|
-
clearHoverOverlays();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Prevent hover effects on SVG path elements
|
|
208
|
-
if (target.tagName.toLowerCase() === "path") {
|
|
209
|
-
clearHoverOverlays();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
194
|
+
// Hover detection via mousemove + elementFromPoint (since app elements have pointer-events: none)
|
|
195
|
+
let lastHoveredSelectorId: string | null = null;
|
|
196
|
+
let pendingMouseMoveRaf: number | null = null;
|
|
212
197
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!element) {
|
|
218
|
-
clearHoverOverlays();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Prefer data-source-location, fallback to data-visual-selector-id
|
|
223
|
-
const htmlElement = element as HTMLElement;
|
|
224
|
-
const selectorId =
|
|
225
|
-
htmlElement.dataset.sourceLocation ||
|
|
226
|
-
htmlElement.dataset.visualSelectorId;
|
|
227
|
-
|
|
228
|
-
// Skip if this element is already selected
|
|
229
|
-
if (selectedElementId === selectorId) {
|
|
230
|
-
clearHoverOverlays();
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Find all elements with the same ID
|
|
235
|
-
const elements = findElementsById(selectorId || null);
|
|
198
|
+
const clearHoverState = () => {
|
|
199
|
+
clearHoverOverlays();
|
|
200
|
+
lastHoveredSelectorId = null;
|
|
201
|
+
};
|
|
236
202
|
|
|
237
|
-
|
|
203
|
+
const applyHoverOverlays = (selectorId: string) => {
|
|
204
|
+
const elements = findElementsById(selectorId);
|
|
238
205
|
clearHoverOverlays();
|
|
239
206
|
|
|
240
|
-
// Create overlays for all matching elements
|
|
241
207
|
elements.forEach((el) => {
|
|
242
208
|
const overlay = createOverlay(false);
|
|
243
209
|
document.body.appendChild(overlay);
|
|
@@ -246,12 +212,33 @@ export function setupVisualEditAgent() {
|
|
|
246
212
|
});
|
|
247
213
|
|
|
248
214
|
currentHighlightedElements = elements;
|
|
215
|
+
lastHoveredSelectorId = selectorId;
|
|
249
216
|
};
|
|
250
217
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
218
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
219
|
+
if (!isVisualEditMode || isPopoverDragging || inlineEdit.isEditing()) return;
|
|
220
|
+
|
|
221
|
+
if (pendingMouseMoveRaf !== null) return;
|
|
222
|
+
pendingMouseMoveRaf = requestAnimationFrame(() => {
|
|
223
|
+
pendingMouseMoveRaf = null;
|
|
224
|
+
|
|
225
|
+
if (isDropdownOpen) { clearHoverState(); return; }
|
|
226
|
+
|
|
227
|
+
const selectorId = resolveHoverTarget(e.clientX, e.clientY, selectedElementId);
|
|
228
|
+
if (!selectorId) { clearHoverState(); return; }
|
|
229
|
+
if (lastHoveredSelectorId === selectorId) return;
|
|
230
|
+
|
|
231
|
+
applyHoverOverlays(selectorId);
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Clear hover overlays when mouse leaves the viewport
|
|
236
|
+
const handleMouseLeave = () => {
|
|
237
|
+
if (pendingMouseMoveRaf !== null) {
|
|
238
|
+
cancelAnimationFrame(pendingMouseMoveRaf);
|
|
239
|
+
pendingMouseMoveRaf = null;
|
|
240
|
+
}
|
|
241
|
+
clearHoverState();
|
|
255
242
|
};
|
|
256
243
|
|
|
257
244
|
// Handle element click
|
|
@@ -288,20 +275,12 @@ export function setupVisualEditAgent() {
|
|
|
288
275
|
return;
|
|
289
276
|
}
|
|
290
277
|
|
|
291
|
-
// Prevent clicking on SVG path elements
|
|
292
|
-
if (target.tagName.toLowerCase() === "path") {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
278
|
// Prevent default behavior immediately when in visual edit mode
|
|
297
279
|
e.preventDefault();
|
|
298
280
|
e.stopPropagation();
|
|
299
281
|
e.stopImmediatePropagation();
|
|
300
282
|
|
|
301
|
-
|
|
302
|
-
const element = target.closest(
|
|
303
|
-
"[data-source-location], [data-visual-selector-id]"
|
|
304
|
-
);
|
|
283
|
+
const element = findInstrumentedElement(e.clientX, e.clientY);
|
|
305
284
|
if (!element) {
|
|
306
285
|
return;
|
|
307
286
|
}
|
|
@@ -435,21 +414,21 @@ export function setupVisualEditAgent() {
|
|
|
435
414
|
isVisualEditMode = isEnabled;
|
|
436
415
|
|
|
437
416
|
if (!isEnabled) {
|
|
417
|
+
resumeAnimations();
|
|
438
418
|
inlineEdit.stopEditing();
|
|
439
419
|
clearSelection();
|
|
440
420
|
layerController.cleanup();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
currentHighlightedElements = [];
|
|
421
|
+
handleMouseLeave();
|
|
444
422
|
document.body.style.cursor = "default";
|
|
445
423
|
|
|
446
|
-
document.removeEventListener("
|
|
447
|
-
document.removeEventListener("
|
|
424
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
425
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
448
426
|
document.removeEventListener("click", handleElementClick, true);
|
|
449
427
|
} else {
|
|
450
428
|
document.body.style.cursor = "crosshair";
|
|
451
|
-
|
|
452
|
-
document.addEventListener("
|
|
429
|
+
stopAnimations();
|
|
430
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
431
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
453
432
|
document.addEventListener("click", handleElementClick, true);
|
|
454
433
|
}
|
|
455
434
|
};
|
|
@@ -620,6 +599,19 @@ export function setupVisualEditAgent() {
|
|
|
620
599
|
}
|
|
621
600
|
break;
|
|
622
601
|
|
|
602
|
+
case "update-theme-variables":
|
|
603
|
+
if (message.data?.variables) {
|
|
604
|
+
const target = message.data.mode === 'dark'
|
|
605
|
+
? document.querySelector('.dark') as HTMLElement
|
|
606
|
+
: document.documentElement;
|
|
607
|
+
if (target) {
|
|
608
|
+
for (const [name, value] of Object.entries(message.data.variables)) {
|
|
609
|
+
target.style.setProperty(name, value as string);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
|
|
623
615
|
case "toggle-inline-edit-mode":
|
|
624
616
|
if (message.data) {
|
|
625
617
|
inlineEdit.handleToggleMessage(message.data);
|
package/src/jsx-processor.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { NodePath } from "@babel/traverse";
|
|
2
2
|
import type * as t from "@babel/types";
|
|
3
|
+
import { CollectionIdProcessor } from "./processors/collection-id-processor.js";
|
|
4
|
+
import { DataItemIdProcessor } from "./processors/collection-item-id-processor.js";
|
|
5
|
+
import { DataItemFieldProcessor } from "./processors/collection-item-field-processor.js";
|
|
3
6
|
import { StaticArrayProcessor } from "./processors/static-array-processor.js";
|
|
4
7
|
|
|
5
8
|
export class JSXProcessor {
|
|
9
|
+
private collectionIdProcessor: CollectionIdProcessor;
|
|
10
|
+
private dataItemIdProcessor: DataItemIdProcessor;
|
|
11
|
+
private dataItemFieldProcessor: DataItemFieldProcessor;
|
|
6
12
|
private staticArrayProcessor: StaticArrayProcessor;
|
|
7
13
|
|
|
8
14
|
constructor(
|
|
9
15
|
private types: typeof t,
|
|
10
16
|
private filename: string
|
|
11
17
|
) {
|
|
18
|
+
this.collectionIdProcessor = new CollectionIdProcessor(types);
|
|
19
|
+
this.dataItemIdProcessor = new DataItemIdProcessor(types);
|
|
20
|
+
this.dataItemFieldProcessor = new DataItemFieldProcessor(types);
|
|
12
21
|
this.staticArrayProcessor = new StaticArrayProcessor(types);
|
|
13
22
|
}
|
|
14
23
|
|
|
@@ -17,7 +26,10 @@ export class JSXProcessor {
|
|
|
17
26
|
|
|
18
27
|
this.addSourceLocationAttribute(path);
|
|
19
28
|
this.addDynamicContentAttribute(path);
|
|
20
|
-
|
|
29
|
+
|
|
30
|
+
this.collectionIdProcessor.process(path);
|
|
31
|
+
this.dataItemIdProcessor.process(path);
|
|
32
|
+
this.dataItemFieldProcessor.process(path);
|
|
21
33
|
this.staticArrayProcessor.process(path);
|
|
22
34
|
}
|
|
23
35
|
|
|
@@ -27,7 +39,7 @@ export class JSXProcessor {
|
|
|
27
39
|
const { line, column } = path.node.loc?.start || { line: 1, column: 0 };
|
|
28
40
|
const value = `${this.filename}:${line}:${column}`;
|
|
29
41
|
|
|
30
|
-
path.node.attributes.
|
|
42
|
+
path.node.attributes.unshift(
|
|
31
43
|
this.types.jsxAttribute(
|
|
32
44
|
this.types.jsxIdentifier("data-source-location"),
|
|
33
45
|
this.types.stringLiteral(value)
|
|
@@ -45,21 +57,19 @@ export class JSXProcessor {
|
|
|
45
57
|
parentElement.node as t.JSXElement
|
|
46
58
|
);
|
|
47
59
|
|
|
48
|
-
path.node.attributes.
|
|
49
|
-
|
|
50
|
-
this.types.
|
|
51
|
-
this.types.
|
|
52
|
-
|
|
60
|
+
const sourceLocIdx = path.node.attributes.findIndex(
|
|
61
|
+
(attr) =>
|
|
62
|
+
this.types.isJSXAttribute(attr) &&
|
|
63
|
+
this.types.isJSXIdentifier(attr.name) &&
|
|
64
|
+
attr.name.name === "data-source-location"
|
|
53
65
|
);
|
|
54
|
-
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
path.node.attributes.push(
|
|
67
|
+
path.node.attributes.splice(
|
|
68
|
+
sourceLocIdx + 1,
|
|
69
|
+
0,
|
|
60
70
|
this.types.jsxAttribute(
|
|
61
|
-
this.types.jsxIdentifier("content
|
|
62
|
-
this.types.stringLiteral("true")
|
|
71
|
+
this.types.jsxIdentifier("data-dynamic-content"),
|
|
72
|
+
this.types.stringLiteral(isDynamic ? "true" : "false")
|
|
63
73
|
)
|
|
64
74
|
);
|
|
65
75
|
}
|
|
@@ -137,6 +147,18 @@ export class JSXProcessor {
|
|
|
137
147
|
}
|
|
138
148
|
};
|
|
139
149
|
|
|
150
|
+
const attributes = jsxElement.openingElement?.attributes || [];
|
|
151
|
+
for (const attr of attributes) {
|
|
152
|
+
if (hasDynamicContent) break;
|
|
153
|
+
if (this.types.isJSXSpreadAttribute(attr)) {
|
|
154
|
+
hasDynamicContent = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (this.types.isJSXAttribute(attr) && attr.value) {
|
|
158
|
+
traverseNode(attr.value as unknown as Record<string, unknown>);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
140
162
|
for (const child of jsxElement.children) {
|
|
141
163
|
if (hasDynamicContent) break;
|
|
142
164
|
traverseNode(child as unknown as Record<string, unknown>);
|
package/src/jsx-utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as t from "@babel/types";
|
|
2
|
+
import { ALLOWED_CUSTOM_COMPONENTS } from "./consts.js";
|
|
2
3
|
|
|
3
4
|
export class JSXUtils {
|
|
4
5
|
private static types: typeof t;
|
|
@@ -12,4 +13,119 @@ export class JSXUtils {
|
|
|
12
13
|
? `${attr.name.namespace.name}:${attr.name.name.name}`
|
|
13
14
|
: attr.name.name;
|
|
14
15
|
}
|
|
16
|
+
|
|
17
|
+
static getElementName(node: t.JSXOpeningElement): string | null {
|
|
18
|
+
const name = node.name;
|
|
19
|
+
|
|
20
|
+
if (this.types.isJSXIdentifier(name)) {
|
|
21
|
+
return name.name;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (this.types.isJSXNamespacedName(name)) {
|
|
25
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (this.types.isJSXMemberExpression(name)) {
|
|
29
|
+
return this.collectJSXMemberName(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private static collectJSXMemberName(
|
|
36
|
+
node: t.JSXMemberExpression
|
|
37
|
+
): string {
|
|
38
|
+
const parts: string[] = [node.property.name];
|
|
39
|
+
let current: t.JSXMemberExpression["object"] = node.object;
|
|
40
|
+
|
|
41
|
+
while (this.types.isJSXMemberExpression(current)) {
|
|
42
|
+
parts.unshift(current.property.name);
|
|
43
|
+
current = current.object;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.types.isJSXIdentifier(current)) {
|
|
47
|
+
parts.unshift(current.name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return parts.join(".");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static isCustomComponent(name: string): boolean {
|
|
54
|
+
return name.length > 0 && name.charAt(0) === name.charAt(0).toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static isAllowedCustomComponent(name: string): boolean {
|
|
58
|
+
return ALLOWED_CUSTOM_COMPONENTS.includes(name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static isDOMElement(name: string): boolean {
|
|
62
|
+
return name.length > 0 && name.charAt(0) === name.charAt(0).toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static isDOMOrAllowed(name: string): boolean {
|
|
66
|
+
return this.isDOMElement(name) || this.isAllowedCustomComponent(name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static producesJSX(node: t.Expression | t.Node): boolean {
|
|
70
|
+
const types = this.types;
|
|
71
|
+
|
|
72
|
+
if (types.isJSXElement(node) || types.isJSXFragment(node)) return true;
|
|
73
|
+
|
|
74
|
+
if (types.isConditionalExpression(node)) {
|
|
75
|
+
return (
|
|
76
|
+
this.producesJSX(node.consequent) || this.producesJSX(node.alternate)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (types.isLogicalExpression(node)) {
|
|
81
|
+
return this.producesJSX(node.left) || this.producesJSX(node.right);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (types.isCallExpression(node)) {
|
|
85
|
+
if (types.isMemberExpression(node.callee)) {
|
|
86
|
+
const prop = node.callee.property;
|
|
87
|
+
if (
|
|
88
|
+
types.isIdentifier(prop) &&
|
|
89
|
+
(prop.name === "map" || prop.name === "flatMap")
|
|
90
|
+
) {
|
|
91
|
+
const callback = node.arguments[0];
|
|
92
|
+
if (callback) return this.callbackProducesJSX(callback);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (types.isParenthesizedExpression(node)) {
|
|
98
|
+
return this.producesJSX(node.expression);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private static callbackProducesJSX(
|
|
105
|
+
callback: t.Expression | t.SpreadElement | t.ArgumentPlaceholder
|
|
106
|
+
): boolean {
|
|
107
|
+
const types = this.types;
|
|
108
|
+
|
|
109
|
+
if (types.isArrowFunctionExpression(callback)) {
|
|
110
|
+
if (types.isBlockStatement(callback.body)) {
|
|
111
|
+
return this.doesBlockReturnJSX(callback.body);
|
|
112
|
+
}
|
|
113
|
+
return this.producesJSX(callback.body);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (types.isFunctionExpression(callback)) {
|
|
117
|
+
return this.doesBlockReturnJSX(callback.body);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static doesBlockReturnJSX(block: t.BlockStatement): boolean {
|
|
124
|
+
for (const stmt of block.body) {
|
|
125
|
+
if (this.types.isReturnStatement(stmt) && stmt.argument) {
|
|
126
|
+
if (this.producesJSX(stmt.argument)) return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
15
131
|
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { NodePath } from "@babel/traverse";
|
|
2
|
+
import type * as t from "@babel/types";
|
|
3
|
+
import type { CollectionInfo } from "../capabilities/inline-edit/types.js";
|
|
4
|
+
import {
|
|
5
|
+
DATA_COLLECTION_ID,
|
|
6
|
+
DATA_COLLECTION_REFERENCE,
|
|
7
|
+
MAX_JSX_DEPTH,
|
|
8
|
+
} from "../consts.js";
|
|
9
|
+
import { JSXUtils } from "../jsx-utils.js";
|
|
10
|
+
import {
|
|
11
|
+
JSXAttributeUtils,
|
|
12
|
+
PathNavigationUtils,
|
|
13
|
+
ExpressionAnalysisUtils,
|
|
14
|
+
} from "./utils/shared-utils.js";
|
|
15
|
+
import { CollectionTracingUtils } from "./utils/collection-tracing-utils.js";
|
|
16
|
+
|
|
17
|
+
export class CollectionIdProcessor {
|
|
18
|
+
private attributeUtils: JSXAttributeUtils;
|
|
19
|
+
private pathUtils: PathNavigationUtils;
|
|
20
|
+
private expressionUtils: ExpressionAnalysisUtils;
|
|
21
|
+
private tracingUtils: CollectionTracingUtils;
|
|
22
|
+
|
|
23
|
+
constructor(private types: typeof t) {
|
|
24
|
+
this.attributeUtils = new JSXAttributeUtils(types);
|
|
25
|
+
this.pathUtils = new PathNavigationUtils(types);
|
|
26
|
+
this.expressionUtils = new ExpressionAnalysisUtils(types);
|
|
27
|
+
this.tracingUtils = new CollectionTracingUtils(types);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process(path: NodePath<t.JSXOpeningElement>): void {
|
|
31
|
+
if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ID)) return;
|
|
32
|
+
|
|
33
|
+
const info =
|
|
34
|
+
this.tryDirectMapInChildren(path) ??
|
|
35
|
+
this.tryGetByIdInChildren(path) ??
|
|
36
|
+
this.tryComponentRootWithCollectionProp(path);
|
|
37
|
+
|
|
38
|
+
if (!info) return;
|
|
39
|
+
|
|
40
|
+
const target = this.pathUtils.findDOMElementTarget(path) ?? path;
|
|
41
|
+
|
|
42
|
+
this.attributeUtils.addStringAttribute(target, DATA_COLLECTION_ID, info.id);
|
|
43
|
+
|
|
44
|
+
if (info.references.length > 0) {
|
|
45
|
+
this.attributeUtils.addStringAttribute(
|
|
46
|
+
target,
|
|
47
|
+
DATA_COLLECTION_REFERENCE,
|
|
48
|
+
info.references.join(",")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private tryDirectMapInChildren(
|
|
54
|
+
path: NodePath<t.JSXOpeningElement>
|
|
55
|
+
): CollectionInfo | null {
|
|
56
|
+
const jsxElement = path.parentPath;
|
|
57
|
+
if (!jsxElement?.isJSXElement()) return null;
|
|
58
|
+
|
|
59
|
+
const children = jsxElement.get("children");
|
|
60
|
+
for (const child of children) {
|
|
61
|
+
if (!child.isJSXExpressionContainer()) continue;
|
|
62
|
+
|
|
63
|
+
const expression = child.get("expression");
|
|
64
|
+
if (expression.isJSXEmptyExpression()) continue;
|
|
65
|
+
|
|
66
|
+
const mapSource = this.extractMapSource(
|
|
67
|
+
expression as NodePath<t.Expression>
|
|
68
|
+
);
|
|
69
|
+
if (mapSource) return mapSource;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private extractMapSource(
|
|
76
|
+
expr: NodePath<t.Expression>
|
|
77
|
+
): CollectionInfo | null {
|
|
78
|
+
if (expr.isCallExpression()) {
|
|
79
|
+
return this.traceMapCall(expr);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (expr.isLogicalExpression()) {
|
|
83
|
+
if (expr.node.operator === "&&") {
|
|
84
|
+
const right = expr.get("right") as NodePath<t.Expression>;
|
|
85
|
+
return this.extractMapSource(right);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (expr.isConditionalExpression()) {
|
|
90
|
+
const consequent = expr.get("consequent") as NodePath<t.Expression>;
|
|
91
|
+
const alternate = expr.get("alternate") as NodePath<t.Expression>;
|
|
92
|
+
return (
|
|
93
|
+
this.extractMapSource(consequent) ?? this.extractMapSource(alternate)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (expr.isOptionalCallExpression()) {
|
|
98
|
+
return this.traceOptionalMapCall(expr);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private traceMapCall(
|
|
105
|
+
callPath: NodePath<t.CallExpression>
|
|
106
|
+
): CollectionInfo | null {
|
|
107
|
+
const callee = callPath.get("callee");
|
|
108
|
+
if (!callee.isMemberExpression()) return null;
|
|
109
|
+
|
|
110
|
+
const property = callee.get("property") as NodePath;
|
|
111
|
+
if (
|
|
112
|
+
!property.isIdentifier() ||
|
|
113
|
+
(property.node.name !== "map" && property.node.name !== "flatMap")
|
|
114
|
+
) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!this.callbackProducesJSX(callPath)) return null;
|
|
119
|
+
|
|
120
|
+
const arrayObj = callee.get("object") as NodePath<t.Expression>;
|
|
121
|
+
return this.tracingUtils.traceCollectionSource(arrayObj);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private traceOptionalMapCall(
|
|
125
|
+
callPath: NodePath<t.OptionalCallExpression>
|
|
126
|
+
): CollectionInfo | null {
|
|
127
|
+
const callee = callPath.get("callee");
|
|
128
|
+
if (
|
|
129
|
+
!callee.isMemberExpression() &&
|
|
130
|
+
!callee.isOptionalMemberExpression()
|
|
131
|
+
) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const property = (callee as NodePath<t.MemberExpression>).get(
|
|
136
|
+
"property"
|
|
137
|
+
) as NodePath;
|
|
138
|
+
if (
|
|
139
|
+
!property.isIdentifier() ||
|
|
140
|
+
(property.node.name !== "map" && property.node.name !== "flatMap")
|
|
141
|
+
) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const arrayObj = (callee as NodePath<t.MemberExpression>).get(
|
|
146
|
+
"object"
|
|
147
|
+
) as NodePath<t.Expression>;
|
|
148
|
+
return this.tracingUtils.traceCollectionSource(arrayObj);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private callbackProducesJSX(
|
|
152
|
+
callPath: NodePath<t.CallExpression>
|
|
153
|
+
): boolean {
|
|
154
|
+
const args = callPath.get("arguments");
|
|
155
|
+
const callback = args[0];
|
|
156
|
+
if (!callback) return false;
|
|
157
|
+
|
|
158
|
+
if (callback.isArrowFunctionExpression()) {
|
|
159
|
+
const body = callback.get("body");
|
|
160
|
+
if (body.isBlockStatement()) {
|
|
161
|
+
return JSXUtils.doesBlockReturnJSX(body.node);
|
|
162
|
+
}
|
|
163
|
+
return JSXUtils.producesJSX(body.node);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (callback.isFunctionExpression()) {
|
|
167
|
+
return JSXUtils.doesBlockReturnJSX(callback.node.body);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private tryGetByIdInChildren(
|
|
174
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
175
|
+
depth: number = 0
|
|
176
|
+
): CollectionInfo | null {
|
|
177
|
+
if (depth > MAX_JSX_DEPTH) return null;
|
|
178
|
+
|
|
179
|
+
const jsxElement = path.parentPath;
|
|
180
|
+
if (!jsxElement?.isJSXElement()) return null;
|
|
181
|
+
|
|
182
|
+
const children = jsxElement.get("children");
|
|
183
|
+
for (const child of children) {
|
|
184
|
+
const info = this.checkChildForGetById(child, depth);
|
|
185
|
+
if (info) return info;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const attr of path.node.attributes) {
|
|
189
|
+
if (
|
|
190
|
+
this.types.isJSXAttribute(attr) &&
|
|
191
|
+
attr.value &&
|
|
192
|
+
this.types.isJSXExpressionContainer(attr.value)
|
|
193
|
+
) {
|
|
194
|
+
const expr = attr.value.expression;
|
|
195
|
+
if (this.types.isMemberExpression(expr)) {
|
|
196
|
+
const info = this.tracingUtils.traceGetByIdSource(path);
|
|
197
|
+
if (info) return info;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private checkChildForGetById(
|
|
206
|
+
child: NodePath,
|
|
207
|
+
depth: number
|
|
208
|
+
): CollectionInfo | null {
|
|
209
|
+
if (child.isJSXExpressionContainer()) {
|
|
210
|
+
const expr = child.get("expression");
|
|
211
|
+
if (
|
|
212
|
+
expr.isMemberExpression() ||
|
|
213
|
+
expr.isOptionalMemberExpression()
|
|
214
|
+
) {
|
|
215
|
+
return this.tracingUtils.traceGetByIdSource(child);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (child.isJSXElement()) {
|
|
220
|
+
const childOpening = child.get("openingElement");
|
|
221
|
+
return this.tryGetByIdInChildren(childOpening, depth + 1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private tryComponentRootWithCollectionProp(
|
|
228
|
+
path: NodePath<t.JSXOpeningElement>
|
|
229
|
+
): CollectionInfo | null {
|
|
230
|
+
if (!this.pathUtils.isRootReturnElement(path)) return null;
|
|
231
|
+
|
|
232
|
+
const fn = this.pathUtils.findEnclosingFunction(path);
|
|
233
|
+
if (!fn) return null;
|
|
234
|
+
|
|
235
|
+
let info: CollectionInfo | null = null;
|
|
236
|
+
|
|
237
|
+
fn.traverse({
|
|
238
|
+
CallExpression: (callPath: NodePath<t.CallExpression>) => {
|
|
239
|
+
if (info) return;
|
|
240
|
+
|
|
241
|
+
const callee = callPath.get("callee");
|
|
242
|
+
if (!callee.isMemberExpression()) return;
|
|
243
|
+
|
|
244
|
+
const prop = callee.get("property") as NodePath;
|
|
245
|
+
if (
|
|
246
|
+
!prop.isIdentifier() ||
|
|
247
|
+
(prop.node.name !== "map" && prop.node.name !== "flatMap")
|
|
248
|
+
) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!this.callbackProducesJSX(callPath)) return;
|
|
253
|
+
|
|
254
|
+
const arrayObj = callee.get("object") as NodePath<t.Expression>;
|
|
255
|
+
info = this.tracingUtils.traceCollectionSource(arrayObj);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return info;
|
|
260
|
+
}
|
|
261
|
+
}
|