@jbpark/live-editor 4.1.0 → 4.2.0
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/dist/ast-CMD20ykx.js +1613 -0
- package/dist/ast-CMD20ykx.js.map +1 -0
- package/dist/{boundary-C90q3YsT.js → boundary-D2IFja-3.js} +2 -2
- package/dist/boundary-D2IFja-3.js.map +1 -0
- package/dist/cache-B0-XcIxJ.js +51 -0
- package/dist/cache-B0-XcIxJ.js.map +1 -0
- package/dist/{utils-BQ7WL7Jh.js → cn-CAnad2vL.js} +3 -128
- package/dist/cn-CAnad2vL.js.map +1 -0
- package/dist/compile-0Dvp8zp5.js +135 -0
- package/dist/compile-0Dvp8zp5.js.map +1 -0
- package/dist/constants-CKvdqZtO.js +1457 -0
- package/dist/constants-CKvdqZtO.js.map +1 -0
- package/dist/{context-Y-M9fViD.js → context-93X3gyfJ.js} +3 -3
- package/dist/context-93X3gyfJ.js.map +1 -0
- package/dist/{core-Co8Nlq16.js → core-B3ruccKb.js} +3 -2
- package/dist/core-B3ruccKb.js.map +1 -0
- package/dist/detect-typescript-5MV4mCM7.js +10 -0
- package/dist/detect-typescript-5MV4mCM7.js.map +1 -0
- package/dist/dnd/index.d.ts +2 -2
- package/dist/dnd/index.js +2 -2
- package/dist/{dnd-D_rG92-R.js → dnd-DBaH2L8N.js} +548 -137
- package/dist/dnd-DBaH2L8N.js.map +1 -0
- package/dist/{extract-abHYmNsu.js → document-C3eCRQlj.js} +6791 -8938
- package/dist/document-C3eCRQlj.js.map +1 -0
- package/dist/{document-J9Lo9p81.d.ts → document-Ct4jW78J.d.ts} +42 -22
- package/dist/editor/index.js +2 -2
- package/dist/{editor-aNRx7hRY.js → editor-DvMruljw.js} +2 -2
- package/dist/{editor-aNRx7hRY.js.map → editor-DvMruljw.js.map} +1 -1
- package/dist/editor-caches-ClMvdcQF.js +23 -0
- package/dist/editor-caches-ClMvdcQF.js.map +1 -0
- package/dist/error/index.js +2 -2
- package/dist/{error-o0SBwtTv.js → error-KW8XCcjH.js} +2 -2
- package/dist/{error-o0SBwtTv.js.map → error-KW8XCcjH.js.map} +1 -1
- package/dist/{index-Doh4VTXr.d.ts → index-DVgwXnPa.d.ts} +109 -34
- package/dist/{index-B_p74BRM.d.ts → index-w3kLcCXl.d.ts} +29 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/preview/index.js +2 -2
- package/dist/{preview-CS6Rsppd.js → preview-B9Jhv1Up.js} +4 -4
- package/dist/preview-B9Jhv1Up.js.map +1 -0
- package/dist/provider/index.js +1 -1
- package/dist/sections-A4TAXUP8.js +24 -0
- package/dist/sections-A4TAXUP8.js.map +1 -0
- package/dist/{use-dynamic-tailwind-SOgrb2qc.js → use-dynamic-tailwind-BDfytfYC.js} +33 -9
- package/dist/use-dynamic-tailwind-BDfytfYC.js.map +1 -0
- package/dist/utils/ast/index.d.ts +3 -3
- package/dist/utils/ast/index.js +3 -3
- package/dist/utils/index.d.ts +20 -9
- package/dist/utils/index.js +7 -3
- package/package.json +1 -1
- package/dist/ast-CZwpVrJv.js +0 -851
- package/dist/ast-CZwpVrJv.js.map +0 -1
- package/dist/boundary-C90q3YsT.js.map +0 -1
- package/dist/context-Y-M9fViD.js.map +0 -1
- package/dist/core-Co8Nlq16.js.map +0 -1
- package/dist/dnd-D_rG92-R.js.map +0 -1
- package/dist/extract-abHYmNsu.js.map +0 -1
- package/dist/preview-CS6Rsppd.js.map +0 -1
- package/dist/use-dynamic-tailwind-SOgrb2qc.js.map +0 -1
- package/dist/utils-BQ7WL7Jh.js.map +0 -1
|
@@ -0,0 +1,1613 @@
|
|
|
1
|
+
import { _ as __toESM, d as traverse, f as getJSXTagName, g as require_lib$1, h as require_lib$2, m as require_lib, p as nanoid } from "./document-C3eCRQlj.js";
|
|
2
|
+
import { c as RESERVED_BINDING_PROPERTIES, n as CONFIG, r as DATA_ATTR, s as REGEX, t as BINDING_PROP } from "./constants-CKvdqZtO.js";
|
|
3
|
+
import { n as registerEditorCache } from "./editor-caches-ClMvdcQF.js";
|
|
4
|
+
import { t as createBoundedCache } from "./cache-B0-XcIxJ.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/utils/ast/types.ts
|
|
7
|
+
var import_lib$1 = require_lib$1();
|
|
8
|
+
var import_lib$2 = /* @__PURE__ */ __toESM(require_lib$2(), 1);
|
|
9
|
+
const BINDING_TYPES = [
|
|
10
|
+
"array",
|
|
11
|
+
"object",
|
|
12
|
+
"string",
|
|
13
|
+
"number",
|
|
14
|
+
"boolean",
|
|
15
|
+
"color",
|
|
16
|
+
"jsx",
|
|
17
|
+
"richtext",
|
|
18
|
+
"date",
|
|
19
|
+
"url"
|
|
20
|
+
];
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/utils/ast/helpers.ts
|
|
23
|
+
var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
|
|
24
|
+
const generate = typeof import_lib.default === "function" ? import_lib.default : import_lib.default.default;
|
|
25
|
+
const wrap = (code) => {
|
|
26
|
+
return `<>${code}</>`;
|
|
27
|
+
};
|
|
28
|
+
const unwrap = (generated) => {
|
|
29
|
+
return generated.replace(/^<>\s*/g, "").replace(/\s*<\/>\s*;?\s*$/g, "").trim();
|
|
30
|
+
};
|
|
31
|
+
const attrValue = ({ value, isStringLiteral }) => {
|
|
32
|
+
if (value === null) return null;
|
|
33
|
+
if (isStringLiteral) return import_lib$2.stringLiteral(value);
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
if (REGEX.NUMBER.test(trimmed)) return import_lib$2.jsxExpressionContainer(import_lib$2.numericLiteral(parseFloat(trimmed)));
|
|
36
|
+
if (REGEX.BOOLEAN_OR_NULL.test(trimmed)) {
|
|
37
|
+
const expr = (0, import_lib$1.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
38
|
+
return import_lib$2.jsxExpressionContainer(expr);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const expr = (0, import_lib$1.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
42
|
+
return import_lib$2.jsxExpressionContainer(expr);
|
|
43
|
+
} catch {
|
|
44
|
+
return import_lib$2.stringLiteral(trimmed);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const generateCode = (node) => {
|
|
48
|
+
return generate(node, { jsescOption: { minimal: true } }).code;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/utils/ast/value.ts
|
|
52
|
+
const dedent = (str) => {
|
|
53
|
+
const lines = str.replace(/^\n/, "").replace(/\n\s*$/, "").split("\n");
|
|
54
|
+
const indent = lines.reduce((min, line) => {
|
|
55
|
+
if (!line.trim()) return min;
|
|
56
|
+
const match = line.match(/^(\s*)/);
|
|
57
|
+
return Math.min(min, match?.[1]?.length ?? 0);
|
|
58
|
+
}, Infinity);
|
|
59
|
+
return indent === Infinity ? str.trim() : lines.map((line) => line.slice(indent)).join("\n");
|
|
60
|
+
};
|
|
61
|
+
const unwrapExpression = (node) => {
|
|
62
|
+
let current = node;
|
|
63
|
+
while (import_lib$2.isTSAsExpression(current) || import_lib$2.isTSSatisfiesExpression(current) || import_lib$2.isTSNonNullExpression(current) || import_lib$2.isTSTypeAssertion(current) || import_lib$2.isParenthesizedExpression(current)) current = current.expression;
|
|
64
|
+
return current;
|
|
65
|
+
};
|
|
66
|
+
const isLosslesslyEvaluable = (node) => {
|
|
67
|
+
const value = unwrapExpression(node);
|
|
68
|
+
if (import_lib$2.isStringLiteral(value) || import_lib$2.isNumericLiteral(value) || import_lib$2.isBooleanLiteral(value) || import_lib$2.isNullLiteral(value)) return true;
|
|
69
|
+
if (import_lib$2.isUnaryExpression(value) && value.operator === "-" && import_lib$2.isNumericLiteral(value.argument)) return true;
|
|
70
|
+
if (import_lib$2.isTemplateLiteral(value)) return value.expressions.length === 0;
|
|
71
|
+
if (import_lib$2.isArrayExpression(value)) return value.elements.every((element) => element !== null && isLosslesslyEvaluable(element));
|
|
72
|
+
if (import_lib$2.isObjectExpression(value)) return value.properties.every((property) => import_lib$2.isObjectProperty(property) && !property.computed && (import_lib$2.isIdentifier(property.key) || import_lib$2.isStringLiteral(property.key) || import_lib$2.isNumericLiteral(property.key)) && isLosslesslyEvaluable(property.value));
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
const canLosslesslyEvaluateSource = (source) => {
|
|
76
|
+
try {
|
|
77
|
+
return isLosslesslyEvaluable((0, import_lib$1.parseExpression)(source, { plugins: ["jsx", "typescript"] }));
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const evaluateLiteral = (rawNode) => {
|
|
83
|
+
const node = unwrapExpression(rawNode);
|
|
84
|
+
if (import_lib$2.isStringLiteral(node)) return node.value;
|
|
85
|
+
if (import_lib$2.isNumericLiteral(node)) return node.value;
|
|
86
|
+
if (import_lib$2.isBooleanLiteral(node)) return node.value;
|
|
87
|
+
if (import_lib$2.isNullLiteral(node)) return null;
|
|
88
|
+
if (import_lib$2.isIdentifier(node) && node.name === "undefined") return;
|
|
89
|
+
if (import_lib$2.isUnaryExpression(node) && node.operator === "-" && import_lib$2.isNumericLiteral(node.argument)) return -node.argument.value;
|
|
90
|
+
if (import_lib$2.isTemplateLiteral(node) && node.expressions.length === 0) return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? "";
|
|
91
|
+
if (import_lib$2.isJSXElement(node) || import_lib$2.isJSXFragment(node)) return generateCode(node);
|
|
92
|
+
if (import_lib$2.isArrayExpression(node)) return node.elements.map((element) => element ? evaluateLiteral(element) : null);
|
|
93
|
+
if (import_lib$2.isObjectExpression(node)) {
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const prop of node.properties) {
|
|
96
|
+
if (!import_lib$2.isObjectProperty(prop)) continue;
|
|
97
|
+
let key = null;
|
|
98
|
+
if (import_lib$2.isIdentifier(prop.key)) key = prop.key.name;
|
|
99
|
+
else if (import_lib$2.isStringLiteral(prop.key)) key = prop.key.value;
|
|
100
|
+
else if (import_lib$2.isNumericLiteral(prop.key)) key = String(prop.key.value);
|
|
101
|
+
if (key === null) continue;
|
|
102
|
+
result[key] = evaluateLiteral(prop.value);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const parseValue = (value) => {
|
|
108
|
+
if (typeof value !== "string") return value;
|
|
109
|
+
const trimmed = value.trim();
|
|
110
|
+
if (!trimmed) return value;
|
|
111
|
+
if (REGEX.NUMBER.test(trimmed)) return parseFloat(trimmed);
|
|
112
|
+
if (REGEX.BOOLEAN_OR_NULL.test(trimmed)) {
|
|
113
|
+
if (trimmed === "true") return true;
|
|
114
|
+
if (trimmed === "false") return false;
|
|
115
|
+
if (trimmed === "null") return null;
|
|
116
|
+
if (trimmed === "undefined") return;
|
|
117
|
+
}
|
|
118
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
119
|
+
try {
|
|
120
|
+
const ast = (0, import_lib$1.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
121
|
+
if (import_lib$2.isObjectExpression(ast) || import_lib$2.isArrayExpression(ast)) return evaluateLiteral(ast);
|
|
122
|
+
} catch {}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
};
|
|
127
|
+
const MAX_FLATTEN_DEPTH = 20;
|
|
128
|
+
const isEditablePrimitive = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
129
|
+
const flattenEditableValue = (value) => {
|
|
130
|
+
const parsed = parseValue(value);
|
|
131
|
+
if (typeof parsed !== "object" || parsed === null || Object.keys(parsed).length === 0) return null;
|
|
132
|
+
const entries = [];
|
|
133
|
+
const walk = (node, path, depth) => {
|
|
134
|
+
if (depth > MAX_FLATTEN_DEPTH) return;
|
|
135
|
+
if (isEditablePrimitive(node)) {
|
|
136
|
+
entries.push({
|
|
137
|
+
path,
|
|
138
|
+
value: node
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(node)) {
|
|
143
|
+
node.forEach((item, index) => walk(item, [...path, index], depth + 1));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (typeof node === "object" && node !== null) Object.entries(node).forEach(([key, item]) => walk(item, [...path, key], depth + 1));
|
|
147
|
+
};
|
|
148
|
+
walk(parsed, [], 0);
|
|
149
|
+
return entries.length ? entries : null;
|
|
150
|
+
};
|
|
151
|
+
const setEditableValue = (value, path, next) => {
|
|
152
|
+
if (!path.length) return value;
|
|
153
|
+
const parsed = parseValue(value);
|
|
154
|
+
if (typeof parsed !== "object" || parsed === null) return value;
|
|
155
|
+
const root = Array.isArray(parsed) ? [...parsed] : { ...parsed };
|
|
156
|
+
let cursor = root;
|
|
157
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
158
|
+
const key = path[i];
|
|
159
|
+
const child = cursor[key];
|
|
160
|
+
if (typeof child !== "object" || child === null) return value;
|
|
161
|
+
const clonedChild = Array.isArray(child) ? [...child] : { ...child };
|
|
162
|
+
cursor[key] = clonedChild;
|
|
163
|
+
cursor = clonedChild;
|
|
164
|
+
}
|
|
165
|
+
const lastKey = path[path.length - 1];
|
|
166
|
+
if (!(lastKey in cursor)) return value;
|
|
167
|
+
cursor[lastKey] = next;
|
|
168
|
+
return JSON.stringify(root);
|
|
169
|
+
};
|
|
170
|
+
const extractNodeValue = (node) => {
|
|
171
|
+
if (import_lib$2.isBooleanLiteral(node)) return {
|
|
172
|
+
type: "boolean",
|
|
173
|
+
value: node.value
|
|
174
|
+
};
|
|
175
|
+
if (import_lib$2.isNumericLiteral(node)) return {
|
|
176
|
+
type: "number",
|
|
177
|
+
value: node.value
|
|
178
|
+
};
|
|
179
|
+
if (import_lib$2.isUnaryExpression(node) && node.operator === "-" && import_lib$2.isNumericLiteral(node.argument)) return {
|
|
180
|
+
type: "number",
|
|
181
|
+
value: -node.argument.value
|
|
182
|
+
};
|
|
183
|
+
if (import_lib$2.isStringLiteral(node)) return {
|
|
184
|
+
type: "string",
|
|
185
|
+
value: node.value
|
|
186
|
+
};
|
|
187
|
+
if (import_lib$2.isTemplateLiteral(node)) {
|
|
188
|
+
if (node.expressions.length === 0 && node.quasis.length === 1) return {
|
|
189
|
+
type: "string",
|
|
190
|
+
value: dedent(node.quasis[0].value.cooked ?? node.quasis[0].value.raw)
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
type: "string",
|
|
194
|
+
value: generateCode(node)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (import_lib$2.isNullLiteral(node)) return {
|
|
198
|
+
type: "null",
|
|
199
|
+
value: null
|
|
200
|
+
};
|
|
201
|
+
if (import_lib$2.isArrayExpression(node)) return {
|
|
202
|
+
type: "array",
|
|
203
|
+
value: generateCode(node)
|
|
204
|
+
};
|
|
205
|
+
if (import_lib$2.isObjectExpression(node)) return {
|
|
206
|
+
type: "object",
|
|
207
|
+
value: generateCode(node)
|
|
208
|
+
};
|
|
209
|
+
if (import_lib$2.isJSXElement(node) || import_lib$2.isJSXFragment(node)) return {
|
|
210
|
+
type: "string",
|
|
211
|
+
value: generateCode(node)
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
type: "unknown",
|
|
215
|
+
value: null
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const createNodeFromValue = (type, value) => {
|
|
219
|
+
switch (type) {
|
|
220
|
+
case "boolean": return import_lib$2.booleanLiteral(value === true);
|
|
221
|
+
case "number": return import_lib$2.numericLiteral(Number(value));
|
|
222
|
+
case "string": return import_lib$2.stringLiteral(String(value));
|
|
223
|
+
case "null": return import_lib$2.nullLiteral();
|
|
224
|
+
case "array":
|
|
225
|
+
case "object":
|
|
226
|
+
case "unknown": return null;
|
|
227
|
+
default: return null;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const valueToExpression = (value) => {
|
|
231
|
+
if (typeof value === "string") return import_lib$2.stringLiteral(value);
|
|
232
|
+
if (typeof value === "number") return value < 0 ? import_lib$2.unaryExpression("-", import_lib$2.numericLiteral(-value)) : import_lib$2.numericLiteral(value);
|
|
233
|
+
if (typeof value === "boolean") return import_lib$2.booleanLiteral(value);
|
|
234
|
+
if (value === null) return import_lib$2.nullLiteral();
|
|
235
|
+
if (Array.isArray(value)) return import_lib$2.arrayExpression(value.map((item) => valueToExpression(item) ?? import_lib$2.nullLiteral()));
|
|
236
|
+
if (typeof value === "object") {
|
|
237
|
+
const properties = [];
|
|
238
|
+
for (const [key, item] of Object.entries(value)) {
|
|
239
|
+
const expr = valueToExpression(item);
|
|
240
|
+
if (expr === null) continue;
|
|
241
|
+
properties.push(import_lib$2.objectProperty(import_lib$2.stringLiteral(key), expr));
|
|
242
|
+
}
|
|
243
|
+
return import_lib$2.objectExpression(properties);
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
};
|
|
247
|
+
const parseArrayExpression = (value) => {
|
|
248
|
+
try {
|
|
249
|
+
const ast = unwrapExpression((0, import_lib$1.parseExpression)(value, { plugins: ["jsx", "typescript"] }));
|
|
250
|
+
if (!import_lib$2.isArrayExpression(ast)) return null;
|
|
251
|
+
return ast;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error("❌ Array parsing error:", error);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const extractObjectProperties = (element) => {
|
|
258
|
+
const properties = {};
|
|
259
|
+
element.properties.forEach((prop) => {
|
|
260
|
+
if (import_lib$2.isObjectProperty(prop) && import_lib$2.isIdentifier(prop.key)) {
|
|
261
|
+
const key = prop.key.name;
|
|
262
|
+
if (key === "children" || import_lib$2.isJSXElement(prop.value) || import_lib$2.isJSXFragment(prop.value)) return;
|
|
263
|
+
const extracted = extractNodeValue(prop.value);
|
|
264
|
+
properties[key] = {
|
|
265
|
+
...extracted,
|
|
266
|
+
astNode: prop.value
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
return properties;
|
|
271
|
+
};
|
|
272
|
+
const arrayExpressionToCode = (elements) => {
|
|
273
|
+
const nextAst = import_lib$2.arrayExpression(elements);
|
|
274
|
+
return generateCode(nextAst);
|
|
275
|
+
};
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/utils/ast/binding.ts
|
|
278
|
+
const bindingTypeSchema = z.enum(BINDING_TYPES);
|
|
279
|
+
const bindingOptionSchema = z.object({
|
|
280
|
+
label: z.string(),
|
|
281
|
+
value: z.string()
|
|
282
|
+
});
|
|
283
|
+
const bindingWidgetSchema = z.object({
|
|
284
|
+
type: z.string().min(1),
|
|
285
|
+
step: z.number().optional(),
|
|
286
|
+
unit: z.string().optional()
|
|
287
|
+
}).passthrough();
|
|
288
|
+
const pick = (schema, value) => {
|
|
289
|
+
const parsed = schema.safeParse(value);
|
|
290
|
+
return parsed.success ? parsed.data : void 0;
|
|
291
|
+
};
|
|
292
|
+
const sanitizeWidget = (value) => {
|
|
293
|
+
if (typeof value === "string") return value ? { type: value } : void 0;
|
|
294
|
+
return pick(bindingWidgetSchema, value);
|
|
295
|
+
};
|
|
296
|
+
const sanitizeOptions = (value) => {
|
|
297
|
+
if (!Array.isArray(value)) return;
|
|
298
|
+
const options = value.flatMap((option) => {
|
|
299
|
+
const parsed = pick(bindingOptionSchema, option);
|
|
300
|
+
return parsed ? [parsed] : [];
|
|
301
|
+
});
|
|
302
|
+
return options.length > 0 ? options : void 0;
|
|
303
|
+
};
|
|
304
|
+
const KNOWN_BINDING_KEYS = /* @__PURE__ */ new Set([
|
|
305
|
+
"label",
|
|
306
|
+
"property",
|
|
307
|
+
"type",
|
|
308
|
+
"widget",
|
|
309
|
+
"options",
|
|
310
|
+
"render",
|
|
311
|
+
"min",
|
|
312
|
+
"max",
|
|
313
|
+
"pattern",
|
|
314
|
+
"required"
|
|
315
|
+
]);
|
|
316
|
+
const isPlainObject = (value) => {
|
|
317
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
318
|
+
};
|
|
319
|
+
const sanitizeField = (raw) => {
|
|
320
|
+
const metaEntries = Object.entries(raw).filter(([key]) => !KNOWN_BINDING_KEYS.has(key));
|
|
321
|
+
const field = {
|
|
322
|
+
label: pick(z.string(), raw.label),
|
|
323
|
+
property: pick(z.string(), raw.property),
|
|
324
|
+
type: pick(bindingTypeSchema, raw.type),
|
|
325
|
+
widget: sanitizeWidget(raw.widget),
|
|
326
|
+
options: sanitizeOptions(raw.options),
|
|
327
|
+
render: sanitizeRenderMap(raw.render),
|
|
328
|
+
min: pick(z.number(), raw.min),
|
|
329
|
+
max: pick(z.number(), raw.max),
|
|
330
|
+
pattern: pick(z.string(), raw.pattern),
|
|
331
|
+
required: pick(z.boolean(), raw.required),
|
|
332
|
+
meta: metaEntries.length > 0 ? Object.fromEntries(metaEntries) : void 0
|
|
333
|
+
};
|
|
334
|
+
return Object.fromEntries(Object.entries(field).filter(([, value]) => value !== void 0));
|
|
335
|
+
};
|
|
336
|
+
function sanitizeRenderMap(value) {
|
|
337
|
+
if (!isPlainObject(value)) return;
|
|
338
|
+
const map = {};
|
|
339
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
340
|
+
if (!isPlainObject(raw)) continue;
|
|
341
|
+
if ("type" in raw) {
|
|
342
|
+
map[key] = {
|
|
343
|
+
type: void 0,
|
|
344
|
+
...sanitizeField(raw)
|
|
345
|
+
};
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const nested = sanitizeRenderMap(raw);
|
|
349
|
+
if (nested) map[key] = nested;
|
|
350
|
+
}
|
|
351
|
+
return Object.keys(map).length > 0 ? map : void 0;
|
|
352
|
+
}
|
|
353
|
+
const buildBindingItems = (raw) => {
|
|
354
|
+
if (!Array.isArray(raw)) return [];
|
|
355
|
+
return raw.flatMap((rawItem) => {
|
|
356
|
+
if (!isPlainObject(rawItem)) return [];
|
|
357
|
+
const { label, property, ...field } = sanitizeField(rawItem);
|
|
358
|
+
if (label === void 0) return [];
|
|
359
|
+
if (property === void 0 && field.type !== "richtext") return [];
|
|
360
|
+
return [{
|
|
361
|
+
label,
|
|
362
|
+
property: property ?? BINDING_PROP.INNER_HTML,
|
|
363
|
+
...field
|
|
364
|
+
}];
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
const parseBinding = (bindingValue) => {
|
|
368
|
+
if (!bindingValue) return [];
|
|
369
|
+
const ast = parseArrayExpression(bindingValue);
|
|
370
|
+
if (!ast) return [];
|
|
371
|
+
return buildBindingItems(evaluateLiteral(ast));
|
|
372
|
+
};
|
|
373
|
+
const parseBindingExpression = (expression) => buildBindingItems(evaluateLiteral(expression));
|
|
374
|
+
const getCurrentValue = (node, property) => {
|
|
375
|
+
switch (property) {
|
|
376
|
+
case BINDING_PROP.INNER_TEXT: return node.textContent || "";
|
|
377
|
+
case BINDING_PROP.INNER_HTML: {
|
|
378
|
+
const dsiAttr = node.attributes.find((a) => a.name === "dangerouslySetInnerHTML");
|
|
379
|
+
if (dsiAttr?.value) try {
|
|
380
|
+
const expr = (0, import_lib$1.parseExpression)(dsiAttr.value, { plugins: ["jsx", "typescript"] });
|
|
381
|
+
if (import_lib$2.isObjectExpression(expr)) {
|
|
382
|
+
const htmlProp = expr.properties.find((p) => import_lib$2.isObjectProperty(p) && import_lib$2.isIdentifier(p.key) && p.key.name === "__html");
|
|
383
|
+
if (htmlProp) {
|
|
384
|
+
if (import_lib$2.isStringLiteral(htmlProp.value)) return htmlProp.value.value;
|
|
385
|
+
if (import_lib$2.isTemplateLiteral(htmlProp.value) && htmlProp.value.expressions.length === 0) return htmlProp.value.quasis[0]?.value.cooked ?? htmlProp.value.quasis[0]?.value.raw ?? "";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch {}
|
|
389
|
+
return node.rawChildren || node.textContent || "";
|
|
390
|
+
}
|
|
391
|
+
case BINDING_PROP.CHILDREN: return JSON.stringify(node?.children || []);
|
|
392
|
+
default: return node.attributes.find((attr) => attr.name === property)?.value || "";
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const STRING_VALUED_TYPES = /* @__PURE__ */ new Set([
|
|
396
|
+
"string",
|
|
397
|
+
"url",
|
|
398
|
+
"date",
|
|
399
|
+
"color",
|
|
400
|
+
"jsx",
|
|
401
|
+
"richtext"
|
|
402
|
+
]);
|
|
403
|
+
const getStructuredValue = (node, property, type) => {
|
|
404
|
+
switch (property) {
|
|
405
|
+
case BINDING_PROP.INNER_TEXT:
|
|
406
|
+
case BINDING_PROP.INNER_HTML: return getCurrentValue(node, property);
|
|
407
|
+
case BINDING_PROP.CHILDREN: return node.children ?? [];
|
|
408
|
+
default: {
|
|
409
|
+
const raw = getCurrentValue(node, property);
|
|
410
|
+
if (node.attributes.find((a) => a.name === property)?.isStringLiteral || type && STRING_VALUED_TYPES.has(type)) return raw;
|
|
411
|
+
return parseValue(raw);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const hasEditableBindings = (node) => {
|
|
416
|
+
const bindingAttr = node.dataAttributes.find((attr) => attr.name === DATA_ATTR.BINDING);
|
|
417
|
+
if (!bindingAttr?.value) return false;
|
|
418
|
+
return (node.bindings || parseBinding(bindingAttr.value)).length > 0;
|
|
419
|
+
};
|
|
420
|
+
const findEditableChildren = (node) => {
|
|
421
|
+
const editableChildren = [];
|
|
422
|
+
const traverse = (children) => {
|
|
423
|
+
if (!children) return;
|
|
424
|
+
for (const child of children) {
|
|
425
|
+
if (hasEditableBindings(child)) editableChildren.push(child);
|
|
426
|
+
traverse(child.children);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
traverse(node.children);
|
|
430
|
+
return editableChildren;
|
|
431
|
+
};
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/utils/selection.ts
|
|
434
|
+
const removeIndices = (items, indices) => {
|
|
435
|
+
return items.filter((_, index) => !indices.has(index));
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Shifts every selected index up/down by one step as a block, preserving
|
|
439
|
+
* relative order — scattered selections stop moving individually once they
|
|
440
|
+
* hit an unselected neighbor, so the whole group slides together instead of
|
|
441
|
+
* items passing through each other.
|
|
442
|
+
*/
|
|
443
|
+
const moveSelectedIndices = (items, indices, direction) => {
|
|
444
|
+
const next = [...items];
|
|
445
|
+
const nextIndices = new Set(indices);
|
|
446
|
+
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
447
|
+
for (const index of ordered) {
|
|
448
|
+
const target = direction === "up" ? index - 1 : index + 1;
|
|
449
|
+
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
450
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
451
|
+
nextIndices.delete(index);
|
|
452
|
+
nextIndices.add(target);
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
items: next,
|
|
456
|
+
indices: nextIndices
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/utils/ast/patch.ts
|
|
461
|
+
const hasOnlyLayoutNewlines = (content) => {
|
|
462
|
+
let index = 0;
|
|
463
|
+
while (index < content.length) {
|
|
464
|
+
const char = content[index];
|
|
465
|
+
if (char === "`" || char === "<") return false;
|
|
466
|
+
if (char === "\"" || char === "'") {
|
|
467
|
+
const next = skipStringLiteral(content, index);
|
|
468
|
+
if (next === -1) return false;
|
|
469
|
+
index = next;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (char === "/") {
|
|
473
|
+
const following = content[index + 1];
|
|
474
|
+
if (following === "/") {
|
|
475
|
+
const lineEnd = content.indexOf("\n", index + 2);
|
|
476
|
+
index = lineEnd === -1 ? content.length : lineEnd;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (following === "*") {
|
|
480
|
+
const commentEnd = content.indexOf("*/", index + 2);
|
|
481
|
+
if (commentEnd === -1) return false;
|
|
482
|
+
index = commentEnd + 2;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
index++;
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
};
|
|
491
|
+
const skipStringLiteral = (content, start) => {
|
|
492
|
+
const quote = content[start];
|
|
493
|
+
for (let index = start + 1; index < content.length; index++) {
|
|
494
|
+
const char = content[index];
|
|
495
|
+
if (char === "\\") {
|
|
496
|
+
const escaped = content[index + 1];
|
|
497
|
+
if (escaped === "\n" || escaped === "\r") return -1;
|
|
498
|
+
index++;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (char === quote) return index + 1;
|
|
502
|
+
if (char === "\n") return -1;
|
|
503
|
+
}
|
|
504
|
+
return -1;
|
|
505
|
+
};
|
|
506
|
+
const lineIndentAt = (source, offset) => {
|
|
507
|
+
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
508
|
+
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
509
|
+
};
|
|
510
|
+
const lineTerminatorOf = (source) => {
|
|
511
|
+
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
512
|
+
};
|
|
513
|
+
const applyEdits = (source, edits) => {
|
|
514
|
+
if (edits.length === 0) return source;
|
|
515
|
+
const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
516
|
+
for (const edit of ordered) if (!Number.isInteger(edit.start) || !Number.isInteger(edit.end) || edit.start < 0 || edit.end > source.length || edit.start > edit.end) throw new Error(`Invalid source edit [${edit.start}, ${edit.end}) for a source of length ${source.length}`);
|
|
517
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
518
|
+
const previous = ordered[i - 1];
|
|
519
|
+
const current = ordered[i];
|
|
520
|
+
if (current.start < previous.end) throw new Error(`Overlapping source edits: [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
|
|
521
|
+
}
|
|
522
|
+
let result = "";
|
|
523
|
+
let cursor = 0;
|
|
524
|
+
const terminator = lineTerminatorOf(source);
|
|
525
|
+
for (const edit of ordered) {
|
|
526
|
+
const content = edit.indent && edit.content.includes("\n") && hasOnlyLayoutNewlines(edit.content) ? edit.content.replace(/\n/g, `${terminator}${lineIndentAt(source, edit.start)}`) : edit.content;
|
|
527
|
+
result += source.slice(cursor, edit.start) + content;
|
|
528
|
+
cursor = edit.end;
|
|
529
|
+
}
|
|
530
|
+
return result + source.slice(cursor);
|
|
531
|
+
};
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/utils/ast/array-source.ts
|
|
534
|
+
const commaIn = (source, start, end) => {
|
|
535
|
+
let comma = null;
|
|
536
|
+
for (let at = start; at < end; at++) {
|
|
537
|
+
if (/\s/.test(source[at])) continue;
|
|
538
|
+
if (source.startsWith("/*", at)) {
|
|
539
|
+
const close = source.indexOf("*/", at + 2);
|
|
540
|
+
if (close < 0 || close + 2 > end) return null;
|
|
541
|
+
at = close + 1;
|
|
542
|
+
} else if (source.startsWith("//", at)) {
|
|
543
|
+
const newline = source.indexOf("\n", at + 2);
|
|
544
|
+
at = newline < 0 ? end : newline;
|
|
545
|
+
} else if (source[at] === "," && comma === null) comma = at;
|
|
546
|
+
else return null;
|
|
547
|
+
}
|
|
548
|
+
return comma;
|
|
549
|
+
};
|
|
550
|
+
const denseArraySource = (source) => {
|
|
551
|
+
const array = parseArrayExpression(source);
|
|
552
|
+
if (!array || array.elements.some((node) => !import_lib$2.isExpression(node) || node.extra?.parenthesized)) return null;
|
|
553
|
+
const elements = array.elements;
|
|
554
|
+
const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
|
|
555
|
+
if (commas.some((comma) => comma === null)) return null;
|
|
556
|
+
const last = elements.at(-1);
|
|
557
|
+
return {
|
|
558
|
+
array,
|
|
559
|
+
elements,
|
|
560
|
+
commas,
|
|
561
|
+
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
562
|
+
};
|
|
563
|
+
};
|
|
564
|
+
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
565
|
+
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
566
|
+
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
567
|
+
start: node.start,
|
|
568
|
+
end: node.end,
|
|
569
|
+
content: source.slice(next[index].start, next[index].end)
|
|
570
|
+
})));
|
|
571
|
+
const removeArraySource = (source, data, indices) => {
|
|
572
|
+
const edits = [];
|
|
573
|
+
const removedCommas = /* @__PURE__ */ new Set();
|
|
574
|
+
const { elements, commas, trailing } = data;
|
|
575
|
+
for (let index = 0; index < elements.length; index++) {
|
|
576
|
+
if (!indices.has(index)) continue;
|
|
577
|
+
const start = index;
|
|
578
|
+
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
579
|
+
for (let current = start; current <= index; current++) {
|
|
580
|
+
const node = elements[current];
|
|
581
|
+
edits.push({
|
|
582
|
+
start: node.start,
|
|
583
|
+
end: node.end,
|
|
584
|
+
content: ""
|
|
585
|
+
});
|
|
586
|
+
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
587
|
+
}
|
|
588
|
+
if (index === elements.length - 1) {
|
|
589
|
+
if (trailing !== null) removedCommas.add(trailing);
|
|
590
|
+
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const comma of removedCommas) edits.push({
|
|
594
|
+
start: comma,
|
|
595
|
+
end: comma + 1,
|
|
596
|
+
content: ""
|
|
597
|
+
});
|
|
598
|
+
return applyEdits(source, edits);
|
|
599
|
+
};
|
|
600
|
+
const appendArraySource = (source, data, copies) => {
|
|
601
|
+
const last = data.elements.at(-1);
|
|
602
|
+
if (!last || copies.length === 0) return source;
|
|
603
|
+
return applyEdits(source, [{
|
|
604
|
+
start: last.end,
|
|
605
|
+
end: last.end,
|
|
606
|
+
content: `, ${copies.join(", ")}`
|
|
607
|
+
}]);
|
|
608
|
+
};
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/utils/ast/items.ts
|
|
611
|
+
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
612
|
+
const kindOf = (element) => import_lib$2.isObjectExpression(element) ? "object" : "primitive";
|
|
613
|
+
const parseItems = (code) => {
|
|
614
|
+
return elementsOf(code)?.flatMap((node, index) => import_lib$2.isExpression(node) ? [{
|
|
615
|
+
index,
|
|
616
|
+
kind: kindOf(node),
|
|
617
|
+
node
|
|
618
|
+
}] : []) ?? null;
|
|
619
|
+
};
|
|
620
|
+
const resolveRenderLeaf = (render, key) => {
|
|
621
|
+
const leaf = render?.[key];
|
|
622
|
+
return leaf && "type" in leaf ? leaf : null;
|
|
623
|
+
};
|
|
624
|
+
const toTemplateRaw = (value) => {
|
|
625
|
+
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
626
|
+
};
|
|
627
|
+
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
628
|
+
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
629
|
+
const raw = String(value);
|
|
630
|
+
return import_lib$2.templateLiteral([import_lib$2.templateElement({
|
|
631
|
+
raw: toTemplateRaw(raw),
|
|
632
|
+
cooked: raw
|
|
633
|
+
}, true)], []);
|
|
634
|
+
}
|
|
635
|
+
if (renderLeaf?.type === "jsx" || import_lib$2.isJSXElement(current) || import_lib$2.isJSXFragment(current)) {
|
|
636
|
+
const trimmed = String(value).trim();
|
|
637
|
+
if (!trimmed.startsWith("<")) return import_lib$2.stringLiteral(trimmed);
|
|
638
|
+
try {
|
|
639
|
+
return (0, import_lib$1.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
640
|
+
} catch {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (declaredType === "array" || declaredType === "object") {
|
|
645
|
+
if (typeof value === "string") try {
|
|
646
|
+
return (0, import_lib$1.parseExpression)(value, { plugins: ["jsx", "typescript"] });
|
|
647
|
+
} catch {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (!isLosslesslyEvaluable(current)) return;
|
|
651
|
+
return valueToExpression(value) ?? void 0;
|
|
652
|
+
}
|
|
653
|
+
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
654
|
+
};
|
|
655
|
+
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
656
|
+
const elements = elementsOf(code);
|
|
657
|
+
const element = elements?.[index];
|
|
658
|
+
if (!elements || !import_lib$2.isObjectExpression(element)) return null;
|
|
659
|
+
const target = element.properties.find((property) => import_lib$2.isObjectProperty(property) && import_lib$2.isIdentifier(property.key) && property.key.name === key);
|
|
660
|
+
if (!target) return null;
|
|
661
|
+
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
662
|
+
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
663
|
+
if (!nextValue) return null;
|
|
664
|
+
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || import_lib$2.isJSXElement(nextValue) || import_lib$2.isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
665
|
+
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
666
|
+
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
667
|
+
return applyEdits(code, [{
|
|
668
|
+
start: target.value.start,
|
|
669
|
+
end: target.value.end,
|
|
670
|
+
content
|
|
671
|
+
}]);
|
|
672
|
+
};
|
|
673
|
+
const updateArrayItemValue = (code, index, value) => {
|
|
674
|
+
const elements = elementsOf(code);
|
|
675
|
+
const element = elements?.[index];
|
|
676
|
+
if (!elements || !import_lib$2.isExpression(element)) return null;
|
|
677
|
+
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
678
|
+
if (!nextValue) return null;
|
|
679
|
+
return applyEdits(code, [{
|
|
680
|
+
start: element.start,
|
|
681
|
+
end: element.end,
|
|
682
|
+
content: generateCode(nextValue)
|
|
683
|
+
}]);
|
|
684
|
+
};
|
|
685
|
+
const moveArrayItem = (code, from, to) => {
|
|
686
|
+
const data = denseArraySource(code);
|
|
687
|
+
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
688
|
+
const next = [...data.elements];
|
|
689
|
+
const [moved] = next.splice(from, 1);
|
|
690
|
+
next.splice(to, 0, moved);
|
|
691
|
+
return reorderArraySource(code, data.elements, next);
|
|
692
|
+
};
|
|
693
|
+
const moveArrayItems = (code, indices, direction) => {
|
|
694
|
+
const data = denseArraySource(code);
|
|
695
|
+
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
696
|
+
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
697
|
+
return {
|
|
698
|
+
code: reorderArraySource(code, data.elements, next.items),
|
|
699
|
+
indices: next.indices
|
|
700
|
+
};
|
|
701
|
+
};
|
|
702
|
+
const removeArrayItems = (code, indices, kind) => {
|
|
703
|
+
const data = denseArraySource(code);
|
|
704
|
+
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
705
|
+
const remaining = removeIndices(data.elements, indices);
|
|
706
|
+
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
707
|
+
return removeArraySource(code, data, indices);
|
|
708
|
+
};
|
|
709
|
+
const copyItem = (code, element, generateId, usedIds) => {
|
|
710
|
+
const edits = [];
|
|
711
|
+
let unsupported = false;
|
|
712
|
+
const reserveId = (id) => {
|
|
713
|
+
if (usedIds.has(id)) unsupported = true;
|
|
714
|
+
usedIds.add(id);
|
|
715
|
+
return JSON.stringify(id);
|
|
716
|
+
};
|
|
717
|
+
import_lib$2.traverseFast(element, (node) => {
|
|
718
|
+
if (!import_lib$2.isJSXOpeningElement(node)) return;
|
|
719
|
+
const ids = node.attributes.filter((attr) => import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
720
|
+
const attr = ids[0];
|
|
721
|
+
if (ids.length > 1 || attr && (!import_lib$2.isStringLiteral(attr.value) || node.attributes.slice(node.attributes.indexOf(attr) + 1).some((a) => import_lib$2.isJSXSpreadAttribute(a)))) {
|
|
722
|
+
unsupported = true;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (attr && import_lib$2.isStringLiteral(attr.value)) edits.push({
|
|
726
|
+
start: attr.value.start,
|
|
727
|
+
end: attr.value.end,
|
|
728
|
+
content: reserveId(generateId())
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
if (import_lib$2.isObjectExpression(element)) {
|
|
732
|
+
const keys = element.properties.filter((prop) => import_lib$2.isObjectProperty(prop) && !prop.computed && (import_lib$2.isIdentifier(prop.key, { name: "key" }) || import_lib$2.isStringLiteral(prop.key, { value: "key" })));
|
|
733
|
+
const key = keys[0];
|
|
734
|
+
if (keys.length > 1 || key && (!import_lib$2.isStringLiteral(key.value) || element.properties.slice(element.properties.indexOf(key) + 1).some((prop) => import_lib$2.isSpreadElement(prop) || "computed" in prop && prop.computed))) return null;
|
|
735
|
+
if (key && import_lib$2.isStringLiteral(key.value)) edits.push({
|
|
736
|
+
start: key.value.start,
|
|
737
|
+
end: key.value.end,
|
|
738
|
+
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
if (unsupported) return null;
|
|
742
|
+
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
743
|
+
...edit,
|
|
744
|
+
start: edit.start - element.start,
|
|
745
|
+
end: edit.end - element.start
|
|
746
|
+
})));
|
|
747
|
+
};
|
|
748
|
+
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
749
|
+
const data = denseArraySource(code);
|
|
750
|
+
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
751
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
752
|
+
import_lib$2.traverseFast(data.array, (node) => {
|
|
753
|
+
if (import_lib$2.isJSXAttribute(node) && import_lib$2.isJSXIdentifier(node.name, { name: "data-id" }) && import_lib$2.isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
754
|
+
else if (import_lib$2.isObjectProperty(node) && !node.computed && (import_lib$2.isIdentifier(node.key, { name: "key" }) || import_lib$2.isStringLiteral(node.key, { value: "key" })) && import_lib$2.isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
755
|
+
});
|
|
756
|
+
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
757
|
+
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
758
|
+
};
|
|
759
|
+
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
760
|
+
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
761
|
+
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
762
|
+
};
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region src/utils/ast/extract.ts
|
|
765
|
+
const collectText = (children) => {
|
|
766
|
+
return children.filter((c) => import_lib$2.isJSXText(c)).map((c) => c.value.trim()).filter((v) => v.length).join(" ");
|
|
767
|
+
};
|
|
768
|
+
const parseJSXName = (tagName) => {
|
|
769
|
+
const parts = tagName.split(".");
|
|
770
|
+
if (parts.length === 1) return import_lib$2.jsxIdentifier(parts[0]);
|
|
771
|
+
let expr = import_lib$2.jsxIdentifier(parts[0]);
|
|
772
|
+
for (let i = 1; i < parts.length; i++) expr = import_lib$2.jsxMemberExpression(expr, import_lib$2.jsxIdentifier(parts[i]));
|
|
773
|
+
return expr;
|
|
774
|
+
};
|
|
775
|
+
const extractCache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
776
|
+
const extractAttributes = (attributes, source) => {
|
|
777
|
+
const allAttrs = [];
|
|
778
|
+
const dataAttrs = [];
|
|
779
|
+
for (const attr of attributes) {
|
|
780
|
+
if (!import_lib$2.isJSXAttribute(attr) || !import_lib$2.isJSXIdentifier(attr.name)) continue;
|
|
781
|
+
const name = attr.name.name;
|
|
782
|
+
let value = null;
|
|
783
|
+
let isStringLiteral = false;
|
|
784
|
+
if (attr.value) {
|
|
785
|
+
if (import_lib$2.isStringLiteral(attr.value)) {
|
|
786
|
+
value = attr.value.value;
|
|
787
|
+
isStringLiteral = true;
|
|
788
|
+
} else if (import_lib$2.isJSXExpressionContainer(attr.value)) try {
|
|
789
|
+
const expression = attr.value.expression;
|
|
790
|
+
const structural = import_lib$2.isArrayExpression(unwrapExpression(expression)) || import_lib$2.isObjectExpression(unwrapExpression(expression));
|
|
791
|
+
value = source && structural && expression.start != null && expression.end != null ? source.slice(expression.start, expression.end) : generateCode(expression);
|
|
792
|
+
isStringLiteral = false;
|
|
793
|
+
} catch {
|
|
794
|
+
value = null;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const entry = {
|
|
798
|
+
name,
|
|
799
|
+
value,
|
|
800
|
+
isStringLiteral
|
|
801
|
+
};
|
|
802
|
+
allAttrs.push(entry);
|
|
803
|
+
if (name.startsWith("data-")) dataAttrs.push(entry);
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
allAttrs,
|
|
807
|
+
dataAttrs
|
|
808
|
+
};
|
|
809
|
+
};
|
|
810
|
+
const buildChildElements = (node) => {
|
|
811
|
+
const childElements = [];
|
|
812
|
+
if (node.textContent) childElements.push(import_lib$2.jsxText(node.textContent));
|
|
813
|
+
node.children?.forEach((child) => {
|
|
814
|
+
const childJSX = nodeToJSX(child);
|
|
815
|
+
if (childJSX) childElements.push(childJSX);
|
|
816
|
+
});
|
|
817
|
+
return childElements;
|
|
818
|
+
};
|
|
819
|
+
const nodeToJSX = (node) => {
|
|
820
|
+
try {
|
|
821
|
+
if (node.isFragment) {
|
|
822
|
+
const childElements = buildChildElements(node);
|
|
823
|
+
return import_lib$2.jsxFragment(import_lib$2.jsxOpeningFragment(), import_lib$2.jsxClosingFragment(), childElements);
|
|
824
|
+
}
|
|
825
|
+
if (node.tagName === "div" && node.dataAttributes.some((attr) => attr.name === "data-item")) {
|
|
826
|
+
if (node.children) return nodeToJSX(node.children[0]);
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
const attributes = node.attributes.map((attr) => {
|
|
830
|
+
const attrName = import_lib$2.jsxIdentifier(attr.name);
|
|
831
|
+
if (!attr.value) return import_lib$2.jsxAttribute(attrName, null);
|
|
832
|
+
return import_lib$2.jsxAttribute(attrName, attrValue(attr));
|
|
833
|
+
});
|
|
834
|
+
const elementName = parseJSXName(node.tagName);
|
|
835
|
+
const openingElement = import_lib$2.jsxOpeningElement(elementName, attributes);
|
|
836
|
+
const closingElement = import_lib$2.jsxClosingElement(elementName);
|
|
837
|
+
const children = buildChildElements(node);
|
|
838
|
+
return import_lib$2.jsxElement(openingElement, closingElement, children, false);
|
|
839
|
+
} catch (error) {
|
|
840
|
+
console.error("❌ DataAttrNode to JSX conversion error:", error);
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
const createWrapperNode = (textContent, children) => ({
|
|
845
|
+
tagName: "div",
|
|
846
|
+
id: nanoid(6),
|
|
847
|
+
attributes: [{
|
|
848
|
+
name: DATA_ATTR.ITEM,
|
|
849
|
+
value: "true"
|
|
850
|
+
}],
|
|
851
|
+
dataAttributes: [{
|
|
852
|
+
name: DATA_ATTR.ITEM,
|
|
853
|
+
value: "true"
|
|
854
|
+
}],
|
|
855
|
+
textContent,
|
|
856
|
+
children
|
|
857
|
+
});
|
|
858
|
+
const createFragmentNode = (textContent, children) => ({
|
|
859
|
+
tagName: "",
|
|
860
|
+
id: nanoid(6),
|
|
861
|
+
attributes: [],
|
|
862
|
+
dataAttributes: [],
|
|
863
|
+
textContent,
|
|
864
|
+
children,
|
|
865
|
+
isFragment: true
|
|
866
|
+
});
|
|
867
|
+
const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, source) => {
|
|
868
|
+
const jsxChildren = jsxElement.children.filter((child) => import_lib$2.isJSXElement(child) || import_lib$2.isJSXFragment(child));
|
|
869
|
+
if (!jsxChildren.length) return;
|
|
870
|
+
const childrenNodes = [];
|
|
871
|
+
jsxChildren.forEach((child) => {
|
|
872
|
+
if (import_lib$2.isJSXElement(child)) {
|
|
873
|
+
const childResults = extractFromNode(child, processedNodes, source);
|
|
874
|
+
if (shouldWrap) {
|
|
875
|
+
const wrapperNode = createWrapperNode(collectText(child.children), childResults);
|
|
876
|
+
wrapperNode.source = source?.slice(child.start, child.end);
|
|
877
|
+
const dataId = childResults[0]?.dataAttributes.find((attr) => attr.name === DATA_ATTR.ID)?.value;
|
|
878
|
+
if (dataId) wrapperNode.id = `child:${dataId}`;
|
|
879
|
+
childrenNodes.push(wrapperNode);
|
|
880
|
+
} else childrenNodes.push(...childResults);
|
|
881
|
+
} else if (import_lib$2.isJSXFragment(child)) {
|
|
882
|
+
processedNodes?.add(child);
|
|
883
|
+
const fragmentChildren = [];
|
|
884
|
+
child.children.forEach((fragmentChild) => {
|
|
885
|
+
if (import_lib$2.isJSXElement(fragmentChild)) {
|
|
886
|
+
const childResults = extractFromNode(fragmentChild, processedNodes, source);
|
|
887
|
+
fragmentChildren.push(...childResults);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
if (fragmentChildren.length) {
|
|
891
|
+
const fragmentNode = createFragmentNode(collectText(child.children), fragmentChildren);
|
|
892
|
+
fragmentNode.source = source?.slice(child.start, child.end);
|
|
893
|
+
childrenNodes.push(fragmentNode);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
return childrenNodes.length ? childrenNodes : void 0;
|
|
898
|
+
};
|
|
899
|
+
const skipItemsChildren = (jsxElement, processedNodes, propertyName = "items") => {
|
|
900
|
+
const itemsAttr = jsxElement.openingElement.attributes.find((attr) => import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name) && attr.name.name === propertyName);
|
|
901
|
+
if (!itemsAttr || !import_lib$2.isJSXAttribute(itemsAttr)) return;
|
|
902
|
+
if (itemsAttr.value && import_lib$2.isJSXExpressionContainer(itemsAttr.value) && import_lib$2.isArrayExpression(itemsAttr.value.expression)) itemsAttr.value.expression.elements.forEach((element) => {
|
|
903
|
+
if (import_lib$2.isObjectExpression(element)) element.properties.forEach((prop) => {
|
|
904
|
+
if (import_lib$2.isObjectProperty(prop) && import_lib$2.isIdentifier(prop.key) && import_lib$2.isJSXElement(prop.value)) markProcessedJSX(prop.value, processedNodes);
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
};
|
|
908
|
+
const markProcessedJSX = (node, processedNodes) => {
|
|
909
|
+
if (import_lib$2.isJSXElement(node) || import_lib$2.isJSXFragment(node)) {
|
|
910
|
+
processedNodes.add(node);
|
|
911
|
+
node.children.forEach((child) => markProcessedJSX(child, processedNodes));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (import_lib$2.isJSXExpressionContainer(node) || import_lib$2.isParenthesizedExpression(node)) markProcessedJSX(node.expression, processedNodes);
|
|
915
|
+
};
|
|
916
|
+
const getBindingExpression = (opening) => {
|
|
917
|
+
for (const attr of opening.attributes) if (import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING && attr.value && import_lib$2.isJSXExpressionContainer(attr.value)) {
|
|
918
|
+
const expression = unwrapExpression(attr.value.expression);
|
|
919
|
+
if (import_lib$2.isArrayExpression(expression)) return expression;
|
|
920
|
+
}
|
|
921
|
+
return null;
|
|
922
|
+
};
|
|
923
|
+
const readNodeBindingInfo = (node, source) => {
|
|
924
|
+
const opening = node.openingElement;
|
|
925
|
+
const tagName = getJSXTagName(opening);
|
|
926
|
+
const { allAttrs, dataAttrs } = extractAttributes(opening.attributes, source);
|
|
927
|
+
const bindingAttr = dataAttrs.find((attr) => attr.name === DATA_ATTR.BINDING);
|
|
928
|
+
const bindingExpr = getBindingExpression(opening);
|
|
929
|
+
const bindings = bindingExpr ? parseBindingExpression(bindingExpr) : bindingAttr?.value ? parseBinding(bindingAttr.value) : [];
|
|
930
|
+
const childrenBinding = bindings.find((b) => b.property === BINDING_PROP.CHILDREN);
|
|
931
|
+
const arrayBindings = bindings.filter((b) => b.property === BINDING_PROP.ITEMS || b.type === "array");
|
|
932
|
+
const innerHtmlBinding = bindings.find((b) => b.property === BINDING_PROP.INNER_HTML);
|
|
933
|
+
let rawChildren;
|
|
934
|
+
if (innerHtmlBinding && node.children.length > 0) rawChildren = node.children.map((child) => generateCode(child)).join("").trim();
|
|
935
|
+
return {
|
|
936
|
+
tagName,
|
|
937
|
+
allAttrs,
|
|
938
|
+
dataAttrs,
|
|
939
|
+
bindings,
|
|
940
|
+
childrenBinding,
|
|
941
|
+
arrayBindings,
|
|
942
|
+
rawChildren
|
|
943
|
+
};
|
|
944
|
+
};
|
|
945
|
+
const parseToNodes = (raw) => {
|
|
946
|
+
const wrapped = wrap(raw);
|
|
947
|
+
const ast = (0, import_lib$1.parse)(wrapped, {
|
|
948
|
+
sourceType: "module",
|
|
949
|
+
plugins: ["jsx", "typescript"],
|
|
950
|
+
errorRecovery: true
|
|
951
|
+
});
|
|
952
|
+
const results = [];
|
|
953
|
+
const processedNodes = /* @__PURE__ */ new WeakSet();
|
|
954
|
+
traverse(ast, { JSXElement(path) {
|
|
955
|
+
if (processedNodes.has(path.node)) return;
|
|
956
|
+
const { tagName, allAttrs, dataAttrs, bindings, childrenBinding, arrayBindings, rawChildren } = readNodeBindingInfo(path.node, wrapped);
|
|
957
|
+
if (!tagName || !dataAttrs.length) return;
|
|
958
|
+
let childrenNodes;
|
|
959
|
+
if (childrenBinding) childrenNodes = processChildrenBinding(path.node, processedNodes, true, wrapped);
|
|
960
|
+
for (const arrayBinding of arrayBindings) skipItemsChildren(path.node, processedNodes, arrayBinding.property);
|
|
961
|
+
results.push({
|
|
962
|
+
tagName,
|
|
963
|
+
attributes: allAttrs,
|
|
964
|
+
dataAttributes: dataAttrs,
|
|
965
|
+
textContent: collectText(path.node.children),
|
|
966
|
+
rawChildren,
|
|
967
|
+
children: childrenNodes,
|
|
968
|
+
bindings,
|
|
969
|
+
loc: path.node.loc ? {
|
|
970
|
+
start: {
|
|
971
|
+
line: path.node.loc.start.line,
|
|
972
|
+
column: path.node.loc.start.column
|
|
973
|
+
},
|
|
974
|
+
end: {
|
|
975
|
+
line: path.node.loc.end.line,
|
|
976
|
+
column: path.node.loc.end.column
|
|
977
|
+
}
|
|
978
|
+
} : void 0
|
|
979
|
+
});
|
|
980
|
+
} });
|
|
981
|
+
return results;
|
|
982
|
+
};
|
|
983
|
+
function extract(raw) {
|
|
984
|
+
if (extractCache.has(raw)) return extractCache.get(raw);
|
|
985
|
+
const results = parseToNodes(raw);
|
|
986
|
+
extractCache.set(raw, results);
|
|
987
|
+
return results;
|
|
988
|
+
}
|
|
989
|
+
function extractFromNode(node, processedNodes, source) {
|
|
990
|
+
processedNodes?.add(node);
|
|
991
|
+
const { tagName, allAttrs, dataAttrs, bindings, childrenBinding, rawChildren } = readNodeBindingInfo(node, source);
|
|
992
|
+
let childrenNodes;
|
|
993
|
+
if (childrenBinding) childrenNodes = processChildrenBinding(node, processedNodes, true, source);
|
|
994
|
+
if (!childrenNodes) childrenNodes = processChildrenBinding(node, processedNodes, false, source);
|
|
995
|
+
return [{
|
|
996
|
+
tagName,
|
|
997
|
+
attributes: allAttrs,
|
|
998
|
+
dataAttributes: dataAttrs,
|
|
999
|
+
textContent: collectText(node.children),
|
|
1000
|
+
rawChildren,
|
|
1001
|
+
children: childrenNodes,
|
|
1002
|
+
bindings
|
|
1003
|
+
}];
|
|
1004
|
+
}
|
|
1005
|
+
function clearExtractCache() {
|
|
1006
|
+
extractCache.clear();
|
|
1007
|
+
}
|
|
1008
|
+
registerEditorCache(clearExtractCache);
|
|
1009
|
+
//#endregion
|
|
1010
|
+
//#region src/utils/ast/children.ts
|
|
1011
|
+
const modeledSignatures = (nodes) => nodes.map((node) => {
|
|
1012
|
+
const jsx = nodeToJSX(node);
|
|
1013
|
+
if (!jsx) throw new Error("Invalid child node");
|
|
1014
|
+
return generateCode(jsx);
|
|
1015
|
+
});
|
|
1016
|
+
const getChildrenSignatures = (nodes) => {
|
|
1017
|
+
const modeled = modeledSignatures(nodes);
|
|
1018
|
+
return nodes.map((node, index) => JSON.stringify([node.source ?? null, modeled[index]]));
|
|
1019
|
+
};
|
|
1020
|
+
const indicesAreValid = (indices, count) => Array.isArray(indices) && new Set(indices).size === indices.length && indices.every((index) => Number.isInteger(index) && index >= 0 && index < count);
|
|
1021
|
+
const copySource = (source, usedIds) => {
|
|
1022
|
+
const node = (0, import_lib$1.parseExpression)(source, { plugins: ["jsx", "typescript"] });
|
|
1023
|
+
const edits = [];
|
|
1024
|
+
const newId = () => {
|
|
1025
|
+
let id = nanoid(12);
|
|
1026
|
+
while (usedIds.has(id)) id = nanoid(12);
|
|
1027
|
+
usedIds.add(id);
|
|
1028
|
+
return id;
|
|
1029
|
+
};
|
|
1030
|
+
import_lib$2.traverseFast(node, (child) => {
|
|
1031
|
+
if (!import_lib$2.isJSXOpeningElement(child)) return;
|
|
1032
|
+
const ids = child.attributes.filter((attr) => import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
1033
|
+
if (ids.length > 1) throw new Error("Duplicate data-id attributes");
|
|
1034
|
+
const attr = ids[0];
|
|
1035
|
+
if (attr && child.attributes.slice(child.attributes.indexOf(attr) + 1).some((attribute) => import_lib$2.isJSXSpreadAttribute(attribute))) throw new Error("A spread may override the copied data-id");
|
|
1036
|
+
if (attr) {
|
|
1037
|
+
const value = attr.value;
|
|
1038
|
+
if (!import_lib$2.isStringLiteral(value) || value.start == null || value.end == null) throw new Error("Cannot copy a dynamic data-id");
|
|
1039
|
+
edits.push({
|
|
1040
|
+
start: value.start,
|
|
1041
|
+
end: value.end,
|
|
1042
|
+
content: JSON.stringify(newId())
|
|
1043
|
+
});
|
|
1044
|
+
} else {
|
|
1045
|
+
const at = child.attributes.at(-1)?.end ?? child.name.end;
|
|
1046
|
+
if (at == null) throw new Error("Missing opening element span");
|
|
1047
|
+
edits.push({
|
|
1048
|
+
start: at,
|
|
1049
|
+
end: at,
|
|
1050
|
+
content: ` data-id="${newId()}"`
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
return applyEdits(source, edits);
|
|
1055
|
+
};
|
|
1056
|
+
const editChildrenSource = (source, parent, input) => {
|
|
1057
|
+
try {
|
|
1058
|
+
if (!parent.closingElement || parent.start == null || parent.end == null) return null;
|
|
1059
|
+
const value = typeof input === "string" ? JSON.parse(input) : input;
|
|
1060
|
+
const children = parent.children.filter((child) => import_lib$2.isJSXElement(child) || import_lib$2.isJSXFragment(child));
|
|
1061
|
+
const raw = children.map((child) => source.slice(child.start, child.end));
|
|
1062
|
+
const models = extract(source.slice(parent.start, parent.end))[0]?.children ?? [];
|
|
1063
|
+
if (models.length !== children.length) return null;
|
|
1064
|
+
const signatures = modeledSignatures(models);
|
|
1065
|
+
const replaceSlots = (next) => {
|
|
1066
|
+
const edits = children.map((child, index) => ({
|
|
1067
|
+
start: child.start,
|
|
1068
|
+
end: child.end,
|
|
1069
|
+
content: next[index] ?? ""
|
|
1070
|
+
}));
|
|
1071
|
+
if (next.length > children.length) {
|
|
1072
|
+
const at = parent.closingElement.start;
|
|
1073
|
+
edits.push({
|
|
1074
|
+
start: at,
|
|
1075
|
+
end: at,
|
|
1076
|
+
content: next.slice(children.length).join("")
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
return edits;
|
|
1080
|
+
};
|
|
1081
|
+
if (Array.isArray(value)) {
|
|
1082
|
+
const desired = modeledSignatures(value);
|
|
1083
|
+
const lossless = raw.every((text, index) => generateCode((0, import_lib$1.parseExpression)(text, { plugins: ["jsx", "typescript"] })) === signatures[index]);
|
|
1084
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
1085
|
+
const next = desired.map((signature, position) => {
|
|
1086
|
+
const index = signatures.findIndex((candidate, i) => candidate === signature && !consumed.has(i) && (value[position].source === void 0 || value[position].source === raw[i]));
|
|
1087
|
+
if (index >= 0) {
|
|
1088
|
+
consumed.add(index);
|
|
1089
|
+
return raw[index];
|
|
1090
|
+
}
|
|
1091
|
+
if (!lossless || value[position].source !== void 0 && !raw.includes(value[position].source)) throw new Error("Use children commands to copy or edit unsupported source");
|
|
1092
|
+
return signature;
|
|
1093
|
+
});
|
|
1094
|
+
const countIds = (text) => {
|
|
1095
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1096
|
+
import_lib$2.traverseFast((0, import_lib$1.parseExpression)(text, { plugins: ["jsx", "typescript"] }), (node) => {
|
|
1097
|
+
if (import_lib$2.isJSXAttribute(node) && import_lib$2.isJSXIdentifier(node.name, { name: "data-id" }) && import_lib$2.isStringLiteral(node.value)) counts.set(node.value.value, (counts.get(node.value.value) ?? 0) + 1);
|
|
1098
|
+
});
|
|
1099
|
+
return counts;
|
|
1100
|
+
};
|
|
1101
|
+
const previousIds = countIds(source);
|
|
1102
|
+
if ([...countIds(applyEdits(source, replaceSlots(next)))].some(([id, count]) => count > 1 && count > (previousIds.get(id) ?? 0))) return null;
|
|
1103
|
+
return replaceSlots(next);
|
|
1104
|
+
}
|
|
1105
|
+
const request = value;
|
|
1106
|
+
if (request?.kind !== "children-edit" || !Array.isArray(request.expected) || request.expected.length !== signatures.length || !getChildrenSignatures(models).every((signature, index) => signature === request.expected[index]) || !request.action) return null;
|
|
1107
|
+
const { action } = request;
|
|
1108
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
1109
|
+
import_lib$2.traverseFast(parent, (node) => {
|
|
1110
|
+
if (import_lib$2.isJSXAttribute(node) && import_lib$2.isJSXIdentifier(node.name, { name: "data-id" }) && import_lib$2.isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
1111
|
+
});
|
|
1112
|
+
switch (action.type) {
|
|
1113
|
+
case "move": {
|
|
1114
|
+
if (!indicesAreValid([action.from], raw.length) || !indicesAreValid([action.to], raw.length)) return null;
|
|
1115
|
+
const next = [...raw];
|
|
1116
|
+
const [moved] = next.splice(action.from, 1);
|
|
1117
|
+
next.splice(action.to, 0, moved);
|
|
1118
|
+
return replaceSlots(next);
|
|
1119
|
+
}
|
|
1120
|
+
case "move-selected":
|
|
1121
|
+
if (!indicesAreValid(action.indices, raw.length) || !["up", "down"].includes(action.direction)) return null;
|
|
1122
|
+
return replaceSlots(moveSelectedIndices(raw, new Set(action.indices), action.direction).items);
|
|
1123
|
+
case "remove":
|
|
1124
|
+
if (!indicesAreValid(action.indices, raw.length)) return null;
|
|
1125
|
+
return action.indices.map((index) => ({
|
|
1126
|
+
start: children[index].start,
|
|
1127
|
+
end: children[index].end,
|
|
1128
|
+
content: ""
|
|
1129
|
+
}));
|
|
1130
|
+
case "duplicate": {
|
|
1131
|
+
if (!indicesAreValid(action.indices, raw.length)) return null;
|
|
1132
|
+
const content = [...action.indices].sort((a, b) => a - b).map((index) => copySource(raw[index], usedIds)).join("");
|
|
1133
|
+
const at = parent.closingElement.start;
|
|
1134
|
+
return [{
|
|
1135
|
+
start: at,
|
|
1136
|
+
end: at,
|
|
1137
|
+
content
|
|
1138
|
+
}];
|
|
1139
|
+
}
|
|
1140
|
+
case "append": {
|
|
1141
|
+
const content = copySource(raw[0] ?? "<div></div>", usedIds);
|
|
1142
|
+
const at = parent.closingElement.start;
|
|
1143
|
+
return [{
|
|
1144
|
+
start: at,
|
|
1145
|
+
end: at,
|
|
1146
|
+
content
|
|
1147
|
+
}];
|
|
1148
|
+
}
|
|
1149
|
+
default: return null;
|
|
1150
|
+
}
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/utils/ast/update.ts
|
|
1157
|
+
const childrenRange = (element) => {
|
|
1158
|
+
const { openingElement, closingElement } = element;
|
|
1159
|
+
if (!closingElement || openingElement.end == null || closingElement.start == null) return null;
|
|
1160
|
+
return {
|
|
1161
|
+
start: openingElement.end,
|
|
1162
|
+
end: closingElement.start
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
1165
|
+
const findAttribute = (opening, propertyName) => {
|
|
1166
|
+
return opening.attributes.find((attr) => import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name) && attr.name.name === propertyName);
|
|
1167
|
+
};
|
|
1168
|
+
const attributeInsertPoint = (opening) => {
|
|
1169
|
+
return opening.attributes[opening.attributes.length - 1]?.end ?? opening.name.end ?? null;
|
|
1170
|
+
};
|
|
1171
|
+
const editInnerText = (source, element, value) => {
|
|
1172
|
+
const range = childrenRange(element);
|
|
1173
|
+
if (!range) return [];
|
|
1174
|
+
const [only] = element.children;
|
|
1175
|
+
if (element.children.length === 1 && import_lib$2.isJSXText(only) && only.start != null && only.end != null) {
|
|
1176
|
+
const raw = source.slice(only.start, only.end);
|
|
1177
|
+
if (raw.trim() !== "") {
|
|
1178
|
+
const leading = raw.length - raw.trimStart().length;
|
|
1179
|
+
const trailing = raw.length - raw.trimEnd().length;
|
|
1180
|
+
return [{
|
|
1181
|
+
start: only.start + leading,
|
|
1182
|
+
end: only.end - trailing,
|
|
1183
|
+
content: value
|
|
1184
|
+
}];
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const edits = [];
|
|
1188
|
+
for (const child of element.children) if (import_lib$2.isJSXText(child) && child.start != null && child.end != null) edits.push({
|
|
1189
|
+
start: child.start,
|
|
1190
|
+
end: child.end,
|
|
1191
|
+
content: ""
|
|
1192
|
+
});
|
|
1193
|
+
edits.push({
|
|
1194
|
+
start: range.end,
|
|
1195
|
+
end: range.end,
|
|
1196
|
+
content: value
|
|
1197
|
+
});
|
|
1198
|
+
return edits;
|
|
1199
|
+
};
|
|
1200
|
+
const editInnerHTML = (element, value) => {
|
|
1201
|
+
const range = childrenRange(element);
|
|
1202
|
+
if (!range) return [];
|
|
1203
|
+
return [{
|
|
1204
|
+
start: range.start,
|
|
1205
|
+
end: range.end,
|
|
1206
|
+
content: value
|
|
1207
|
+
}];
|
|
1208
|
+
};
|
|
1209
|
+
const editRichtext = (element, value) => {
|
|
1210
|
+
const edits = [];
|
|
1211
|
+
const range = childrenRange(element);
|
|
1212
|
+
if (range && range.start !== range.end) edits.push({
|
|
1213
|
+
start: range.start,
|
|
1214
|
+
end: range.end,
|
|
1215
|
+
content: ""
|
|
1216
|
+
});
|
|
1217
|
+
const opening = element.openingElement;
|
|
1218
|
+
const attribute = import_lib$2.jsxAttribute(import_lib$2.jsxIdentifier("dangerouslySetInnerHTML"), import_lib$2.jsxExpressionContainer(import_lib$2.objectExpression([import_lib$2.objectProperty(import_lib$2.identifier("__html"), import_lib$2.stringLiteral(value))])));
|
|
1219
|
+
const content = generateCode(attribute);
|
|
1220
|
+
const existing = findAttribute(opening, "dangerouslySetInnerHTML");
|
|
1221
|
+
if (existing && existing.start != null && existing.end != null) {
|
|
1222
|
+
edits.push({
|
|
1223
|
+
start: existing.start,
|
|
1224
|
+
end: existing.end,
|
|
1225
|
+
content,
|
|
1226
|
+
indent: true
|
|
1227
|
+
});
|
|
1228
|
+
return edits;
|
|
1229
|
+
}
|
|
1230
|
+
const insertAt = attributeInsertPoint(opening);
|
|
1231
|
+
if (insertAt == null) return null;
|
|
1232
|
+
edits.push({
|
|
1233
|
+
start: insertAt,
|
|
1234
|
+
end: insertAt,
|
|
1235
|
+
content: ` ${content}`,
|
|
1236
|
+
indent: true
|
|
1237
|
+
});
|
|
1238
|
+
return edits;
|
|
1239
|
+
};
|
|
1240
|
+
const editJsxAttribute = (opening, propertyName, value) => {
|
|
1241
|
+
const attribute = findAttribute(opening, propertyName);
|
|
1242
|
+
if (!attribute) return null;
|
|
1243
|
+
const content = `{${String(value).trim()}}`;
|
|
1244
|
+
if (attribute.value?.start != null && attribute.value.end != null) return [{
|
|
1245
|
+
start: attribute.value.start,
|
|
1246
|
+
end: attribute.value.end,
|
|
1247
|
+
content
|
|
1248
|
+
}];
|
|
1249
|
+
const insertAt = attribute.name.end;
|
|
1250
|
+
if (insertAt == null) return null;
|
|
1251
|
+
return [{
|
|
1252
|
+
start: insertAt,
|
|
1253
|
+
end: insertAt,
|
|
1254
|
+
content: `=${content}`
|
|
1255
|
+
}];
|
|
1256
|
+
};
|
|
1257
|
+
const holdsStructuralExpression = (value) => import_lib$2.isJSXExpressionContainer(value) && (import_lib$2.isArrayExpression(unwrapExpression(value.expression)) || import_lib$2.isObjectExpression(unwrapExpression(value.expression)));
|
|
1258
|
+
const structuralSource = (value) => {
|
|
1259
|
+
try {
|
|
1260
|
+
const expression = (0, import_lib$1.parseExpression)(value.trim(), { plugins: ["jsx", "typescript"] });
|
|
1261
|
+
return import_lib$2.isArrayExpression(unwrapExpression(expression)) || import_lib$2.isObjectExpression(unwrapExpression(expression)) ? expression : null;
|
|
1262
|
+
} catch {
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
const buildAttributeValue = (value, type, current) => {
|
|
1267
|
+
if (type === "array" || type === "object") {
|
|
1268
|
+
if (typeof value === "string") try {
|
|
1269
|
+
return import_lib$2.jsxExpressionContainer((0, import_lib$1.parseExpression)(value.trim(), { plugins: ["jsx", "typescript"] }));
|
|
1270
|
+
} catch {
|
|
1271
|
+
return import_lib$2.stringLiteral(value);
|
|
1272
|
+
}
|
|
1273
|
+
const expr = valueToExpression(value);
|
|
1274
|
+
return expr ? import_lib$2.jsxExpressionContainer(expr) : import_lib$2.stringLiteral("");
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof value === "string") {
|
|
1277
|
+
if (holdsStructuralExpression(current) && !(type && STRING_VALUED_TYPES.has(type))) {
|
|
1278
|
+
const structural = structuralSource(value);
|
|
1279
|
+
if (structural) return import_lib$2.jsxExpressionContainer(structural);
|
|
1280
|
+
}
|
|
1281
|
+
return import_lib$2.stringLiteral(value);
|
|
1282
|
+
}
|
|
1283
|
+
const expr = valueToExpression(value);
|
|
1284
|
+
return expr ? import_lib$2.jsxExpressionContainer(expr) : import_lib$2.stringLiteral(String(value));
|
|
1285
|
+
};
|
|
1286
|
+
const editAttribute = (opening, propertyName, value, type) => {
|
|
1287
|
+
const attribute = findAttribute(opening, propertyName);
|
|
1288
|
+
if (!attribute || attribute.start == null || attribute.end == null) return null;
|
|
1289
|
+
if (typeof value === "string" && (type === "array" || type === "object" || holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)))) {
|
|
1290
|
+
const expression = structuralSource(value);
|
|
1291
|
+
if (expression && import_lib$2.isJSXExpressionContainer(attribute.value)) {
|
|
1292
|
+
const text = value.trim();
|
|
1293
|
+
const lineComment = expression.trailingComments?.some((comment) => comment.type === "CommentLine");
|
|
1294
|
+
return [{
|
|
1295
|
+
start: attribute.value.expression.start,
|
|
1296
|
+
end: attribute.value.expression.end,
|
|
1297
|
+
content: `${text}${lineComment ? "\n" : ""}`
|
|
1298
|
+
}];
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
const content = generateCode(import_lib$2.jsxAttribute(attribute.name, buildAttributeValue(value, type, attribute.value)));
|
|
1302
|
+
return [{
|
|
1303
|
+
start: attribute.start,
|
|
1304
|
+
end: attribute.end,
|
|
1305
|
+
content,
|
|
1306
|
+
indent: true
|
|
1307
|
+
}];
|
|
1308
|
+
};
|
|
1309
|
+
const removeAttribute = (source, attribute) => {
|
|
1310
|
+
if (attribute.start == null || attribute.end == null) return null;
|
|
1311
|
+
let start = attribute.start;
|
|
1312
|
+
while (start > 0 && /\s/.test(source[start - 1])) start -= 1;
|
|
1313
|
+
return [{
|
|
1314
|
+
start,
|
|
1315
|
+
end: attribute.end,
|
|
1316
|
+
content: ""
|
|
1317
|
+
}];
|
|
1318
|
+
};
|
|
1319
|
+
const addAttribute = (source, opening, propertyName, value, type) => {
|
|
1320
|
+
const insertAt = attributeInsertPoint(opening);
|
|
1321
|
+
if (insertAt == null) return null;
|
|
1322
|
+
const lastStart = opening.attributes[opening.attributes.length - 1]?.start;
|
|
1323
|
+
const lineStart = lastStart == null ? -1 : source.lastIndexOf("\n", lastStart - 1) + 1;
|
|
1324
|
+
return [{
|
|
1325
|
+
start: insertAt,
|
|
1326
|
+
end: insertAt,
|
|
1327
|
+
content: `${lastStart != null && lineStart > 0 && /^\s*$/.test(source.slice(lineStart, lastStart)) ? "\n" : " "}${type === "jsx" ? `${propertyName}={${String(value).trim()}}` : generateCode(import_lib$2.jsxAttribute(import_lib$2.jsxIdentifier(propertyName), buildAttributeValue(value, type)))}`,
|
|
1328
|
+
indent: true
|
|
1329
|
+
}];
|
|
1330
|
+
};
|
|
1331
|
+
const canEditAttributeValue = (attribute, value, type) => {
|
|
1332
|
+
if (!import_lib$2.isJSXExpressionContainer(attribute.value) || import_lib$2.isJSXEmptyExpression(attribute.value.expression) || isLosslesslyEvaluable(attribute.value.expression)) return true;
|
|
1333
|
+
return typeof value === "string" && holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)) && structuralSource(value) !== null;
|
|
1334
|
+
};
|
|
1335
|
+
const update = (code, dataId, label, value, property) => {
|
|
1336
|
+
try {
|
|
1337
|
+
const wrapped = wrap(code);
|
|
1338
|
+
const ast = (0, import_lib$1.parse)(wrapped, {
|
|
1339
|
+
sourceType: "module",
|
|
1340
|
+
plugins: ["jsx", "typescript"]
|
|
1341
|
+
});
|
|
1342
|
+
let changed = false;
|
|
1343
|
+
let failure;
|
|
1344
|
+
const edits = [];
|
|
1345
|
+
const collect = (result, onNull) => {
|
|
1346
|
+
if (!result) {
|
|
1347
|
+
failure = onNull();
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
edits.push(...result);
|
|
1351
|
+
changed = true;
|
|
1352
|
+
};
|
|
1353
|
+
traverse(ast, { JSXElement(path) {
|
|
1354
|
+
const opening = path.node.openingElement;
|
|
1355
|
+
if (!opening.attributes.find((attr) => {
|
|
1356
|
+
return import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.ID && attr.value && import_lib$2.isStringLiteral(attr.value) && attr.value.value === dataId;
|
|
1357
|
+
})) return;
|
|
1358
|
+
const bindingAttr = opening.attributes.find((attr) => import_lib$2.isJSXAttribute(attr) && import_lib$2.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING);
|
|
1359
|
+
if (!bindingAttr?.value) {
|
|
1360
|
+
failure = {
|
|
1361
|
+
reason: "no-binding",
|
|
1362
|
+
dataId
|
|
1363
|
+
};
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
let bindingValue = "";
|
|
1367
|
+
if (import_lib$2.isStringLiteral(bindingAttr.value)) bindingValue = bindingAttr.value.value;
|
|
1368
|
+
else if (import_lib$2.isJSXExpressionContainer(bindingAttr.value)) try {
|
|
1369
|
+
bindingValue = generateCode(bindingAttr.value.expression);
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
failure = {
|
|
1372
|
+
reason: "parse-error",
|
|
1373
|
+
error
|
|
1374
|
+
};
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const matches = parseBinding(bindingValue).filter((binding) => property !== void 0 ? binding.property === property : binding.label === label);
|
|
1378
|
+
if (matches.length === 0) {
|
|
1379
|
+
failure = {
|
|
1380
|
+
reason: "binding-not-declared",
|
|
1381
|
+
dataId,
|
|
1382
|
+
label,
|
|
1383
|
+
property
|
|
1384
|
+
};
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (matches.length > 1) {
|
|
1388
|
+
failure = {
|
|
1389
|
+
reason: "duplicate-binding",
|
|
1390
|
+
dataId,
|
|
1391
|
+
label,
|
|
1392
|
+
property,
|
|
1393
|
+
count: matches.length
|
|
1394
|
+
};
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const propertyBinding = matches[0];
|
|
1398
|
+
const prop = propertyBinding.property;
|
|
1399
|
+
if (RESERVED_BINDING_PROPERTIES.includes(prop)) {
|
|
1400
|
+
failure = {
|
|
1401
|
+
reason: "reserved-property",
|
|
1402
|
+
dataId,
|
|
1403
|
+
property: prop
|
|
1404
|
+
};
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const unset = value === void 0;
|
|
1408
|
+
if (unset && propertyBinding.required) {
|
|
1409
|
+
failure = {
|
|
1410
|
+
reason: "required-property",
|
|
1411
|
+
dataId,
|
|
1412
|
+
property: prop
|
|
1413
|
+
};
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
switch (prop) {
|
|
1417
|
+
case BINDING_PROP.INNER_TEXT:
|
|
1418
|
+
collect(editInnerText(wrapped, path.node, unset ? "" : String(value)), () => ({
|
|
1419
|
+
reason: "parse-error",
|
|
1420
|
+
error: /* @__PURE__ */ new Error(`could not update "${prop}"`)
|
|
1421
|
+
}));
|
|
1422
|
+
break;
|
|
1423
|
+
case BINDING_PROP.INNER_HTML:
|
|
1424
|
+
collect(propertyBinding.type === "richtext" ? editRichtext(path.node, unset ? "" : String(value)) : editInnerHTML(path.node, unset ? "" : String(value)), () => ({
|
|
1425
|
+
reason: "parse-error",
|
|
1426
|
+
error: /* @__PURE__ */ new Error(`could not update "${prop}"`)
|
|
1427
|
+
}));
|
|
1428
|
+
break;
|
|
1429
|
+
case BINDING_PROP.CHILDREN:
|
|
1430
|
+
if (unset) {
|
|
1431
|
+
failure = {
|
|
1432
|
+
reason: "unsupported-syntax",
|
|
1433
|
+
dataId,
|
|
1434
|
+
property: prop
|
|
1435
|
+
};
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
collect(editChildrenSource(wrapped, path.node, value), () => ({
|
|
1439
|
+
reason: "parse-error",
|
|
1440
|
+
error: /* @__PURE__ */ new Error("Unsupported or stale children edit; source was preserved")
|
|
1441
|
+
}));
|
|
1442
|
+
break;
|
|
1443
|
+
default: {
|
|
1444
|
+
const attribute = findAttribute(opening, prop);
|
|
1445
|
+
if (unset) {
|
|
1446
|
+
collect(attribute ? removeAttribute(wrapped, attribute) : [], () => ({
|
|
1447
|
+
reason: "parse-error",
|
|
1448
|
+
error: /* @__PURE__ */ new Error(`could not remove "${prop}"`)
|
|
1449
|
+
}));
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
if (!attribute) {
|
|
1453
|
+
collect(value === "" ? [] : addAttribute(wrapped, opening, prop, value, propertyBinding.type), () => ({
|
|
1454
|
+
reason: "attribute-not-found",
|
|
1455
|
+
dataId,
|
|
1456
|
+
property: prop
|
|
1457
|
+
}));
|
|
1458
|
+
break;
|
|
1459
|
+
}
|
|
1460
|
+
if (attribute && propertyBinding.type !== "jsx" && !canEditAttributeValue(attribute, value, propertyBinding.type)) {
|
|
1461
|
+
failure = {
|
|
1462
|
+
reason: "unsupported-syntax",
|
|
1463
|
+
dataId,
|
|
1464
|
+
property: prop
|
|
1465
|
+
};
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
collect(propertyBinding.type === "jsx" ? editJsxAttribute(opening, prop, value) : editAttribute(opening, prop, value, propertyBinding.type), () => ({
|
|
1469
|
+
reason: "attribute-not-found",
|
|
1470
|
+
dataId,
|
|
1471
|
+
property: prop
|
|
1472
|
+
}));
|
|
1473
|
+
break;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
} });
|
|
1477
|
+
if (!changed) return {
|
|
1478
|
+
code,
|
|
1479
|
+
success: false,
|
|
1480
|
+
failure: failure ?? {
|
|
1481
|
+
reason: "element-not-found",
|
|
1482
|
+
dataId
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
return {
|
|
1486
|
+
code: unwrap(applyEdits(wrapped, edits)),
|
|
1487
|
+
success: true
|
|
1488
|
+
};
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
console.error("❌ Code update error:", error);
|
|
1491
|
+
return {
|
|
1492
|
+
code,
|
|
1493
|
+
success: false,
|
|
1494
|
+
failure: {
|
|
1495
|
+
reason: "parse-error",
|
|
1496
|
+
error
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
const updateAll = (raw, entries) => {
|
|
1502
|
+
let current = raw;
|
|
1503
|
+
for (const [index, entry] of entries.entries()) {
|
|
1504
|
+
const result = update(current, entry.dataId, entry.label, entry.value, entry.property);
|
|
1505
|
+
if (!result.success) return {
|
|
1506
|
+
success: false,
|
|
1507
|
+
code: raw,
|
|
1508
|
+
failure: result.failure ?? {
|
|
1509
|
+
reason: "element-not-found",
|
|
1510
|
+
dataId: entry.dataId
|
|
1511
|
+
},
|
|
1512
|
+
index
|
|
1513
|
+
};
|
|
1514
|
+
current = result.code;
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
success: true,
|
|
1518
|
+
code: current
|
|
1519
|
+
};
|
|
1520
|
+
};
|
|
1521
|
+
/**
|
|
1522
|
+
* @deprecated Not atomic: when an entry fails, the ones before and after it
|
|
1523
|
+
* are still applied, and the returned `code` carries them. Use `updateAll`,
|
|
1524
|
+
* which applies every entry or none. Will be removed in the next major.
|
|
1525
|
+
*/
|
|
1526
|
+
const bulkUpdate = (raw, entries) => {
|
|
1527
|
+
let current = raw;
|
|
1528
|
+
const failures = [];
|
|
1529
|
+
for (const entry of entries) {
|
|
1530
|
+
const result = update(current, entry.dataId, entry.label, entry.value, entry.property);
|
|
1531
|
+
current = result.code;
|
|
1532
|
+
if (!result.success && result.failure) failures.push(result.failure);
|
|
1533
|
+
}
|
|
1534
|
+
return failures.length > 0 ? {
|
|
1535
|
+
code: current,
|
|
1536
|
+
success: false,
|
|
1537
|
+
failures
|
|
1538
|
+
} : {
|
|
1539
|
+
code: current,
|
|
1540
|
+
success: true
|
|
1541
|
+
};
|
|
1542
|
+
};
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/utils/ast/tree.ts
|
|
1545
|
+
const replaceIds = (code, generateId = () => nanoid(6)) => {
|
|
1546
|
+
return code.replace(new RegExp(`${DATA_ATTR.ID}="[^"]*"`, "g"), () => {
|
|
1547
|
+
return `${DATA_ATTR.ID}="${generateId()}"`;
|
|
1548
|
+
});
|
|
1549
|
+
};
|
|
1550
|
+
const fillIds = (code, generateId = () => nanoid(6)) => {
|
|
1551
|
+
return code.replace(new RegExp(`${DATA_ATTR.ID}=""`, "g"), () => {
|
|
1552
|
+
return `${DATA_ATTR.ID}="${generateId()}"`;
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
const clone = (element, generateId = () => nanoid(6)) => {
|
|
1556
|
+
const cloned = import_lib$2.cloneNode(element, true);
|
|
1557
|
+
const generatedCode = generateCode(cloned);
|
|
1558
|
+
const code = replaceIds(generatedCode, generateId);
|
|
1559
|
+
return (0, import_lib$1.parseExpression)(code, { plugins: ["jsx", "typescript"] });
|
|
1560
|
+
};
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region src/utils/ast/validate.ts
|
|
1563
|
+
const VALID = { valid: true };
|
|
1564
|
+
const validateBindingValue = (binding, value) => {
|
|
1565
|
+
if (value === "" || value === null || value === void 0) {
|
|
1566
|
+
if (binding.required) return {
|
|
1567
|
+
valid: false,
|
|
1568
|
+
message: "This field is required."
|
|
1569
|
+
};
|
|
1570
|
+
return VALID;
|
|
1571
|
+
}
|
|
1572
|
+
if (typeof value === "number") {
|
|
1573
|
+
if (binding.min !== void 0 && value < binding.min) return {
|
|
1574
|
+
valid: false,
|
|
1575
|
+
message: `Must be at least ${binding.min}.`
|
|
1576
|
+
};
|
|
1577
|
+
if (binding.max !== void 0 && value > binding.max) return {
|
|
1578
|
+
valid: false,
|
|
1579
|
+
message: `Must be at most ${binding.max}.`
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
if (typeof value === "string" && binding.pattern) {
|
|
1583
|
+
let regex;
|
|
1584
|
+
try {
|
|
1585
|
+
regex = new RegExp(binding.pattern);
|
|
1586
|
+
} catch {
|
|
1587
|
+
return VALID;
|
|
1588
|
+
}
|
|
1589
|
+
if (!regex.test(value)) return {
|
|
1590
|
+
valid: false,
|
|
1591
|
+
message: "Value does not match the required format."
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
if (typeof value === "string" && binding.type === "url") try {
|
|
1595
|
+
new URL(value);
|
|
1596
|
+
} catch {
|
|
1597
|
+
return {
|
|
1598
|
+
valid: false,
|
|
1599
|
+
message: "Must be a valid URL."
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
if (typeof value === "string" && binding.type === "date") {
|
|
1603
|
+
if (Number.isNaN(Date.parse(value))) return {
|
|
1604
|
+
valid: false,
|
|
1605
|
+
message: "Must be a valid date."
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
return VALID;
|
|
1609
|
+
};
|
|
1610
|
+
//#endregion
|
|
1611
|
+
export { extractObjectProperties as A, getStructuredValue as C, canLosslesslyEvaluateSource as D, arrayExpressionToCode as E, generateCode as F, parseArrayExpression as M, parseValue as N, createNodeFromValue as O, setEditableValue as P, getCurrentValue as S, parseBindingExpression as T, updateArrayItemProperty as _, bulkUpdate as a, moveSelectedIndices as b, getChildrenSignatures as c, appendArrayItem as d, duplicateArrayItems as f, removeArrayItems as g, parseItems as h, replaceIds as i, flattenEditableValue as j, extractNodeValue as k, clearExtractCache as l, moveArrayItems as m, clone as n, update as o, moveArrayItem as p, fillIds as r, updateAll as s, validateBindingValue as t, extract as u, updateArrayItemValue as v, parseBinding as w, findEditableChildren as x, canStructurallyEditArray as y };
|
|
1612
|
+
|
|
1613
|
+
//# sourceMappingURL=ast-CMD20ykx.js.map
|