@jbpark/live-editor 4.4.0 → 4.6.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 +4 -1
- package/README.md +7 -3
- package/dist/{boundary-B17cepvm.js → boundary-BLB80tyw.js} +8 -5
- package/dist/boundary-BLB80tyw.js.map +1 -0
- package/dist/cache-B0-XcIxJ.js.map +1 -1
- package/dist/{compile-DDfk5P25.js → compile-Dxas-D05.js} +3 -3
- package/dist/compile-Dxas-D05.js.map +1 -0
- package/dist/{constants-h1JAgOeW.js → constants-DU17Tdwo.js} +3 -3
- package/dist/constants-DU17Tdwo.js.map +1 -0
- package/dist/{context-DB5kW3qe.js → context-B8MzP7_p.js} +11 -5
- package/dist/context-B8MzP7_p.js.map +1 -0
- package/dist/{core-CRsAGpei.js → core-Bgfek364.js} +2 -2
- package/dist/core-Bgfek364.js.map +1 -0
- package/dist/{detect-typescript-CCj9ALXc.js → detect-typescript-Dr_oLniM.js} +2 -2
- package/dist/detect-typescript-Dr_oLniM.js.map +1 -0
- package/dist/dnd/index.d.ts +1 -1
- package/dist/dnd/index.js +1 -1
- package/dist/{dnd-BE5ZizqX.js → dnd-UoJILA75.js} +973 -885
- package/dist/dnd-UoJILA75.js.map +1 -0
- package/dist/{document-BDn_f08_.js → document-CGbe6bSV.js} +7739 -7736
- package/dist/document-CGbe6bSV.js.map +1 -0
- package/dist/editor/index.js +2 -2
- package/dist/{editor-DQ8Q--qR.js → editor-CMElfHer.js} +2 -2
- package/dist/editor-CMElfHer.js.map +1 -0
- package/dist/editor-caches-ClMvdcQF.js.map +1 -1
- package/dist/error/index.d.ts +1 -1
- package/dist/error/index.js +2 -2
- package/dist/{error-DWhxQEom.js → error-CN_GCnXB.js} +13 -6
- package/dist/error-CN_GCnXB.js.map +1 -0
- package/dist/{index-Dgh1JTX3.d.ts → index-C69Lv-jR.d.ts} +72 -69
- package/dist/{index-C4SGQ3Ci.d.ts → index-CRwEhJ7w.d.ts} +4 -1
- package/dist/{index-f7ijuNTa.d.ts → index-DKYUxi-E.d.ts} +115 -155
- package/dist/index-DrWtSKXu.d.ts +11 -0
- package/dist/index.d.ts +18 -18
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/dist/{ast-dYb78955.js → items-B6OXxcLG.js} +588 -718
- package/dist/items-B6OXxcLG.js.map +1 -0
- package/dist/messages-CSrMI4Rm.js +133 -0
- package/dist/messages-CSrMI4Rm.js.map +1 -0
- package/dist/messages-IBPs4TkZ.d.ts +130 -0
- package/dist/preview/index.js +2 -2
- package/dist/{preview-BnaCEFsF.js → preview-CtyG5UbH.js} +6 -4
- package/dist/preview-CtyG5UbH.js.map +1 -0
- package/dist/provider/index.d.ts +3 -8
- package/dist/provider/index.js +3 -2
- package/dist/{sections-CBVMO8Jl.js → sections-BYWOBISr.js} +9 -4
- package/dist/sections-BYWOBISr.js.map +1 -0
- package/dist/states-Ci1AvoQ9.js.map +1 -1
- package/dist/style.css +505 -21
- package/dist/{use-dynamic-tailwind-Bcipc1hL.js → use-dynamic-tailwind-BP1toJkW.js} +66 -40
- package/dist/use-dynamic-tailwind-BP1toJkW.js.map +1 -0
- package/dist/utils/ast/index.d.ts +3 -2
- package/dist/utils/ast/index.js +150 -3
- package/dist/utils/ast/index.js.map +1 -0
- package/dist/utils/index.d.ts +30 -13
- package/dist/utils/index.js +26 -6
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/tailwind/index.js.map +1 -1
- package/dist/validate-DB59Z_Nx.d.ts +100 -0
- package/dist/validate-iLo8IONY.js +64 -0
- package/dist/validate-iLo8IONY.js.map +1 -0
- package/package.json +3 -3
- package/dist/ast-dYb78955.js.map +0 -1
- package/dist/boundary-B17cepvm.js.map +0 -1
- package/dist/compile-DDfk5P25.js.map +0 -1
- package/dist/constants-h1JAgOeW.js.map +0 -1
- package/dist/context-DB5kW3qe.js.map +0 -1
- package/dist/core-CRsAGpei.js.map +0 -1
- package/dist/detect-typescript-CCj9ALXc.js.map +0 -1
- package/dist/dnd-BE5ZizqX.js.map +0 -1
- package/dist/document-BDn_f08_.js.map +0 -1
- package/dist/editor-DQ8Q--qR.js.map +0 -1
- package/dist/error-DWhxQEom.js.map +0 -1
- package/dist/preview-BnaCEFsF.js.map +0 -1
- package/dist/sections-CBVMO8Jl.js.map +0 -1
- package/dist/use-dynamic-tailwind-Bcipc1hL.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as require_lib, S as require_lib$1, _ as
|
|
2
|
-
import { c as RESERVED_BINDING_PROPERTIES, n as CONFIG, r as DATA_ATTR, s as REGEX, t as BINDING_PROP } from "./constants-
|
|
1
|
+
import { C as require_lib, S as require_lib$1, _ as generateCode, b as getJSXTagName, d as traverse, g as attrValue, v as unwrap, w as __toESM, x as nanoid, y as wrap } from "./document-CGbe6bSV.js";
|
|
2
|
+
import { c as RESERVED_BINDING_PROPERTIES, n as CONFIG, r as DATA_ATTR, s as REGEX, t as BINDING_PROP } from "./constants-DU17Tdwo.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";
|
|
@@ -95,86 +95,6 @@ const parseValue = (value) => {
|
|
|
95
95
|
}
|
|
96
96
|
return value;
|
|
97
97
|
};
|
|
98
|
-
const MAX_FLATTEN_DEPTH = 20;
|
|
99
|
-
const isEditablePrimitive = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
100
|
-
const flattenEditableValue = (value) => {
|
|
101
|
-
const parsed = parseValue(value);
|
|
102
|
-
if (typeof parsed !== "object" || parsed === null || Object.keys(parsed).length === 0) return null;
|
|
103
|
-
const entries = [];
|
|
104
|
-
const walk = (node, path, depth) => {
|
|
105
|
-
if (depth > MAX_FLATTEN_DEPTH) return;
|
|
106
|
-
if (isEditablePrimitive(node)) {
|
|
107
|
-
entries.push({
|
|
108
|
-
path,
|
|
109
|
-
value: node
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (Array.isArray(node)) {
|
|
114
|
-
node.forEach((item, index) => walk(item, [...path, index], depth + 1));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (typeof node === "object" && node !== null) Object.entries(node).forEach(([key, item]) => walk(item, [...path, key], depth + 1));
|
|
118
|
-
};
|
|
119
|
-
walk(parsed, [], 0);
|
|
120
|
-
return entries.length ? entries : null;
|
|
121
|
-
};
|
|
122
|
-
const findEditableLeaf = (root, path) => {
|
|
123
|
-
let node = unwrapExpression(root);
|
|
124
|
-
for (const segment of path) {
|
|
125
|
-
if (import_lib$1.isArrayExpression(node)) {
|
|
126
|
-
if (typeof segment !== "number") return null;
|
|
127
|
-
const elements = node.elements.slice(0, segment + 1);
|
|
128
|
-
const element = node.elements[segment];
|
|
129
|
-
if (!element || elements.some((item) => import_lib$1.isSpreadElement(item))) return null;
|
|
130
|
-
node = unwrapExpression(element);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (import_lib$1.isObjectExpression(node)) {
|
|
134
|
-
const key = String(segment);
|
|
135
|
-
const matches = (property) => import_lib$1.isObjectProperty(property) && !property.computed && (import_lib$1.isIdentifier(property.key) && property.key.name === key || import_lib$1.isStringLiteral(property.key) && property.key.value === key || import_lib$1.isNumericLiteral(property.key) && String(property.key.value) === key);
|
|
136
|
-
let index = node.properties.length - 1;
|
|
137
|
-
while (index >= 0 && !matches(node.properties[index])) index--;
|
|
138
|
-
const property = node.properties[index];
|
|
139
|
-
if (!property || !import_lib$1.isObjectProperty(property) || node.properties.slice(index + 1).some((item) => import_lib$1.isSpreadElement(item))) return null;
|
|
140
|
-
node = unwrapExpression(property.value);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
return node;
|
|
146
|
-
};
|
|
147
|
-
const isEditableLeaf = (node) => import_lib$1.isStringLiteral(node) || import_lib$1.isNumericLiteral(node) || import_lib$1.isBooleanLiteral(node) || import_lib$1.isUnaryExpression(node) && node.operator === "-" && import_lib$1.isNumericLiteral(node.argument) || import_lib$1.isTemplateLiteral(node) && node.expressions.length === 0 || import_lib$1.isJSXElement(node) || import_lib$1.isJSXFragment(node);
|
|
148
|
-
const quoteString = (text, quote) => {
|
|
149
|
-
if (quote === "`") return `\`${text.replace(/[\\`]|\$\{/g, (match) => `\\${match}`)}\``;
|
|
150
|
-
const escaped = JSON.stringify(text).slice(1, -1);
|
|
151
|
-
return quote === "\"" ? `"${escaped}"` : `'${escaped.replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
|
|
152
|
-
};
|
|
153
|
-
const isJSXSource = (text) => {
|
|
154
|
-
try {
|
|
155
|
-
const expression = (0, import_lib.parseExpression)(text, { plugins: ["jsx"] });
|
|
156
|
-
return import_lib$1.isJSXElement(expression) || import_lib$1.isJSXFragment(expression);
|
|
157
|
-
} catch {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
const leafSource = (value, leaf, next) => {
|
|
162
|
-
if (typeof next !== "string") return String(next);
|
|
163
|
-
if ((import_lib$1.isJSXElement(leaf) || import_lib$1.isJSXFragment(leaf)) && isJSXSource(next)) return next.trim();
|
|
164
|
-
const quote = import_lib$1.isStringLiteral(leaf) || import_lib$1.isTemplateLiteral(leaf) ? value[leaf.start] : "'";
|
|
165
|
-
return quoteString(next, quote);
|
|
166
|
-
};
|
|
167
|
-
const setEditableValue = (value, path, next) => {
|
|
168
|
-
if (!path.length) return value;
|
|
169
|
-
let leaf;
|
|
170
|
-
try {
|
|
171
|
-
leaf = findEditableLeaf((0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] }), path);
|
|
172
|
-
} catch {
|
|
173
|
-
return value;
|
|
174
|
-
}
|
|
175
|
-
if (!leaf || !isEditableLeaf(leaf) || leaf.start == null || leaf.end == null) return value;
|
|
176
|
-
return `${value.slice(0, leaf.start)}${leafSource(value, leaf, next)}${value.slice(leaf.end)}`;
|
|
177
|
-
};
|
|
178
98
|
const extractNodeValue = (node) => {
|
|
179
99
|
if (import_lib$1.isBooleanLiteral(node)) return {
|
|
180
100
|
type: "boolean",
|
|
@@ -223,35 +143,6 @@ const extractNodeValue = (node) => {
|
|
|
223
143
|
value: null
|
|
224
144
|
};
|
|
225
145
|
};
|
|
226
|
-
const createNodeFromValue = (type, value) => {
|
|
227
|
-
switch (type) {
|
|
228
|
-
case "boolean": return import_lib$1.booleanLiteral(value === true);
|
|
229
|
-
case "number": return import_lib$1.numericLiteral(Number(value));
|
|
230
|
-
case "string": return import_lib$1.stringLiteral(String(value));
|
|
231
|
-
case "null": return import_lib$1.nullLiteral();
|
|
232
|
-
case "array":
|
|
233
|
-
case "object":
|
|
234
|
-
case "unknown": return null;
|
|
235
|
-
default: return null;
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
const valueToExpression = (value) => {
|
|
239
|
-
if (typeof value === "string") return import_lib$1.stringLiteral(value);
|
|
240
|
-
if (typeof value === "number") return value < 0 ? import_lib$1.unaryExpression("-", import_lib$1.numericLiteral(-value)) : import_lib$1.numericLiteral(value);
|
|
241
|
-
if (typeof value === "boolean") return import_lib$1.booleanLiteral(value);
|
|
242
|
-
if (value === null) return import_lib$1.nullLiteral();
|
|
243
|
-
if (Array.isArray(value)) return import_lib$1.arrayExpression(value.map((item) => valueToExpression(item) ?? import_lib$1.nullLiteral()));
|
|
244
|
-
if (typeof value === "object") {
|
|
245
|
-
const properties = [];
|
|
246
|
-
for (const [key, item] of Object.entries(value)) {
|
|
247
|
-
const expr = valueToExpression(item);
|
|
248
|
-
if (expr === null) continue;
|
|
249
|
-
properties.push(import_lib$1.objectProperty(import_lib$1.stringLiteral(key), expr));
|
|
250
|
-
}
|
|
251
|
-
return import_lib$1.objectExpression(properties);
|
|
252
|
-
}
|
|
253
|
-
return null;
|
|
254
|
-
};
|
|
255
146
|
const parseArrayExpression = (value) => {
|
|
256
147
|
try {
|
|
257
148
|
const ast = unwrapExpression((0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] }));
|
|
@@ -277,10 +168,6 @@ const extractObjectProperties = (element) => {
|
|
|
277
168
|
});
|
|
278
169
|
return properties;
|
|
279
170
|
};
|
|
280
|
-
const arrayExpressionToCode = (elements) => {
|
|
281
|
-
const nextAst = import_lib$1.arrayExpression(elements);
|
|
282
|
-
return generateCode(nextAst);
|
|
283
|
-
};
|
|
284
171
|
//#endregion
|
|
285
172
|
//#region src/utils/ast/binding.ts
|
|
286
173
|
const bindingTypeSchema = z.enum(BINDING_TYPES);
|
|
@@ -496,431 +383,100 @@ const findEditableChildren = (node) => {
|
|
|
496
383
|
return editableChildren;
|
|
497
384
|
};
|
|
498
385
|
//#endregion
|
|
499
|
-
//#region src/utils/
|
|
500
|
-
const
|
|
501
|
-
return
|
|
386
|
+
//#region src/utils/ast/extract.ts
|
|
387
|
+
const collectText = (children) => {
|
|
388
|
+
return children.filter((c) => import_lib$1.isJSXText(c)).map((c) => c.value.trim()).filter((v) => v.length).join(" ");
|
|
502
389
|
};
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const moveSelectedIndices = (items, indices, direction) => {
|
|
510
|
-
const next = [...items];
|
|
511
|
-
const nextIndices = new Set(indices);
|
|
512
|
-
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
513
|
-
for (const index of ordered) {
|
|
514
|
-
const target = direction === "up" ? index - 1 : index + 1;
|
|
515
|
-
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
516
|
-
[next[index], next[target]] = [next[target], next[index]];
|
|
517
|
-
nextIndices.delete(index);
|
|
518
|
-
nextIndices.add(target);
|
|
519
|
-
}
|
|
520
|
-
return {
|
|
521
|
-
items: next,
|
|
522
|
-
indices: nextIndices
|
|
523
|
-
};
|
|
390
|
+
const parseJSXName = (tagName) => {
|
|
391
|
+
const parts = tagName.split(".");
|
|
392
|
+
if (parts.length === 1) return import_lib$1.jsxIdentifier(parts[0]);
|
|
393
|
+
let expr = import_lib$1.jsxIdentifier(parts[0]);
|
|
394
|
+
for (let i = 1; i < parts.length; i++) expr = import_lib$1.jsxMemberExpression(expr, import_lib$1.jsxIdentifier(parts[i]));
|
|
395
|
+
return expr;
|
|
524
396
|
};
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (next === -1) return false;
|
|
535
|
-
index = next;
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (char === "/") {
|
|
539
|
-
const following = content[index + 1];
|
|
540
|
-
if (following === "/") {
|
|
541
|
-
const lineEnd = content.indexOf("\n", index + 2);
|
|
542
|
-
index = lineEnd === -1 ? content.length : lineEnd;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
if (following === "*") {
|
|
546
|
-
const commentEnd = content.indexOf("*/", index + 2);
|
|
547
|
-
if (commentEnd === -1) return false;
|
|
548
|
-
index = commentEnd + 2;
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
|
-
index++;
|
|
397
|
+
const extractCache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
398
|
+
const NO_MAP = {};
|
|
399
|
+
let optionExtractCaches = /* @__PURE__ */ new WeakMap();
|
|
400
|
+
const cacheFor = ({ bindings, bindingKeys }) => {
|
|
401
|
+
if (!bindings && !bindingKeys) return extractCache;
|
|
402
|
+
let byKeys = optionExtractCaches.get(bindings ?? NO_MAP);
|
|
403
|
+
if (!byKeys) {
|
|
404
|
+
byKeys = /* @__PURE__ */ new WeakMap();
|
|
405
|
+
optionExtractCaches.set(bindings ?? NO_MAP, byKeys);
|
|
554
406
|
}
|
|
555
|
-
|
|
407
|
+
let cache = byKeys.get(bindingKeys ?? NO_MAP);
|
|
408
|
+
if (!cache) {
|
|
409
|
+
cache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
410
|
+
byKeys.set(bindingKeys ?? NO_MAP, cache);
|
|
411
|
+
}
|
|
412
|
+
return cache;
|
|
556
413
|
};
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
414
|
+
const extractAttributes = (attributes, source) => {
|
|
415
|
+
const allAttrs = [];
|
|
416
|
+
const dataAttrs = [];
|
|
417
|
+
for (const attr of attributes) {
|
|
418
|
+
if (!import_lib$1.isJSXAttribute(attr) || !import_lib$1.isJSXIdentifier(attr.name)) continue;
|
|
419
|
+
const name = attr.name.name;
|
|
420
|
+
let value = null;
|
|
421
|
+
let isStringLiteral = false;
|
|
422
|
+
if (attr.value) {
|
|
423
|
+
if (import_lib$1.isStringLiteral(attr.value)) {
|
|
424
|
+
value = attr.value.value;
|
|
425
|
+
isStringLiteral = true;
|
|
426
|
+
} else if (import_lib$1.isJSXExpressionContainer(attr.value)) try {
|
|
427
|
+
const expression = attr.value.expression;
|
|
428
|
+
const structural = import_lib$1.isArrayExpression(unwrapExpression(expression)) || import_lib$1.isObjectExpression(unwrapExpression(expression));
|
|
429
|
+
value = source && structural && expression.start != null && expression.end != null ? source.slice(expression.start, expression.end) : generateCode(expression);
|
|
430
|
+
isStringLiteral = false;
|
|
431
|
+
} catch {
|
|
432
|
+
value = null;
|
|
433
|
+
}
|
|
566
434
|
}
|
|
567
|
-
|
|
568
|
-
|
|
435
|
+
const entry = {
|
|
436
|
+
name,
|
|
437
|
+
value,
|
|
438
|
+
isStringLiteral
|
|
439
|
+
};
|
|
440
|
+
allAttrs.push(entry);
|
|
441
|
+
if (name.startsWith("data-")) dataAttrs.push(entry);
|
|
569
442
|
}
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
575
|
-
};
|
|
576
|
-
const lineTerminatorOf = (source) => {
|
|
577
|
-
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
443
|
+
return {
|
|
444
|
+
allAttrs,
|
|
445
|
+
dataAttrs
|
|
446
|
+
};
|
|
578
447
|
};
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
let result = "";
|
|
589
|
-
let cursor = 0;
|
|
590
|
-
const terminator = lineTerminatorOf(source);
|
|
591
|
-
for (const edit of ordered) {
|
|
592
|
-
const content = edit.indent && edit.content.includes("\n") && hasOnlyLayoutNewlines(edit.content) ? edit.content.replace(/\n/g, `${terminator}${lineIndentAt(source, edit.start)}`) : edit.content;
|
|
593
|
-
result += source.slice(cursor, edit.start) + content;
|
|
594
|
-
cursor = edit.end;
|
|
595
|
-
}
|
|
596
|
-
return result + source.slice(cursor);
|
|
448
|
+
const buildChildElements = (node) => {
|
|
449
|
+
const childElements = [];
|
|
450
|
+
if (node.textContent) childElements.push(import_lib$1.jsxText(node.textContent));
|
|
451
|
+
node.children?.forEach((child) => {
|
|
452
|
+
const childJSX = nodeToJSX(child);
|
|
453
|
+
if (childJSX) childElements.push(childJSX);
|
|
454
|
+
});
|
|
455
|
+
return childElements;
|
|
597
456
|
};
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (commas.some((comma) => comma === null)) return null;
|
|
622
|
-
const last = elements.at(-1);
|
|
623
|
-
return {
|
|
624
|
-
array,
|
|
625
|
-
elements,
|
|
626
|
-
commas,
|
|
627
|
-
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
628
|
-
};
|
|
629
|
-
};
|
|
630
|
-
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
631
|
-
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
632
|
-
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
633
|
-
start: node.start,
|
|
634
|
-
end: node.end,
|
|
635
|
-
content: source.slice(next[index].start, next[index].end)
|
|
636
|
-
})));
|
|
637
|
-
const removeArraySource = (source, data, indices) => {
|
|
638
|
-
const edits = [];
|
|
639
|
-
const removedCommas = /* @__PURE__ */ new Set();
|
|
640
|
-
const { elements, commas, trailing } = data;
|
|
641
|
-
for (let index = 0; index < elements.length; index++) {
|
|
642
|
-
if (!indices.has(index)) continue;
|
|
643
|
-
const start = index;
|
|
644
|
-
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
645
|
-
for (let current = start; current <= index; current++) {
|
|
646
|
-
const node = elements[current];
|
|
647
|
-
edits.push({
|
|
648
|
-
start: node.start,
|
|
649
|
-
end: node.end,
|
|
650
|
-
content: ""
|
|
651
|
-
});
|
|
652
|
-
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
653
|
-
}
|
|
654
|
-
if (index === elements.length - 1) {
|
|
655
|
-
if (trailing !== null) removedCommas.add(trailing);
|
|
656
|
-
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
for (const comma of removedCommas) edits.push({
|
|
660
|
-
start: comma,
|
|
661
|
-
end: comma + 1,
|
|
662
|
-
content: ""
|
|
663
|
-
});
|
|
664
|
-
return applyEdits(source, edits);
|
|
665
|
-
};
|
|
666
|
-
const appendArraySource = (source, data, copies) => {
|
|
667
|
-
const last = data.elements.at(-1);
|
|
668
|
-
if (!last || copies.length === 0) return source;
|
|
669
|
-
return applyEdits(source, [{
|
|
670
|
-
start: last.end,
|
|
671
|
-
end: last.end,
|
|
672
|
-
content: `, ${copies.join(", ")}`
|
|
673
|
-
}]);
|
|
674
|
-
};
|
|
675
|
-
//#endregion
|
|
676
|
-
//#region src/utils/ast/items.ts
|
|
677
|
-
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
678
|
-
const kindOf = (element) => import_lib$1.isObjectExpression(element) ? "object" : "primitive";
|
|
679
|
-
const parseItems = (code) => {
|
|
680
|
-
return elementsOf(code)?.flatMap((node, index) => import_lib$1.isExpression(node) ? [{
|
|
681
|
-
index,
|
|
682
|
-
kind: kindOf(node),
|
|
683
|
-
node
|
|
684
|
-
}] : []) ?? null;
|
|
685
|
-
};
|
|
686
|
-
const resolveRenderLeaf = (render, key) => {
|
|
687
|
-
const leaf = render?.[key];
|
|
688
|
-
return leaf && "type" in leaf ? leaf : null;
|
|
689
|
-
};
|
|
690
|
-
const toTemplateRaw = (value) => {
|
|
691
|
-
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
692
|
-
};
|
|
693
|
-
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
694
|
-
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
695
|
-
const raw = String(value);
|
|
696
|
-
return import_lib$1.templateLiteral([import_lib$1.templateElement({
|
|
697
|
-
raw: toTemplateRaw(raw),
|
|
698
|
-
cooked: raw
|
|
699
|
-
}, true)], []);
|
|
700
|
-
}
|
|
701
|
-
if (renderLeaf?.type === "jsx" || import_lib$1.isJSXElement(current) || import_lib$1.isJSXFragment(current)) {
|
|
702
|
-
const trimmed = String(value).trim();
|
|
703
|
-
if (!trimmed.startsWith("<")) return import_lib$1.stringLiteral(trimmed);
|
|
704
|
-
try {
|
|
705
|
-
return (0, import_lib.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
706
|
-
} catch {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
if (declaredType === "array" || declaredType === "object") {
|
|
711
|
-
if (typeof value === "string") try {
|
|
712
|
-
return (0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] });
|
|
713
|
-
} catch {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
if (!isLosslesslyEvaluable(current)) return;
|
|
717
|
-
return valueToExpression(value) ?? void 0;
|
|
718
|
-
}
|
|
719
|
-
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
720
|
-
};
|
|
721
|
-
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
722
|
-
const elements = elementsOf(code);
|
|
723
|
-
const element = elements?.[index];
|
|
724
|
-
if (!elements || !import_lib$1.isObjectExpression(element)) return null;
|
|
725
|
-
const target = element.properties.find((property) => import_lib$1.isObjectProperty(property) && import_lib$1.isIdentifier(property.key) && property.key.name === key);
|
|
726
|
-
if (!target) return null;
|
|
727
|
-
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
728
|
-
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
729
|
-
if (!nextValue) return null;
|
|
730
|
-
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || import_lib$1.isJSXElement(nextValue) || import_lib$1.isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
731
|
-
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
732
|
-
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
733
|
-
return applyEdits(code, [{
|
|
734
|
-
start: target.value.start,
|
|
735
|
-
end: target.value.end,
|
|
736
|
-
content
|
|
737
|
-
}]);
|
|
738
|
-
};
|
|
739
|
-
const updateArrayItemValue = (code, index, value) => {
|
|
740
|
-
const elements = elementsOf(code);
|
|
741
|
-
const element = elements?.[index];
|
|
742
|
-
if (!elements || !import_lib$1.isExpression(element)) return null;
|
|
743
|
-
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
744
|
-
if (!nextValue) return null;
|
|
745
|
-
return applyEdits(code, [{
|
|
746
|
-
start: element.start,
|
|
747
|
-
end: element.end,
|
|
748
|
-
content: generateCode(nextValue)
|
|
749
|
-
}]);
|
|
750
|
-
};
|
|
751
|
-
const moveArrayItem = (code, from, to) => {
|
|
752
|
-
const data = denseArraySource(code);
|
|
753
|
-
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
754
|
-
const next = [...data.elements];
|
|
755
|
-
const [moved] = next.splice(from, 1);
|
|
756
|
-
next.splice(to, 0, moved);
|
|
757
|
-
return reorderArraySource(code, data.elements, next);
|
|
758
|
-
};
|
|
759
|
-
const moveArrayItems = (code, indices, direction) => {
|
|
760
|
-
const data = denseArraySource(code);
|
|
761
|
-
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
762
|
-
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
763
|
-
return {
|
|
764
|
-
code: reorderArraySource(code, data.elements, next.items),
|
|
765
|
-
indices: next.indices
|
|
766
|
-
};
|
|
767
|
-
};
|
|
768
|
-
const removeArrayItems = (code, indices, kind) => {
|
|
769
|
-
const data = denseArraySource(code);
|
|
770
|
-
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
771
|
-
const remaining = removeIndices(data.elements, indices);
|
|
772
|
-
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
773
|
-
return removeArraySource(code, data, indices);
|
|
774
|
-
};
|
|
775
|
-
const copyItem = (code, element, generateId, usedIds) => {
|
|
776
|
-
const edits = [];
|
|
777
|
-
let unsupported = false;
|
|
778
|
-
const reserveId = (id) => {
|
|
779
|
-
if (usedIds.has(id)) unsupported = true;
|
|
780
|
-
usedIds.add(id);
|
|
781
|
-
return JSON.stringify(id);
|
|
782
|
-
};
|
|
783
|
-
import_lib$1.traverseFast(element, (node) => {
|
|
784
|
-
if (!import_lib$1.isJSXOpeningElement(node)) return;
|
|
785
|
-
const ids = node.attributes.filter((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
786
|
-
const attr = ids[0];
|
|
787
|
-
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)))) {
|
|
788
|
-
unsupported = true;
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
if (attr && import_lib$1.isStringLiteral(attr.value)) edits.push({
|
|
792
|
-
start: attr.value.start,
|
|
793
|
-
end: attr.value.end,
|
|
794
|
-
content: reserveId(generateId())
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
if (import_lib$1.isObjectExpression(element)) {
|
|
798
|
-
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" })));
|
|
799
|
-
const key = keys[0];
|
|
800
|
-
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;
|
|
801
|
-
if (key && import_lib$1.isStringLiteral(key.value)) edits.push({
|
|
802
|
-
start: key.value.start,
|
|
803
|
-
end: key.value.end,
|
|
804
|
-
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
if (unsupported) return null;
|
|
808
|
-
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
809
|
-
...edit,
|
|
810
|
-
start: edit.start - element.start,
|
|
811
|
-
end: edit.end - element.start
|
|
812
|
-
})));
|
|
813
|
-
};
|
|
814
|
-
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
815
|
-
const data = denseArraySource(code);
|
|
816
|
-
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
817
|
-
const usedIds = /* @__PURE__ */ new Set();
|
|
818
|
-
import_lib$1.traverseFast(data.array, (node) => {
|
|
819
|
-
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);
|
|
820
|
-
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);
|
|
821
|
-
});
|
|
822
|
-
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
823
|
-
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
824
|
-
};
|
|
825
|
-
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
826
|
-
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
827
|
-
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
828
|
-
};
|
|
829
|
-
//#endregion
|
|
830
|
-
//#region src/utils/ast/extract.ts
|
|
831
|
-
const collectText = (children) => {
|
|
832
|
-
return children.filter((c) => import_lib$1.isJSXText(c)).map((c) => c.value.trim()).filter((v) => v.length).join(" ");
|
|
833
|
-
};
|
|
834
|
-
const parseJSXName = (tagName) => {
|
|
835
|
-
const parts = tagName.split(".");
|
|
836
|
-
if (parts.length === 1) return import_lib$1.jsxIdentifier(parts[0]);
|
|
837
|
-
let expr = import_lib$1.jsxIdentifier(parts[0]);
|
|
838
|
-
for (let i = 1; i < parts.length; i++) expr = import_lib$1.jsxMemberExpression(expr, import_lib$1.jsxIdentifier(parts[i]));
|
|
839
|
-
return expr;
|
|
840
|
-
};
|
|
841
|
-
const extractCache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
842
|
-
const NO_MAP = {};
|
|
843
|
-
let optionExtractCaches = /* @__PURE__ */ new WeakMap();
|
|
844
|
-
const cacheFor = ({ bindings, bindingKeys }) => {
|
|
845
|
-
if (!bindings && !bindingKeys) return extractCache;
|
|
846
|
-
let byKeys = optionExtractCaches.get(bindings ?? NO_MAP);
|
|
847
|
-
if (!byKeys) {
|
|
848
|
-
byKeys = /* @__PURE__ */ new WeakMap();
|
|
849
|
-
optionExtractCaches.set(bindings ?? NO_MAP, byKeys);
|
|
850
|
-
}
|
|
851
|
-
let cache = byKeys.get(bindingKeys ?? NO_MAP);
|
|
852
|
-
if (!cache) {
|
|
853
|
-
cache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
854
|
-
byKeys.set(bindingKeys ?? NO_MAP, cache);
|
|
855
|
-
}
|
|
856
|
-
return cache;
|
|
857
|
-
};
|
|
858
|
-
const extractAttributes = (attributes, source) => {
|
|
859
|
-
const allAttrs = [];
|
|
860
|
-
const dataAttrs = [];
|
|
861
|
-
for (const attr of attributes) {
|
|
862
|
-
if (!import_lib$1.isJSXAttribute(attr) || !import_lib$1.isJSXIdentifier(attr.name)) continue;
|
|
863
|
-
const name = attr.name.name;
|
|
864
|
-
let value = null;
|
|
865
|
-
let isStringLiteral = false;
|
|
866
|
-
if (attr.value) {
|
|
867
|
-
if (import_lib$1.isStringLiteral(attr.value)) {
|
|
868
|
-
value = attr.value.value;
|
|
869
|
-
isStringLiteral = true;
|
|
870
|
-
} else if (import_lib$1.isJSXExpressionContainer(attr.value)) try {
|
|
871
|
-
const expression = attr.value.expression;
|
|
872
|
-
const structural = import_lib$1.isArrayExpression(unwrapExpression(expression)) || import_lib$1.isObjectExpression(unwrapExpression(expression));
|
|
873
|
-
value = source && structural && expression.start != null && expression.end != null ? source.slice(expression.start, expression.end) : generateCode(expression);
|
|
874
|
-
isStringLiteral = false;
|
|
875
|
-
} catch {
|
|
876
|
-
value = null;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
const entry = {
|
|
880
|
-
name,
|
|
881
|
-
value,
|
|
882
|
-
isStringLiteral
|
|
883
|
-
};
|
|
884
|
-
allAttrs.push(entry);
|
|
885
|
-
if (name.startsWith("data-")) dataAttrs.push(entry);
|
|
886
|
-
}
|
|
887
|
-
return {
|
|
888
|
-
allAttrs,
|
|
889
|
-
dataAttrs
|
|
890
|
-
};
|
|
891
|
-
};
|
|
892
|
-
const buildChildElements = (node) => {
|
|
893
|
-
const childElements = [];
|
|
894
|
-
if (node.textContent) childElements.push(import_lib$1.jsxText(node.textContent));
|
|
895
|
-
node.children?.forEach((child) => {
|
|
896
|
-
const childJSX = nodeToJSX(child);
|
|
897
|
-
if (childJSX) childElements.push(childJSX);
|
|
898
|
-
});
|
|
899
|
-
return childElements;
|
|
900
|
-
};
|
|
901
|
-
const nodeToJSX = (node) => {
|
|
902
|
-
try {
|
|
903
|
-
if (node.isFragment) {
|
|
904
|
-
const childElements = buildChildElements(node);
|
|
905
|
-
return import_lib$1.jsxFragment(import_lib$1.jsxOpeningFragment(), import_lib$1.jsxClosingFragment(), childElements);
|
|
906
|
-
}
|
|
907
|
-
if (node.tagName === "div" && node.dataAttributes.some((attr) => attr.name === "data-item")) {
|
|
908
|
-
if (node.children) return nodeToJSX(node.children[0]);
|
|
909
|
-
return null;
|
|
910
|
-
}
|
|
911
|
-
const attributes = node.attributes.map((attr) => {
|
|
912
|
-
const attrName = import_lib$1.jsxIdentifier(attr.name);
|
|
913
|
-
if (!attr.value) return import_lib$1.jsxAttribute(attrName, null);
|
|
914
|
-
return import_lib$1.jsxAttribute(attrName, attrValue(attr));
|
|
915
|
-
});
|
|
916
|
-
const elementName = parseJSXName(node.tagName);
|
|
917
|
-
const openingElement = import_lib$1.jsxOpeningElement(elementName, attributes);
|
|
918
|
-
const closingElement = import_lib$1.jsxClosingElement(elementName);
|
|
919
|
-
const children = buildChildElements(node);
|
|
920
|
-
return import_lib$1.jsxElement(openingElement, closingElement, children, false);
|
|
921
|
-
} catch (error) {
|
|
922
|
-
console.error("❌ DataAttrNode to JSX conversion error:", error);
|
|
923
|
-
return null;
|
|
457
|
+
const nodeToJSX = (node) => {
|
|
458
|
+
try {
|
|
459
|
+
if (node.isFragment) {
|
|
460
|
+
const childElements = buildChildElements(node);
|
|
461
|
+
return import_lib$1.jsxFragment(import_lib$1.jsxOpeningFragment(), import_lib$1.jsxClosingFragment(), childElements);
|
|
462
|
+
}
|
|
463
|
+
if (node.tagName === "div" && node.dataAttributes.some((attr) => attr.name === "data-item")) {
|
|
464
|
+
if (node.children) return nodeToJSX(node.children[0]);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
const attributes = node.attributes.map((attr) => {
|
|
468
|
+
const attrName = import_lib$1.jsxIdentifier(attr.name);
|
|
469
|
+
if (!attr.value) return import_lib$1.jsxAttribute(attrName, null);
|
|
470
|
+
return import_lib$1.jsxAttribute(attrName, attrValue(attr));
|
|
471
|
+
});
|
|
472
|
+
const elementName = parseJSXName(node.tagName);
|
|
473
|
+
const openingElement = import_lib$1.jsxOpeningElement(elementName, attributes);
|
|
474
|
+
const closingElement = import_lib$1.jsxClosingElement(elementName);
|
|
475
|
+
const children = buildChildElements(node);
|
|
476
|
+
return import_lib$1.jsxElement(openingElement, closingElement, children, false);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error("❌ DataAttrNode to JSX conversion error:", error);
|
|
479
|
+
return null;
|
|
924
480
|
}
|
|
925
481
|
};
|
|
926
482
|
const createWrapperNode = (textContent, children) => ({
|
|
@@ -1092,6 +648,106 @@ function clearExtractCache() {
|
|
|
1092
648
|
}
|
|
1093
649
|
registerEditorCache(clearExtractCache);
|
|
1094
650
|
//#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
|
|
1095
751
|
//#region src/utils/ast/children.ts
|
|
1096
752
|
const modeledSignatures = (nodes) => nodes.map((node) => {
|
|
1097
753
|
const jsx = nodeToJSX(node);
|
|
@@ -1238,7 +894,42 @@ const editChildrenSource = (source, parent, input, options) => {
|
|
|
1238
894
|
}
|
|
1239
895
|
};
|
|
1240
896
|
//#endregion
|
|
1241
|
-
//#region src/utils/ast/
|
|
897
|
+
//#region src/utils/ast/value-expression.ts
|
|
898
|
+
const createNodeFromValue = (type, value) => {
|
|
899
|
+
switch (type) {
|
|
900
|
+
case "boolean": return import_lib$1.booleanLiteral(value === true);
|
|
901
|
+
case "number": return import_lib$1.numericLiteral(Number(value));
|
|
902
|
+
case "string": return import_lib$1.stringLiteral(String(value));
|
|
903
|
+
case "null": return import_lib$1.nullLiteral();
|
|
904
|
+
case "array":
|
|
905
|
+
case "object":
|
|
906
|
+
case "unknown": return null;
|
|
907
|
+
default: return null;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
const valueToExpression = (value) => {
|
|
911
|
+
if (typeof value === "string") return import_lib$1.stringLiteral(value);
|
|
912
|
+
if (typeof value === "number") return value < 0 ? import_lib$1.unaryExpression("-", import_lib$1.numericLiteral(-value)) : import_lib$1.numericLiteral(value);
|
|
913
|
+
if (typeof value === "boolean") return import_lib$1.booleanLiteral(value);
|
|
914
|
+
if (value === null) return import_lib$1.nullLiteral();
|
|
915
|
+
if (Array.isArray(value)) return import_lib$1.arrayExpression(value.map((item) => valueToExpression(item) ?? import_lib$1.nullLiteral()));
|
|
916
|
+
if (typeof value === "object") {
|
|
917
|
+
const properties = [];
|
|
918
|
+
for (const [key, item] of Object.entries(value)) {
|
|
919
|
+
const expr = valueToExpression(item);
|
|
920
|
+
if (expr === null) continue;
|
|
921
|
+
properties.push(import_lib$1.objectProperty(import_lib$1.stringLiteral(key), expr));
|
|
922
|
+
}
|
|
923
|
+
return import_lib$1.objectExpression(properties);
|
|
924
|
+
}
|
|
925
|
+
return null;
|
|
926
|
+
};
|
|
927
|
+
const arrayExpressionToCode = (elements) => {
|
|
928
|
+
const nextAst = import_lib$1.arrayExpression(elements);
|
|
929
|
+
return generateCode(nextAst);
|
|
930
|
+
};
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/utils/ast/edit-source.ts
|
|
1242
933
|
const childrenRange = (element) => {
|
|
1243
934
|
const { openingElement, closingElement } = element;
|
|
1244
935
|
if (!closingElement || openingElement.end == null || closingElement.start == null) return null;
|
|
@@ -1413,11 +1104,143 @@ const addAttribute = (source, opening, propertyName, value, type) => {
|
|
|
1413
1104
|
indent: true
|
|
1414
1105
|
}];
|
|
1415
1106
|
};
|
|
1416
|
-
const canEditAttributeValue = (attribute, value, type) => {
|
|
1417
|
-
if (!import_lib$1.isJSXExpressionContainer(attribute.value) || import_lib$1.isJSXEmptyExpression(attribute.value.expression) || isLosslesslyEvaluable(attribute.value.expression)) return true;
|
|
1418
|
-
return typeof value === "string" && holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)) && structuralSource(value) !== null;
|
|
1107
|
+
const canEditAttributeValue = (attribute, value, type) => {
|
|
1108
|
+
if (!import_lib$1.isJSXExpressionContainer(attribute.value) || import_lib$1.isJSXEmptyExpression(attribute.value.expression) || isLosslesslyEvaluable(attribute.value.expression)) return true;
|
|
1109
|
+
return typeof value === "string" && holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)) && structuralSource(value) !== null;
|
|
1110
|
+
};
|
|
1111
|
+
//#endregion
|
|
1112
|
+
//#region src/utils/ast/update.ts
|
|
1113
|
+
const hasDataId = (opening, dataId) => opening.attributes.some((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.ID && attr.value && import_lib$1.isStringLiteral(attr.value) && attr.value.value === dataId);
|
|
1114
|
+
const findJSXAttribute = (opening, name) => opening.attributes.find((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === name);
|
|
1115
|
+
const findBinding = (opening, { dataId, label, property, options }) => {
|
|
1116
|
+
const bindingAttr = findJSXAttribute(opening, DATA_ATTR.BINDING);
|
|
1117
|
+
if (bindingAttr && !bindingAttr.value) return { failure: {
|
|
1118
|
+
reason: "no-binding",
|
|
1119
|
+
dataId
|
|
1120
|
+
} };
|
|
1121
|
+
let own;
|
|
1122
|
+
if (bindingAttr?.value) {
|
|
1123
|
+
let bindingValue = "";
|
|
1124
|
+
if (import_lib$1.isStringLiteral(bindingAttr.value)) bindingValue = bindingAttr.value.value;
|
|
1125
|
+
else if (import_lib$1.isJSXExpressionContainer(bindingAttr.value)) try {
|
|
1126
|
+
bindingValue = generateCode(bindingAttr.value.expression);
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
return { failure: {
|
|
1129
|
+
reason: "parse-error",
|
|
1130
|
+
error
|
|
1131
|
+
} };
|
|
1132
|
+
}
|
|
1133
|
+
own = parseBinding(bindingValue);
|
|
1134
|
+
}
|
|
1135
|
+
const keyAttr = findJSXAttribute(opening, DATA_ATTR.BINDING_KEY);
|
|
1136
|
+
const bindings = resolveBindings(own, keyAttr && (keyAttr.value && import_lib$1.isStringLiteral(keyAttr.value) ? keyAttr.value.value : null), getJSXTagName(opening), options);
|
|
1137
|
+
if (!own && bindings.length === 0) return { failure: {
|
|
1138
|
+
reason: "no-binding",
|
|
1139
|
+
dataId
|
|
1140
|
+
} };
|
|
1141
|
+
const matches = bindings.filter((binding) => property !== void 0 ? binding.property === property : binding.label === label);
|
|
1142
|
+
if (matches.length === 0) return { failure: {
|
|
1143
|
+
reason: "binding-not-declared",
|
|
1144
|
+
dataId,
|
|
1145
|
+
label,
|
|
1146
|
+
property
|
|
1147
|
+
} };
|
|
1148
|
+
if (matches.length > 1) return { failure: {
|
|
1149
|
+
reason: "duplicate-binding",
|
|
1150
|
+
dataId,
|
|
1151
|
+
label,
|
|
1152
|
+
property,
|
|
1153
|
+
count: matches.length
|
|
1154
|
+
} };
|
|
1155
|
+
return { binding: matches[0] };
|
|
1156
|
+
};
|
|
1157
|
+
const checkEdit = (binding, { dataId, value }) => {
|
|
1158
|
+
const property = binding.property;
|
|
1159
|
+
if (RESERVED_BINDING_PROPERTIES.includes(property)) return {
|
|
1160
|
+
reason: "reserved-property",
|
|
1161
|
+
dataId,
|
|
1162
|
+
property
|
|
1163
|
+
};
|
|
1164
|
+
if (value === void 0 && binding.required) return {
|
|
1165
|
+
reason: "required-property",
|
|
1166
|
+
dataId,
|
|
1167
|
+
property
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1170
|
+
const toElementEdit = (result, onNull) => result ? { edits: result } : { failure: onNull() };
|
|
1171
|
+
const editProperty = (source, element, binding, { dataId, value, options }) => {
|
|
1172
|
+
const opening = element.openingElement;
|
|
1173
|
+
const prop = binding.property;
|
|
1174
|
+
const unset = value === void 0;
|
|
1175
|
+
const couldNotUpdate = () => ({
|
|
1176
|
+
reason: "parse-error",
|
|
1177
|
+
error: /* @__PURE__ */ new Error(`could not update "${prop}"`)
|
|
1178
|
+
});
|
|
1179
|
+
const contentWithoutSlot = !unset && String(value) !== "" && !childrenRange(element);
|
|
1180
|
+
switch (prop) {
|
|
1181
|
+
case BINDING_PROP.INNER_TEXT:
|
|
1182
|
+
if (contentWithoutSlot) return { failure: {
|
|
1183
|
+
reason: "self-closing",
|
|
1184
|
+
dataId,
|
|
1185
|
+
property: prop
|
|
1186
|
+
} };
|
|
1187
|
+
return toElementEdit(editInnerText(source, element, unset ? "" : String(value)), couldNotUpdate);
|
|
1188
|
+
case BINDING_PROP.INNER_HTML:
|
|
1189
|
+
if (contentWithoutSlot && binding.type !== "richtext") return { failure: {
|
|
1190
|
+
reason: "self-closing",
|
|
1191
|
+
dataId,
|
|
1192
|
+
property: prop
|
|
1193
|
+
} };
|
|
1194
|
+
return toElementEdit(binding.type === "richtext" ? editRichtext(element, unset ? "" : String(value)) : editInnerHTML(element, unset ? "" : String(value)), couldNotUpdate);
|
|
1195
|
+
case BINDING_PROP.CHILDREN:
|
|
1196
|
+
if (unset) return { failure: {
|
|
1197
|
+
reason: "unsupported-syntax",
|
|
1198
|
+
dataId,
|
|
1199
|
+
property: prop
|
|
1200
|
+
} };
|
|
1201
|
+
return toElementEdit(editChildrenSource(source, element, value, options), () => ({
|
|
1202
|
+
reason: "parse-error",
|
|
1203
|
+
error: /* @__PURE__ */ new Error("Unsupported or stale children edit; source was preserved")
|
|
1204
|
+
}));
|
|
1205
|
+
default: {
|
|
1206
|
+
const attribute = findAttribute(opening, prop);
|
|
1207
|
+
if (unset) return toElementEdit(attribute ? removeAttribute(source, attribute) : [], () => ({
|
|
1208
|
+
reason: "parse-error",
|
|
1209
|
+
error: /* @__PURE__ */ new Error(`could not remove "${prop}"`)
|
|
1210
|
+
}));
|
|
1211
|
+
if (!attribute) return toElementEdit(value === "" ? [] : addAttribute(source, opening, prop, value, binding.type), () => ({
|
|
1212
|
+
reason: "attribute-not-found",
|
|
1213
|
+
dataId,
|
|
1214
|
+
property: prop
|
|
1215
|
+
}));
|
|
1216
|
+
if (binding.type !== "jsx" && !canEditAttributeValue(attribute, value, binding.type)) return { failure: {
|
|
1217
|
+
reason: "unsupported-syntax",
|
|
1218
|
+
dataId,
|
|
1219
|
+
property: prop
|
|
1220
|
+
} };
|
|
1221
|
+
return toElementEdit(binding.type === "jsx" ? editJsxAttribute(opening, prop, value) : editAttribute(opening, prop, value, binding.type), () => ({
|
|
1222
|
+
reason: "attribute-not-found",
|
|
1223
|
+
dataId,
|
|
1224
|
+
property: prop
|
|
1225
|
+
}));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
const editElement = (source, element, request) => {
|
|
1230
|
+
const found = findBinding(element.openingElement, request);
|
|
1231
|
+
if ("failure" in found) return found;
|
|
1232
|
+
const refused = checkEdit(found.binding, request);
|
|
1233
|
+
if (refused) return { failure: refused };
|
|
1234
|
+
return editProperty(source, element, found.binding, request);
|
|
1419
1235
|
};
|
|
1420
1236
|
const update = (code, dataId, label, value, property, options = {}) => {
|
|
1237
|
+
const request = {
|
|
1238
|
+
dataId,
|
|
1239
|
+
label,
|
|
1240
|
+
value,
|
|
1241
|
+
property,
|
|
1242
|
+
options
|
|
1243
|
+
};
|
|
1421
1244
|
try {
|
|
1422
1245
|
const wrapped = wrap(code);
|
|
1423
1246
|
const ast = (0, import_lib.parse)(wrapped, {
|
|
@@ -1427,150 +1250,15 @@ const update = (code, dataId, label, value, property, options = {}) => {
|
|
|
1427
1250
|
let changed = false;
|
|
1428
1251
|
let failure;
|
|
1429
1252
|
const edits = [];
|
|
1430
|
-
const collect = (result, onNull) => {
|
|
1431
|
-
if (!result) {
|
|
1432
|
-
failure = onNull();
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
edits.push(...result);
|
|
1436
|
-
changed = true;
|
|
1437
|
-
};
|
|
1438
1253
|
traverse(ast, { JSXElement(path) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
const bindingAttr = opening.attributes.find((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING);
|
|
1444
|
-
if (bindingAttr && !bindingAttr.value) {
|
|
1445
|
-
failure = {
|
|
1446
|
-
reason: "no-binding",
|
|
1447
|
-
dataId
|
|
1448
|
-
};
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
let own;
|
|
1452
|
-
if (bindingAttr?.value) {
|
|
1453
|
-
let bindingValue = "";
|
|
1454
|
-
if (import_lib$1.isStringLiteral(bindingAttr.value)) bindingValue = bindingAttr.value.value;
|
|
1455
|
-
else if (import_lib$1.isJSXExpressionContainer(bindingAttr.value)) try {
|
|
1456
|
-
bindingValue = generateCode(bindingAttr.value.expression);
|
|
1457
|
-
} catch (error) {
|
|
1458
|
-
failure = {
|
|
1459
|
-
reason: "parse-error",
|
|
1460
|
-
error
|
|
1461
|
-
};
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
own = parseBinding(bindingValue);
|
|
1465
|
-
}
|
|
1466
|
-
const keyAttr = opening.attributes.find((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING_KEY);
|
|
1467
|
-
const bindings = resolveBindings(own, keyAttr && (keyAttr.value && import_lib$1.isStringLiteral(keyAttr.value) ? keyAttr.value.value : null), getJSXTagName(opening), options);
|
|
1468
|
-
if (!own && bindings.length === 0) {
|
|
1469
|
-
failure = {
|
|
1470
|
-
reason: "no-binding",
|
|
1471
|
-
dataId
|
|
1472
|
-
};
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
const matches = bindings.filter((binding) => property !== void 0 ? binding.property === property : binding.label === label);
|
|
1476
|
-
if (matches.length === 0) {
|
|
1477
|
-
failure = {
|
|
1478
|
-
reason: "binding-not-declared",
|
|
1479
|
-
dataId,
|
|
1480
|
-
label,
|
|
1481
|
-
property
|
|
1482
|
-
};
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
if (matches.length > 1) {
|
|
1486
|
-
failure = {
|
|
1487
|
-
reason: "duplicate-binding",
|
|
1488
|
-
dataId,
|
|
1489
|
-
label,
|
|
1490
|
-
property,
|
|
1491
|
-
count: matches.length
|
|
1492
|
-
};
|
|
1254
|
+
if (!hasDataId(path.node.openingElement, dataId)) return;
|
|
1255
|
+
const result = editElement(wrapped, path.node, request);
|
|
1256
|
+
if ("failure" in result) {
|
|
1257
|
+
failure = result.failure;
|
|
1493
1258
|
return;
|
|
1494
1259
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if (RESERVED_BINDING_PROPERTIES.includes(prop)) {
|
|
1498
|
-
failure = {
|
|
1499
|
-
reason: "reserved-property",
|
|
1500
|
-
dataId,
|
|
1501
|
-
property: prop
|
|
1502
|
-
};
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
const unset = value === void 0;
|
|
1506
|
-
if (unset && propertyBinding.required) {
|
|
1507
|
-
failure = {
|
|
1508
|
-
reason: "required-property",
|
|
1509
|
-
dataId,
|
|
1510
|
-
property: prop
|
|
1511
|
-
};
|
|
1512
|
-
return;
|
|
1513
|
-
}
|
|
1514
|
-
switch (prop) {
|
|
1515
|
-
case BINDING_PROP.INNER_TEXT:
|
|
1516
|
-
collect(editInnerText(wrapped, path.node, unset ? "" : String(value)), () => ({
|
|
1517
|
-
reason: "parse-error",
|
|
1518
|
-
error: /* @__PURE__ */ new Error(`could not update "${prop}"`)
|
|
1519
|
-
}));
|
|
1520
|
-
break;
|
|
1521
|
-
case BINDING_PROP.INNER_HTML:
|
|
1522
|
-
collect(propertyBinding.type === "richtext" ? editRichtext(path.node, unset ? "" : String(value)) : editInnerHTML(path.node, unset ? "" : String(value)), () => ({
|
|
1523
|
-
reason: "parse-error",
|
|
1524
|
-
error: /* @__PURE__ */ new Error(`could not update "${prop}"`)
|
|
1525
|
-
}));
|
|
1526
|
-
break;
|
|
1527
|
-
case BINDING_PROP.CHILDREN:
|
|
1528
|
-
if (unset) {
|
|
1529
|
-
failure = {
|
|
1530
|
-
reason: "unsupported-syntax",
|
|
1531
|
-
dataId,
|
|
1532
|
-
property: prop
|
|
1533
|
-
};
|
|
1534
|
-
break;
|
|
1535
|
-
}
|
|
1536
|
-
collect(editChildrenSource(wrapped, path.node, value, options), () => ({
|
|
1537
|
-
reason: "parse-error",
|
|
1538
|
-
error: /* @__PURE__ */ new Error("Unsupported or stale children edit; source was preserved")
|
|
1539
|
-
}));
|
|
1540
|
-
break;
|
|
1541
|
-
default: {
|
|
1542
|
-
const attribute = findAttribute(opening, prop);
|
|
1543
|
-
if (unset) {
|
|
1544
|
-
collect(attribute ? removeAttribute(wrapped, attribute) : [], () => ({
|
|
1545
|
-
reason: "parse-error",
|
|
1546
|
-
error: /* @__PURE__ */ new Error(`could not remove "${prop}"`)
|
|
1547
|
-
}));
|
|
1548
|
-
break;
|
|
1549
|
-
}
|
|
1550
|
-
if (!attribute) {
|
|
1551
|
-
collect(value === "" ? [] : addAttribute(wrapped, opening, prop, value, propertyBinding.type), () => ({
|
|
1552
|
-
reason: "attribute-not-found",
|
|
1553
|
-
dataId,
|
|
1554
|
-
property: prop
|
|
1555
|
-
}));
|
|
1556
|
-
break;
|
|
1557
|
-
}
|
|
1558
|
-
if (attribute && propertyBinding.type !== "jsx" && !canEditAttributeValue(attribute, value, propertyBinding.type)) {
|
|
1559
|
-
failure = {
|
|
1560
|
-
reason: "unsupported-syntax",
|
|
1561
|
-
dataId,
|
|
1562
|
-
property: prop
|
|
1563
|
-
};
|
|
1564
|
-
break;
|
|
1565
|
-
}
|
|
1566
|
-
collect(propertyBinding.type === "jsx" ? editJsxAttribute(opening, prop, value) : editAttribute(opening, prop, value, propertyBinding.type), () => ({
|
|
1567
|
-
reason: "attribute-not-found",
|
|
1568
|
-
dataId,
|
|
1569
|
-
property: prop
|
|
1570
|
-
}));
|
|
1571
|
-
break;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1260
|
+
edits.push(...result.edits);
|
|
1261
|
+
changed = true;
|
|
1574
1262
|
} });
|
|
1575
1263
|
if (!changed) return {
|
|
1576
1264
|
code,
|
|
@@ -1639,55 +1327,237 @@ const bulkUpdate = (raw, entries) => {
|
|
|
1639
1327
|
};
|
|
1640
1328
|
};
|
|
1641
1329
|
//#endregion
|
|
1642
|
-
//#region src/utils/ast/
|
|
1643
|
-
const
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
if (
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
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;
|
|
1651
1344
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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)], []);
|
|
1661
1431
|
}
|
|
1662
|
-
if (
|
|
1663
|
-
|
|
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);
|
|
1664
1435
|
try {
|
|
1665
|
-
|
|
1436
|
+
return (0, import_lib.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
1666
1437
|
} catch {
|
|
1667
|
-
return
|
|
1438
|
+
return;
|
|
1668
1439
|
}
|
|
1669
|
-
if (!regex.test(value)) return {
|
|
1670
|
-
valid: false,
|
|
1671
|
-
message: "Value does not match the required format."
|
|
1672
|
-
};
|
|
1673
1440
|
}
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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;
|
|
1681
1449
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
+
});
|
|
1687
1537
|
}
|
|
1688
|
-
return
|
|
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);
|
|
1689
1559
|
};
|
|
1690
1560
|
//#endregion
|
|
1691
|
-
export {
|
|
1561
|
+
export { extractNodeValue as A, getStructuredValue as C, readNodeBindings as D, parseBindingExpression as E, parseArrayExpression as M, parseValue as N, resolveBindings as O, unwrapExpression as P, getRegistryBindings as S, parseBinding as T, clearExtractCache as _, parseItems as a, getCurrentValue as b, updateArrayItemValue as c, update as d, updateAll as f, moveSelectedIndices as g, getChildrenSignatures as h, moveArrayItems as i, extractObjectProperties as j, canLosslesslyEvaluateSource as k, canStructurallyEditArray as l, createNodeFromValue as m, duplicateArrayItems as n, removeArrayItems as o, arrayExpressionToCode as p, moveArrayItem as r, updateArrayItemProperty as s, appendArrayItem as t, bulkUpdate as u, extract as v, isComponentTagName as w, getKeyBindings as x, findEditableChildren as y };
|
|
1692
1562
|
|
|
1693
|
-
//# sourceMappingURL=
|
|
1563
|
+
//# sourceMappingURL=items-B6OXxcLG.js.map
|