@jbpark/live-editor 4.3.1 → 4.5.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.md +2 -2
- package/dist/{boundary-B17cepvm.js → boundary-DnjGgsK6.js} +8 -5
- package/dist/boundary-DnjGgsK6.js.map +1 -0
- package/dist/{compile-0Dvp8zp5.js → compile-DDfk5P25.js} +3 -3
- package/dist/{compile-0Dvp8zp5.js.map → compile-DDfk5P25.js.map} +1 -1
- package/dist/{constants-CKvdqZtO.js → constants-h1JAgOeW.js} +4 -2
- package/dist/constants-h1JAgOeW.js.map +1 -0
- package/dist/{context-93X3gyfJ.js → context-DsEXJl4h.js} +11 -5
- package/dist/context-DsEXJl4h.js.map +1 -0
- package/dist/{core-B3ruccKb.js → core-CRsAGpei.js} +2 -2
- package/dist/{core-B3ruccKb.js.map → core-CRsAGpei.js.map} +1 -1
- package/dist/{detect-typescript-5MV4mCM7.js → detect-typescript-CCj9ALXc.js} +2 -2
- package/dist/{detect-typescript-5MV4mCM7.js.map → detect-typescript-CCj9ALXc.js.map} +1 -1
- package/dist/dnd/index.d.ts +2 -2
- package/dist/dnd/index.js +1 -1
- package/dist/{dnd-x8Mvj--6.js → dnd-DGNp20Nk.js} +496 -307
- package/dist/dnd-DGNp20Nk.js.map +1 -0
- package/dist/{document-C-hGKMkk.js → document-DGHTfrda.js} +7739 -7736
- package/dist/document-DGHTfrda.js.map +1 -0
- package/dist/editor/index.js +2 -2
- package/dist/{editor-DvMruljw.js → editor-DQ8Q--qR.js} +2 -2
- package/dist/{editor-DvMruljw.js.map → editor-DQ8Q--qR.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-DwqwjJ2C.js} +13 -6
- package/dist/error-DwqwjJ2C.js.map +1 -0
- package/dist/index-CHknA6k1.d.ts +221 -0
- package/dist/{index-CtlUYnWq.d.ts → index-P9Aifaui.d.ts} +14 -4
- package/dist/index-WQgFy510.d.ts +11 -0
- package/dist/{index-C4SGQ3Ci.d.ts → index-vtyggWy0.d.ts} +4 -1
- package/dist/index.d.ts +18 -17
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/dist/{ast-CehbCXwh.js → items-DdrwGxJw.js} +467 -426
- package/dist/items-DdrwGxJw.js.map +1 -0
- package/dist/messages-Cp3g1mn1.d.ts +129 -0
- package/dist/messages-CzUPVnzH.js +132 -0
- package/dist/messages-CzUPVnzH.js.map +1 -0
- package/dist/preview/index.js +2 -2
- package/dist/{preview-BYck9mVK.js → preview-BKFgq_5u.js} +6 -4
- package/dist/preview-BKFgq_5u.js.map +1 -0
- package/dist/provider/index.d.ts +3 -8
- package/dist/provider/index.js +3 -2
- package/dist/{sections-Ce63wbtG.js → sections-BRH3KHYc.js} +9 -4
- package/dist/sections-BRH3KHYc.js.map +1 -0
- package/dist/style.css +505 -21
- package/dist/{use-dynamic-tailwind-QKcGvUJs.js → use-dynamic-tailwind-T9r9ExbR.js} +21 -4
- package/dist/use-dynamic-tailwind-T9r9ExbR.js.map +1 -0
- package/dist/utils/ast/index.d.ts +3 -2
- package/dist/utils/ast/index.js +66 -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/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-CehbCXwh.js.map +0 -1
- package/dist/boundary-B17cepvm.js.map +0 -1
- package/dist/constants-CKvdqZtO.js.map +0 -1
- package/dist/context-93X3gyfJ.js.map +0 -1
- package/dist/dnd-x8Mvj--6.js.map +0 -1
- package/dist/document-C-hGKMkk.js.map +0 -1
- package/dist/error-DWhxQEom.js.map +0 -1
- package/dist/index-w3kLcCXl.d.ts +0 -255
- package/dist/preview-BYck9mVK.js.map +0 -1
- package/dist/sections-Ce63wbtG.js.map +0 -1
- package/dist/use-dynamic-tailwind-QKcGvUJs.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-DGHTfrda.js";
|
|
2
|
+
import { c as RESERVED_BINDING_PROPERTIES, n as CONFIG, r as DATA_ATTR, s as REGEX, t as BINDING_PROP } from "./constants-h1JAgOeW.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";
|
|
@@ -372,6 +372,63 @@ const buildBindingItems = (raw) => {
|
|
|
372
372
|
}];
|
|
373
373
|
});
|
|
374
374
|
};
|
|
375
|
+
const registryEntries = /* @__PURE__ */ new WeakMap();
|
|
376
|
+
const isComponentTagName = (tagName) => tagName !== "" && (tagName.includes(".") || !/^[a-z]/.test(tagName));
|
|
377
|
+
const getRegistryBindings = (registry, tagName) => {
|
|
378
|
+
if (!registry) return;
|
|
379
|
+
let entries = registryEntries.get(registry);
|
|
380
|
+
if (!entries) {
|
|
381
|
+
entries = /* @__PURE__ */ new Map();
|
|
382
|
+
registryEntries.set(registry, entries);
|
|
383
|
+
const ignored = Object.keys(registry).filter((key) => !isComponentTagName(key));
|
|
384
|
+
if (ignored.length > 0) console.warn(`Live.Dnd \`bindings\` only applies to components. Ignored: ${ignored.join(", ")}. Bind an HTML element with its own data-binding.`);
|
|
385
|
+
}
|
|
386
|
+
if (!isComponentTagName(tagName) || !Object.hasOwn(registry, tagName)) return;
|
|
387
|
+
let bindings = entries.get(tagName);
|
|
388
|
+
if (!bindings) {
|
|
389
|
+
bindings = buildBindingItems(registry[tagName]);
|
|
390
|
+
entries.set(tagName, bindings);
|
|
391
|
+
}
|
|
392
|
+
return bindings;
|
|
393
|
+
};
|
|
394
|
+
const keyEntries = /* @__PURE__ */ new WeakMap();
|
|
395
|
+
const warnedKeys = /* @__PURE__ */ new Set();
|
|
396
|
+
const warnOnce = (id, message) => {
|
|
397
|
+
if (!warnedKeys.has(id)) {
|
|
398
|
+
warnedKeys.add(id);
|
|
399
|
+
console.warn(message);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
const getKeyBindings = (bindingKeys, key) => {
|
|
403
|
+
if (!bindingKeys || !Object.hasOwn(bindingKeys, key)) return;
|
|
404
|
+
let entries = keyEntries.get(bindingKeys);
|
|
405
|
+
if (!entries) {
|
|
406
|
+
entries = /* @__PURE__ */ new Map();
|
|
407
|
+
keyEntries.set(bindingKeys, entries);
|
|
408
|
+
}
|
|
409
|
+
let bindings = entries.get(key);
|
|
410
|
+
if (!bindings) {
|
|
411
|
+
bindings = buildBindingItems(bindingKeys[key]);
|
|
412
|
+
entries.set(key, bindings);
|
|
413
|
+
}
|
|
414
|
+
return bindings;
|
|
415
|
+
};
|
|
416
|
+
const resolveBindings = (own, key, tagName, options = {}) => {
|
|
417
|
+
if (own) {
|
|
418
|
+
if (key !== void 0) warnOnce(`both:${key}`, `Live.Dnd: an element has both data-binding and data-binding-key="${key ?? ""}". Its data-binding is used; remove one of them.`);
|
|
419
|
+
return own;
|
|
420
|
+
}
|
|
421
|
+
if (key === null) {
|
|
422
|
+
warnOnce("not-a-string", "Live.Dnd: data-binding-key must be a plain string, such as data-binding-key=\"hero-title\". The element has no fields.");
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
if (key !== void 0) {
|
|
426
|
+
const bindings = getKeyBindings(options.bindingKeys, key);
|
|
427
|
+
if (!bindings) warnOnce(`missing:${key}`, `Live.Dnd: data-binding-key="${key}" isn't in bindingKeys. The element has no fields.`);
|
|
428
|
+
return bindings ?? [];
|
|
429
|
+
}
|
|
430
|
+
return getRegistryBindings(options.bindings, tagName) ?? [];
|
|
431
|
+
};
|
|
375
432
|
const parseBinding = (bindingValue) => {
|
|
376
433
|
if (!bindingValue) return [];
|
|
377
434
|
const ast = parseArrayExpression(bindingValue);
|
|
@@ -420,11 +477,12 @@ const getStructuredValue = (node, property, type) => {
|
|
|
420
477
|
}
|
|
421
478
|
}
|
|
422
479
|
};
|
|
423
|
-
const
|
|
480
|
+
const readNodeBindings = (node) => {
|
|
481
|
+
if (node.bindings) return node.bindings;
|
|
424
482
|
const bindingAttr = node.dataAttributes.find((attr) => attr.name === DATA_ATTR.BINDING);
|
|
425
|
-
|
|
426
|
-
return (node.bindings || parseBinding(bindingAttr.value)).length > 0;
|
|
483
|
+
return bindingAttr?.value ? parseBinding(bindingAttr.value) : [];
|
|
427
484
|
};
|
|
485
|
+
const hasEditableBindings = (node) => readNodeBindings(node).length > 0;
|
|
428
486
|
const findEditableChildren = (node) => {
|
|
429
487
|
const editableChildren = [];
|
|
430
488
|
const traverse = (children) => {
|
|
@@ -438,337 +496,6 @@ const findEditableChildren = (node) => {
|
|
|
438
496
|
return editableChildren;
|
|
439
497
|
};
|
|
440
498
|
//#endregion
|
|
441
|
-
//#region src/utils/selection.ts
|
|
442
|
-
const removeIndices = (items, indices) => {
|
|
443
|
-
return items.filter((_, index) => !indices.has(index));
|
|
444
|
-
};
|
|
445
|
-
/**
|
|
446
|
-
* Shifts every selected index up/down by one step as a block, preserving
|
|
447
|
-
* relative order — scattered selections stop moving individually once they
|
|
448
|
-
* hit an unselected neighbor, so the whole group slides together instead of
|
|
449
|
-
* items passing through each other.
|
|
450
|
-
*/
|
|
451
|
-
const moveSelectedIndices = (items, indices, direction) => {
|
|
452
|
-
const next = [...items];
|
|
453
|
-
const nextIndices = new Set(indices);
|
|
454
|
-
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
455
|
-
for (const index of ordered) {
|
|
456
|
-
const target = direction === "up" ? index - 1 : index + 1;
|
|
457
|
-
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
458
|
-
[next[index], next[target]] = [next[target], next[index]];
|
|
459
|
-
nextIndices.delete(index);
|
|
460
|
-
nextIndices.add(target);
|
|
461
|
-
}
|
|
462
|
-
return {
|
|
463
|
-
items: next,
|
|
464
|
-
indices: nextIndices
|
|
465
|
-
};
|
|
466
|
-
};
|
|
467
|
-
//#endregion
|
|
468
|
-
//#region src/utils/ast/patch.ts
|
|
469
|
-
const hasOnlyLayoutNewlines = (content) => {
|
|
470
|
-
let index = 0;
|
|
471
|
-
while (index < content.length) {
|
|
472
|
-
const char = content[index];
|
|
473
|
-
if (char === "`" || char === "<") return false;
|
|
474
|
-
if (char === "\"" || char === "'") {
|
|
475
|
-
const next = skipStringLiteral(content, index);
|
|
476
|
-
if (next === -1) return false;
|
|
477
|
-
index = next;
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
if (char === "/") {
|
|
481
|
-
const following = content[index + 1];
|
|
482
|
-
if (following === "/") {
|
|
483
|
-
const lineEnd = content.indexOf("\n", index + 2);
|
|
484
|
-
index = lineEnd === -1 ? content.length : lineEnd;
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
if (following === "*") {
|
|
488
|
-
const commentEnd = content.indexOf("*/", index + 2);
|
|
489
|
-
if (commentEnd === -1) return false;
|
|
490
|
-
index = commentEnd + 2;
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
495
|
-
index++;
|
|
496
|
-
}
|
|
497
|
-
return true;
|
|
498
|
-
};
|
|
499
|
-
const skipStringLiteral = (content, start) => {
|
|
500
|
-
const quote = content[start];
|
|
501
|
-
for (let index = start + 1; index < content.length; index++) {
|
|
502
|
-
const char = content[index];
|
|
503
|
-
if (char === "\\") {
|
|
504
|
-
const escaped = content[index + 1];
|
|
505
|
-
if (escaped === "\n" || escaped === "\r") return -1;
|
|
506
|
-
index++;
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
if (char === quote) return index + 1;
|
|
510
|
-
if (char === "\n") return -1;
|
|
511
|
-
}
|
|
512
|
-
return -1;
|
|
513
|
-
};
|
|
514
|
-
const lineIndentAt = (source, offset) => {
|
|
515
|
-
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
516
|
-
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
517
|
-
};
|
|
518
|
-
const lineTerminatorOf = (source) => {
|
|
519
|
-
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
520
|
-
};
|
|
521
|
-
const applyEdits = (source, edits) => {
|
|
522
|
-
if (edits.length === 0) return source;
|
|
523
|
-
const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
524
|
-
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}`);
|
|
525
|
-
for (let i = 1; i < ordered.length; i++) {
|
|
526
|
-
const previous = ordered[i - 1];
|
|
527
|
-
const current = ordered[i];
|
|
528
|
-
if (current.start < previous.end) throw new Error(`Overlapping source edits: [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
|
|
529
|
-
}
|
|
530
|
-
let result = "";
|
|
531
|
-
let cursor = 0;
|
|
532
|
-
const terminator = lineTerminatorOf(source);
|
|
533
|
-
for (const edit of ordered) {
|
|
534
|
-
const content = edit.indent && edit.content.includes("\n") && hasOnlyLayoutNewlines(edit.content) ? edit.content.replace(/\n/g, `${terminator}${lineIndentAt(source, edit.start)}`) : edit.content;
|
|
535
|
-
result += source.slice(cursor, edit.start) + content;
|
|
536
|
-
cursor = edit.end;
|
|
537
|
-
}
|
|
538
|
-
return result + source.slice(cursor);
|
|
539
|
-
};
|
|
540
|
-
//#endregion
|
|
541
|
-
//#region src/utils/ast/array-source.ts
|
|
542
|
-
const commaIn = (source, start, end) => {
|
|
543
|
-
let comma = null;
|
|
544
|
-
for (let at = start; at < end; at++) {
|
|
545
|
-
if (/\s/.test(source[at])) continue;
|
|
546
|
-
if (source.startsWith("/*", at)) {
|
|
547
|
-
const close = source.indexOf("*/", at + 2);
|
|
548
|
-
if (close < 0 || close + 2 > end) return null;
|
|
549
|
-
at = close + 1;
|
|
550
|
-
} else if (source.startsWith("//", at)) {
|
|
551
|
-
const newline = source.indexOf("\n", at + 2);
|
|
552
|
-
at = newline < 0 ? end : newline;
|
|
553
|
-
} else if (source[at] === "," && comma === null) comma = at;
|
|
554
|
-
else return null;
|
|
555
|
-
}
|
|
556
|
-
return comma;
|
|
557
|
-
};
|
|
558
|
-
const denseArraySource = (source) => {
|
|
559
|
-
const array = parseArrayExpression(source);
|
|
560
|
-
if (!array || array.elements.some((node) => !import_lib$1.isExpression(node) || node.extra?.parenthesized)) return null;
|
|
561
|
-
const elements = array.elements;
|
|
562
|
-
const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
|
|
563
|
-
if (commas.some((comma) => comma === null)) return null;
|
|
564
|
-
const last = elements.at(-1);
|
|
565
|
-
return {
|
|
566
|
-
array,
|
|
567
|
-
elements,
|
|
568
|
-
commas,
|
|
569
|
-
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
570
|
-
};
|
|
571
|
-
};
|
|
572
|
-
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
573
|
-
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
574
|
-
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
575
|
-
start: node.start,
|
|
576
|
-
end: node.end,
|
|
577
|
-
content: source.slice(next[index].start, next[index].end)
|
|
578
|
-
})));
|
|
579
|
-
const removeArraySource = (source, data, indices) => {
|
|
580
|
-
const edits = [];
|
|
581
|
-
const removedCommas = /* @__PURE__ */ new Set();
|
|
582
|
-
const { elements, commas, trailing } = data;
|
|
583
|
-
for (let index = 0; index < elements.length; index++) {
|
|
584
|
-
if (!indices.has(index)) continue;
|
|
585
|
-
const start = index;
|
|
586
|
-
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
587
|
-
for (let current = start; current <= index; current++) {
|
|
588
|
-
const node = elements[current];
|
|
589
|
-
edits.push({
|
|
590
|
-
start: node.start,
|
|
591
|
-
end: node.end,
|
|
592
|
-
content: ""
|
|
593
|
-
});
|
|
594
|
-
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
595
|
-
}
|
|
596
|
-
if (index === elements.length - 1) {
|
|
597
|
-
if (trailing !== null) removedCommas.add(trailing);
|
|
598
|
-
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
for (const comma of removedCommas) edits.push({
|
|
602
|
-
start: comma,
|
|
603
|
-
end: comma + 1,
|
|
604
|
-
content: ""
|
|
605
|
-
});
|
|
606
|
-
return applyEdits(source, edits);
|
|
607
|
-
};
|
|
608
|
-
const appendArraySource = (source, data, copies) => {
|
|
609
|
-
const last = data.elements.at(-1);
|
|
610
|
-
if (!last || copies.length === 0) return source;
|
|
611
|
-
return applyEdits(source, [{
|
|
612
|
-
start: last.end,
|
|
613
|
-
end: last.end,
|
|
614
|
-
content: `, ${copies.join(", ")}`
|
|
615
|
-
}]);
|
|
616
|
-
};
|
|
617
|
-
//#endregion
|
|
618
|
-
//#region src/utils/ast/items.ts
|
|
619
|
-
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
620
|
-
const kindOf = (element) => import_lib$1.isObjectExpression(element) ? "object" : "primitive";
|
|
621
|
-
const parseItems = (code) => {
|
|
622
|
-
return elementsOf(code)?.flatMap((node, index) => import_lib$1.isExpression(node) ? [{
|
|
623
|
-
index,
|
|
624
|
-
kind: kindOf(node),
|
|
625
|
-
node
|
|
626
|
-
}] : []) ?? null;
|
|
627
|
-
};
|
|
628
|
-
const resolveRenderLeaf = (render, key) => {
|
|
629
|
-
const leaf = render?.[key];
|
|
630
|
-
return leaf && "type" in leaf ? leaf : null;
|
|
631
|
-
};
|
|
632
|
-
const toTemplateRaw = (value) => {
|
|
633
|
-
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
634
|
-
};
|
|
635
|
-
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
636
|
-
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
637
|
-
const raw = String(value);
|
|
638
|
-
return import_lib$1.templateLiteral([import_lib$1.templateElement({
|
|
639
|
-
raw: toTemplateRaw(raw),
|
|
640
|
-
cooked: raw
|
|
641
|
-
}, true)], []);
|
|
642
|
-
}
|
|
643
|
-
if (renderLeaf?.type === "jsx" || import_lib$1.isJSXElement(current) || import_lib$1.isJSXFragment(current)) {
|
|
644
|
-
const trimmed = String(value).trim();
|
|
645
|
-
if (!trimmed.startsWith("<")) return import_lib$1.stringLiteral(trimmed);
|
|
646
|
-
try {
|
|
647
|
-
return (0, import_lib.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
648
|
-
} catch {
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (declaredType === "array" || declaredType === "object") {
|
|
653
|
-
if (typeof value === "string") try {
|
|
654
|
-
return (0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] });
|
|
655
|
-
} catch {
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
if (!isLosslesslyEvaluable(current)) return;
|
|
659
|
-
return valueToExpression(value) ?? void 0;
|
|
660
|
-
}
|
|
661
|
-
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
662
|
-
};
|
|
663
|
-
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
664
|
-
const elements = elementsOf(code);
|
|
665
|
-
const element = elements?.[index];
|
|
666
|
-
if (!elements || !import_lib$1.isObjectExpression(element)) return null;
|
|
667
|
-
const target = element.properties.find((property) => import_lib$1.isObjectProperty(property) && import_lib$1.isIdentifier(property.key) && property.key.name === key);
|
|
668
|
-
if (!target) return null;
|
|
669
|
-
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
670
|
-
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
671
|
-
if (!nextValue) return null;
|
|
672
|
-
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || import_lib$1.isJSXElement(nextValue) || import_lib$1.isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
673
|
-
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
674
|
-
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
675
|
-
return applyEdits(code, [{
|
|
676
|
-
start: target.value.start,
|
|
677
|
-
end: target.value.end,
|
|
678
|
-
content
|
|
679
|
-
}]);
|
|
680
|
-
};
|
|
681
|
-
const updateArrayItemValue = (code, index, value) => {
|
|
682
|
-
const elements = elementsOf(code);
|
|
683
|
-
const element = elements?.[index];
|
|
684
|
-
if (!elements || !import_lib$1.isExpression(element)) return null;
|
|
685
|
-
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
686
|
-
if (!nextValue) return null;
|
|
687
|
-
return applyEdits(code, [{
|
|
688
|
-
start: element.start,
|
|
689
|
-
end: element.end,
|
|
690
|
-
content: generateCode(nextValue)
|
|
691
|
-
}]);
|
|
692
|
-
};
|
|
693
|
-
const moveArrayItem = (code, from, to) => {
|
|
694
|
-
const data = denseArraySource(code);
|
|
695
|
-
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
696
|
-
const next = [...data.elements];
|
|
697
|
-
const [moved] = next.splice(from, 1);
|
|
698
|
-
next.splice(to, 0, moved);
|
|
699
|
-
return reorderArraySource(code, data.elements, next);
|
|
700
|
-
};
|
|
701
|
-
const moveArrayItems = (code, indices, direction) => {
|
|
702
|
-
const data = denseArraySource(code);
|
|
703
|
-
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
704
|
-
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
705
|
-
return {
|
|
706
|
-
code: reorderArraySource(code, data.elements, next.items),
|
|
707
|
-
indices: next.indices
|
|
708
|
-
};
|
|
709
|
-
};
|
|
710
|
-
const removeArrayItems = (code, indices, kind) => {
|
|
711
|
-
const data = denseArraySource(code);
|
|
712
|
-
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
713
|
-
const remaining = removeIndices(data.elements, indices);
|
|
714
|
-
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
715
|
-
return removeArraySource(code, data, indices);
|
|
716
|
-
};
|
|
717
|
-
const copyItem = (code, element, generateId, usedIds) => {
|
|
718
|
-
const edits = [];
|
|
719
|
-
let unsupported = false;
|
|
720
|
-
const reserveId = (id) => {
|
|
721
|
-
if (usedIds.has(id)) unsupported = true;
|
|
722
|
-
usedIds.add(id);
|
|
723
|
-
return JSON.stringify(id);
|
|
724
|
-
};
|
|
725
|
-
import_lib$1.traverseFast(element, (node) => {
|
|
726
|
-
if (!import_lib$1.isJSXOpeningElement(node)) return;
|
|
727
|
-
const ids = node.attributes.filter((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
728
|
-
const attr = ids[0];
|
|
729
|
-
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)))) {
|
|
730
|
-
unsupported = true;
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (attr && import_lib$1.isStringLiteral(attr.value)) edits.push({
|
|
734
|
-
start: attr.value.start,
|
|
735
|
-
end: attr.value.end,
|
|
736
|
-
content: reserveId(generateId())
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
if (import_lib$1.isObjectExpression(element)) {
|
|
740
|
-
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" })));
|
|
741
|
-
const key = keys[0];
|
|
742
|
-
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;
|
|
743
|
-
if (key && import_lib$1.isStringLiteral(key.value)) edits.push({
|
|
744
|
-
start: key.value.start,
|
|
745
|
-
end: key.value.end,
|
|
746
|
-
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
if (unsupported) return null;
|
|
750
|
-
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
751
|
-
...edit,
|
|
752
|
-
start: edit.start - element.start,
|
|
753
|
-
end: edit.end - element.start
|
|
754
|
-
})));
|
|
755
|
-
};
|
|
756
|
-
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
757
|
-
const data = denseArraySource(code);
|
|
758
|
-
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
759
|
-
const usedIds = /* @__PURE__ */ new Set();
|
|
760
|
-
import_lib$1.traverseFast(data.array, (node) => {
|
|
761
|
-
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);
|
|
762
|
-
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);
|
|
763
|
-
});
|
|
764
|
-
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
765
|
-
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
766
|
-
};
|
|
767
|
-
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
768
|
-
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
769
|
-
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
770
|
-
};
|
|
771
|
-
//#endregion
|
|
772
499
|
//#region src/utils/ast/extract.ts
|
|
773
500
|
const collectText = (children) => {
|
|
774
501
|
return children.filter((c) => import_lib$1.isJSXText(c)).map((c) => c.value.trim()).filter((v) => v.length).join(" ");
|
|
@@ -781,6 +508,22 @@ const parseJSXName = (tagName) => {
|
|
|
781
508
|
return expr;
|
|
782
509
|
};
|
|
783
510
|
const extractCache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
511
|
+
const NO_MAP = {};
|
|
512
|
+
let optionExtractCaches = /* @__PURE__ */ new WeakMap();
|
|
513
|
+
const cacheFor = ({ bindings, bindingKeys }) => {
|
|
514
|
+
if (!bindings && !bindingKeys) return extractCache;
|
|
515
|
+
let byKeys = optionExtractCaches.get(bindings ?? NO_MAP);
|
|
516
|
+
if (!byKeys) {
|
|
517
|
+
byKeys = /* @__PURE__ */ new WeakMap();
|
|
518
|
+
optionExtractCaches.set(bindings ?? NO_MAP, byKeys);
|
|
519
|
+
}
|
|
520
|
+
let cache = byKeys.get(bindingKeys ?? NO_MAP);
|
|
521
|
+
if (!cache) {
|
|
522
|
+
cache = createBoundedCache(CONFIG.CACHE_LIMIT);
|
|
523
|
+
byKeys.set(bindingKeys ?? NO_MAP, cache);
|
|
524
|
+
}
|
|
525
|
+
return cache;
|
|
526
|
+
};
|
|
784
527
|
const extractAttributes = (attributes, source) => {
|
|
785
528
|
const allAttrs = [];
|
|
786
529
|
const dataAttrs = [];
|
|
@@ -872,13 +615,13 @@ const createFragmentNode = (textContent, children) => ({
|
|
|
872
615
|
children,
|
|
873
616
|
isFragment: true
|
|
874
617
|
});
|
|
875
|
-
const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, source) => {
|
|
618
|
+
const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, source, options) => {
|
|
876
619
|
const jsxChildren = jsxElement.children.filter((child) => import_lib$1.isJSXElement(child) || import_lib$1.isJSXFragment(child));
|
|
877
620
|
if (!jsxChildren.length) return;
|
|
878
621
|
const childrenNodes = [];
|
|
879
622
|
jsxChildren.forEach((child) => {
|
|
880
623
|
if (import_lib$1.isJSXElement(child)) {
|
|
881
|
-
const childResults = extractFromNode(child, processedNodes, source);
|
|
624
|
+
const childResults = extractFromNode(child, processedNodes, source, options);
|
|
882
625
|
if (shouldWrap) {
|
|
883
626
|
const wrapperNode = createWrapperNode(collectText(child.children), childResults);
|
|
884
627
|
wrapperNode.source = source?.slice(child.start, child.end);
|
|
@@ -891,7 +634,7 @@ const processChildrenBinding = (jsxElement, processedNodes, shouldWrap = true, s
|
|
|
891
634
|
const fragmentChildren = [];
|
|
892
635
|
child.children.forEach((fragmentChild) => {
|
|
893
636
|
if (import_lib$1.isJSXElement(fragmentChild)) {
|
|
894
|
-
const childResults = extractFromNode(fragmentChild, processedNodes, source);
|
|
637
|
+
const childResults = extractFromNode(fragmentChild, processedNodes, source, options);
|
|
895
638
|
fragmentChildren.push(...childResults);
|
|
896
639
|
}
|
|
897
640
|
});
|
|
@@ -928,13 +671,14 @@ const getBindingExpression = (opening) => {
|
|
|
928
671
|
}
|
|
929
672
|
return null;
|
|
930
673
|
};
|
|
931
|
-
const readNodeBindingInfo = (node, source) => {
|
|
674
|
+
const readNodeBindingInfo = (node, source, options) => {
|
|
932
675
|
const opening = node.openingElement;
|
|
933
676
|
const tagName = getJSXTagName(opening);
|
|
934
677
|
const { allAttrs, dataAttrs } = extractAttributes(opening.attributes, source);
|
|
935
678
|
const bindingAttr = dataAttrs.find((attr) => attr.name === DATA_ATTR.BINDING);
|
|
936
679
|
const bindingExpr = getBindingExpression(opening);
|
|
937
|
-
const
|
|
680
|
+
const keyAttr = dataAttrs.find((attr) => attr.name === DATA_ATTR.BINDING_KEY);
|
|
681
|
+
const bindings = resolveBindings(bindingExpr ? parseBindingExpression(bindingExpr) : bindingAttr ? parseBinding(bindingAttr.value) : void 0, keyAttr && (keyAttr.isStringLiteral ? keyAttr.value : null), tagName, options);
|
|
938
682
|
const childrenBinding = bindings.find((b) => b.property === BINDING_PROP.CHILDREN);
|
|
939
683
|
const arrayBindings = bindings.filter((b) => b.property === BINDING_PROP.ITEMS || b.type === "array");
|
|
940
684
|
const innerHtmlBinding = bindings.find((b) => b.property === BINDING_PROP.INNER_HTML);
|
|
@@ -950,7 +694,7 @@ const readNodeBindingInfo = (node, source) => {
|
|
|
950
694
|
rawChildren
|
|
951
695
|
};
|
|
952
696
|
};
|
|
953
|
-
const parseToNodes = (raw) => {
|
|
697
|
+
const parseToNodes = (raw, options) => {
|
|
954
698
|
const wrapped = wrap(raw);
|
|
955
699
|
const ast = (0, import_lib.parse)(wrapped, {
|
|
956
700
|
sourceType: "module",
|
|
@@ -961,10 +705,10 @@ const parseToNodes = (raw) => {
|
|
|
961
705
|
const processedNodes = /* @__PURE__ */ new WeakSet();
|
|
962
706
|
traverse(ast, { JSXElement(path) {
|
|
963
707
|
if (processedNodes.has(path.node)) return;
|
|
964
|
-
const { tagName, allAttrs, dataAttrs, bindings, childrenBinding, arrayBindings, rawChildren } = readNodeBindingInfo(path.node, wrapped);
|
|
708
|
+
const { tagName, allAttrs, dataAttrs, bindings, childrenBinding, arrayBindings, rawChildren } = readNodeBindingInfo(path.node, wrapped, options);
|
|
965
709
|
if (!tagName || !dataAttrs.length) return;
|
|
966
710
|
let childrenNodes;
|
|
967
|
-
if (childrenBinding) childrenNodes = processChildrenBinding(path.node, processedNodes, true, wrapped);
|
|
711
|
+
if (childrenBinding) childrenNodes = processChildrenBinding(path.node, processedNodes, true, wrapped, options);
|
|
968
712
|
for (const arrayBinding of arrayBindings) skipItemsChildren(path.node, processedNodes, arrayBinding.property);
|
|
969
713
|
results.push({
|
|
970
714
|
tagName,
|
|
@@ -988,32 +732,134 @@ const parseToNodes = (raw) => {
|
|
|
988
732
|
} });
|
|
989
733
|
return results;
|
|
990
734
|
};
|
|
991
|
-
function extract(raw) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
735
|
+
function extract(raw, options = {}) {
|
|
736
|
+
const cache = cacheFor(options);
|
|
737
|
+
if (cache.has(raw)) return cache.get(raw);
|
|
738
|
+
const results = parseToNodes(raw, options);
|
|
739
|
+
cache.set(raw, results);
|
|
740
|
+
return results;
|
|
741
|
+
}
|
|
742
|
+
function extractFromNode(node, processedNodes, source, options) {
|
|
743
|
+
processedNodes?.add(node);
|
|
744
|
+
const { tagName, allAttrs, dataAttrs, bindings, childrenBinding, rawChildren } = readNodeBindingInfo(node, source, options);
|
|
745
|
+
let childrenNodes;
|
|
746
|
+
if (childrenBinding) childrenNodes = processChildrenBinding(node, processedNodes, true, source, options);
|
|
747
|
+
if (!childrenNodes) childrenNodes = processChildrenBinding(node, processedNodes, false, source, options);
|
|
748
|
+
return [{
|
|
749
|
+
tagName,
|
|
750
|
+
attributes: allAttrs,
|
|
751
|
+
dataAttributes: dataAttrs,
|
|
752
|
+
textContent: collectText(node.children),
|
|
753
|
+
rawChildren,
|
|
754
|
+
children: childrenNodes,
|
|
755
|
+
bindings
|
|
756
|
+
}];
|
|
757
|
+
}
|
|
758
|
+
function clearExtractCache() {
|
|
759
|
+
extractCache.clear();
|
|
760
|
+
optionExtractCaches = /* @__PURE__ */ new WeakMap();
|
|
761
|
+
}
|
|
762
|
+
registerEditorCache(clearExtractCache);
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region src/utils/selection.ts
|
|
765
|
+
const removeIndices = (items, indices) => {
|
|
766
|
+
return items.filter((_, index) => !indices.has(index));
|
|
767
|
+
};
|
|
768
|
+
/**
|
|
769
|
+
* Shifts every selected index up/down by one step as a block, preserving
|
|
770
|
+
* relative order — scattered selections stop moving individually once they
|
|
771
|
+
* hit an unselected neighbor, so the whole group slides together instead of
|
|
772
|
+
* items passing through each other.
|
|
773
|
+
*/
|
|
774
|
+
const moveSelectedIndices = (items, indices, direction) => {
|
|
775
|
+
const next = [...items];
|
|
776
|
+
const nextIndices = new Set(indices);
|
|
777
|
+
const ordered = [...indices].sort((a, b) => direction === "up" ? a - b : b - a);
|
|
778
|
+
for (const index of ordered) {
|
|
779
|
+
const target = direction === "up" ? index - 1 : index + 1;
|
|
780
|
+
if (target < 0 || target >= next.length || nextIndices.has(target)) continue;
|
|
781
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
782
|
+
nextIndices.delete(index);
|
|
783
|
+
nextIndices.add(target);
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
items: next,
|
|
787
|
+
indices: nextIndices
|
|
788
|
+
};
|
|
789
|
+
};
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region src/utils/ast/patch.ts
|
|
792
|
+
const hasOnlyLayoutNewlines = (content) => {
|
|
793
|
+
let index = 0;
|
|
794
|
+
while (index < content.length) {
|
|
795
|
+
const char = content[index];
|
|
796
|
+
if (char === "`" || char === "<") return false;
|
|
797
|
+
if (char === "\"" || char === "'") {
|
|
798
|
+
const next = skipStringLiteral(content, index);
|
|
799
|
+
if (next === -1) return false;
|
|
800
|
+
index = next;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (char === "/") {
|
|
804
|
+
const following = content[index + 1];
|
|
805
|
+
if (following === "/") {
|
|
806
|
+
const lineEnd = content.indexOf("\n", index + 2);
|
|
807
|
+
index = lineEnd === -1 ? content.length : lineEnd;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (following === "*") {
|
|
811
|
+
const commentEnd = content.indexOf("*/", index + 2);
|
|
812
|
+
if (commentEnd === -1) return false;
|
|
813
|
+
index = commentEnd + 2;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
index++;
|
|
819
|
+
}
|
|
820
|
+
return true;
|
|
821
|
+
};
|
|
822
|
+
const skipStringLiteral = (content, start) => {
|
|
823
|
+
const quote = content[start];
|
|
824
|
+
for (let index = start + 1; index < content.length; index++) {
|
|
825
|
+
const char = content[index];
|
|
826
|
+
if (char === "\\") {
|
|
827
|
+
const escaped = content[index + 1];
|
|
828
|
+
if (escaped === "\n" || escaped === "\r") return -1;
|
|
829
|
+
index++;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (char === quote) return index + 1;
|
|
833
|
+
if (char === "\n") return -1;
|
|
834
|
+
}
|
|
835
|
+
return -1;
|
|
836
|
+
};
|
|
837
|
+
const lineIndentAt = (source, offset) => {
|
|
838
|
+
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
839
|
+
return /^[ \t]*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
|
|
840
|
+
};
|
|
841
|
+
const lineTerminatorOf = (source) => {
|
|
842
|
+
return source.includes("\r\n") ? "\r\n" : "\n";
|
|
843
|
+
};
|
|
844
|
+
const applyEdits = (source, edits) => {
|
|
845
|
+
if (edits.length === 0) return source;
|
|
846
|
+
const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
847
|
+
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}`);
|
|
848
|
+
for (let i = 1; i < ordered.length; i++) {
|
|
849
|
+
const previous = ordered[i - 1];
|
|
850
|
+
const current = ordered[i];
|
|
851
|
+
if (current.start < previous.end) throw new Error(`Overlapping source edits: [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
|
|
852
|
+
}
|
|
853
|
+
let result = "";
|
|
854
|
+
let cursor = 0;
|
|
855
|
+
const terminator = lineTerminatorOf(source);
|
|
856
|
+
for (const edit of ordered) {
|
|
857
|
+
const content = edit.indent && edit.content.includes("\n") && hasOnlyLayoutNewlines(edit.content) ? edit.content.replace(/\n/g, `${terminator}${lineIndentAt(source, edit.start)}`) : edit.content;
|
|
858
|
+
result += source.slice(cursor, edit.start) + content;
|
|
859
|
+
cursor = edit.end;
|
|
860
|
+
}
|
|
861
|
+
return result + source.slice(cursor);
|
|
862
|
+
};
|
|
1017
863
|
//#endregion
|
|
1018
864
|
//#region src/utils/ast/children.ts
|
|
1019
865
|
const modeledSignatures = (nodes) => nodes.map((node) => {
|
|
@@ -1061,13 +907,13 @@ const copySource = (source, usedIds) => {
|
|
|
1061
907
|
});
|
|
1062
908
|
return applyEdits(source, edits);
|
|
1063
909
|
};
|
|
1064
|
-
const editChildrenSource = (source, parent, input) => {
|
|
910
|
+
const editChildrenSource = (source, parent, input, options) => {
|
|
1065
911
|
try {
|
|
1066
912
|
if (!parent.closingElement || parent.start == null || parent.end == null) return null;
|
|
1067
913
|
const value = typeof input === "string" ? JSON.parse(input) : input;
|
|
1068
914
|
const children = parent.children.filter((child) => import_lib$1.isJSXElement(child) || import_lib$1.isJSXFragment(child));
|
|
1069
915
|
const raw = children.map((child) => source.slice(child.start, child.end));
|
|
1070
|
-
const models = extract(source.slice(parent.start, parent.end))[0]?.children ?? [];
|
|
916
|
+
const models = extract(source.slice(parent.start, parent.end), options)[0]?.children ?? [];
|
|
1071
917
|
if (models.length !== children.length) return null;
|
|
1072
918
|
const signatures = modeledSignatures(models);
|
|
1073
919
|
const replaceSlots = (next) => {
|
|
@@ -1340,7 +1186,7 @@ const canEditAttributeValue = (attribute, value, type) => {
|
|
|
1340
1186
|
if (!import_lib$1.isJSXExpressionContainer(attribute.value) || import_lib$1.isJSXEmptyExpression(attribute.value.expression) || isLosslesslyEvaluable(attribute.value.expression)) return true;
|
|
1341
1187
|
return typeof value === "string" && holdsStructuralExpression(attribute.value) && !(type && STRING_VALUED_TYPES.has(type)) && structuralSource(value) !== null;
|
|
1342
1188
|
};
|
|
1343
|
-
const update = (code, dataId, label, value, property) => {
|
|
1189
|
+
const update = (code, dataId, label, value, property, options = {}) => {
|
|
1344
1190
|
try {
|
|
1345
1191
|
const wrapped = wrap(code);
|
|
1346
1192
|
const ast = (0, import_lib.parse)(wrapped, {
|
|
@@ -1364,25 +1210,38 @@ const update = (code, dataId, label, value, property) => {
|
|
|
1364
1210
|
return 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;
|
|
1365
1211
|
})) return;
|
|
1366
1212
|
const bindingAttr = opening.attributes.find((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING);
|
|
1367
|
-
if (!bindingAttr
|
|
1213
|
+
if (bindingAttr && !bindingAttr.value) {
|
|
1368
1214
|
failure = {
|
|
1369
1215
|
reason: "no-binding",
|
|
1370
1216
|
dataId
|
|
1371
1217
|
};
|
|
1372
1218
|
return;
|
|
1373
1219
|
}
|
|
1374
|
-
let
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
bindingValue =
|
|
1378
|
-
|
|
1220
|
+
let own;
|
|
1221
|
+
if (bindingAttr?.value) {
|
|
1222
|
+
let bindingValue = "";
|
|
1223
|
+
if (import_lib$1.isStringLiteral(bindingAttr.value)) bindingValue = bindingAttr.value.value;
|
|
1224
|
+
else if (import_lib$1.isJSXExpressionContainer(bindingAttr.value)) try {
|
|
1225
|
+
bindingValue = generateCode(bindingAttr.value.expression);
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
failure = {
|
|
1228
|
+
reason: "parse-error",
|
|
1229
|
+
error
|
|
1230
|
+
};
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
own = parseBinding(bindingValue);
|
|
1234
|
+
}
|
|
1235
|
+
const keyAttr = opening.attributes.find((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name) && attr.name.name === DATA_ATTR.BINDING_KEY);
|
|
1236
|
+
const bindings = resolveBindings(own, keyAttr && (keyAttr.value && import_lib$1.isStringLiteral(keyAttr.value) ? keyAttr.value.value : null), getJSXTagName(opening), options);
|
|
1237
|
+
if (!own && bindings.length === 0) {
|
|
1379
1238
|
failure = {
|
|
1380
|
-
reason: "
|
|
1381
|
-
|
|
1239
|
+
reason: "no-binding",
|
|
1240
|
+
dataId
|
|
1382
1241
|
};
|
|
1383
1242
|
return;
|
|
1384
1243
|
}
|
|
1385
|
-
const matches =
|
|
1244
|
+
const matches = bindings.filter((binding) => property !== void 0 ? binding.property === property : binding.label === label);
|
|
1386
1245
|
if (matches.length === 0) {
|
|
1387
1246
|
failure = {
|
|
1388
1247
|
reason: "binding-not-declared",
|
|
@@ -1443,7 +1302,7 @@ const update = (code, dataId, label, value, property) => {
|
|
|
1443
1302
|
};
|
|
1444
1303
|
break;
|
|
1445
1304
|
}
|
|
1446
|
-
collect(editChildrenSource(wrapped, path.node, value), () => ({
|
|
1305
|
+
collect(editChildrenSource(wrapped, path.node, value, options), () => ({
|
|
1447
1306
|
reason: "parse-error",
|
|
1448
1307
|
error: /* @__PURE__ */ new Error("Unsupported or stale children edit; source was preserved")
|
|
1449
1308
|
}));
|
|
@@ -1506,10 +1365,10 @@ const update = (code, dataId, label, value, property) => {
|
|
|
1506
1365
|
};
|
|
1507
1366
|
}
|
|
1508
1367
|
};
|
|
1509
|
-
const updateAll = (raw, entries) => {
|
|
1368
|
+
const updateAll = (raw, entries, options = {}) => {
|
|
1510
1369
|
let current = raw;
|
|
1511
1370
|
for (const [index, entry] of entries.entries()) {
|
|
1512
|
-
const result = update(current, entry.dataId, entry.label, entry.value, entry.property);
|
|
1371
|
+
const result = update(current, entry.dataId, entry.label, entry.value, entry.property, options);
|
|
1513
1372
|
if (!result.success) return {
|
|
1514
1373
|
success: false,
|
|
1515
1374
|
code: raw,
|
|
@@ -1549,55 +1408,237 @@ const bulkUpdate = (raw, entries) => {
|
|
|
1549
1408
|
};
|
|
1550
1409
|
};
|
|
1551
1410
|
//#endregion
|
|
1552
|
-
//#region src/utils/ast/
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
if (
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1411
|
+
//#region src/utils/ast/array-source.ts
|
|
1412
|
+
const commaIn = (source, start, end) => {
|
|
1413
|
+
let comma = null;
|
|
1414
|
+
for (let at = start; at < end; at++) {
|
|
1415
|
+
if (/\s/.test(source[at])) continue;
|
|
1416
|
+
if (source.startsWith("/*", at)) {
|
|
1417
|
+
const close = source.indexOf("*/", at + 2);
|
|
1418
|
+
if (close < 0 || close + 2 > end) return null;
|
|
1419
|
+
at = close + 1;
|
|
1420
|
+
} else if (source.startsWith("//", at)) {
|
|
1421
|
+
const newline = source.indexOf("\n", at + 2);
|
|
1422
|
+
at = newline < 0 ? end : newline;
|
|
1423
|
+
} else if (source[at] === "," && comma === null) comma = at;
|
|
1424
|
+
else return null;
|
|
1561
1425
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1426
|
+
return comma;
|
|
1427
|
+
};
|
|
1428
|
+
const denseArraySource = (source) => {
|
|
1429
|
+
const array = parseArrayExpression(source);
|
|
1430
|
+
if (!array || array.elements.some((node) => !import_lib$1.isExpression(node) || node.extra?.parenthesized)) return null;
|
|
1431
|
+
const elements = array.elements;
|
|
1432
|
+
const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
|
|
1433
|
+
if (commas.some((comma) => comma === null)) return null;
|
|
1434
|
+
const last = elements.at(-1);
|
|
1435
|
+
return {
|
|
1436
|
+
array,
|
|
1437
|
+
elements,
|
|
1438
|
+
commas,
|
|
1439
|
+
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
1440
|
+
};
|
|
1441
|
+
};
|
|
1442
|
+
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
1443
|
+
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
1444
|
+
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
1445
|
+
start: node.start,
|
|
1446
|
+
end: node.end,
|
|
1447
|
+
content: source.slice(next[index].start, next[index].end)
|
|
1448
|
+
})));
|
|
1449
|
+
const removeArraySource = (source, data, indices) => {
|
|
1450
|
+
const edits = [];
|
|
1451
|
+
const removedCommas = /* @__PURE__ */ new Set();
|
|
1452
|
+
const { elements, commas, trailing } = data;
|
|
1453
|
+
for (let index = 0; index < elements.length; index++) {
|
|
1454
|
+
if (!indices.has(index)) continue;
|
|
1455
|
+
const start = index;
|
|
1456
|
+
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
1457
|
+
for (let current = start; current <= index; current++) {
|
|
1458
|
+
const node = elements[current];
|
|
1459
|
+
edits.push({
|
|
1460
|
+
start: node.start,
|
|
1461
|
+
end: node.end,
|
|
1462
|
+
content: ""
|
|
1463
|
+
});
|
|
1464
|
+
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
1465
|
+
}
|
|
1466
|
+
if (index === elements.length - 1) {
|
|
1467
|
+
if (trailing !== null) removedCommas.add(trailing);
|
|
1468
|
+
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
for (const comma of removedCommas) edits.push({
|
|
1472
|
+
start: comma,
|
|
1473
|
+
end: comma + 1,
|
|
1474
|
+
content: ""
|
|
1475
|
+
});
|
|
1476
|
+
return applyEdits(source, edits);
|
|
1477
|
+
};
|
|
1478
|
+
const appendArraySource = (source, data, copies) => {
|
|
1479
|
+
const last = data.elements.at(-1);
|
|
1480
|
+
if (!last || copies.length === 0) return source;
|
|
1481
|
+
return applyEdits(source, [{
|
|
1482
|
+
start: last.end,
|
|
1483
|
+
end: last.end,
|
|
1484
|
+
content: `, ${copies.join(", ")}`
|
|
1485
|
+
}]);
|
|
1486
|
+
};
|
|
1487
|
+
//#endregion
|
|
1488
|
+
//#region src/utils/ast/items.ts
|
|
1489
|
+
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
1490
|
+
const kindOf = (element) => import_lib$1.isObjectExpression(element) ? "object" : "primitive";
|
|
1491
|
+
const parseItems = (code) => {
|
|
1492
|
+
return elementsOf(code)?.flatMap((node, index) => import_lib$1.isExpression(node) ? [{
|
|
1493
|
+
index,
|
|
1494
|
+
kind: kindOf(node),
|
|
1495
|
+
node
|
|
1496
|
+
}] : []) ?? null;
|
|
1497
|
+
};
|
|
1498
|
+
const resolveRenderLeaf = (render, key) => {
|
|
1499
|
+
const leaf = render?.[key];
|
|
1500
|
+
return leaf && "type" in leaf ? leaf : null;
|
|
1501
|
+
};
|
|
1502
|
+
const toTemplateRaw = (value) => {
|
|
1503
|
+
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
1504
|
+
};
|
|
1505
|
+
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
1506
|
+
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
1507
|
+
const raw = String(value);
|
|
1508
|
+
return import_lib$1.templateLiteral([import_lib$1.templateElement({
|
|
1509
|
+
raw: toTemplateRaw(raw),
|
|
1510
|
+
cooked: raw
|
|
1511
|
+
}, true)], []);
|
|
1571
1512
|
}
|
|
1572
|
-
if (
|
|
1573
|
-
|
|
1513
|
+
if (renderLeaf?.type === "jsx" || import_lib$1.isJSXElement(current) || import_lib$1.isJSXFragment(current)) {
|
|
1514
|
+
const trimmed = String(value).trim();
|
|
1515
|
+
if (!trimmed.startsWith("<")) return import_lib$1.stringLiteral(trimmed);
|
|
1574
1516
|
try {
|
|
1575
|
-
|
|
1517
|
+
return (0, import_lib.parseExpression)(trimmed, { plugins: ["jsx", "typescript"] });
|
|
1576
1518
|
} catch {
|
|
1577
|
-
return
|
|
1519
|
+
return;
|
|
1578
1520
|
}
|
|
1579
|
-
if (!regex.test(value)) return {
|
|
1580
|
-
valid: false,
|
|
1581
|
-
message: "Value does not match the required format."
|
|
1582
|
-
};
|
|
1583
1521
|
}
|
|
1584
|
-
if (
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1522
|
+
if (declaredType === "array" || declaredType === "object") {
|
|
1523
|
+
if (typeof value === "string") try {
|
|
1524
|
+
return (0, import_lib.parseExpression)(value, { plugins: ["jsx", "typescript"] });
|
|
1525
|
+
} catch {
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (!isLosslesslyEvaluable(current)) return;
|
|
1529
|
+
return valueToExpression(value) ?? void 0;
|
|
1591
1530
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1531
|
+
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
1532
|
+
};
|
|
1533
|
+
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
1534
|
+
const elements = elementsOf(code);
|
|
1535
|
+
const element = elements?.[index];
|
|
1536
|
+
if (!elements || !import_lib$1.isObjectExpression(element)) return null;
|
|
1537
|
+
const target = element.properties.find((property) => import_lib$1.isObjectProperty(property) && import_lib$1.isIdentifier(property.key) && property.key.name === key);
|
|
1538
|
+
if (!target) return null;
|
|
1539
|
+
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
1540
|
+
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
1541
|
+
if (!nextValue) return null;
|
|
1542
|
+
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || import_lib$1.isJSXElement(nextValue) || import_lib$1.isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
1543
|
+
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
1544
|
+
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
1545
|
+
return applyEdits(code, [{
|
|
1546
|
+
start: target.value.start,
|
|
1547
|
+
end: target.value.end,
|
|
1548
|
+
content
|
|
1549
|
+
}]);
|
|
1550
|
+
};
|
|
1551
|
+
const updateArrayItemValue = (code, index, value) => {
|
|
1552
|
+
const elements = elementsOf(code);
|
|
1553
|
+
const element = elements?.[index];
|
|
1554
|
+
if (!elements || !import_lib$1.isExpression(element)) return null;
|
|
1555
|
+
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
1556
|
+
if (!nextValue) return null;
|
|
1557
|
+
return applyEdits(code, [{
|
|
1558
|
+
start: element.start,
|
|
1559
|
+
end: element.end,
|
|
1560
|
+
content: generateCode(nextValue)
|
|
1561
|
+
}]);
|
|
1562
|
+
};
|
|
1563
|
+
const moveArrayItem = (code, from, to) => {
|
|
1564
|
+
const data = denseArraySource(code);
|
|
1565
|
+
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
1566
|
+
const next = [...data.elements];
|
|
1567
|
+
const [moved] = next.splice(from, 1);
|
|
1568
|
+
next.splice(to, 0, moved);
|
|
1569
|
+
return reorderArraySource(code, data.elements, next);
|
|
1570
|
+
};
|
|
1571
|
+
const moveArrayItems = (code, indices, direction) => {
|
|
1572
|
+
const data = denseArraySource(code);
|
|
1573
|
+
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
1574
|
+
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
1575
|
+
return {
|
|
1576
|
+
code: reorderArraySource(code, data.elements, next.items),
|
|
1577
|
+
indices: next.indices
|
|
1578
|
+
};
|
|
1579
|
+
};
|
|
1580
|
+
const removeArrayItems = (code, indices, kind) => {
|
|
1581
|
+
const data = denseArraySource(code);
|
|
1582
|
+
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
1583
|
+
const remaining = removeIndices(data.elements, indices);
|
|
1584
|
+
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
1585
|
+
return removeArraySource(code, data, indices);
|
|
1586
|
+
};
|
|
1587
|
+
const copyItem = (code, element, generateId, usedIds) => {
|
|
1588
|
+
const edits = [];
|
|
1589
|
+
let unsupported = false;
|
|
1590
|
+
const reserveId = (id) => {
|
|
1591
|
+
if (usedIds.has(id)) unsupported = true;
|
|
1592
|
+
usedIds.add(id);
|
|
1593
|
+
return JSON.stringify(id);
|
|
1594
|
+
};
|
|
1595
|
+
import_lib$1.traverseFast(element, (node) => {
|
|
1596
|
+
if (!import_lib$1.isJSXOpeningElement(node)) return;
|
|
1597
|
+
const ids = node.attributes.filter((attr) => import_lib$1.isJSXAttribute(attr) && import_lib$1.isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
1598
|
+
const attr = ids[0];
|
|
1599
|
+
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)))) {
|
|
1600
|
+
unsupported = true;
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (attr && import_lib$1.isStringLiteral(attr.value)) edits.push({
|
|
1604
|
+
start: attr.value.start,
|
|
1605
|
+
end: attr.value.end,
|
|
1606
|
+
content: reserveId(generateId())
|
|
1607
|
+
});
|
|
1608
|
+
});
|
|
1609
|
+
if (import_lib$1.isObjectExpression(element)) {
|
|
1610
|
+
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" })));
|
|
1611
|
+
const key = keys[0];
|
|
1612
|
+
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;
|
|
1613
|
+
if (key && import_lib$1.isStringLiteral(key.value)) edits.push({
|
|
1614
|
+
start: key.value.start,
|
|
1615
|
+
end: key.value.end,
|
|
1616
|
+
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
1617
|
+
});
|
|
1597
1618
|
}
|
|
1598
|
-
return
|
|
1619
|
+
if (unsupported) return null;
|
|
1620
|
+
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
1621
|
+
...edit,
|
|
1622
|
+
start: edit.start - element.start,
|
|
1623
|
+
end: edit.end - element.start
|
|
1624
|
+
})));
|
|
1625
|
+
};
|
|
1626
|
+
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
1627
|
+
const data = denseArraySource(code);
|
|
1628
|
+
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
1629
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
1630
|
+
import_lib$1.traverseFast(data.array, (node) => {
|
|
1631
|
+
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);
|
|
1632
|
+
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);
|
|
1633
|
+
});
|
|
1634
|
+
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
1635
|
+
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
1636
|
+
};
|
|
1637
|
+
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
1638
|
+
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
1639
|
+
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
1599
1640
|
};
|
|
1600
1641
|
//#endregion
|
|
1601
|
-
export {
|
|
1642
|
+
export { extractNodeValue as A, parseBinding as C, arrayExpressionToCode as D, resolveBindings as E, setEditableValue as F, flattenEditableValue as M, parseArrayExpression as N, canLosslesslyEvaluateSource as O, parseValue as P, isComponentTagName as S, readNodeBindings as T, findEditableChildren as _, parseItems as a, getRegistryBindings as b, updateArrayItemValue as c, update as d, updateAll as f, extract as g, clearExtractCache as h, moveArrayItems as i, extractObjectProperties as j, createNodeFromValue as k, canStructurallyEditArray as l, moveSelectedIndices as m, duplicateArrayItems as n, removeArrayItems as o, getChildrenSignatures as p, moveArrayItem as r, updateArrayItemProperty as s, appendArrayItem as t, bulkUpdate as u, getCurrentValue as v, parseBindingExpression as w, getStructuredValue as x, getKeyBindings as y };
|
|
1602
1643
|
|
|
1603
|
-
//# sourceMappingURL=
|
|
1644
|
+
//# sourceMappingURL=items-DdrwGxJw.js.map
|