@jbpark/live-editor 4.6.0 → 5.0.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 +2 -2
- package/README.md +2 -2
- package/dist/{boundary-BLB80tyw.js → boundary-6_nTsWxP.js} +2 -2
- package/dist/{boundary-BLB80tyw.js.map → boundary-6_nTsWxP.js.map} +1 -1
- package/dist/{compile-Dxas-D05.js → compile-DwOyWrmI.js} +8 -7
- package/dist/compile-DwOyWrmI.js.map +1 -0
- package/dist/constants-D-XKuEY2.js +58 -0
- package/dist/constants-D-XKuEY2.js.map +1 -0
- package/dist/{context-B8MzP7_p.js → context-DDR1xVKo.js} +3 -3
- package/dist/{context-B8MzP7_p.js.map → context-DDR1xVKo.js.map} +1 -1
- package/dist/{core-Bgfek364.js → core-CKazr4tT.js} +2 -2
- package/dist/core-CKazr4tT.js.map +1 -0
- package/dist/{detect-typescript-Dr_oLniM.js → detect-typescript-CoAO-QUJ.js} +2 -2
- package/dist/{detect-typescript-Dr_oLniM.js.map → detect-typescript-CoAO-QUJ.js.map} +1 -1
- package/dist/dnd/index.d.ts +2 -2
- package/dist/dnd/index.js +2 -2
- package/dist/dnd-BlC1gqUy.js +4446 -0
- package/dist/dnd-BlC1gqUy.js.map +1 -0
- package/dist/document-BQhb7YR8.d.ts +17 -0
- package/dist/document-DcwIEHRV.js +34077 -0
- package/dist/document-DcwIEHRV.js.map +1 -0
- package/dist/editor/index.js +2 -2
- package/dist/{editor-CMElfHer.js → editor-Bty5XzpV.js} +2 -2
- package/dist/{editor-CMElfHer.js.map → editor-Bty5XzpV.js.map} +1 -1
- package/dist/error/index.d.ts +1 -1
- package/dist/error/index.js +2 -2
- package/dist/{error-CN_GCnXB.js → error-DyYx2NQ2.js} +17 -31
- package/dist/error-DyYx2NQ2.js.map +1 -0
- package/dist/{index-C69Lv-jR.d.ts → index-0VB5C3Jk.d.ts} +28 -50
- package/dist/{index-DpDU1IAw.d.ts → index-92L7F440.d.ts} +2 -2
- package/dist/index-C8h8TcIl.d.ts +15 -0
- package/dist/{index-DQkL37eL.d.ts → index-Cl79M7ZJ.d.ts} +6 -4
- package/dist/{index-CRwEhJ7w.d.ts → index-kKUjo9lr.d.ts} +2 -2
- package/dist/{index-DrWtSKXu.d.ts → index-tAiuHZsC.d.ts} +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/{messages-CSrMI4Rm.js → messages-Bt5WXKTE.js} +5 -2
- package/dist/messages-Bt5WXKTE.js.map +1 -0
- package/dist/{messages-IBPs4TkZ.d.ts → messages-Yb3b7YJt.d.ts} +5 -2
- package/dist/preview/index.d.ts +1 -1
- package/dist/preview/index.js +2 -2
- package/dist/{preview-CtyG5UbH.js → preview-DC0pyOdu.js} +4 -4
- package/dist/preview-DC0pyOdu.js.map +1 -0
- package/dist/provider/index.d.ts +2 -2
- package/dist/provider/index.js +2 -2
- package/dist/{sections-BYWOBISr.js → sections-4uirsXRU.js} +4 -7
- package/dist/sections-4uirsXRU.js.map +1 -0
- package/dist/style.css +776 -346
- package/dist/{validate-DB59Z_Nx.d.ts → types-Czps1Txy.d.ts} +12 -28
- package/dist/update-2PJbwoS8.d.ts +68 -0
- package/dist/{items-B6OXxcLG.js → update-DNInQSM7.js} +261 -512
- package/dist/update-DNInQSM7.js.map +1 -0
- package/dist/{use-dynamic-tailwind-BP1toJkW.js → use-dynamic-tailwind-CSYKXDmX.js} +301 -273
- package/dist/use-dynamic-tailwind-CSYKXDmX.js.map +1 -0
- package/dist/utils/ast/index.d.ts +47 -4
- package/dist/utils/ast/index.js +15 -77
- package/dist/utils/ast/index.js.map +1 -1
- package/dist/utils/index.d.ts +13 -32
- package/dist/utils/index.js +4 -26
- package/dist/validate-CZz30Pnb.js +122 -0
- package/dist/validate-CZz30Pnb.js.map +1 -0
- package/dist/validate-CiA1LF6S.d.ts +33 -0
- package/package.json +13 -15
- package/dist/compile-Dxas-D05.js.map +0 -1
- package/dist/constants-DU17Tdwo.js +0 -1459
- package/dist/constants-DU17Tdwo.js.map +0 -1
- package/dist/core-Bgfek364.js.map +0 -1
- package/dist/dnd-UoJILA75.js +0 -2756
- package/dist/dnd-UoJILA75.js.map +0 -1
- package/dist/document-CGbe6bSV.js +0 -38667
- package/dist/document-CGbe6bSV.js.map +0 -1
- package/dist/document-Ct4jW78J.d.ts +0 -1553
- package/dist/error-CN_GCnXB.js.map +0 -1
- package/dist/index-DKYUxi-E.d.ts +0 -227
- package/dist/items-B6OXxcLG.js.map +0 -1
- package/dist/messages-CSrMI4Rm.js.map +0 -1
- package/dist/preview-CtyG5UbH.js.map +0 -1
- package/dist/sections-BYWOBISr.js.map +0 -1
- package/dist/use-dynamic-tailwind-BP1toJkW.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/validate-iLo8IONY.js +0 -64
- package/dist/validate-iLo8IONY.js.map +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import {
|
|
1
|
+
import { $ as jsxOpeningFragment, A as isJSXText, B as isTSSatisfiesExpression, C as isJSXElement, D as isJSXIdentifier, E as isJSXFragment, F as isParenthesizedExpression, G as jsxClosingElement, H as isTemplateLiteral, J as jsxExpressionContainer, K as jsxClosingFragment, L as isStringLiteral, M as isNumericLiteral, N as isObjectExpression, O as isJSXOpeningElement, P as isObjectProperty, Q as jsxOpeningElement, R as isTSAsExpression, S as isJSXAttribute, T as isJSXExpressionContainer, U as isUnaryExpression, V as isTSTypeAssertion, W as jsxAttribute, X as jsxIdentifier, Y as jsxFragment, Z as jsxMemberExpression, _ as identifier, at as stringLiteral, c as traverse, ct as traverseFast, d as applyEdits, dt as parseExpression, et as jsxText, f as getJSXTagName, g as booleanLiteral, h as arrayExpression, it as objectProperty, j as isNullLiteral, k as isJSXSpreadAttribute, lt as unaryExpression, m as generate, nt as numericLiteral, p as nanoid, q as jsxElement, rt as objectExpression, tt as nullLiteral, ut as parse, v as isArrayExpression, w as isJSXEmptyExpression, x as isIdentifier, y as isBooleanLiteral, z as isTSNonNullExpression } from "./document-DcwIEHRV.js";
|
|
2
|
+
import { n as CONFIG, o as REGEX, r as DATA_ATTR, s as RESERVED_BINDING_PROPERTIES, t as BINDING_PROP } from "./constants-D-XKuEY2.js";
|
|
3
3
|
import { n as registerEditorCache } from "./editor-caches-ClMvdcQF.js";
|
|
4
4
|
import { t as createBoundedCache } from "./cache-B0-XcIxJ.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/utils/ast/types.ts
|
|
7
|
-
var import_lib = require_lib();
|
|
8
|
-
var import_lib$1 = /* @__PURE__ */ __toESM(require_lib$1(), 1);
|
|
9
7
|
const BINDING_TYPES = [
|
|
10
8
|
"array",
|
|
11
9
|
"object",
|
|
@@ -16,9 +14,40 @@ const BINDING_TYPES = [
|
|
|
16
14
|
"jsx",
|
|
17
15
|
"richtext",
|
|
18
16
|
"date",
|
|
19
|
-
"url"
|
|
17
|
+
"url",
|
|
18
|
+
"email",
|
|
19
|
+
"tel",
|
|
20
|
+
"time",
|
|
21
|
+
"datetime"
|
|
20
22
|
];
|
|
21
23
|
//#endregion
|
|
24
|
+
//#region src/utils/ast/helpers.ts
|
|
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 stringLiteral(value);
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
if (REGEX.NUMBER.test(trimmed)) return jsxExpressionContainer(numericLiteral(parseFloat(trimmed)));
|
|
36
|
+
if (REGEX.BOOLEAN_OR_NULL.test(trimmed)) {
|
|
37
|
+
const expr = parseExpression(trimmed, { plugins: ["jsx", "typescript"] });
|
|
38
|
+
return jsxExpressionContainer(expr);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const expr = parseExpression(trimmed, { plugins: ["jsx", "typescript"] });
|
|
42
|
+
return jsxExpressionContainer(expr);
|
|
43
|
+
} catch {
|
|
44
|
+
return stringLiteral(trimmed);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const generateCode = (node) => {
|
|
48
|
+
return generate(node, { jsescOption: { minimal: true } }).code;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
22
51
|
//#region src/utils/ast/value.ts
|
|
23
52
|
const dedent = (str) => {
|
|
24
53
|
const lines = str.replace(/^\n/, "").replace(/\n\s*$/, "").split("\n");
|
|
@@ -31,44 +60,44 @@ const dedent = (str) => {
|
|
|
31
60
|
};
|
|
32
61
|
const unwrapExpression = (node) => {
|
|
33
62
|
let current = node;
|
|
34
|
-
while (
|
|
63
|
+
while (isTSAsExpression(current) || isTSSatisfiesExpression(current) || isTSNonNullExpression(current) || isTSTypeAssertion(current) || isParenthesizedExpression(current)) current = current.expression;
|
|
35
64
|
return current;
|
|
36
65
|
};
|
|
37
66
|
const isLosslesslyEvaluable = (node) => {
|
|
38
67
|
const value = unwrapExpression(node);
|
|
39
|
-
if (
|
|
40
|
-
if (
|
|
41
|
-
if (
|
|
42
|
-
if (
|
|
43
|
-
if (
|
|
68
|
+
if (isStringLiteral(value) || isNumericLiteral(value) || isBooleanLiteral(value) || isNullLiteral(value)) return true;
|
|
69
|
+
if (isUnaryExpression(value) && value.operator === "-" && isNumericLiteral(value.argument)) return true;
|
|
70
|
+
if (isTemplateLiteral(value)) return value.expressions.length === 0;
|
|
71
|
+
if (isArrayExpression(value)) return value.elements.every((element) => element !== null && isLosslesslyEvaluable(element));
|
|
72
|
+
if (isObjectExpression(value)) return value.properties.every((property) => isObjectProperty(property) && !property.computed && (isIdentifier(property.key) || isStringLiteral(property.key) || isNumericLiteral(property.key)) && isLosslesslyEvaluable(property.value));
|
|
44
73
|
return false;
|
|
45
74
|
};
|
|
46
75
|
const canLosslesslyEvaluateSource = (source) => {
|
|
47
76
|
try {
|
|
48
|
-
return isLosslesslyEvaluable(
|
|
77
|
+
return isLosslesslyEvaluable(parseExpression(source, { plugins: ["jsx", "typescript"] }));
|
|
49
78
|
} catch {
|
|
50
79
|
return false;
|
|
51
80
|
}
|
|
52
81
|
};
|
|
53
82
|
const evaluateLiteral = (rawNode) => {
|
|
54
83
|
const node = unwrapExpression(rawNode);
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
if (
|
|
58
|
-
if (
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
if (
|
|
64
|
-
if (
|
|
84
|
+
if (isStringLiteral(node)) return node.value;
|
|
85
|
+
if (isNumericLiteral(node)) return node.value;
|
|
86
|
+
if (isBooleanLiteral(node)) return node.value;
|
|
87
|
+
if (isNullLiteral(node)) return null;
|
|
88
|
+
if (isIdentifier(node) && node.name === "undefined") return;
|
|
89
|
+
if (isUnaryExpression(node) && node.operator === "-" && isNumericLiteral(node.argument)) return -node.argument.value;
|
|
90
|
+
if (isTemplateLiteral(node) && node.expressions.length === 0) return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? "";
|
|
91
|
+
if (isJSXElement(node) || isJSXFragment(node)) return generateCode(node);
|
|
92
|
+
if (isArrayExpression(node)) return node.elements.map((element) => element ? evaluateLiteral(element) : null);
|
|
93
|
+
if (isObjectExpression(node)) {
|
|
65
94
|
const result = {};
|
|
66
95
|
for (const prop of node.properties) {
|
|
67
|
-
if (!
|
|
96
|
+
if (!isObjectProperty(prop)) continue;
|
|
68
97
|
let key = null;
|
|
69
|
-
if (
|
|
70
|
-
else if (
|
|
71
|
-
else if (
|
|
98
|
+
if (isIdentifier(prop.key)) key = prop.key.name;
|
|
99
|
+
else if (isStringLiteral(prop.key)) key = prop.key.value;
|
|
100
|
+
else if (isNumericLiteral(prop.key)) key = String(prop.key.value);
|
|
72
101
|
if (key === null) continue;
|
|
73
102
|
result[key] = evaluateLiteral(prop.value);
|
|
74
103
|
}
|
|
@@ -88,31 +117,31 @@ const parseValue = (value) => {
|
|
|
88
117
|
}
|
|
89
118
|
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
90
119
|
try {
|
|
91
|
-
const ast =
|
|
92
|
-
if (
|
|
120
|
+
const ast = parseExpression(trimmed, { plugins: ["jsx", "typescript"] });
|
|
121
|
+
if (isObjectExpression(ast) || isArrayExpression(ast)) return evaluateLiteral(ast);
|
|
93
122
|
} catch {}
|
|
94
123
|
return value;
|
|
95
124
|
}
|
|
96
125
|
return value;
|
|
97
126
|
};
|
|
98
127
|
const extractNodeValue = (node) => {
|
|
99
|
-
if (
|
|
128
|
+
if (isBooleanLiteral(node)) return {
|
|
100
129
|
type: "boolean",
|
|
101
130
|
value: node.value
|
|
102
131
|
};
|
|
103
|
-
if (
|
|
132
|
+
if (isNumericLiteral(node)) return {
|
|
104
133
|
type: "number",
|
|
105
134
|
value: node.value
|
|
106
135
|
};
|
|
107
|
-
if (
|
|
136
|
+
if (isUnaryExpression(node) && node.operator === "-" && isNumericLiteral(node.argument)) return {
|
|
108
137
|
type: "number",
|
|
109
138
|
value: -node.argument.value
|
|
110
139
|
};
|
|
111
|
-
if (
|
|
140
|
+
if (isStringLiteral(node)) return {
|
|
112
141
|
type: "string",
|
|
113
142
|
value: node.value
|
|
114
143
|
};
|
|
115
|
-
if (
|
|
144
|
+
if (isTemplateLiteral(node)) {
|
|
116
145
|
if (node.expressions.length === 0 && node.quasis.length === 1) return {
|
|
117
146
|
type: "string",
|
|
118
147
|
value: dedent(node.quasis[0].value.cooked ?? node.quasis[0].value.raw)
|
|
@@ -122,19 +151,19 @@ const extractNodeValue = (node) => {
|
|
|
122
151
|
value: generateCode(node)
|
|
123
152
|
};
|
|
124
153
|
}
|
|
125
|
-
if (
|
|
154
|
+
if (isNullLiteral(node)) return {
|
|
126
155
|
type: "null",
|
|
127
156
|
value: null
|
|
128
157
|
};
|
|
129
|
-
if (
|
|
158
|
+
if (isArrayExpression(node)) return {
|
|
130
159
|
type: "array",
|
|
131
160
|
value: generateCode(node)
|
|
132
161
|
};
|
|
133
|
-
if (
|
|
162
|
+
if (isObjectExpression(node)) return {
|
|
134
163
|
type: "object",
|
|
135
164
|
value: generateCode(node)
|
|
136
165
|
};
|
|
137
|
-
if (
|
|
166
|
+
if (isJSXElement(node) || isJSXFragment(node)) return {
|
|
138
167
|
type: "string",
|
|
139
168
|
value: generateCode(node)
|
|
140
169
|
};
|
|
@@ -145,8 +174,8 @@ const extractNodeValue = (node) => {
|
|
|
145
174
|
};
|
|
146
175
|
const parseArrayExpression = (value) => {
|
|
147
176
|
try {
|
|
148
|
-
const ast = unwrapExpression(
|
|
149
|
-
if (!
|
|
177
|
+
const ast = unwrapExpression(parseExpression(value, { plugins: ["jsx", "typescript"] }));
|
|
178
|
+
if (!isArrayExpression(ast)) return null;
|
|
150
179
|
return ast;
|
|
151
180
|
} catch (error) {
|
|
152
181
|
console.error("❌ Array parsing error:", error);
|
|
@@ -156,9 +185,9 @@ const parseArrayExpression = (value) => {
|
|
|
156
185
|
const extractObjectProperties = (element) => {
|
|
157
186
|
const properties = {};
|
|
158
187
|
element.properties.forEach((prop) => {
|
|
159
|
-
if (
|
|
188
|
+
if (isObjectProperty(prop) && isIdentifier(prop.key)) {
|
|
160
189
|
const key = prop.key.name;
|
|
161
|
-
if (key === "children" ||
|
|
190
|
+
if (key === "children" || isJSXElement(prop.value) || isJSXFragment(prop.value)) return;
|
|
162
191
|
const extracted = extractNodeValue(prop.value);
|
|
163
192
|
properties[key] = {
|
|
164
193
|
...extracted,
|
|
@@ -171,6 +200,8 @@ const extractObjectProperties = (element) => {
|
|
|
171
200
|
//#endregion
|
|
172
201
|
//#region src/utils/ast/binding.ts
|
|
173
202
|
const bindingTypeSchema = z.enum(BINDING_TYPES);
|
|
203
|
+
const countSchema = z.number().int().nonnegative();
|
|
204
|
+
const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2})?$/);
|
|
174
205
|
const bindingOptionSchema = z.object({
|
|
175
206
|
label: z.string(),
|
|
176
207
|
value: z.string()
|
|
@@ -206,7 +237,16 @@ const KNOWN_BINDING_KEYS = /* @__PURE__ */ new Set([
|
|
|
206
237
|
"min",
|
|
207
238
|
"max",
|
|
208
239
|
"pattern",
|
|
209
|
-
"
|
|
240
|
+
"patternMessage",
|
|
241
|
+
"required",
|
|
242
|
+
"minLength",
|
|
243
|
+
"maxLength",
|
|
244
|
+
"step",
|
|
245
|
+
"minItems",
|
|
246
|
+
"maxItems",
|
|
247
|
+
"minDate",
|
|
248
|
+
"maxDate",
|
|
249
|
+
"protocols"
|
|
210
250
|
]);
|
|
211
251
|
const isPlainObject = (value) => {
|
|
212
252
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -223,7 +263,16 @@ const sanitizeField = (raw) => {
|
|
|
223
263
|
min: pick(z.number(), raw.min),
|
|
224
264
|
max: pick(z.number(), raw.max),
|
|
225
265
|
pattern: pick(z.string(), raw.pattern),
|
|
266
|
+
patternMessage: pick(z.string(), raw.patternMessage),
|
|
226
267
|
required: pick(z.boolean(), raw.required),
|
|
268
|
+
minLength: pick(countSchema, raw.minLength),
|
|
269
|
+
maxLength: pick(countSchema, raw.maxLength),
|
|
270
|
+
step: pick(z.number().positive(), raw.step),
|
|
271
|
+
minItems: pick(countSchema, raw.minItems),
|
|
272
|
+
maxItems: pick(countSchema, raw.maxItems),
|
|
273
|
+
minDate: pick(dateSchema, raw.minDate),
|
|
274
|
+
maxDate: pick(dateSchema, raw.maxDate),
|
|
275
|
+
protocols: pick(z.array(z.string().min(1)), raw.protocols),
|
|
227
276
|
meta: metaEntries.length > 0 ? Object.fromEntries(metaEntries) : void 0
|
|
228
277
|
};
|
|
229
278
|
return Object.fromEntries(Object.entries(field).filter(([, value]) => value !== void 0));
|
|
@@ -329,12 +378,12 @@ const getCurrentValue = (node, property) => {
|
|
|
329
378
|
case BINDING_PROP.INNER_HTML: {
|
|
330
379
|
const dsiAttr = node.attributes.find((a) => a.name === "dangerouslySetInnerHTML");
|
|
331
380
|
if (dsiAttr?.value) try {
|
|
332
|
-
const expr =
|
|
333
|
-
if (
|
|
334
|
-
const htmlProp = expr.properties.find((p) =>
|
|
381
|
+
const expr = parseExpression(dsiAttr.value, { plugins: ["jsx", "typescript"] });
|
|
382
|
+
if (isObjectExpression(expr)) {
|
|
383
|
+
const htmlProp = expr.properties.find((p) => isObjectProperty(p) && isIdentifier(p.key) && p.key.name === "__html");
|
|
335
384
|
if (htmlProp) {
|
|
336
|
-
if (
|
|
337
|
-
if (
|
|
385
|
+
if (isStringLiteral(htmlProp.value)) return htmlProp.value.value;
|
|
386
|
+
if (isTemplateLiteral(htmlProp.value) && htmlProp.value.expressions.length === 0) return htmlProp.value.quasis[0]?.value.cooked ?? htmlProp.value.quasis[0]?.value.raw ?? "";
|
|
338
387
|
}
|
|
339
388
|
}
|
|
340
389
|
} catch {}
|
|
@@ -347,7 +396,11 @@ const getCurrentValue = (node, property) => {
|
|
|
347
396
|
const STRING_VALUED_TYPES = /* @__PURE__ */ new Set([
|
|
348
397
|
"string",
|
|
349
398
|
"url",
|
|
399
|
+
"email",
|
|
400
|
+
"tel",
|
|
350
401
|
"date",
|
|
402
|
+
"time",
|
|
403
|
+
"datetime",
|
|
351
404
|
"color",
|
|
352
405
|
"jsx",
|
|
353
406
|
"richtext"
|
|
@@ -383,15 +436,42 @@ const findEditableChildren = (node) => {
|
|
|
383
436
|
return editableChildren;
|
|
384
437
|
};
|
|
385
438
|
//#endregion
|
|
439
|
+
//#region src/utils/selection.ts
|
|
440
|
+
const removeIndices = (items, indices) => {
|
|
441
|
+
return items.filter((_, index) => !indices.has(index));
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Shifts every selected index up/down by one step as a block, preserving
|
|
445
|
+
* relative order — scattered selections stop moving individually once they
|
|
446
|
+
* hit an unselected neighbor, so the whole group slides together instead of
|
|
447
|
+
* items passing through each other.
|
|
448
|
+
*/
|
|
449
|
+
const moveSelectedIndices = (items, indices, direction) => {
|
|
450
|
+
const next = [...items];
|
|
451
|
+
const nextIndices = new Set(indices);
|
|
452
|
+
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
453
|
+
for (const index of ordered) {
|
|
454
|
+
const target = direction === "up" ? index - 1 : index + 1;
|
|
455
|
+
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
456
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
457
|
+
nextIndices.delete(index);
|
|
458
|
+
nextIndices.add(target);
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
items: next,
|
|
462
|
+
indices: nextIndices
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
//#endregion
|
|
386
466
|
//#region src/utils/ast/extract.ts
|
|
387
467
|
const collectText = (children) => {
|
|
388
|
-
return children.filter((c) =>
|
|
468
|
+
return children.filter((c) => isJSXText(c)).map((c) => c.value.trim()).filter((v) => v.length).join(" ");
|
|
389
469
|
};
|
|
390
470
|
const parseJSXName = (tagName) => {
|
|
391
471
|
const parts = tagName.split(".");
|
|
392
|
-
if (parts.length === 1) return
|
|
393
|
-
let expr =
|
|
394
|
-
for (let i = 1; i < parts.length; i++) expr =
|
|
472
|
+
if (parts.length === 1) return jsxIdentifier(parts[0]);
|
|
473
|
+
let expr = jsxIdentifier(parts[0]);
|
|
474
|
+
for (let i = 1; i < parts.length; i++) expr = jsxMemberExpression(expr, jsxIdentifier(parts[i]));
|
|
395
475
|
return expr;
|
|
396
476
|
};
|
|
397
477
|
const extractCache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
@@ -415,19 +495,19 @@ const extractAttributes = (attributes, source) => {
|
|
|
415
495
|
const allAttrs = [];
|
|
416
496
|
const dataAttrs = [];
|
|
417
497
|
for (const attr of attributes) {
|
|
418
|
-
if (!
|
|
498
|
+
if (!isJSXAttribute(attr) || !isJSXIdentifier(attr.name)) continue;
|
|
419
499
|
const name = attr.name.name;
|
|
420
500
|
let value = null;
|
|
421
|
-
let isStringLiteral = false;
|
|
501
|
+
let isStringLiteral$1 = false;
|
|
422
502
|
if (attr.value) {
|
|
423
|
-
if (
|
|
503
|
+
if (isStringLiteral(attr.value)) {
|
|
424
504
|
value = attr.value.value;
|
|
425
|
-
isStringLiteral = true;
|
|
426
|
-
} else if (
|
|
505
|
+
isStringLiteral$1 = true;
|
|
506
|
+
} else if (isJSXExpressionContainer(attr.value)) try {
|
|
427
507
|
const expression = attr.value.expression;
|
|
428
|
-
const structural =
|
|
508
|
+
const structural = isArrayExpression(unwrapExpression(expression)) || isObjectExpression(unwrapExpression(expression));
|
|
429
509
|
value = source && structural && expression.start != null && expression.end != null ? source.slice(expression.start, expression.end) : generateCode(expression);
|
|
430
|
-
isStringLiteral = false;
|
|
510
|
+
isStringLiteral$1 = false;
|
|
431
511
|
} catch {
|
|
432
512
|
value = null;
|
|
433
513
|
}
|
|
@@ -435,7 +515,7 @@ const extractAttributes = (attributes, source) => {
|
|
|
435
515
|
const entry = {
|
|
436
516
|
name,
|
|
437
517
|
value,
|
|
438
|
-
isStringLiteral
|
|
518
|
+
isStringLiteral: isStringLiteral$1
|
|
439
519
|
};
|
|
440
520
|
allAttrs.push(entry);
|
|
441
521
|
if (name.startsWith("data-")) dataAttrs.push(entry);
|
|
@@ -447,7 +527,7 @@ const extractAttributes = (attributes, source) => {
|
|
|
447
527
|
};
|
|
448
528
|
const buildChildElements = (node) => {
|
|
449
529
|
const childElements = [];
|
|
450
|
-
if (node.textContent) childElements.push(
|
|
530
|
+
if (node.textContent) childElements.push(jsxText(node.textContent));
|
|
451
531
|
node.children?.forEach((child) => {
|
|
452
532
|
const childJSX = nodeToJSX(child);
|
|
453
533
|
if (childJSX) childElements.push(childJSX);
|
|
@@ -458,22 +538,22 @@ const nodeToJSX = (node) => {
|
|
|
458
538
|
try {
|
|
459
539
|
if (node.isFragment) {
|
|
460
540
|
const childElements = buildChildElements(node);
|
|
461
|
-
return
|
|
541
|
+
return jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), childElements);
|
|
462
542
|
}
|
|
463
543
|
if (node.tagName === "div" && node.dataAttributes.some((attr) => attr.name === "data-item")) {
|
|
464
544
|
if (node.children) return nodeToJSX(node.children[0]);
|
|
465
545
|
return null;
|
|
466
546
|
}
|
|
467
547
|
const attributes = node.attributes.map((attr) => {
|
|
468
|
-
const attrName =
|
|
469
|
-
if (!attr.value) return
|
|
470
|
-
return
|
|
548
|
+
const attrName = jsxIdentifier(attr.name);
|
|
549
|
+
if (!attr.value) return jsxAttribute(attrName, null);
|
|
550
|
+
return jsxAttribute(attrName, attrValue(attr));
|
|
471
551
|
});
|
|
472
552
|
const elementName = parseJSXName(node.tagName);
|
|
473
|
-
const openingElement =
|
|
474
|
-
const closingElement =
|
|
553
|
+
const openingElement = jsxOpeningElement(elementName, attributes);
|
|
554
|
+
const closingElement = jsxClosingElement(elementName);
|
|
475
555
|
const children = buildChildElements(node);
|
|
476
|
-
return
|
|
556
|
+
return jsxElement(openingElement, closingElement, children);
|
|
477
557
|
} catch (error) {
|
|
478
558
|
console.error("❌ DataAttrNode to JSX conversion error:", error);
|
|
479
559
|
return null;
|
|
@@ -503,11 +583,11 @@ const createFragmentNode = (textContent, children) => ({
|
|
|
503
583
|
isFragment: true
|
|
504
584
|
});
|
|
505
585
|
const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, source, options) => {
|
|
506
|
-
const jsxChildren = jsxElement.children.filter((child) =>
|
|
586
|
+
const jsxChildren = jsxElement.children.filter((child) => isJSXElement(child) || isJSXFragment(child));
|
|
507
587
|
if (!jsxChildren.length) return;
|
|
508
588
|
const childrenNodes = [];
|
|
509
589
|
jsxChildren.forEach((child) => {
|
|
510
|
-
if (
|
|
590
|
+
if (isJSXElement(child)) {
|
|
511
591
|
const childResults = extractFromNode(child, processedNodes, source, options);
|
|
512
592
|
if (shouldWrap) {
|
|
513
593
|
const wrapperNode = createWrapperNode(collectText(child.children), childResults);
|
|
@@ -516,11 +596,11 @@ const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, s
|
|
|
516
596
|
if (dataId) wrapperNode.id = `child:${dataId}`;
|
|
517
597
|
childrenNodes.push(wrapperNode);
|
|
518
598
|
} else childrenNodes.push(...childResults);
|
|
519
|
-
} else if (
|
|
599
|
+
} else if (isJSXFragment(child)) {
|
|
520
600
|
processedNodes?.add(child);
|
|
521
601
|
const fragmentChildren = [];
|
|
522
602
|
child.children.forEach((fragmentChild) => {
|
|
523
|
-
if (
|
|
603
|
+
if (isJSXElement(fragmentChild)) {
|
|
524
604
|
const childResults = extractFromNode(fragmentChild, processedNodes, source, options);
|
|
525
605
|
fragmentChildren.push(...childResults);
|
|
526
606
|
}
|
|
@@ -535,26 +615,26 @@ const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, s
|
|
|
535
615
|
return childrenNodes.length ? childrenNodes : void 0;
|
|
536
616
|
};
|
|
537
617
|
const skipItemsChildren = (jsxElement, processedNodes, propertyName = "items") => {
|
|
538
|
-
const itemsAttr = jsxElement.openingElement.attributes.find((attr) =>
|
|
539
|
-
if (!itemsAttr || !
|
|
540
|
-
if (itemsAttr.value &&
|
|
541
|
-
if (
|
|
542
|
-
if (
|
|
618
|
+
const itemsAttr = jsxElement.openingElement.attributes.find((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === propertyName);
|
|
619
|
+
if (!itemsAttr || !isJSXAttribute(itemsAttr)) return;
|
|
620
|
+
if (itemsAttr.value && isJSXExpressionContainer(itemsAttr.value) && isArrayExpression(itemsAttr.value.expression)) itemsAttr.value.expression.elements.forEach((element) => {
|
|
621
|
+
if (isObjectExpression(element)) element.properties.forEach((prop) => {
|
|
622
|
+
if (isObjectProperty(prop) && isIdentifier(prop.key) && isJSXElement(prop.value)) markProcessedJSX(prop.value, processedNodes);
|
|
543
623
|
});
|
|
544
624
|
});
|
|
545
625
|
};
|
|
546
626
|
const markProcessedJSX = (node, processedNodes) => {
|
|
547
|
-
if (
|
|
627
|
+
if (isJSXElement(node) || isJSXFragment(node)) {
|
|
548
628
|
processedNodes.add(node);
|
|
549
629
|
node.children.forEach((child) => markProcessedJSX(child, processedNodes));
|
|
550
630
|
return;
|
|
551
631
|
}
|
|
552
|
-
if (
|
|
632
|
+
if (isJSXExpressionContainer(node) || isParenthesizedExpression(node)) markProcessedJSX(node.expression, processedNodes);
|
|
553
633
|
};
|
|
554
634
|
const getBindingExpression = (opening) => {
|
|
555
|
-
for (const attr of opening.attributes) if (
|
|
635
|
+
for (const attr of opening.attributes) if (isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING && attr.value && isJSXExpressionContainer(attr.value)) {
|
|
556
636
|
const expression = unwrapExpression(attr.value.expression);
|
|
557
|
-
if (
|
|
637
|
+
if (isArrayExpression(expression)) return expression;
|
|
558
638
|
}
|
|
559
639
|
return null;
|
|
560
640
|
};
|
|
@@ -583,7 +663,7 @@ const readNodeBindingInfo = (node, source, options) => {
|
|
|
583
663
|
};
|
|
584
664
|
const parseToNodes = (raw, options) => {
|
|
585
665
|
const wrapped = wrap(raw);
|
|
586
|
-
const ast =
|
|
666
|
+
const ast = parse(wrapped, {
|
|
587
667
|
sourceType: "module",
|
|
588
668
|
plugins: ["jsx", "typescript"],
|
|
589
669
|
errorRecovery: true
|
|
@@ -648,106 +728,6 @@ function clearExtractCache() {
|
|
|
648
728
|
}
|
|
649
729
|
registerEditorCache(clearExtractCache);
|
|
650
730
|
//#endregion
|
|
651
|
-
//#region src/utils/selection.ts
|
|
652
|
-
const removeIndices = (items, indices) => {
|
|
653
|
-
return items.filter((_, index) => !indices.has(index));
|
|
654
|
-
};
|
|
655
|
-
/**
|
|
656
|
-
* Shifts every selected index up/down by one step as a block, preserving
|
|
657
|
-
* relative order — scattered selections stop moving individually once they
|
|
658
|
-
* hit an unselected neighbor, so the whole group slides together instead of
|
|
659
|
-
* items passing through each other.
|
|
660
|
-
*/
|
|
661
|
-
const moveSelectedIndices = (items, indices, direction) => {
|
|
662
|
-
const next = [...items];
|
|
663
|
-
const nextIndices = new Set(indices);
|
|
664
|
-
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
665
|
-
for (const index of ordered) {
|
|
666
|
-
const target = direction === "up" ? index - 1 : index + 1;
|
|
667
|
-
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
668
|
-
[next[index], next[target]] = [next[target], next[index]];
|
|
669
|
-
nextIndices.delete(index);
|
|
670
|
-
nextIndices.add(target);
|
|
671
|
-
}
|
|
672
|
-
return {
|
|
673
|
-
items: next,
|
|
674
|
-
indices: nextIndices
|
|
675
|
-
};
|
|
676
|
-
};
|
|
677
|
-
//#endregion
|
|
678
|
-
//#region src/utils/ast/patch.ts
|
|
679
|
-
const hasOnlyLayoutNewlines = (content) => {
|
|
680
|
-
let index = 0;
|
|
681
|
-
while (index < content.length) {
|
|
682
|
-
const char = content[index];
|
|
683
|
-
if (char === "`" || char === "<") return false;
|
|
684
|
-
if (char === "\"" || char === "'") {
|
|
685
|
-
const next = skipStringLiteral(content, index);
|
|
686
|
-
if (next === -1) return false;
|
|
687
|
-
index = next;
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
if (char === "/") {
|
|
691
|
-
const following = content[index + 1];
|
|
692
|
-
if (following === "/") {
|
|
693
|
-
const lineEnd = content.indexOf("\n", index + 2);
|
|
694
|
-
index = lineEnd === -1 ? content.length : lineEnd;
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
if (following === "*") {
|
|
698
|
-
const commentEnd = content.indexOf("*/", index + 2);
|
|
699
|
-
if (commentEnd === -1) return false;
|
|
700
|
-
index = commentEnd + 2;
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
index++;
|
|
706
|
-
}
|
|
707
|
-
return true;
|
|
708
|
-
};
|
|
709
|
-
const skipStringLiteral = (content, start) => {
|
|
710
|
-
const quote = content[start];
|
|
711
|
-
for (let index = start + 1; index < content.length; index++) {
|
|
712
|
-
const char = content[index];
|
|
713
|
-
if (char === "\\") {
|
|
714
|
-
const escaped = content[index + 1];
|
|
715
|
-
if (escaped === "\n" || escaped === "\r") return -1;
|
|
716
|
-
index++;
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
if (char === quote) return index + 1;
|
|
720
|
-
if (char === "\n") return -1;
|
|
721
|
-
}
|
|
722
|
-
return -1;
|
|
723
|
-
};
|
|
724
|
-
const lineIndentAt = (source, offset) => {
|
|
725
|
-
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
726
|
-
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
727
|
-
};
|
|
728
|
-
const lineTerminatorOf = (source) => {
|
|
729
|
-
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
730
|
-
};
|
|
731
|
-
const applyEdits = (source, edits) => {
|
|
732
|
-
if (edits.length === 0) return source;
|
|
733
|
-
const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
734
|
-
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}`);
|
|
735
|
-
for (let i = 1; i < ordered.length; i++) {
|
|
736
|
-
const previous = ordered[i - 1];
|
|
737
|
-
const current = ordered[i];
|
|
738
|
-
if (current.start < previous.end) throw new Error(`Overlapping source edits: [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
|
|
739
|
-
}
|
|
740
|
-
let result = "";
|
|
741
|
-
let cursor = 0;
|
|
742
|
-
const terminator = lineTerminatorOf(source);
|
|
743
|
-
for (const edit of ordered) {
|
|
744
|
-
const content = edit.indent && edit.content.includes("\n") && hasOnlyLayoutNewlines(edit.content) ? edit.content.replace(/\n/g, `${terminator}${lineIndentAt(source, edit.start)}`) : edit.content;
|
|
745
|
-
result += source.slice(cursor, edit.start) + content;
|
|
746
|
-
cursor = edit.end;
|
|
747
|
-
}
|
|
748
|
-
return result + source.slice(cursor);
|
|
749
|
-
};
|
|
750
|
-
//#endregion
|
|
751
731
|
//#region src/utils/ast/children.ts
|
|
752
732
|
const modeledSignatures = (nodes) => nodes.map((node) => {
|
|
753
733
|
const jsx = nodeToJSX(node);
|
|
@@ -760,7 +740,7 @@ const getChildrenSignatures = (nodes) => {
|
|
|
760
740
|
};
|
|
761
741
|
const indicesAreValid = (indices, count) => Array.isArray(indices) && new Set(indices).size === indices.length && indices.every((index) => Number.isInteger(index) && index >= 0 && index < count);
|
|
762
742
|
const copySource = (source, usedIds) => {
|
|
763
|
-
const node =
|
|
743
|
+
const node = parseExpression(source, { plugins: ["jsx", "typescript"] });
|
|
764
744
|
const edits = [];
|
|
765
745
|
const newId = () => {
|
|
766
746
|
let id = nanoid(12);
|
|
@@ -768,15 +748,15 @@ const copySource = (source, usedIds) => {
|
|
|
768
748
|
usedIds.add(id);
|
|
769
749
|
return id;
|
|
770
750
|
};
|
|
771
|
-
|
|
772
|
-
if (!
|
|
773
|
-
const ids = child.attributes.filter((attr) =>
|
|
751
|
+
traverseFast(node, (child) => {
|
|
752
|
+
if (!isJSXOpeningElement(child)) return;
|
|
753
|
+
const ids = child.attributes.filter((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
774
754
|
if (ids.length > 1) throw new Error("Duplicate data-id attributes");
|
|
775
755
|
const attr = ids[0];
|
|
776
|
-
if (attr && child.attributes.slice(child.attributes.indexOf(attr) + 1).some((attribute) =>
|
|
756
|
+
if (attr && child.attributes.slice(child.attributes.indexOf(attr) + 1).some((attribute) => isJSXSpreadAttribute(attribute))) throw new Error("A spread may override the copied data-id");
|
|
777
757
|
if (attr) {
|
|
778
758
|
const value = attr.value;
|
|
779
|
-
if (!
|
|
759
|
+
if (!isStringLiteral(value) || value.start == null || value.end == null) throw new Error("Cannot copy a dynamic data-id");
|
|
780
760
|
edits.push({
|
|
781
761
|
start: value.start,
|
|
782
762
|
end: value.end,
|
|
@@ -794,11 +774,29 @@ const copySource = (source, usedIds) => {
|
|
|
794
774
|
});
|
|
795
775
|
return applyEdits(source, edits);
|
|
796
776
|
};
|
|
777
|
+
const insertChildren = (source, parent, last, copies) => {
|
|
778
|
+
const closing = parent.closingElement.start;
|
|
779
|
+
if (last?.start != null && last.end != null) {
|
|
780
|
+
const lineStart = source.lastIndexOf("\n", last.start - 1) + 1;
|
|
781
|
+
const indent = source.slice(lineStart, last.start);
|
|
782
|
+
const after = /^[ \t]*(\r?\n)/.exec(source.slice(last.end, closing));
|
|
783
|
+
if (after && /^[ \t]*$/.test(indent)) return {
|
|
784
|
+
start: last.end,
|
|
785
|
+
end: last.end,
|
|
786
|
+
content: copies.map((copy) => `${after[1]}${indent}${copy}`).join("")
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
start: closing,
|
|
791
|
+
end: closing,
|
|
792
|
+
content: copies.join("")
|
|
793
|
+
};
|
|
794
|
+
};
|
|
797
795
|
const editChildrenSource = (source, parent, input, options) => {
|
|
798
796
|
try {
|
|
799
797
|
if (!parent.closingElement || parent.start == null || parent.end == null) return null;
|
|
800
798
|
const value = typeof input === "string" ? JSON.parse(input) : input;
|
|
801
|
-
const children = parent.children.filter((child) =>
|
|
799
|
+
const children = parent.children.filter((child) => isJSXElement(child) || isJSXFragment(child));
|
|
802
800
|
const raw = children.map((child) => source.slice(child.start, child.end));
|
|
803
801
|
const models = extract(source.slice(parent.start, parent.end), options)[0]?.children ?? [];
|
|
804
802
|
if (models.length !== children.length) return null;
|
|
@@ -809,19 +807,12 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
809
807
|
end: child.end,
|
|
810
808
|
content: next[index] ?? ""
|
|
811
809
|
}));
|
|
812
|
-
if (next.length > children.length)
|
|
813
|
-
const at = parent.closingElement.start;
|
|
814
|
-
edits.push({
|
|
815
|
-
start: at,
|
|
816
|
-
end: at,
|
|
817
|
-
content: next.slice(children.length).join("")
|
|
818
|
-
});
|
|
819
|
-
}
|
|
810
|
+
if (next.length > children.length) edits.push(insertChildren(source, parent, children.at(-1), next.slice(children.length)));
|
|
820
811
|
return edits;
|
|
821
812
|
};
|
|
822
813
|
if (Array.isArray(value)) {
|
|
823
814
|
const desired = modeledSignatures(value);
|
|
824
|
-
const lossless = raw.every((text, index) => generateCode(
|
|
815
|
+
const lossless = raw.every((text, index) => generateCode(parseExpression(text, { plugins: ["jsx", "typescript"] })) === signatures[index]);
|
|
825
816
|
const consumed = /* @__PURE__ */ new Set();
|
|
826
817
|
const next = desired.map((signature, position) => {
|
|
827
818
|
const index = signatures.findIndex((candidate, i) => candidate === signature && !consumed.has(i) && (value[position].source === void 0 || value[position].source === raw[i]));
|
|
@@ -834,8 +825,8 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
834
825
|
});
|
|
835
826
|
const countIds = (text) => {
|
|
836
827
|
const counts = /* @__PURE__ */ new Map();
|
|
837
|
-
|
|
838
|
-
if (
|
|
828
|
+
traverseFast(parseExpression(text, { plugins: ["jsx", "typescript"] }), (node) => {
|
|
829
|
+
if (isJSXAttribute(node) && isJSXIdentifier(node.name, { name: "data-id" }) && isStringLiteral(node.value)) counts.set(node.value.value, (counts.get(node.value.value) ?? 0) + 1);
|
|
839
830
|
});
|
|
840
831
|
return counts;
|
|
841
832
|
};
|
|
@@ -847,8 +838,8 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
847
838
|
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;
|
|
848
839
|
const { action } = request;
|
|
849
840
|
const usedIds = /* @__PURE__ */ new Set();
|
|
850
|
-
|
|
851
|
-
if (
|
|
841
|
+
traverseFast(parent, (node) => {
|
|
842
|
+
if (isJSXAttribute(node) && isJSXIdentifier(node.name, { name: "data-id" }) && isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
852
843
|
});
|
|
853
844
|
switch (action.type) {
|
|
854
845
|
case "move": {
|
|
@@ -870,22 +861,12 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
870
861
|
}));
|
|
871
862
|
case "duplicate": {
|
|
872
863
|
if (!indicesAreValid(action.indices, raw.length)) return null;
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
return [{
|
|
876
|
-
start: at,
|
|
877
|
-
end: at,
|
|
878
|
-
content
|
|
879
|
-
}];
|
|
864
|
+
const copies = [...action.indices].sort((a, b) => a - b).map((index) => copySource(raw[index], usedIds));
|
|
865
|
+
return [insertChildren(source, parent, children.at(-1), copies)];
|
|
880
866
|
}
|
|
881
867
|
case "append": {
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
return [{
|
|
885
|
-
start: at,
|
|
886
|
-
end: at,
|
|
887
|
-
content
|
|
888
|
-
}];
|
|
868
|
+
const copy = copySource(raw[0] ?? "<div></div>", usedIds);
|
|
869
|
+
return [insertChildren(source, parent, children.at(-1), [copy])];
|
|
889
870
|
}
|
|
890
871
|
default: return null;
|
|
891
872
|
}
|
|
@@ -897,10 +878,10 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
897
878
|
//#region src/utils/ast/value-expression.ts
|
|
898
879
|
const createNodeFromValue = (type, value) => {
|
|
899
880
|
switch (type) {
|
|
900
|
-
case "boolean": return
|
|
901
|
-
case "number": return
|
|
902
|
-
case "string": return
|
|
903
|
-
case "null": return
|
|
881
|
+
case "boolean": return booleanLiteral(value === true);
|
|
882
|
+
case "number": return numericLiteral(Number(value));
|
|
883
|
+
case "string": return stringLiteral(String(value));
|
|
884
|
+
case "null": return nullLiteral();
|
|
904
885
|
case "array":
|
|
905
886
|
case "object":
|
|
906
887
|
case "unknown": return null;
|
|
@@ -908,26 +889,22 @@ const createNodeFromValue = (type, value) => {
|
|
|
908
889
|
}
|
|
909
890
|
};
|
|
910
891
|
const valueToExpression = (value) => {
|
|
911
|
-
if (typeof value === "string") return
|
|
912
|
-
if (typeof value === "number") return value < 0 ?
|
|
913
|
-
if (typeof value === "boolean") return
|
|
914
|
-
if (value === null) return
|
|
915
|
-
if (Array.isArray(value)) return
|
|
892
|
+
if (typeof value === "string") return stringLiteral(value);
|
|
893
|
+
if (typeof value === "number") return value < 0 ? unaryExpression("-", numericLiteral(-value)) : numericLiteral(value);
|
|
894
|
+
if (typeof value === "boolean") return booleanLiteral(value);
|
|
895
|
+
if (value === null) return nullLiteral();
|
|
896
|
+
if (Array.isArray(value)) return arrayExpression(value.map((item) => valueToExpression(item) ?? nullLiteral()));
|
|
916
897
|
if (typeof value === "object") {
|
|
917
898
|
const properties = [];
|
|
918
899
|
for (const [key, item] of Object.entries(value)) {
|
|
919
900
|
const expr = valueToExpression(item);
|
|
920
901
|
if (expr === null) continue;
|
|
921
|
-
properties.push(
|
|
902
|
+
properties.push(objectProperty(stringLiteral(key), expr));
|
|
922
903
|
}
|
|
923
|
-
return
|
|
904
|
+
return objectExpression(properties);
|
|
924
905
|
}
|
|
925
906
|
return null;
|
|
926
907
|
};
|
|
927
|
-
const arrayExpressionToCode = (elements) => {
|
|
928
|
-
const nextAst = import_lib$1.arrayExpression(elements);
|
|
929
|
-
return generateCode(nextAst);
|
|
930
|
-
};
|
|
931
908
|
//#endregion
|
|
932
909
|
//#region src/utils/ast/edit-source.ts
|
|
933
910
|
const childrenRange = (element) => {
|
|
@@ -939,29 +916,33 @@ const childrenRange = (element) => {
|
|
|
939
916
|
};
|
|
940
917
|
};
|
|
941
918
|
const findAttribute = (opening, propertyName) => {
|
|
942
|
-
return opening.attributes.find((attr) =>
|
|
919
|
+
return opening.attributes.find((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === propertyName);
|
|
943
920
|
};
|
|
944
921
|
const attributeInsertPoint = (opening) => {
|
|
945
922
|
return opening.attributes[opening.attributes.length - 1]?.end ?? opening.name.end ?? null;
|
|
946
923
|
};
|
|
924
|
+
const escapeJsxText = (value) => value.replace(/&(?=#?[A-Za-z0-9]+;)/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\{/g, "{").replace(/\}/g, "}");
|
|
947
925
|
const editInnerText = (source, element, value) => {
|
|
948
926
|
const range = childrenRange(element);
|
|
949
927
|
if (!range) return [];
|
|
928
|
+
const text = escapeJsxText(value);
|
|
950
929
|
const [only] = element.children;
|
|
951
|
-
if (element.children.length === 1 &&
|
|
930
|
+
if (element.children.length === 1 && isJSXText(only) && only.start != null && only.end != null) {
|
|
952
931
|
const raw = source.slice(only.start, only.end);
|
|
953
932
|
if (raw.trim() !== "") {
|
|
954
|
-
const
|
|
955
|
-
const
|
|
933
|
+
const leadingSpace = raw.slice(0, raw.length - raw.trimStart().length);
|
|
934
|
+
const trailingSpace = raw.slice(raw.trimEnd().length);
|
|
935
|
+
const leading = /[\r\n]/.test(leadingSpace) ? leadingSpace.length : 0;
|
|
936
|
+
const trailing = /[\r\n]/.test(trailingSpace) ? trailingSpace.length : 0;
|
|
956
937
|
return [{
|
|
957
938
|
start: only.start + leading,
|
|
958
939
|
end: only.end - trailing,
|
|
959
|
-
content:
|
|
940
|
+
content: text
|
|
960
941
|
}];
|
|
961
942
|
}
|
|
962
943
|
}
|
|
963
944
|
const edits = [];
|
|
964
|
-
for (const child of element.children) if (
|
|
945
|
+
for (const child of element.children) if (isJSXText(child) && child.start != null && child.end != null) edits.push({
|
|
965
946
|
start: child.start,
|
|
966
947
|
end: child.end,
|
|
967
948
|
content: ""
|
|
@@ -969,7 +950,7 @@ const editInnerText = (source, element, value) => {
|
|
|
969
950
|
edits.push({
|
|
970
951
|
start: range.end,
|
|
971
952
|
end: range.end,
|
|
972
|
-
content:
|
|
953
|
+
content: text
|
|
973
954
|
});
|
|
974
955
|
return edits;
|
|
975
956
|
};
|
|
@@ -991,7 +972,7 @@ const editRichtext = (element, value) => {
|
|
|
991
972
|
content: ""
|
|
992
973
|
});
|
|
993
974
|
const opening = element.openingElement;
|
|
994
|
-
const attribute =
|
|
975
|
+
const attribute = jsxAttribute(jsxIdentifier("dangerouslySetInnerHTML"), jsxExpressionContainer(objectExpression([objectProperty(identifier("__html"), stringLiteral(value))])));
|
|
995
976
|
const content = generateCode(attribute);
|
|
996
977
|
const existing = findAttribute(opening, "dangerouslySetInnerHTML");
|
|
997
978
|
if (existing && existing.start != null && existing.end != null) {
|
|
@@ -1030,41 +1011,54 @@ const editJsxAttribute = (opening, propertyName, value) => {
|
|
|
1030
1011
|
content: `=${content}`
|
|
1031
1012
|
}];
|
|
1032
1013
|
};
|
|
1033
|
-
const holdsStructuralExpression = (value) =>
|
|
1014
|
+
const holdsStructuralExpression = (value) => isJSXExpressionContainer(value) && (isArrayExpression(unwrapExpression(value.expression)) || isObjectExpression(unwrapExpression(value.expression)));
|
|
1034
1015
|
const structuralSource = (value) => {
|
|
1035
1016
|
try {
|
|
1036
|
-
const expression =
|
|
1037
|
-
return
|
|
1017
|
+
const expression = parseExpression(value.trim(), { plugins: ["jsx", "typescript"] });
|
|
1018
|
+
return isArrayExpression(unwrapExpression(expression)) || isObjectExpression(unwrapExpression(expression)) ? expression : null;
|
|
1038
1019
|
} catch {
|
|
1039
1020
|
return null;
|
|
1040
1021
|
}
|
|
1041
1022
|
};
|
|
1023
|
+
const hasControlCharacter = (value) => Array.from(value).some((char) => char.charCodeAt(0) < 32);
|
|
1024
|
+
const jsxStringValue = (value) => {
|
|
1025
|
+
if (hasControlCharacter(value)) return jsxExpressionContainer(stringLiteral(value));
|
|
1026
|
+
const quote = value.includes("\"") && !value.includes("'") ? "'" : "\"";
|
|
1027
|
+
const text = value.replace(/&(?=#?[A-Za-z0-9]+;)/g, "&");
|
|
1028
|
+
const escaped = quote === "\"" ? text.replace(/"/g, """) : text;
|
|
1029
|
+
const literal = stringLiteral(value);
|
|
1030
|
+
literal.extra = {
|
|
1031
|
+
raw: `${quote}${escaped}${quote}`,
|
|
1032
|
+
rawValue: value
|
|
1033
|
+
};
|
|
1034
|
+
return literal;
|
|
1035
|
+
};
|
|
1042
1036
|
const buildAttributeValue = (value, type, current) => {
|
|
1043
1037
|
if (type === "array" || type === "object") {
|
|
1044
1038
|
if (typeof value === "string") try {
|
|
1045
|
-
return
|
|
1039
|
+
return jsxExpressionContainer(parseExpression(value.trim(), { plugins: ["jsx", "typescript"] }));
|
|
1046
1040
|
} catch {
|
|
1047
|
-
return
|
|
1041
|
+
return jsxStringValue(value);
|
|
1048
1042
|
}
|
|
1049
1043
|
const expr = valueToExpression(value);
|
|
1050
|
-
return expr ?
|
|
1044
|
+
return expr ? jsxExpressionContainer(expr) : stringLiteral("");
|
|
1051
1045
|
}
|
|
1052
1046
|
if (typeof value === "string") {
|
|
1053
1047
|
if (holdsStructuralExpression(current) && !(type && STRING_VALUED_TYPES.has(type))) {
|
|
1054
1048
|
const structural = structuralSource(value);
|
|
1055
|
-
if (structural) return
|
|
1049
|
+
if (structural) return jsxExpressionContainer(structural);
|
|
1056
1050
|
}
|
|
1057
|
-
return
|
|
1051
|
+
return jsxStringValue(value);
|
|
1058
1052
|
}
|
|
1059
1053
|
const expr = valueToExpression(value);
|
|
1060
|
-
return expr ?
|
|
1054
|
+
return expr ? jsxExpressionContainer(expr) : jsxStringValue(String(value));
|
|
1061
1055
|
};
|
|
1062
1056
|
const editAttribute = (opening, propertyName, value, type) => {
|
|
1063
1057
|
const attribute = findAttribute(opening, propertyName);
|
|
1064
1058
|
if (!attribute || attribute.start == null || attribute.end == null) return null;
|
|
1065
1059
|
if (typeof value === "string" && (type === "array" || type === "object" || holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)))) {
|
|
1066
1060
|
const expression = structuralSource(value);
|
|
1067
|
-
if (expression &&
|
|
1061
|
+
if (expression && isJSXExpressionContainer(attribute.value)) {
|
|
1068
1062
|
const text = value.trim();
|
|
1069
1063
|
const lineComment = expression.trailingComments?.some((comment) => comment.type === "CommentLine");
|
|
1070
1064
|
return [{
|
|
@@ -1074,7 +1068,7 @@ const editAttribute = (opening, propertyName, value, type) => {
|
|
|
1074
1068
|
}];
|
|
1075
1069
|
}
|
|
1076
1070
|
}
|
|
1077
|
-
const content = generateCode(
|
|
1071
|
+
const content = generateCode(jsxAttribute(attribute.name, buildAttributeValue(value, type, attribute.value)));
|
|
1078
1072
|
return [{
|
|
1079
1073
|
start: attribute.start,
|
|
1080
1074
|
end: attribute.end,
|
|
@@ -1082,8 +1076,16 @@ const editAttribute = (opening, propertyName, value, type) => {
|
|
|
1082
1076
|
indent: true
|
|
1083
1077
|
}];
|
|
1084
1078
|
};
|
|
1079
|
+
const TRAILING_COMMENTS = /^[ \t]*(?:\/\*[^\r\n]*?\*\/[ \t]*)*(?:\/\/[^\r\n]*)?\r?\n/;
|
|
1085
1080
|
const removeAttribute = (source, attribute) => {
|
|
1086
1081
|
if (attribute.start == null || attribute.end == null) return null;
|
|
1082
|
+
const lineStart = source.lastIndexOf("\n", attribute.start - 1) + 1;
|
|
1083
|
+
const rest = TRAILING_COMMENTS.exec(source.slice(attribute.end));
|
|
1084
|
+
if (/^[ \t]*$/.test(source.slice(lineStart, attribute.start)) && rest) return [{
|
|
1085
|
+
start: lineStart,
|
|
1086
|
+
end: attribute.end + rest[0].length,
|
|
1087
|
+
content: ""
|
|
1088
|
+
}];
|
|
1087
1089
|
let start = attribute.start;
|
|
1088
1090
|
while (start > 0 && /\s/.test(source[start - 1])) start -= 1;
|
|
1089
1091
|
return [{
|
|
@@ -1100,18 +1102,18 @@ const addAttribute = (source, opening, propertyName, value, type) => {
|
|
|
1100
1102
|
return [{
|
|
1101
1103
|
start: insertAt,
|
|
1102
1104
|
end: insertAt,
|
|
1103
|
-
content: `${lastStart != null && lineStart > 0 && /^\s*$/.test(source.slice(lineStart, lastStart)) ? "\n" : " "}${type === "jsx" ? `${propertyName}={${String(value).trim()}}` : generateCode(
|
|
1105
|
+
content: `${lastStart != null && lineStart > 0 && /^\s*$/.test(source.slice(lineStart, lastStart)) ? "\n" : " "}${type === "jsx" ? `${propertyName}={${String(value).trim()}}` : generateCode(jsxAttribute(jsxIdentifier(propertyName), buildAttributeValue(value, type)))}`,
|
|
1104
1106
|
indent: true
|
|
1105
1107
|
}];
|
|
1106
1108
|
};
|
|
1107
1109
|
const canEditAttributeValue = (attribute, value, type) => {
|
|
1108
|
-
if (!
|
|
1110
|
+
if (!isJSXExpressionContainer(attribute.value) || isJSXEmptyExpression(attribute.value.expression) || isLosslesslyEvaluable(attribute.value.expression)) return true;
|
|
1109
1111
|
return typeof value === "string" && holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)) && structuralSource(value) !== null;
|
|
1110
1112
|
};
|
|
1111
1113
|
//#endregion
|
|
1112
1114
|
//#region src/utils/ast/update.ts
|
|
1113
|
-
const hasDataId = (opening, dataId) => opening.attributes.some((attr) =>
|
|
1114
|
-
const findJSXAttribute = (opening, name) => opening.attributes.find((attr) =>
|
|
1115
|
+
const hasDataId = (opening, dataId) => opening.attributes.some((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.ID && attr.value && isStringLiteral(attr.value) && attr.value.value === dataId);
|
|
1116
|
+
const findJSXAttribute = (opening, name) => opening.attributes.find((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === name);
|
|
1115
1117
|
const findBinding = (opening, { dataId, label, property, options }) => {
|
|
1116
1118
|
const bindingAttr = findJSXAttribute(opening, DATA_ATTR.BINDING);
|
|
1117
1119
|
if (bindingAttr && !bindingAttr.value) return { failure: {
|
|
@@ -1121,8 +1123,8 @@ const findBinding = (opening, { dataId, label, property, options }) => {
|
|
|
1121
1123
|
let own;
|
|
1122
1124
|
if (bindingAttr?.value) {
|
|
1123
1125
|
let bindingValue = "";
|
|
1124
|
-
if (
|
|
1125
|
-
else if (
|
|
1126
|
+
if (isStringLiteral(bindingAttr.value)) bindingValue = bindingAttr.value.value;
|
|
1127
|
+
else if (isJSXExpressionContainer(bindingAttr.value)) try {
|
|
1126
1128
|
bindingValue = generateCode(bindingAttr.value.expression);
|
|
1127
1129
|
} catch (error) {
|
|
1128
1130
|
return { failure: {
|
|
@@ -1133,7 +1135,7 @@ const findBinding = (opening, { dataId, label, property, options }) => {
|
|
|
1133
1135
|
own = parseBinding(bindingValue);
|
|
1134
1136
|
}
|
|
1135
1137
|
const keyAttr = findJSXAttribute(opening, DATA_ATTR.BINDING_KEY);
|
|
1136
|
-
const bindings = resolveBindings(own, keyAttr && (keyAttr.value &&
|
|
1138
|
+
const bindings = resolveBindings(own, keyAttr && (keyAttr.value && isStringLiteral(keyAttr.value) ? keyAttr.value.value : null), getJSXTagName(opening), options);
|
|
1137
1139
|
if (!own && bindings.length === 0) return { failure: {
|
|
1138
1140
|
reason: "no-binding",
|
|
1139
1141
|
dataId
|
|
@@ -1243,7 +1245,7 @@ const update = (code, dataId, label, value, property, options = {}) => {
|
|
|
1243
1245
|
};
|
|
1244
1246
|
try {
|
|
1245
1247
|
const wrapped = wrap(code);
|
|
1246
|
-
const ast =
|
|
1248
|
+
const ast = parse(wrapped, {
|
|
1247
1249
|
sourceType: "module",
|
|
1248
1250
|
plugins: ["jsx", "typescript"]
|
|
1249
1251
|
});
|
|
@@ -1304,260 +1306,7 @@ const updateAll = (raw, entries, options = {}) => {
|
|
|
1304
1306
|
code: current
|
|
1305
1307
|
};
|
|
1306
1308
|
};
|
|
1307
|
-
/**
|
|
1308
|
-
* @deprecated Not atomic: when an entry fails, the ones before and after it
|
|
1309
|
-
* are still applied, and the returned `code` carries them. Use `updateAll`,
|
|
1310
|
-
* which applies every entry or none. Will be removed in the next major.
|
|
1311
|
-
*/
|
|
1312
|
-
const bulkUpdate = (raw, entries) => {
|
|
1313
|
-
let current = raw;
|
|
1314
|
-
const failures = [];
|
|
1315
|
-
for (const entry of entries) {
|
|
1316
|
-
const result = update(current, entry.dataId, entry.label, entry.value, entry.property);
|
|
1317
|
-
current = result.code;
|
|
1318
|
-
if (!result.success && result.failure) failures.push(result.failure);
|
|
1319
|
-
}
|
|
1320
|
-
return failures.length > 0 ? {
|
|
1321
|
-
code: current,
|
|
1322
|
-
success: false,
|
|
1323
|
-
failures
|
|
1324
|
-
} : {
|
|
1325
|
-
code: current,
|
|
1326
|
-
success: true
|
|
1327
|
-
};
|
|
1328
|
-
};
|
|
1329
|
-
//#endregion
|
|
1330
|
-
//#region src/utils/ast/array-source.ts
|
|
1331
|
-
const commaIn = (source, start, end) => {
|
|
1332
|
-
let comma = null;
|
|
1333
|
-
for (let at = start; at < end; at++) {
|
|
1334
|
-
if (/\s/.test(source[at])) continue;
|
|
1335
|
-
if (source.startsWith("/*", at)) {
|
|
1336
|
-
const close = source.indexOf("*/", at + 2);
|
|
1337
|
-
if (close < 0 || close + 2 > end) return null;
|
|
1338
|
-
at = close + 1;
|
|
1339
|
-
} else if (source.startsWith("//", at)) {
|
|
1340
|
-
const newline = source.indexOf("\n", at + 2);
|
|
1341
|
-
at = newline < 0 ? end : newline;
|
|
1342
|
-
} else if (source[at] === "," && comma === null) comma = at;
|
|
1343
|
-
else return null;
|
|
1344
|
-
}
|
|
1345
|
-
return comma;
|
|
1346
|
-
};
|
|
1347
|
-
const denseArraySource = (source) => {
|
|
1348
|
-
const array = parseArrayExpression(source);
|
|
1349
|
-
if (!array || array.elements.some((node) => !import_lib$1.isExpression(node) || node.extra?.parenthesized)) return null;
|
|
1350
|
-
const elements = array.elements;
|
|
1351
|
-
const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
|
|
1352
|
-
if (commas.some((comma) => comma === null)) return null;
|
|
1353
|
-
const last = elements.at(-1);
|
|
1354
|
-
return {
|
|
1355
|
-
array,
|
|
1356
|
-
elements,
|
|
1357
|
-
commas,
|
|
1358
|
-
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
1359
|
-
};
|
|
1360
|
-
};
|
|
1361
|
-
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
1362
|
-
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
1363
|
-
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
1364
|
-
start: node.start,
|
|
1365
|
-
end: node.end,
|
|
1366
|
-
content: source.slice(next[index].start, next[index].end)
|
|
1367
|
-
})));
|
|
1368
|
-
const removeArraySource = (source, data, indices) => {
|
|
1369
|
-
const edits = [];
|
|
1370
|
-
const removedCommas = /* @__PURE__ */ new Set();
|
|
1371
|
-
const { elements, commas, trailing } = data;
|
|
1372
|
-
for (let index = 0; index < elements.length; index++) {
|
|
1373
|
-
if (!indices.has(index)) continue;
|
|
1374
|
-
const start = index;
|
|
1375
|
-
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
1376
|
-
for (let current = start; current <= index; current++) {
|
|
1377
|
-
const node = elements[current];
|
|
1378
|
-
edits.push({
|
|
1379
|
-
start: node.start,
|
|
1380
|
-
end: node.end,
|
|
1381
|
-
content: ""
|
|
1382
|
-
});
|
|
1383
|
-
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
1384
|
-
}
|
|
1385
|
-
if (index === elements.length - 1) {
|
|
1386
|
-
if (trailing !== null) removedCommas.add(trailing);
|
|
1387
|
-
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
for (const comma of removedCommas) edits.push({
|
|
1391
|
-
start: comma,
|
|
1392
|
-
end: comma + 1,
|
|
1393
|
-
content: ""
|
|
1394
|
-
});
|
|
1395
|
-
return applyEdits(source, edits);
|
|
1396
|
-
};
|
|
1397
|
-
const appendArraySource = (source, data, copies) => {
|
|
1398
|
-
const last = data.elements.at(-1);
|
|
1399
|
-
if (!last || copies.length === 0) return source;
|
|
1400
|
-
return applyEdits(source, [{
|
|
1401
|
-
start: last.end,
|
|
1402
|
-
end: last.end,
|
|
1403
|
-
content: `, ${copies.join(", ")}`
|
|
1404
|
-
}]);
|
|
1405
|
-
};
|
|
1406
|
-
//#endregion
|
|
1407
|
-
//#region src/utils/ast/items.ts
|
|
1408
|
-
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
1409
|
-
const kindOf = (element) => import_lib$1.isObjectExpression(element) ? "object" : "primitive";
|
|
1410
|
-
const parseItems = (code) => {
|
|
1411
|
-
return elementsOf(code)?.flatMap((node, index) => import_lib$1.isExpression(node) ? [{
|
|
1412
|
-
index,
|
|
1413
|
-
kind: kindOf(node),
|
|
1414
|
-
node
|
|
1415
|
-
}] : []) ?? null;
|
|
1416
|
-
};
|
|
1417
|
-
const resolveRenderLeaf = (render, key) => {
|
|
1418
|
-
const leaf = render?.[key];
|
|
1419
|
-
return leaf && "type" in leaf ? leaf : null;
|
|
1420
|
-
};
|
|
1421
|
-
const toTemplateRaw = (value) => {
|
|
1422
|
-
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
1423
|
-
};
|
|
1424
|
-
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
1425
|
-
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
1426
|
-
const raw = String(value);
|
|
1427
|
-
return import_lib$1.templateLiteral([import_lib$1.templateElement({
|
|
1428
|
-
raw: toTemplateRaw(raw),
|
|
1429
|
-
cooked: raw
|
|
1430
|
-
}, true)], []);
|
|
1431
|
-
}
|
|
1432
|
-
if (renderLeaf?.type === "jsx" || import_lib$1.isJSXElement(current) || import_lib$1.isJSXFragment(current)) {
|
|
1433
|
-
const trimmed = String(value).trim();
|
|
1434
|
-
if (!trimmed.startsWith("<")) return import_lib$1.stringLiteral(trimmed);
|
|
1435
|
-
try {
|
|
1436
|
-
return (0, import_lib.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
1437
|
-
} catch {
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
if (declaredType === "array" || declaredType === "object") {
|
|
1442
|
-
if (typeof value === "string") try {
|
|
1443
|
-
return (0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] });
|
|
1444
|
-
} catch {
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
if (!isLosslesslyEvaluable(current)) return;
|
|
1448
|
-
return valueToExpression(value) ?? void 0;
|
|
1449
|
-
}
|
|
1450
|
-
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
1451
|
-
};
|
|
1452
|
-
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
1453
|
-
const elements = elementsOf(code);
|
|
1454
|
-
const element = elements?.[index];
|
|
1455
|
-
if (!elements || !import_lib$1.isObjectExpression(element)) return null;
|
|
1456
|
-
const target = element.properties.find((property) => import_lib$1.isObjectProperty(property) && import_lib$1.isIdentifier(property.key) && property.key.name === key);
|
|
1457
|
-
if (!target) return null;
|
|
1458
|
-
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
1459
|
-
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
1460
|
-
if (!nextValue) return null;
|
|
1461
|
-
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || import_lib$1.isJSXElement(nextValue) || import_lib$1.isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
1462
|
-
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
1463
|
-
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
1464
|
-
return applyEdits(code, [{
|
|
1465
|
-
start: target.value.start,
|
|
1466
|
-
end: target.value.end,
|
|
1467
|
-
content
|
|
1468
|
-
}]);
|
|
1469
|
-
};
|
|
1470
|
-
const updateArrayItemValue = (code, index, value) => {
|
|
1471
|
-
const elements = elementsOf(code);
|
|
1472
|
-
const element = elements?.[index];
|
|
1473
|
-
if (!elements || !import_lib$1.isExpression(element)) return null;
|
|
1474
|
-
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
1475
|
-
if (!nextValue) return null;
|
|
1476
|
-
return applyEdits(code, [{
|
|
1477
|
-
start: element.start,
|
|
1478
|
-
end: element.end,
|
|
1479
|
-
content: generateCode(nextValue)
|
|
1480
|
-
}]);
|
|
1481
|
-
};
|
|
1482
|
-
const moveArrayItem = (code, from, to) => {
|
|
1483
|
-
const data = denseArraySource(code);
|
|
1484
|
-
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
1485
|
-
const next = [...data.elements];
|
|
1486
|
-
const [moved] = next.splice(from, 1);
|
|
1487
|
-
next.splice(to, 0, moved);
|
|
1488
|
-
return reorderArraySource(code, data.elements, next);
|
|
1489
|
-
};
|
|
1490
|
-
const moveArrayItems = (code, indices, direction) => {
|
|
1491
|
-
const data = denseArraySource(code);
|
|
1492
|
-
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
1493
|
-
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
1494
|
-
return {
|
|
1495
|
-
code: reorderArraySource(code, data.elements, next.items),
|
|
1496
|
-
indices: next.indices
|
|
1497
|
-
};
|
|
1498
|
-
};
|
|
1499
|
-
const removeArrayItems = (code, indices, kind) => {
|
|
1500
|
-
const data = denseArraySource(code);
|
|
1501
|
-
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
1502
|
-
const remaining = removeIndices(data.elements, indices);
|
|
1503
|
-
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
1504
|
-
return removeArraySource(code, data, indices);
|
|
1505
|
-
};
|
|
1506
|
-
const copyItem = (code, element, generateId, usedIds) => {
|
|
1507
|
-
const edits = [];
|
|
1508
|
-
let unsupported = false;
|
|
1509
|
-
const reserveId = (id) => {
|
|
1510
|
-
if (usedIds.has(id)) unsupported = true;
|
|
1511
|
-
usedIds.add(id);
|
|
1512
|
-
return JSON.stringify(id);
|
|
1513
|
-
};
|
|
1514
|
-
import_lib$1.traverseFast(element, (node) => {
|
|
1515
|
-
if (!import_lib$1.isJSXOpeningElement(node)) return;
|
|
1516
|
-
const ids = node.attributes.filter((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
1517
|
-
const attr = ids[0];
|
|
1518
|
-
if (ids.length > 1 || attr && (!import_lib$1.isStringLiteral(attr.value) || node.attributes.slice(node.attributes.indexOf(attr) + 1).some((a) => import_lib$1.isJSXSpreadAttribute(a)))) {
|
|
1519
|
-
unsupported = true;
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
if (attr && import_lib$1.isStringLiteral(attr.value)) edits.push({
|
|
1523
|
-
start: attr.value.start,
|
|
1524
|
-
end: attr.value.end,
|
|
1525
|
-
content: reserveId(generateId())
|
|
1526
|
-
});
|
|
1527
|
-
});
|
|
1528
|
-
if (import_lib$1.isObjectExpression(element)) {
|
|
1529
|
-
const keys = element.properties.filter((prop) => import_lib$1.isObjectProperty(prop) && !prop.computed && (import_lib$1.isIdentifier(prop.key, { name: "key" }) || import_lib$1.isStringLiteral(prop.key, { value: "key" })));
|
|
1530
|
-
const key = keys[0];
|
|
1531
|
-
if (keys.length > 1 || key && (!import_lib$1.isStringLiteral(key.value) || element.properties.slice(element.properties.indexOf(key) + 1).some((prop) => import_lib$1.isSpreadElement(prop) || "computed" in prop && prop.computed))) return null;
|
|
1532
|
-
if (key && import_lib$1.isStringLiteral(key.value)) edits.push({
|
|
1533
|
-
start: key.value.start,
|
|
1534
|
-
end: key.value.end,
|
|
1535
|
-
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
if (unsupported) return null;
|
|
1539
|
-
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
1540
|
-
...edit,
|
|
1541
|
-
start: edit.start - element.start,
|
|
1542
|
-
end: edit.end - element.start
|
|
1543
|
-
})));
|
|
1544
|
-
};
|
|
1545
|
-
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
1546
|
-
const data = denseArraySource(code);
|
|
1547
|
-
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
1548
|
-
const usedIds = /* @__PURE__ */ new Set();
|
|
1549
|
-
import_lib$1.traverseFast(data.array, (node) => {
|
|
1550
|
-
if (import_lib$1.isJSXAttribute(node) && import_lib$1.isJSXIdentifier(node.name, { name: "data-id" }) && import_lib$1.isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
1551
|
-
else if (import_lib$1.isObjectProperty(node) && !node.computed && (import_lib$1.isIdentifier(node.key, { name: "key" }) || import_lib$1.isStringLiteral(node.key, { value: "key" })) && import_lib$1.isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
1552
|
-
});
|
|
1553
|
-
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
1554
|
-
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
1555
|
-
};
|
|
1556
|
-
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
1557
|
-
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
1558
|
-
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
1559
|
-
};
|
|
1560
1309
|
//#endregion
|
|
1561
|
-
export {
|
|
1310
|
+
export { parseArrayExpression as _, getChildrenSignatures as a, generateCode as b, removeIndices as c, getStructuredValue as d, readNodeBindings as f, isLosslesslyEvaluable as g, extractObjectProperties as h, valueToExpression as i, findEditableChildren as l, extractNodeValue as m, updateAll as n, extract as o, canLosslesslyEvaluateSource as p, createNodeFromValue as r, moveSelectedIndices as s, update as t, getCurrentValue as u, parseValue as v, unwrapExpression as y };
|
|
1562
1311
|
|
|
1563
|
-
//# sourceMappingURL=
|
|
1312
|
+
//# sourceMappingURL=update-DNInQSM7.js.map
|