@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
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAElC,OAAO,EAAE,
|
|
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
|
@@ -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";
|
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(
|
package/src/injections/utils.ts
CHANGED
|
@@ -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
|
+
}
|