@samva/markup 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +25 -5
  2. package/README.md +92 -26
  3. package/dist/{ast-DAhnAvQN.d.ts → ast-63I-VLQx.d.ts} +15 -2
  4. package/dist/ast.d.ts +2 -2
  5. package/dist/ast.js +15 -2
  6. package/dist/{checks-CW0nFj5n.js → checks-DXuERO4B.js} +37 -3
  7. package/dist/checks.d.ts +8 -1
  8. package/dist/checks.js +2 -2
  9. package/dist/compile-sms.d.ts +2 -2
  10. package/dist/compile-whatsapp.d.ts +2 -2
  11. package/dist/compile.d.ts +9 -84
  12. package/dist/compile.js +11 -920
  13. package/dist/compiler.d.ts +301 -0
  14. package/dist/compiler.js +1389 -0
  15. package/dist/components.d.ts +4 -4
  16. package/dist/conditionals.js +1 -1
  17. package/dist/corpus-Dz5irJ9x.d.ts +161 -0
  18. package/dist/diagnostics-Djd9ckLg.d.ts +90 -0
  19. package/dist/diagnostics.js +6 -3
  20. package/dist/elements-DtwKlFVH.d.ts +820 -0
  21. package/dist/email/components.d.ts +119 -0
  22. package/dist/email/components.js +207 -0
  23. package/dist/email/diagnostics.d.ts +2 -0
  24. package/dist/email/diagnostics.js +56 -0
  25. package/dist/email/elements.d.ts +2 -0
  26. package/dist/email/elements.js +299 -0
  27. package/dist/email/html.d.ts +42 -0
  28. package/dist/email/html.js +266 -0
  29. package/dist/email/jsx-dev-runtime.d.ts +26 -0
  30. package/dist/email/jsx-dev-runtime.js +12 -0
  31. package/dist/email/jsx-runtime.d.ts +2 -0
  32. package/dist/email/jsx-runtime.js +60 -0
  33. package/dist/email/render.d.ts +36 -0
  34. package/dist/email/render.js +6 -0
  35. package/dist/email/selection.d.ts +102 -0
  36. package/dist/email/selection.js +143 -0
  37. package/dist/email/source-edit.d.ts +67 -0
  38. package/dist/email/source-edit.js +185 -0
  39. package/dist/email/stylesheets.d.ts +2 -0
  40. package/dist/email/stylesheets.js +43 -0
  41. package/dist/email/text.d.ts +7 -0
  42. package/dist/email/text.js +92 -0
  43. package/dist/emit.d.ts +13 -12
  44. package/dist/emit.js +63 -63
  45. package/dist/input-schema-BIfzI63B.d.ts +23 -0
  46. package/dist/input-schema.d.ts +2 -0
  47. package/dist/input-schema.js +170 -0
  48. package/dist/jsx-dev-runtime.d.ts +4 -3
  49. package/dist/jsx-dev-runtime.js +9 -1
  50. package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-C5k_Zq0A.d.ts} +4 -4
  51. package/dist/jsx-runtime.d.ts +1 -1
  52. package/dist/jsx-runtime.js +20 -1
  53. package/dist/model.d.ts +1 -1
  54. package/dist/ops.d.ts +1 -1
  55. package/dist/ops.js +2 -2
  56. package/dist/{parser-yzjUOGhc.d.ts → parser-CGBS73Gz.d.ts} +12 -3
  57. package/dist/parser.d.ts +2 -2
  58. package/dist/parser.js +48 -9
  59. package/dist/predicate-CvkzT-a2.js +539 -0
  60. package/dist/{predicate-DTtVtLK7.d.ts → predicate-G2SeSvoN.d.ts} +9 -2
  61. package/dist/predicate.d.ts +2 -2
  62. package/dist/predicate.js +2 -417
  63. package/dist/presets/corpus.d.ts +2 -0
  64. package/dist/presets/corpus.js +186 -0
  65. package/dist/presets/public-gallery.d.ts +241 -459
  66. package/dist/presets/public-gallery.js +305 -5
  67. package/dist/presets/public-handoff.d.ts +75 -3
  68. package/dist/presets/public-handoff.js +220 -37
  69. package/dist/presets/public-preview-artifacts.js +20 -26
  70. package/dist/presets/public-preview.d.ts +40 -1152
  71. package/dist/presets/public-preview.js +388 -1152
  72. package/dist/presets/sources.d.ts +17 -4
  73. package/dist/presets/sources.js +1212 -43
  74. package/dist/presets.d.ts +2 -2
  75. package/dist/printer.d.ts +1 -1
  76. package/dist/render-Cddsy_XF.js +585 -0
  77. package/dist/render-template.d.ts +6 -3
  78. package/dist/render-template.js +4 -2
  79. package/dist/source-locations-BCJQnIfU.d.ts +89 -0
  80. package/dist/source-locations.d.ts +2 -0
  81. package/dist/source-locations.js +125 -0
  82. package/dist/stylesheets-oDRAnYA3.d.ts +94 -0
  83. package/dist/template-Dy5yOPyp.d.ts +60 -0
  84. package/dist/template.d.ts +2 -0
  85. package/dist/template.js +37 -0
  86. package/dist/tsx-source-instrumentation.d.ts +23 -0
  87. package/dist/tsx-source-instrumentation.js +97 -0
  88. package/dist/tsx-tracer.d.ts +116 -0
  89. package/dist/tsx-tracer.js +985 -0
  90. package/dist/{variables-3NGLpNTB.d.ts → variables-if1VrV34.d.ts} +2 -2
  91. package/dist/variables.d.ts +1 -1
  92. package/dist/variables.js +1 -1
  93. package/generated/sml-agent-contract.json +574 -782
  94. package/generated/sml-email-agent-contract.json +502 -677
  95. package/generated/sml-email-language-reference.md +94 -160
  96. package/generated/sml-language-reference.md +195 -220
  97. package/package.json +111 -8
package/dist/presets.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { d as Channel } from "./ast-DAhnAvQN.js";
1
+ import { d as Channel } from "./ast-63I-VLQx.js";
2
2
  import { o as TokenOverrides } from "./theme-6ApjjiBo.js";
3
3
  //#region presets/catalog.d.ts
4
4
  type PresetCategory = "transactional" | "digest" | "marketing" | "lifecycle";
5
5
  interface PresetMeta {
6
- /** Stable id and source filename stem (`${id}.samva`). */
6
+ /** Stable id and source filename stem (`${id}.tsx`). */
7
7
  readonly id: string;
8
8
  /** The source's root-tag channel; the gallery filters by it. */
9
9
  readonly channel: Channel;
package/dist/printer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as SmlDocument } from "./ast-DAhnAvQN.js";
1
+ import { w as SmlDocument } from "./ast-63I-VLQx.js";
2
2
  //#region src/printer.d.ts
3
3
  declare const printSml: (document: SmlDocument) => string;
4
4
  //#endregion
@@ -0,0 +1,585 @@
1
+ import { EmailNode, drainEmailDiagnostics } from "./email/jsx-runtime.js";
2
+ import { declarationValue, kebab, serializeEmailHtml } from "./email/html.js";
3
+ import { PREHEADER_ATTRIBUTE, preheaderNode } from "./email/components.js";
4
+ import { EmailCompileError, hasBlockingDiagnostic } from "./email/diagnostics.js";
5
+ import { collectStylesheets, hasStylesheets } from "./email/stylesheets.js";
6
+ import { deriveEmailText } from "./email/text.js";
7
+ import { Predicate } from "effect";
8
+ //#region src/email/css-values.ts
9
+ /** Expand `var(--x, fallback)` against the collected custom properties. */
10
+ const resolveVariables = (value, variables, depth = 0) => {
11
+ if (depth > 8 || !value.includes("var(")) return value;
12
+ let result = "";
13
+ let rest = value;
14
+ while (rest.length > 0) {
15
+ const start = rest.indexOf("var(");
16
+ if (start < 0) {
17
+ result += rest;
18
+ break;
19
+ }
20
+ result += rest.slice(0, start);
21
+ let level = 0;
22
+ let end = -1;
23
+ for (let index = start + 3; index < rest.length; index++) if (rest[index] === "(") level += 1;
24
+ else if (rest[index] === ")") {
25
+ level -= 1;
26
+ if (level === 0) {
27
+ end = index;
28
+ break;
29
+ }
30
+ }
31
+ if (end < 0) {
32
+ result += rest.slice(start);
33
+ break;
34
+ }
35
+ const body = rest.slice(start + 4, end);
36
+ const comma = body.indexOf(",");
37
+ const name = (comma < 0 ? body : body.slice(0, comma)).trim();
38
+ const fallback = comma < 0 ? void 0 : body.slice(comma + 1).trim();
39
+ const replacement = variables[name] ?? fallback;
40
+ result += replacement === void 0 ? rest.slice(start, end + 1) : resolveVariables(replacement, variables, depth + 1);
41
+ rest = rest.slice(end + 1);
42
+ }
43
+ return result;
44
+ };
45
+ /**
46
+ * Fold a constant `calc()` to a plain value. Tailwind writes every spacing
47
+ * utility as `calc(var(--spacing) * n)`, and classic Outlook does not implement
48
+ * `calc()` at all, so an unfolded value is a silently missing style. An
49
+ * expression that cannot be reduced to one term is left exactly as authored.
50
+ */
51
+ const foldCalc = (value) => {
52
+ if (!value.includes("calc(")) return value;
53
+ let result = "";
54
+ let rest = value;
55
+ while (rest.length > 0) {
56
+ const start = rest.indexOf("calc(");
57
+ if (start < 0) {
58
+ result += rest;
59
+ break;
60
+ }
61
+ result += rest.slice(0, start);
62
+ let level = 0;
63
+ let end = -1;
64
+ for (let index = start + 4; index < rest.length; index++) if (rest[index] === "(") level += 1;
65
+ else if (rest[index] === ")") {
66
+ level -= 1;
67
+ if (level === 0) {
68
+ end = index;
69
+ break;
70
+ }
71
+ }
72
+ if (end < 0) {
73
+ result += rest.slice(start);
74
+ break;
75
+ }
76
+ const folded = evaluate(rest.slice(start + 5, end));
77
+ result += folded === void 0 ? rest.slice(start, end + 1) : format(folded);
78
+ rest = rest.slice(end + 1);
79
+ }
80
+ return result;
81
+ };
82
+ const format = (value) => `${Math.round(value.n * 1e6) / 1e6}${value.n === 0 && value.unit !== "%" ? "" : value.unit}`;
83
+ const evaluate = (expression) => {
84
+ const tokens = expression.match(/\(|\)|[+-]\s|[*/]|[^\s()*/]+/g);
85
+ if (tokens === null) return void 0;
86
+ let position = 0;
87
+ const peek = () => tokens[position]?.trim();
88
+ const sum = () => {
89
+ let left = product();
90
+ while (left !== void 0 && (peek() === "+" || peek() === "-")) {
91
+ const operator = peek();
92
+ position += 1;
93
+ const right = product();
94
+ if (right === void 0 || left.unit !== right.unit) return void 0;
95
+ left = {
96
+ n: operator === "+" ? left.n + right.n : left.n - right.n,
97
+ unit: left.unit
98
+ };
99
+ }
100
+ return left;
101
+ };
102
+ const product = () => {
103
+ let left = term();
104
+ while (left !== void 0 && (peek() === "*" || peek() === "/")) {
105
+ const operator = peek();
106
+ position += 1;
107
+ const right = term();
108
+ if (right === void 0) return void 0;
109
+ if (operator === "*") {
110
+ if (left.unit !== "" && right.unit !== "") return void 0;
111
+ left = {
112
+ n: left.n * right.n,
113
+ unit: left.unit === "" ? right.unit : left.unit
114
+ };
115
+ } else {
116
+ if (right.unit !== "" || right.n === 0) return void 0;
117
+ left = {
118
+ n: left.n / right.n,
119
+ unit: left.unit
120
+ };
121
+ }
122
+ }
123
+ return left;
124
+ };
125
+ const term = () => {
126
+ const token = tokens[position];
127
+ if (token === void 0) return void 0;
128
+ if (token === "(") {
129
+ position += 1;
130
+ const inner = sum();
131
+ if (peek() !== ")") return void 0;
132
+ position += 1;
133
+ return inner;
134
+ }
135
+ const match = /^(-?(?:\d+\.?\d*|\.\d+))([a-z%]*)$/i.exec(token.trim());
136
+ if (match === null) return void 0;
137
+ position += 1;
138
+ return {
139
+ n: Number.parseFloat(match[1]),
140
+ unit: (match[2] ?? "").toLowerCase()
141
+ };
142
+ };
143
+ const value = sum();
144
+ return position === tokens.length ? value : void 0;
145
+ };
146
+ /** Resolve custom properties, then fold what the arithmetic allows. */
147
+ const finalizeValue = (value, variables) => foldCalc(resolveVariables(value, variables));
148
+ const RANGE_FEATURE = /\(\s*([a-z-]+)\s*(<=|>=|<|>)\s*([^)]+?)\s*\)/gi;
149
+ /** Media Queries Level 4 range syntax down to the `min-`/`max-` prefixes clients support. */
150
+ const downlevelMediaQuery = (condition) => condition.replace(RANGE_FEATURE, (whole, feature, comparison, bound) => {
151
+ return `(${comparison.startsWith(">") ? "min-" : "max-"}${feature}:${bound})`;
152
+ });
153
+ /** Render the rules that could not be inlined as one `<style>` body. */
154
+ const renderHeadCss = (rules, headAtRules, variables) => {
155
+ const blocks = [...headAtRules];
156
+ const grouped = /* @__PURE__ */ new Map();
157
+ for (const rule of rules) {
158
+ const conditions = rule.conditions.map(downlevelMediaQuery);
159
+ const key = conditions.join("\0");
160
+ const bucket = grouped.get(key);
161
+ if (bucket === void 0) grouped.set(key, {
162
+ conditions,
163
+ rules: [rule]
164
+ });
165
+ else bucket.rules.push(rule);
166
+ }
167
+ for (const { conditions, rules: bucket } of grouped.values()) {
168
+ const body = bucket.map((rule) => {
169
+ const declarations = rule.declarations.filter((declaration) => !declaration.property.startsWith("--")).map((declaration) => `${declaration.property}:${finalizeValue(declaration.value, variables)}!important`).join(";");
170
+ return declarations === "" ? "" : `${rule.selector}{${declarations}}`;
171
+ }).filter((block) => block !== "").join("");
172
+ if (body === "") continue;
173
+ blocks.push(conditions.reduceRight((inner, condition) => `${condition}{${inner}}`, body));
174
+ }
175
+ return blocks.join("");
176
+ };
177
+ //#endregion
178
+ //#region src/email/selectors.ts
179
+ const classListOf = (element) => {
180
+ const value = element.props.class ?? element.props.className;
181
+ return typeof value === "string" ? value.split(/\s+/).filter((name) => name !== "") : [];
182
+ };
183
+ const attributeOf = (element, name) => {
184
+ const value = element.props[name];
185
+ if (value === void 0 || value === null || value === false) return void 0;
186
+ if (value === true) return "";
187
+ return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
188
+ };
189
+ const matchesAttribute = (element, test) => {
190
+ const actual = test.name === "class" ? classListOf(element).join(" ") : attributeOf(element, test.name);
191
+ if (actual === void 0) return false;
192
+ if (test.operator === void 0 || test.value === void 0) return true;
193
+ switch (test.operator) {
194
+ case "=": return actual === test.value;
195
+ case "~=": return actual.split(/\s+/).includes(test.value);
196
+ case "^=": return actual.startsWith(test.value);
197
+ case "$=": return actual.endsWith(test.value);
198
+ case "*=": return actual.includes(test.value);
199
+ case "|=": return actual === test.value || actual.startsWith(`${test.value}-`);
200
+ default: return false;
201
+ }
202
+ };
203
+ const matchesCompound = (element, compound) => {
204
+ if (compound.opaque) return false;
205
+ if (compound.tag !== void 0 && compound.tag !== element.tag) return false;
206
+ if (compound.id !== void 0 && attributeOf(element, "id") !== compound.id) return false;
207
+ if (compound.classes.length > 0) {
208
+ const classes = classListOf(element);
209
+ if (!compound.classes.every((name) => classes.includes(name))) return false;
210
+ }
211
+ return compound.attributes.every((test) => matchesAttribute(element, test));
212
+ };
213
+ const matchesFrom = (selector, index, ancestry) => {
214
+ if (ancestry === void 0) return false;
215
+ const part = selector.parts[index];
216
+ if (part === void 0) return false;
217
+ if (!matchesCompound(ancestry.element, part.compound)) return false;
218
+ const left = selector.parts[index - 1];
219
+ if (left === void 0) return true;
220
+ switch (left.after ?? "descendant") {
221
+ case "child": return matchesFrom(selector, index - 1, ancestry.parent);
222
+ case "descendant":
223
+ for (let parent = ancestry.parent; parent !== void 0; parent = parent.parent) if (matchesFrom(selector, index - 1, parent)) return true;
224
+ return false;
225
+ case "adjacent": {
226
+ const sibling = ancestry.previous.at(-1);
227
+ return sibling === void 0 ? false : matchesFrom(selector, index - 1, {
228
+ element: sibling,
229
+ parent: ancestry.parent,
230
+ previous: ancestry.previous.slice(0, -1)
231
+ });
232
+ }
233
+ case "sibling":
234
+ for (let position = ancestry.previous.length - 1; position >= 0; position--) if (matchesFrom(selector, index - 1, {
235
+ element: ancestry.previous[position],
236
+ parent: ancestry.parent,
237
+ previous: ancestry.previous.slice(0, position)
238
+ })) return true;
239
+ return false;
240
+ }
241
+ };
242
+ /** Match a compiled selector against one element in its tree position. */
243
+ const matchesSelector = (selector, ancestry) => selector === void 0 || selector.parts.length === 0 ? false : matchesFrom(selector, selector.parts.length - 1, ancestry);
244
+ //#endregion
245
+ //#region src/email/cascade.ts
246
+ /**
247
+ * Cascade rank as the tuple CSS actually sorts by: importance first, then
248
+ * whether the declaration is attached to the element itself, then layer, then
249
+ * specificity, then order of appearance down to the declaration. Normal
250
+ * declarations sort by layer ascending (unlayered last, so it wins); important
251
+ * declarations reverse the layer order, which is why the layer term is negated
252
+ * for them. Element-attached declarations are compared before layers and
253
+ * specificity, so an author's important inline value beats an important
254
+ * stylesheet declaration exactly as it does in a browser. Two declarations never
255
+ * tie: rule order is unique across every registered sheet, and the position
256
+ * inside a rule separates the declarations that share it.
257
+ */
258
+ const rank = (important, attached, layer, specificity, order, position) => [
259
+ important ? 1 : 0,
260
+ attached ? 1 : 0,
261
+ important ? -layer : layer,
262
+ specificity[0],
263
+ specificity[1],
264
+ specificity[2],
265
+ order,
266
+ position
267
+ ];
268
+ const compareRank = (left, right) => {
269
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
270
+ const a = left[index] ?? 0;
271
+ const b = right[index] ?? 0;
272
+ if (a !== b) return a - b;
273
+ }
274
+ return 0;
275
+ };
276
+ const outranks = (left, right) => compareRank(left, right) >= 0;
277
+ /** An authored `style` object becomes declarations with the same normalization the serializer uses. */
278
+ const inlineDeclarations = (style) => {
279
+ if (!Predicate.isObject(style) || Array.isArray(style)) return [];
280
+ const output = [];
281
+ for (const [property, value] of Object.entries(style)) {
282
+ if (value === void 0 || value === null) continue;
283
+ if (typeof value !== "string" && typeof value !== "number") continue;
284
+ const text = declarationValue(property, value);
285
+ const important = /!important\s*$/i.test(text);
286
+ output.push({
287
+ property: kebab(property),
288
+ value: important ? text.replace(/\s*!important\s*$/i, "") : text,
289
+ important
290
+ });
291
+ }
292
+ return output;
293
+ };
294
+ const horizontalWriting = {
295
+ direction: "ltr",
296
+ writingMode: "horizontal-tb"
297
+ };
298
+ const paddingValues = (value) => {
299
+ const values = [];
300
+ let depth = 0;
301
+ let start = 0;
302
+ for (let index = 0; index < value.length; index++) {
303
+ const character = value[index];
304
+ if (character === "(") depth++;
305
+ else if (character === ")") depth--;
306
+ else if (/\s/.test(character) && depth === 0) {
307
+ if (index > start) values.push(value.slice(start, index));
308
+ start = index + 1;
309
+ }
310
+ }
311
+ if (start < value.length) values.push(value.slice(start));
312
+ return values;
313
+ };
314
+ const physicalPadding = (property, value, context) => {
315
+ const original = [[property, value]];
316
+ const match = /^padding-(inline|block)(?:-(start|end))?$/.exec(property);
317
+ if (match === null || context.writingMode !== "horizontal-tb" || !["ltr", "rtl"].includes(context.direction) || /^(inherit|revert|revert-layer)$/.test(value) || value.includes("var(")) return original;
318
+ const values = paddingValues(value.trim());
319
+ if (values.length < 1 || values.length > (match[2] === void 0 ? 2 : 1)) return original;
320
+ const sides = match[1] === "block" ? ["top", "bottom"] : context.direction === "rtl" ? ["right", "left"] : ["left", "right"];
321
+ if (match[2] !== void 0) return [[`padding-${sides[match[2] === "start" ? 0 : 1]}`, values[0]]];
322
+ return [[`padding-${sides[0]}`, values[0]], [`padding-${sides[1]}`, values[1] ?? values[0]]];
323
+ };
324
+ /**
325
+ * Resolve every stylesheet against the tree: inline what a client will honor on
326
+ * the element, and keep the rest for `<head>`.
327
+ */
328
+ const applyStylesheet = (root, sheet, options = {}) => {
329
+ const diagnostics = [...sheet.diagnostics];
330
+ const inlinable = [];
331
+ const retained = [];
332
+ const styledClasses = /* @__PURE__ */ new Set();
333
+ for (const rule of sheet.rules) {
334
+ (rule.inlinable ? inlinable : retained).push(rule);
335
+ for (const name of rule.classes) styledClasses.add(name);
336
+ }
337
+ const conditionalWritingRules = retained.filter((rule) => rule.declarations.some((declaration) => declaration.property === "direction" || declaration.property === "writing-mode" || declaration.property === "all"));
338
+ const unmatched = /* @__PURE__ */ new Map();
339
+ /**
340
+ * Children are visited with their own preceding-sibling list so `+`/`~` work.
341
+ * A fragment is not an element in the serialized document, so its children
342
+ * join that same list rather than starting a new one.
343
+ */
344
+ const visitChildren = (children, ancestry, variables, writing) => {
345
+ const previous = [];
346
+ const visit = (nodes) => nodes.map((child) => {
347
+ if (child._tag === "Element") {
348
+ const result = applyTo(child, {
349
+ element: child,
350
+ parent: ancestry,
351
+ previous: [...previous]
352
+ }, variables, writing);
353
+ previous.push(child);
354
+ return result;
355
+ }
356
+ if (child._tag === "Fragment") return EmailNode.Fragment({ children: visit(child.children) });
357
+ return child;
358
+ });
359
+ return visit(children);
360
+ };
361
+ /**
362
+ * `inherited` is the custom-property scope this element sees from its
363
+ * ancestors, rooted in the sheet's unconditional properties. Custom properties
364
+ * cascade like any other declaration and then inherit, so a `--brand` declared
365
+ * on `.card` has to reach `var(--brand)` on `.card` and everything under it.
366
+ */
367
+ const applyTo = (element, ancestry, inherited, inheritedWriting) => {
368
+ const conditionalWriting = conditionalWritingRules.some((rule) => {
369
+ if (rule.matcher === void 0 || rule.matcher.parts.length === 0 || rule.matcher.parts.some((part) => part.compound.opaque)) return true;
370
+ for (let context = ancestry; context !== void 0; context = context.parent) if (matchesSelector(rule.matcher, context)) return true;
371
+ return false;
372
+ });
373
+ const winners = /* @__PURE__ */ new Map();
374
+ const claim = (declaration, key) => {
375
+ const current = winners.get(declaration.property);
376
+ if (current === void 0 || outranks(key, current.key)) winners.set(declaration.property, {
377
+ value: declaration.value,
378
+ key
379
+ });
380
+ };
381
+ for (const rule of inlinable) {
382
+ if (!matchesSelector(rule.matcher, ancestry)) continue;
383
+ for (const [position, declaration] of rule.declarations.entries()) claim(declaration, rank(declaration.important, false, rule.layer, rule.specificity, rule.order, position));
384
+ }
385
+ for (const [position, declaration] of inlineDeclarations(element.props.style).entries()) claim(declaration, rank(declaration.important, true, 0, [
386
+ 0,
387
+ 0,
388
+ 0
389
+ ], 0, position));
390
+ for (const name of classListOf(element)) if (!styledClasses.has(name) && options.knownClasses?.has(name) !== true) unmatched.set(name, [...unmatched.get(name) ?? [], ...element.origins]);
391
+ const declared = {};
392
+ for (const [property, entry] of winners) if (property.startsWith("--")) declared[property] = entry.value;
393
+ const variables = Object.keys(declared).length === 0 ? inherited : {
394
+ ...inherited,
395
+ ...declared
396
+ };
397
+ const inheritedValue = (property, parent, initial, fallback = parent) => {
398
+ const value = winners.get(property)?.value;
399
+ if (value === void 0) return fallback;
400
+ const resolved = finalizeValue(value, variables);
401
+ return resolved === "inherit" || resolved === "unset" ? parent : resolved === "initial" ? initial : resolved;
402
+ };
403
+ const writing = {
404
+ direction: inheritedValue("direction", inheritedWriting.direction, "ltr", typeof element.props.dir === "string" ? element.props.dir : element.tag === "bdi" ? "auto" : inheritedWriting.direction),
405
+ writingMode: winners.has("all") ? "unknown" : inheritedValue("writing-mode", inheritedWriting.writingMode, "horizontal-tb")
406
+ };
407
+ const emitted = [...winners].filter(([property]) => !property.startsWith("--")).sort(([, left], [, right]) => compareRank(left.key, right.key)).map(([property, entry]) => [property, finalizeValue(entry.value, variables)]);
408
+ const physical = /* @__PURE__ */ new Map();
409
+ for (const [property, value] of emitted) for (const [name, lowered] of conditionalWriting ? [[property, value]] : physicalPadding(property, value, writing)) {
410
+ physical.delete(name);
411
+ physical.set(name, lowered);
412
+ }
413
+ return EmailNode.Element({
414
+ ...element,
415
+ props: emitted.length === 0 ? element.props : {
416
+ ...element.props,
417
+ style: Object.fromEntries(physical)
418
+ },
419
+ children: visitChildren(element.children, ancestry, variables, writing)
420
+ });
421
+ };
422
+ const tree = root._tag === "Element" ? applyTo(root, {
423
+ element: root,
424
+ parent: void 0,
425
+ previous: []
426
+ }, sheet.variables, horizontalWriting) : root._tag === "Fragment" ? EmailNode.Fragment({ children: visitChildren(root.children, void 0, sheet.variables, horizontalWriting) }) : root;
427
+ for (const [name, origins] of unmatched) diagnostics.push({
428
+ code: "unmatched-class",
429
+ severity: "warning",
430
+ message: `Class ${JSON.stringify(name)} produced no styles. If it is composed at run time, the compiler cannot discover it; list it in the template's declared classes or write the style inline.`,
431
+ origins: origins.slice(0, 1)
432
+ });
433
+ return {
434
+ tree,
435
+ headCss: renderHeadCss(retained, sheet.headAtRules, sheet.variables),
436
+ diagnostics
437
+ };
438
+ };
439
+ //#endregion
440
+ //#region src/email/render.ts
441
+ /** Classic Outlook lays a document out correctly only under a transitional doctype. */
442
+ const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
443
+ const settle = (diagnostics) => {
444
+ if (hasBlockingDiagnostic(diagnostics)) throw new EmailCompileError({ diagnostics });
445
+ return diagnostics;
446
+ };
447
+ /** Lower the derived tree to HTML and its plain-text alternative. */
448
+ const compileEmail = (node, options = {}) => {
449
+ const serialized = serializeEmailHtml(node, options);
450
+ const diagnostics = settle([...drainEmailDiagnostics(), ...serialized.diagnostics]);
451
+ return {
452
+ html: serialized.html,
453
+ text: deriveEmailText(node),
454
+ diagnostics,
455
+ positions: serialized.positions
456
+ };
457
+ };
458
+ /** The preview text already present in the tree, from `<Email preheader>`. */
459
+ const declaredPreheader = (node) => {
460
+ switch (node._tag) {
461
+ case "Text":
462
+ case "Raw": return;
463
+ case "Fragment": return node.children.reduce((found, child) => found ?? declaredPreheader(child), void 0);
464
+ case "Element": {
465
+ const declared = node.props[PREHEADER_ATTRIBUTE];
466
+ if (typeof declared === "string") return declared;
467
+ return node.children.reduce((found, child) => found ?? declaredPreheader(child), void 0);
468
+ }
469
+ }
470
+ };
471
+ /** Put the hidden preview block first inside `<body>`, or first overall when there is no shell. */
472
+ const withPreheader = (node, text) => {
473
+ const insert = (current) => {
474
+ if (current._tag === "Fragment") {
475
+ for (const [index, child] of current.children.entries()) {
476
+ const replaced = insert(child);
477
+ if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
478
+ }
479
+ return;
480
+ }
481
+ if (current._tag !== "Element") return void 0;
482
+ if (current.tag === "body") return EmailNode.Element({
483
+ ...current,
484
+ children: [preheaderNode(text), ...current.children]
485
+ });
486
+ for (const [index, child] of current.children.entries()) {
487
+ const replaced = insert(child);
488
+ if (replaced !== void 0) return EmailNode.Element({
489
+ ...current,
490
+ children: current.children.with(index, replaced)
491
+ });
492
+ }
493
+ };
494
+ return insert(node) ?? EmailNode.Fragment({ children: [preheaderNode(text), node] });
495
+ };
496
+ /** Put the retained rules in a `<style>` at the end of `<head>`. */
497
+ const withHeadCss = (node, css) => {
498
+ const insert = (current) => {
499
+ if (current._tag === "Fragment") {
500
+ for (const [index, child] of current.children.entries()) {
501
+ const replaced = insert(child);
502
+ if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
503
+ }
504
+ return;
505
+ }
506
+ if (current._tag !== "Element") return void 0;
507
+ if (current.tag === "head") return EmailNode.Element({
508
+ ...current,
509
+ children: [...current.children, EmailNode.Element({
510
+ tag: "style",
511
+ props: {},
512
+ children: [EmailNode.Text({ value: css })],
513
+ origins: current.origins,
514
+ authored: false
515
+ })]
516
+ });
517
+ for (const [index, child] of current.children.entries()) {
518
+ const replaced = insert(child);
519
+ if (replaced !== void 0) return EmailNode.Element({
520
+ ...current,
521
+ children: current.children.with(index, replaced)
522
+ });
523
+ }
524
+ };
525
+ return insert(node);
526
+ };
527
+ /** Render one validated input through the template's only content function. Host isolation is required for untrusted source. */
528
+ const renderEmail = (template, data, options = {}) => {
529
+ if (!template.schema.validate(data)) throw new TypeError(`Invalid template input: ${JSON.stringify(template.schema.validate.errors)}`);
530
+ drainEmailDiagnostics();
531
+ const content = template.render(data);
532
+ const invalid = [];
533
+ const fail = (code, message) => invalid.push({
534
+ code,
535
+ severity: "error",
536
+ message,
537
+ origins: []
538
+ });
539
+ if (typeof content.subject !== "string" || /[\r\n\u0000]/.test(content.subject)) fail("invalid-subject", "Invalid email subject: it must be one line of text.");
540
+ if (content.preheader !== void 0 && typeof content.preheader !== "string") fail("invalid-preheader", "Invalid email preheader: it must be a string.");
541
+ if (content.text !== void 0 && typeof content.text !== "string") fail("invalid-text", "Invalid plain text: it must be a string.");
542
+ const declared = declaredPreheader(content.body);
543
+ if (content.preheader !== void 0 && declared !== void 0) fail("duplicate-preheader", "Preheader is set on both the template content and <Email preheader>; keep one.");
544
+ settle(invalid);
545
+ const base = content.preheader === void 0 ? content.body : withPreheader(content.body, content.preheader);
546
+ const styled = hasStylesheets() ? applyStylesheet(base, collectStylesheets()) : {
547
+ tree: base,
548
+ headCss: "",
549
+ diagnostics: []
550
+ };
551
+ const style = [...styled.diagnostics];
552
+ let tree = styled.tree;
553
+ if (styled.headCss !== "") {
554
+ const placed = withHeadCss(tree, styled.headCss);
555
+ if (placed === void 0) style.push({
556
+ code: "missing-head",
557
+ severity: "error",
558
+ message: "Styles that cannot be inlined — media queries, dark-scheme rules and pseudo-selectors — need a <head> to live in. Wrap the message in <Email>, or write those declarations as inline styles.",
559
+ origins: []
560
+ });
561
+ else tree = placed;
562
+ }
563
+ settle(style);
564
+ const compiled = compileEmail(tree, options);
565
+ const prefix = compiled.html.startsWith("<html") ? EMAIL_DOCTYPE : "";
566
+ const html = `${prefix}${compiled.html}`;
567
+ const positions = prefix === "" ? compiled.positions : compiled.positions.map((position) => ({
568
+ ...position,
569
+ start: position.start + prefix.length,
570
+ end: position.end + prefix.length
571
+ }));
572
+ return {
573
+ subject: content.subject,
574
+ preheader: content.preheader ?? declared,
575
+ html,
576
+ text: content.text ?? compiled.text,
577
+ tree,
578
+ diagnostics: [...style, ...compiled.diagnostics],
579
+ positions
580
+ };
581
+ };
582
+ //#endregion
583
+ export { classListOf as a, finalizeValue as c, resolveVariables as d, applyStylesheet as i, foldCalc as l, compileEmail as n, matchesSelector as o, renderEmail as r, downlevelMediaQuery as s, EMAIL_DOCTYPE as t, renderHeadCss as u };
584
+
585
+ //# sourceMappingURL=render-Cddsy_XF.js.map
@@ -1,7 +1,8 @@
1
1
  import { o as SmlParseError } from "./diagnostics-oveE-laV.js";
2
- import { C as SmlDocument } from "./ast-DAhnAvQN.js";
3
- import { a as VariableKind, l as Variables } from "./variables-3NGLpNTB.js";
4
- import { a as JsxResult } from "./jsx-runtime-fSxcvlx4.js";
2
+ import { w as SmlDocument } from "./ast-63I-VLQx.js";
3
+ import { c as UnboundTemplateSourceLocations } from "./source-locations-BCJQnIfU.js";
4
+ import { a as VariableKind, l as Variables } from "./variables-if1VrV34.js";
5
+ import { a as JsxResult } from "./jsx-runtime-C5k_Zq0A.js";
5
6
  import { Result } from "effect";
6
7
  //#region src/render-template.d.ts
7
8
  /**
@@ -27,6 +28,8 @@ interface RenderedTemplate {
27
28
  readonly document: SmlDocument;
28
29
  readonly source: string;
29
30
  readonly variables: ReadonlyArray<TemplateVariable>;
31
+ /** Ephemeral parsed-node-id → authored JSX source origins, when available. */
32
+ readonly sourceLocations: UnboundTemplateSourceLocations;
30
33
  }
31
34
  /**
32
35
  * Runs a template function and returns its canonical document, source, and
@@ -1,4 +1,5 @@
1
- import { collectPredicateVariables, parsePredicate } from "./predicate.js";
1
+ import { mapJsxSourceLocations } from "./source-locations.js";
2
+ import { i as parsePredicate, n as collectPredicateVariables } from "./predicate-CvkzT-a2.js";
2
3
  import { isRootTag } from "./ast.js";
3
4
  import { createVariablesProxy } from "./variables.js";
4
5
  import { parseSml } from "./parser.js";
@@ -27,7 +28,8 @@ const renderTemplate = (template) => {
27
28
  return Result.succeed({
28
29
  document,
29
30
  source,
30
- variables: collectVariableSchema(document, recorder)
31
+ variables: collectVariableSchema(document, recorder),
32
+ sourceLocations: mapJsxSourceLocations(root, document.root)
31
33
  });
32
34
  };
33
35
  /**