@samva/markup 0.4.0 → 0.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.
Files changed (107) hide show
  1. package/AGENTS.md +19 -0
  2. package/dist/{ast-63I-VLQx.d.ts → ast-BrDtApwS.d.ts} +34 -60
  3. package/dist/ast.d.ts +2 -2
  4. package/dist/ast.js +4 -52
  5. package/dist/{catalog-Ck1tAGwO.d.ts → catalog-60eaYC7b.d.ts} +2 -2
  6. package/dist/{checks-DXuERO4B.js → checks-DBweSIPN.js} +18 -20
  7. package/dist/checks.d.ts +1 -1
  8. package/dist/checks.js +1 -1
  9. package/dist/classes.d.ts +1 -1
  10. package/dist/compile-sms.d.ts +3 -3
  11. package/dist/compile-whatsapp.d.ts +5 -5
  12. package/dist/compile-whatsapp.js +3 -3
  13. package/dist/compile.d.ts +5 -5
  14. package/dist/compile.js +5 -5
  15. package/dist/compiler.d.ts +8 -5
  16. package/dist/compiler.js +45 -45
  17. package/dist/components.d.ts +4 -4
  18. package/dist/conditionals.js +56 -24
  19. package/dist/corpus-BjYMqOoY.d.ts +106 -0
  20. package/dist/{css-ar2lU3NC.d.ts → css-COcFOT1y.d.ts} +4 -5
  21. package/dist/diagnostics-BMYtBCQk.d.ts +46 -0
  22. package/dist/diagnostics-OcQKQYSY.d.ts +49 -0
  23. package/dist/diagnostics.d.ts +2 -2
  24. package/dist/diagnostics.js +24 -33
  25. package/dist/{elements-DtwKlFVH.d.ts → elements-BzjjnoCS.d.ts} +40 -161
  26. package/dist/email/components.d.ts +1 -1
  27. package/dist/email/components.js +40 -10
  28. package/dist/email/diagnostics.d.ts +1 -1
  29. package/dist/email/diagnostics.js +8 -43
  30. package/dist/email/elements.d.ts +1 -1
  31. package/dist/email/html.d.ts +2 -2
  32. package/dist/email/html.js +11 -10
  33. package/dist/email/jsx-dev-runtime.d.ts +3 -21
  34. package/dist/email/jsx-runtime.d.ts +1 -1
  35. package/dist/email/jsx-runtime.js +29 -3
  36. package/dist/email/render.d.ts +4 -4
  37. package/dist/email/render.js +1 -1
  38. package/dist/email/selection.d.ts +20 -31
  39. package/dist/email/selection.js +1 -53
  40. package/dist/email/source-edit.d.ts +1 -1
  41. package/dist/email/source-edit.js +2 -2
  42. package/dist/email/stylesheets.d.ts +1 -1
  43. package/dist/email/text.d.ts +1 -1
  44. package/dist/email/text.js +3 -3
  45. package/dist/emit.d.ts +2 -2
  46. package/dist/emit.js +5 -5
  47. package/dist/footer.js +7 -4
  48. package/dist/guards-eV1qLbf2.js +9 -0
  49. package/dist/{input-schema-BIfzI63B.d.ts → input-schema-7dPFVvNE.d.ts} +2 -2
  50. package/dist/input-schema-zjkgrXn2.js +455 -0
  51. package/dist/input-schema.d.ts +1 -1
  52. package/dist/input-schema.js +1 -169
  53. package/dist/interpolate.d.ts +9 -12
  54. package/dist/interpolate.js +1 -19
  55. package/dist/jsx-dev-runtime.d.ts +2 -2
  56. package/dist/{jsx-runtime-C5k_Zq0A.d.ts → jsx-runtime-I-1nCtl2.d.ts} +3 -3
  57. package/dist/jsx-runtime.d.ts +1 -1
  58. package/dist/jsx-runtime.js +3 -3
  59. package/dist/lexer.js +17 -5
  60. package/dist/model.d.ts +18 -33
  61. package/dist/model.js +4 -17
  62. package/dist/ops-2aiiYO7m.js +630 -0
  63. package/dist/ops.d.ts +78 -172
  64. package/dist/ops.js +2 -546
  65. package/dist/{parser-CGBS73Gz.d.ts → parser-CQcASEyy.d.ts} +5 -5
  66. package/dist/parser.d.ts +1 -1
  67. package/dist/parser.js +8 -8
  68. package/dist/{predicate-CvkzT-a2.js → predicate-BI6uRRM-.js} +24 -14
  69. package/dist/{predicate-G2SeSvoN.d.ts → predicate-M-05Nd7R.d.ts} +4 -4
  70. package/dist/predicate.d.ts +1 -1
  71. package/dist/predicate.js +1 -1
  72. package/dist/presets/category-mapping.d.ts +1 -1
  73. package/dist/presets/corpus.d.ts +1 -1
  74. package/dist/presets/corpus.js +74 -117
  75. package/dist/presets/public-gallery.d.ts +2 -2
  76. package/dist/presets/public-gallery.js +17 -9
  77. package/dist/presets/public-handoff.d.ts +4 -4
  78. package/dist/presets/public-handoff.js +10 -9
  79. package/dist/presets/public-preview-artifacts.d.ts +7 -5
  80. package/dist/presets/public-preview-artifacts.js +10 -9
  81. package/dist/presets/showcase.d.ts +1 -1
  82. package/dist/presets.d.ts +1 -1
  83. package/dist/printer.d.ts +1 -1
  84. package/dist/{render-ykQhtMs_.js → render-aaJBnsXK.js} +44 -27
  85. package/dist/render-template.d.ts +7 -7
  86. package/dist/render-template.js +6 -6
  87. package/dist/{showcase-C_xgl6hz.d.ts → showcase-DGZAWn4Z.d.ts} +2 -2
  88. package/dist/{source-locations-BCJQnIfU.d.ts → source-locations-DrCM82Ym.d.ts} +4 -26
  89. package/dist/source-locations.d.ts +2 -2
  90. package/dist/source-locations.js +3 -26
  91. package/dist/{template-Dy5yOPyp.d.ts → template-DWftqh4x.d.ts} +4 -4
  92. package/dist/template.d.ts +1 -1
  93. package/dist/template.js +3 -3
  94. package/dist/theme-css.d.ts +3 -3
  95. package/dist/theme-css.js +3 -3
  96. package/dist/theme.js +25 -2
  97. package/dist/tsx-source-instrumentation.d.ts +1 -3
  98. package/dist/tsx-source-instrumentation.js +6 -6
  99. package/dist/tsx-tracer.d.ts +7 -11
  100. package/dist/tsx-tracer.js +165 -165
  101. package/dist/{variables-if1VrV34.d.ts → variables-J_L3qxRa.d.ts} +2 -2
  102. package/dist/variables.d.ts +1 -1
  103. package/dist/variables.js +4 -4
  104. package/package.json +4 -3
  105. package/dist/corpus-U2EeALvS.d.ts +0 -234
  106. package/dist/diagnostics-Djd9ckLg.d.ts +0 -90
  107. package/dist/diagnostics-oveE-laV.d.ts +0 -79
@@ -0,0 +1,630 @@
1
+ import { documentChannel } from "./ast.js";
2
+ import { CHANNEL_TAG_SPECS, allowedChildren, parseSml } from "./parser.js";
3
+ import { compileSml } from "./compile.js";
4
+ import { a as isBreakingCompatibilityCheck, r as checkSmlCompatibility } from "./checks-DBweSIPN.js";
5
+ import { printSml } from "./printer.js";
6
+ import { Result, TaggedError } from "better-result";
7
+ //#region src/internal/sha256.ts
8
+ const ROUND_CONSTANTS = new Uint32Array([
9
+ 1116352408,
10
+ 1899447441,
11
+ 3049323471,
12
+ 3921009573,
13
+ 961987163,
14
+ 1508970993,
15
+ 2453635748,
16
+ 2870763221,
17
+ 3624381080,
18
+ 310598401,
19
+ 607225278,
20
+ 1426881987,
21
+ 1925078388,
22
+ 2162078206,
23
+ 2614888103,
24
+ 3248222580,
25
+ 3835390401,
26
+ 4022224774,
27
+ 264347078,
28
+ 604807628,
29
+ 770255983,
30
+ 1249150122,
31
+ 1555081692,
32
+ 1996064986,
33
+ 2554220882,
34
+ 2821834349,
35
+ 2952996808,
36
+ 3210313671,
37
+ 3336571891,
38
+ 3584528711,
39
+ 113926993,
40
+ 338241895,
41
+ 666307205,
42
+ 773529912,
43
+ 1294757372,
44
+ 1396182291,
45
+ 1695183700,
46
+ 1986661051,
47
+ 2177026350,
48
+ 2456956037,
49
+ 2730485921,
50
+ 2820302411,
51
+ 3259730800,
52
+ 3345764771,
53
+ 3516065817,
54
+ 3600352804,
55
+ 4094571909,
56
+ 275423344,
57
+ 430227734,
58
+ 506948616,
59
+ 659060556,
60
+ 883997877,
61
+ 958139571,
62
+ 1322822218,
63
+ 1537002063,
64
+ 1747873779,
65
+ 1955562222,
66
+ 2024104815,
67
+ 2227730452,
68
+ 2361852424,
69
+ 2428436474,
70
+ 2756734187,
71
+ 3204031479,
72
+ 3329325298
73
+ ]);
74
+ const rotateRight = (value, count) => value >>> count | value << 32 - count;
75
+ /**
76
+ * Encode valid Unicode as UTF-8 while preserving lone UTF-16 surrogates as
77
+ * their three-byte WTF-8 form. TextEncoder replaces every lone surrogate with
78
+ * U+FFFD, which would make distinct JavaScript source strings hash identically.
79
+ */
80
+ const encodeSource = (source) => {
81
+ const output = [];
82
+ for (let index = 0; index < source.length; index++) {
83
+ const first = source.charCodeAt(index);
84
+ let codePoint = first;
85
+ if (first >= 55296 && first <= 56319) {
86
+ const second = source.charCodeAt(index + 1);
87
+ if (second >= 56320 && second <= 57343) {
88
+ codePoint = 65536 + (first - 55296 << 10) + (second - 56320);
89
+ index++;
90
+ }
91
+ }
92
+ if (codePoint <= 127) output.push(codePoint);
93
+ else if (codePoint <= 2047) output.push(192 | codePoint >>> 6, 128 | codePoint & 63);
94
+ else if (codePoint <= 65535) output.push(224 | codePoint >>> 12, 128 | codePoint >>> 6 & 63, 128 | codePoint & 63);
95
+ else output.push(240 | codePoint >>> 18, 128 | codePoint >>> 12 & 63, 128 | codePoint >>> 6 & 63, 128 | codePoint & 63);
96
+ }
97
+ return Uint8Array.from(output);
98
+ };
99
+ /** Browser-safe synchronous SHA-256 for compiler identity tokens. */
100
+ const sha256 = (source) => {
101
+ const bytes = encodeSource(source);
102
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
103
+ const padded = new Uint8Array(paddedLength);
104
+ padded.set(bytes);
105
+ padded[bytes.length] = 128;
106
+ const view = new DataView(padded.buffer);
107
+ const bitLength = bytes.length * 8;
108
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
109
+ view.setUint32(paddedLength - 4, bitLength >>> 0);
110
+ const state = new Uint32Array([
111
+ 1779033703,
112
+ 3144134277,
113
+ 1013904242,
114
+ 2773480762,
115
+ 1359893119,
116
+ 2600822924,
117
+ 528734635,
118
+ 1541459225
119
+ ]);
120
+ const words = /* @__PURE__ */ new Uint32Array(64);
121
+ for (let offset = 0; offset < paddedLength; offset += 64) {
122
+ for (let index = 0; index < 16; index++) words[index] = view.getUint32(offset + index * 4);
123
+ for (let index = 16; index < 64; index++) {
124
+ const previous15 = words[index - 15];
125
+ const previous2 = words[index - 2];
126
+ const sigma0 = rotateRight(previous15, 7) ^ rotateRight(previous15, 18) ^ previous15 >>> 3;
127
+ const sigma1 = rotateRight(previous2, 17) ^ rotateRight(previous2, 19) ^ previous2 >>> 10;
128
+ words[index] = words[index - 16] + sigma0 + words[index - 7] + sigma1 >>> 0;
129
+ }
130
+ let a = state[0];
131
+ let b = state[1];
132
+ let c = state[2];
133
+ let d = state[3];
134
+ let e = state[4];
135
+ let f = state[5];
136
+ let g = state[6];
137
+ let h = state[7];
138
+ for (let index = 0; index < 64; index++) {
139
+ const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
140
+ const choice = e & f ^ ~e & g;
141
+ const temporary1 = h + sum1 + choice + ROUND_CONSTANTS[index] + words[index] >>> 0;
142
+ const temporary2 = (rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
143
+ h = g;
144
+ g = f;
145
+ f = e;
146
+ e = d + temporary1 >>> 0;
147
+ d = c;
148
+ c = b;
149
+ b = a;
150
+ a = temporary1 + temporary2 >>> 0;
151
+ }
152
+ state[0] = state[0] + a >>> 0;
153
+ state[1] = state[1] + b >>> 0;
154
+ state[2] = state[2] + c >>> 0;
155
+ state[3] = state[3] + d >>> 0;
156
+ state[4] = state[4] + e >>> 0;
157
+ state[5] = state[5] + f >>> 0;
158
+ state[6] = state[6] + g >>> 0;
159
+ state[7] = state[7] + h >>> 0;
160
+ }
161
+ return [...state].map((word) => word.toString(16).padStart(8, "0")).join("");
162
+ };
163
+ //#endregion
164
+ //#region src/ops.ts
165
+ var SmlOpError = class extends TaggedError("SmlOpError") {
166
+ constructor(args) {
167
+ super({
168
+ diagnostics: args.diagnostics,
169
+ message: args.diagnostics.map((diagnostic) => diagnostic.message).join("\n")
170
+ });
171
+ }
172
+ };
173
+ const zeroSpan = {
174
+ start: 0,
175
+ end: 0
176
+ };
177
+ const diagnostic = (code, message, span = zeroSpan) => ({
178
+ code,
179
+ message,
180
+ span
181
+ });
182
+ const cloneAttribute = (attribute) => ({ ...attribute });
183
+ /**
184
+ * Deep-copy a subtree, blanking every id to "". A duplicated node must not
185
+ * alias the original's object graph or share its ids: batch ids resolve
186
+ * against the pre-batch snapshot, and mapElement transforms every node whose
187
+ * id matches — so two live copies sharing an id would both be rewritten by a
188
+ * later op. Blank ids make the clone unaddressable (like any freshly created
189
+ * node) until the next reprint reassigns positional ids.
190
+ */
191
+ const cloneSubtree = (node) => {
192
+ if (node.kind === "element") return {
193
+ ...node,
194
+ id: "",
195
+ attributes: node.attributes.map(cloneAttribute),
196
+ children: node.children.map(cloneSubtree)
197
+ };
198
+ return {
199
+ ...node,
200
+ id: ""
201
+ };
202
+ };
203
+ const makeAttribute = (name, value) => ({
204
+ name,
205
+ value,
206
+ span: zeroSpan,
207
+ valueSpan: zeroSpan
208
+ });
209
+ const classAttr = (value) => makeAttribute("class", {
210
+ kind: "string",
211
+ value
212
+ });
213
+ const sourceRevision = (source) => `sml:${sha256(source)}`;
214
+ const normalizeSml = (source) => {
215
+ const parsed = parseSml(source);
216
+ if (Result.isError(parsed)) return Result.err(parsed.error);
217
+ return Result.ok(printSml(parsed.value));
218
+ };
219
+ const findElement = (node, nodeId) => {
220
+ if (node.kind !== "element") return null;
221
+ if (node.id === nodeId) return node;
222
+ for (const child of node.children) {
223
+ const found = findElement(child, nodeId);
224
+ if (found !== null) return found;
225
+ }
226
+ return null;
227
+ };
228
+ const mapElement = (node, targetId, transform) => {
229
+ if (node.kind !== "element") return node;
230
+ if (node.id === targetId) return transform(node);
231
+ return {
232
+ ...node,
233
+ children: node.children.map((child) => mapElement(child, targetId, transform))
234
+ };
235
+ };
236
+ const transformElement = (document, targetId, transform) => {
237
+ if (findElement(document.root, targetId) === null) return Result.err([diagnostic("op-target-not-found", `node "${targetId}" was not found in the current source`)]);
238
+ return Result.ok({
239
+ ...document,
240
+ root: mapElement(document.root, targetId, transform)
241
+ });
242
+ };
243
+ const setElementAttribute = (element, name, value) => {
244
+ const rest = element.attributes.filter((attribute) => attribute.name !== name);
245
+ if (value === null || value.kind === "string" && value.value === "") return {
246
+ ...element,
247
+ attributes: rest
248
+ };
249
+ const next = makeAttribute(name, value);
250
+ const attributes = element.attributes.some((attribute) => attribute.name === name) ? element.attributes.map((attribute) => attribute.name === name ? next : cloneAttribute(attribute)) : [...element.attributes, next];
251
+ return {
252
+ ...element,
253
+ attributes
254
+ };
255
+ };
256
+ const setImageSchemeSource = (document, nodeId, scheme, value) => transformElement(document, nodeId, (element) => {
257
+ if (element.tag !== "Image") return element;
258
+ const src = value?.kind === "string" ? value.value : "";
259
+ const children = element.children.filter((child) => !(child.kind === "element" && child.tag === "Source" && child.attributes.some((attribute) => attribute.name === "scheme" && attribute.value.kind === "string" && attribute.value.value === scheme)));
260
+ if (src === "") return {
261
+ ...element,
262
+ children
263
+ };
264
+ const sourceNode = {
265
+ kind: "element",
266
+ id: "",
267
+ tag: "Source",
268
+ attributes: [makeAttribute("scheme", {
269
+ kind: "string",
270
+ value: scheme
271
+ }), makeAttribute("src", {
272
+ kind: "string",
273
+ value: src
274
+ })],
275
+ children: [],
276
+ span: zeroSpan
277
+ };
278
+ return {
279
+ ...element,
280
+ children: [...children, sourceNode]
281
+ };
282
+ });
283
+ const setProp = (document, op) => {
284
+ if (op.name === "src:dark") return setImageSchemeSource(document, op.nodeId, "dark", op.value);
285
+ return transformElement(document, op.nodeId, (element) => {
286
+ const value = op.name === "class" && op.value?.kind === "string" ? {
287
+ kind: "string",
288
+ value: op.value.value.trim().replace(/\s+/g, " ")
289
+ } : op.value;
290
+ return setElementAttribute(element, op.name, value);
291
+ });
292
+ };
293
+ const setChildren = (document, op) => transformElement(document, op.nodeId, (element) => ({
294
+ ...element,
295
+ children: [...op.children]
296
+ }));
297
+ const makeColumn = () => ({
298
+ kind: "element",
299
+ id: "",
300
+ tag: "Column",
301
+ attributes: [classAttr("px-8")],
302
+ children: [{
303
+ kind: "element",
304
+ id: "",
305
+ tag: "Text",
306
+ attributes: [classAttr("text-base text-gray-700")],
307
+ children: [{
308
+ kind: "text",
309
+ id: "",
310
+ value: "Column",
311
+ span: zeroSpan
312
+ }],
313
+ span: zeroSpan
314
+ }],
315
+ span: zeroSpan
316
+ });
317
+ const makeBlock = (tag) => {
318
+ const base = (attributes, children) => ({
319
+ kind: "element",
320
+ id: "",
321
+ tag,
322
+ attributes,
323
+ children,
324
+ span: zeroSpan
325
+ });
326
+ const text = (value) => ({
327
+ kind: "text",
328
+ id: "",
329
+ value,
330
+ span: zeroSpan
331
+ });
332
+ switch (tag) {
333
+ case "Heading": return base([makeAttribute("level", {
334
+ kind: "number",
335
+ value: 2
336
+ }), classAttr("text-xl text-gray-900")], [text("Heading")]);
337
+ case "Text": return base([classAttr("text-base text-gray-700")], [text("Add your text here.")]);
338
+ case "Button": return base([makeAttribute("href", {
339
+ kind: "string",
340
+ value: "https://example.com"
341
+ }), classAttr("bg-blue-600 text-white px-20 py-12 rounded-md")], [text("Button")]);
342
+ case "Image": return base([
343
+ makeAttribute("src", {
344
+ kind: "string",
345
+ value: "https://placehold.co/600x200"
346
+ }),
347
+ makeAttribute("alt", {
348
+ kind: "string",
349
+ value: ""
350
+ }),
351
+ makeAttribute("width", {
352
+ kind: "number",
353
+ value: 600
354
+ })
355
+ ], []);
356
+ case "Divider": return base([classAttr("border-gray-200")], []);
357
+ case "Spacer": return base([makeAttribute("height", {
358
+ kind: "number",
359
+ value: 24
360
+ })], []);
361
+ case "Section": return base([classAttr("py-16")], [makeBlock("Text")]);
362
+ case "Container": return base([classAttr("bg-white p-24")], [makeBlock("Text")]);
363
+ case "Row": return base([], [makeColumn(), makeColumn()]);
364
+ default: return base([], []);
365
+ }
366
+ };
367
+ const elementChildIndexToNodeIndex = (children, insertionIndex) => {
368
+ if (insertionIndex <= 0) {
369
+ const firstElement = children.findIndex((child) => child.kind === "element");
370
+ return firstElement === -1 ? children.length : firstElement;
371
+ }
372
+ let elementIndex = 0;
373
+ for (let index = 0; index < children.length; index++) {
374
+ if (children[index]?.kind !== "element") continue;
375
+ if (elementIndex === insertionIndex) return index;
376
+ elementIndex++;
377
+ }
378
+ return children.length;
379
+ };
380
+ const insertChildren = (document, childrenToInsert, referenceId, position) => {
381
+ if (childrenToInsert.length === 0) return Result.err([diagnostic("op-empty-insert", "insert operation produced no element nodes")]);
382
+ if (referenceId === null || referenceId === document.root.id) {
383
+ const children = position === "before" ? [...childrenToInsert, ...document.root.children] : [...document.root.children, ...childrenToInsert];
384
+ return Result.ok({
385
+ ...document,
386
+ root: {
387
+ ...document.root,
388
+ children
389
+ }
390
+ });
391
+ }
392
+ if (findElement(document.root, referenceId) === null) return Result.err([diagnostic("op-target-not-found", `node "${referenceId}" was not found in the current source`)]);
393
+ if (position === "inside") return transformElement(document, referenceId, (element) => ({
394
+ ...element,
395
+ children: [...element.children, ...childrenToInsert]
396
+ }));
397
+ const insertInParent = (node) => {
398
+ if (node.kind !== "element") return node;
399
+ const index = node.children.findIndex((child) => child.kind === "element" && child.id === referenceId);
400
+ if (index >= 0) {
401
+ const at = position === "after" ? index + 1 : index;
402
+ return {
403
+ ...node,
404
+ children: [
405
+ ...node.children.slice(0, at),
406
+ ...childrenToInsert,
407
+ ...node.children.slice(at)
408
+ ]
409
+ };
410
+ }
411
+ return {
412
+ ...node,
413
+ children: node.children.map(insertInParent)
414
+ };
415
+ };
416
+ return Result.ok({
417
+ ...document,
418
+ root: insertInParent(document.root)
419
+ });
420
+ };
421
+ const insertBlock = (document, op) => insertChildren(document, [makeBlock(op.tag)], op.referenceId, op.position);
422
+ const insertSnippet = (document, op) => {
423
+ const rootTag = document.root.tag;
424
+ const parsed = parseSml(`<${rootTag}>${op.snippet}</${rootTag}>`);
425
+ if (Result.isError(parsed)) return Result.err(parsed.error.diagnostics);
426
+ const children = parsed.value.root.children.filter((child) => child.kind === "element");
427
+ return insertChildren(document, children, op.referenceId, op.position);
428
+ };
429
+ const removeNodeById = (document, nodeId) => {
430
+ if (document.root.id === nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be removed")]);
431
+ if (findElement(document.root, nodeId) === null) return Result.err([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
432
+ const prune = (node) => {
433
+ if (node.kind !== "element") return node;
434
+ return {
435
+ ...node,
436
+ children: node.children.filter((child) => !(child.kind === "element" && child.id === nodeId)).map(prune)
437
+ };
438
+ };
439
+ return Result.ok({
440
+ ...document,
441
+ root: prune(document.root)
442
+ });
443
+ };
444
+ const isDescendant = (ancestor, nodeId) => ancestor.children.some((child) => {
445
+ if (child.kind !== "element") return false;
446
+ return child.id === nodeId || isDescendant(child, nodeId);
447
+ });
448
+ const removeElement = (node, nodeId) => {
449
+ let removed = null;
450
+ const children = [];
451
+ for (const child of node.children) {
452
+ if (child.kind === "element" && child.id === nodeId) {
453
+ removed = child;
454
+ continue;
455
+ }
456
+ if (child.kind === "element") {
457
+ const result = removeElement(child, nodeId);
458
+ if (result.removed !== null) removed = result.removed;
459
+ children.push(result.node);
460
+ } else children.push(child);
461
+ }
462
+ return {
463
+ node: {
464
+ ...node,
465
+ children
466
+ },
467
+ removed
468
+ };
469
+ };
470
+ const insertIntoParent = (node, targetParentId, insertionIndex, moved) => {
471
+ if (node.id === targetParentId) {
472
+ const at = elementChildIndexToNodeIndex(node.children, insertionIndex);
473
+ return {
474
+ ...node,
475
+ children: [
476
+ ...node.children.slice(0, at),
477
+ moved,
478
+ ...node.children.slice(at)
479
+ ]
480
+ };
481
+ }
482
+ return {
483
+ ...node,
484
+ children: node.children.map((child) => child.kind === "element" ? insertIntoParent(child, targetParentId, insertionIndex, moved) : child)
485
+ };
486
+ };
487
+ const moveNode = (document, op) => {
488
+ if (document.root.id === op.nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be moved")]);
489
+ const moved = findElement(document.root, op.nodeId);
490
+ if (moved === null) return Result.err([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
491
+ const parent = findElement(document.root, op.targetParentId);
492
+ if (parent === null) return Result.err([diagnostic("op-target-not-found", `target parent "${op.targetParentId}" was not found in the current source`)]);
493
+ if (moved.id === parent.id || isDescendant(moved, parent.id)) return Result.err([diagnostic("op-invalid-reparent", "a node cannot be moved into itself or its descendants")]);
494
+ const channel = documentChannel(document);
495
+ const childModel = CHANNEL_TAG_SPECS[channel][parent.tag]?.content;
496
+ if (!(childModel === void 0 ? [] : allowedChildren(childModel)).includes(moved.tag)) return Result.err([diagnostic("op-invalid-reparent", `<${moved.tag}> is not allowed directly inside <${parent.tag}>`, parent.span)]);
497
+ const { node: rootWithoutMoved, removed } = removeElement(document.root, op.nodeId);
498
+ if (removed === null) return Result.err([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
499
+ return Result.ok({
500
+ ...document,
501
+ root: insertIntoParent(rootWithoutMoved, op.targetParentId, op.insertionIndex, removed)
502
+ });
503
+ };
504
+ const duplicateNode = (document, nodeId) => {
505
+ if (document.root.id === nodeId) return Result.err([diagnostic("op-root-target", "the document root cannot be duplicated")]);
506
+ if (findElement(document.root, nodeId) === null) return Result.err([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
507
+ const insertClone = (node) => {
508
+ if (node.kind !== "element") return node;
509
+ const index = node.children.findIndex((child) => child.kind === "element" && child.id === nodeId);
510
+ if (index >= 0) {
511
+ const clone = cloneSubtree(node.children[index]);
512
+ return {
513
+ ...node,
514
+ children: [
515
+ ...node.children.slice(0, index + 1),
516
+ clone,
517
+ ...node.children.slice(index + 1)
518
+ ]
519
+ };
520
+ }
521
+ return {
522
+ ...node,
523
+ children: node.children.map(insertClone)
524
+ };
525
+ };
526
+ return Result.ok({
527
+ ...document,
528
+ root: insertClone(document.root)
529
+ });
530
+ };
531
+ const mutate = (document, op) => {
532
+ switch (op.type) {
533
+ case "setProp": return setProp(document, op);
534
+ case "setChildren": return setChildren(document, op);
535
+ case "insertBlock": return insertBlock(document, op);
536
+ case "insertSnippet": return insertSnippet(document, op);
537
+ case "moveNode": return moveNode(document, op);
538
+ case "duplicateNode": return duplicateNode(document, op.nodeId);
539
+ case "removeNode": return removeNodeById(document, op.nodeId);
540
+ }
541
+ };
542
+ const preflight = (source) => {
543
+ const reparsed = parseSml(source);
544
+ if (Result.isError(reparsed)) return Result.err(reparsed.error.diagnostics);
545
+ const compiled = compileSml(reparsed.value);
546
+ if (Result.isError(compiled)) return Result.err(compiled.error.diagnostics);
547
+ const compatibilityErrors = checkSmlCompatibility({ source }).filter(isBreakingCompatibilityCheck).map((message) => diagnostic("compatibility-error", message));
548
+ if (compatibilityErrors.length > 0) return Result.err(compatibilityErrors);
549
+ return Result.ok(reparsed.value);
550
+ };
551
+ const failedResult = (index, op, diagnostics) => ({
552
+ index,
553
+ status: "failed",
554
+ op,
555
+ diagnostics: [...diagnostics]
556
+ });
557
+ /**
558
+ * Apply a batch of ops to SML source, returning the rewritten source, its new
559
+ * revision, and a per-op result.
560
+ *
561
+ * Id contract: every id in the batch resolves against the id-space of the
562
+ * source the caller read (the `expectedRevision` snapshot). Ids are positional
563
+ * (`n0..nN` in document order) and are NOT rewritten between ops, so:
564
+ *
565
+ * - An op cannot target a node created earlier in the same batch — freshly
566
+ * inserted/duplicated nodes carry a blank id until the next reprint reassigns
567
+ * positional ids, and are unaddressable within the batch (fails with
568
+ * `op-target-not-found`).
569
+ * - After `duplicateNode`, the duplicated id still addresses the ORIGINAL (the
570
+ * first match in document order); the clone is a blank-id copy.
571
+ * - Structural ops (insert/remove/move) do not shift the ids of the nodes that
572
+ * survive, so a later op targeting a surviving sibling lands on the intended
573
+ * node regardless of what earlier ops did.
574
+ *
575
+ * Each op is still preflighted (reprint, reparse, compile, compatibility check)
576
+ * as an independent validity gate; a failing op is recorded as `failed` and
577
+ * does not advance the working document.
578
+ */
579
+ const applyOps = (source, ops, options) => {
580
+ const parsed = parseSml(source);
581
+ if (Result.isError(parsed)) return Result.err(new SmlOpError({ diagnostics: parsed.error.diagnostics }));
582
+ const currentRevision = String(options?.currentRevision ?? sourceRevision(source));
583
+ if (options?.expectedRevision !== void 0 && String(options.expectedRevision) !== currentRevision) {
584
+ const stale = diagnostic("stale-revision", `source revision changed from ${String(options.expectedRevision)} to ${currentRevision}`);
585
+ return Result.ok({
586
+ source,
587
+ revision: currentRevision,
588
+ diagnostics: [stale],
589
+ results: ops.map((op, index) => failedResult(index, op, [stale]))
590
+ });
591
+ }
592
+ let currentSource = source;
593
+ let currentDocument = parsed.value;
594
+ const results = [];
595
+ const diagnostics = [];
596
+ ops.forEach((op, index) => {
597
+ const mutated = mutate(currentDocument, op);
598
+ if (Result.isError(mutated)) {
599
+ results.push(failedResult(index, op, mutated.error));
600
+ diagnostics.push(...mutated.error);
601
+ return;
602
+ }
603
+ const candidateSource = printSml(mutated.value);
604
+ const checked = preflight(candidateSource);
605
+ if (Result.isError(checked)) {
606
+ results.push(failedResult(index, op, checked.error));
607
+ diagnostics.push(...checked.error);
608
+ return;
609
+ }
610
+ currentSource = candidateSource;
611
+ currentDocument = mutated.value;
612
+ results.push({
613
+ index,
614
+ status: "applied",
615
+ op,
616
+ diagnostics: []
617
+ });
618
+ });
619
+ return Result.ok({
620
+ source: currentSource,
621
+ revision: sourceRevision(currentSource),
622
+ diagnostics,
623
+ results
624
+ });
625
+ };
626
+ const applyOp = (source, op, options) => applyOps(source, [op], options);
627
+ //#endregion
628
+ export { sourceRevision as a, normalizeSml as i, applyOp as n, applyOps as r, SmlOpError as t };
629
+
630
+ //# sourceMappingURL=ops-2aiiYO7m.js.map