@samva/markup 0.9.0 → 0.10.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 (88) hide show
  1. package/AGENTS.md +48 -40
  2. package/CHANGELOG.md +345 -0
  3. package/README.md +6 -6
  4. package/dist/{brand-B-0bN_Ir.d.ts → assets-BIsyMQ92.d.ts} +41 -11
  5. package/dist/{email/brand.js → brand-C0MKTwmX.js} +64 -4
  6. package/dist/{theme-css.d.ts → brand.d.ts} +12 -3
  7. package/dist/brand.js +4 -0
  8. package/dist/compiler.d.ts +231 -11
  9. package/dist/compiler.js +1312 -13
  10. package/dist/components-BZCqegS8.js +1864 -0
  11. package/dist/diagnostic-model-DJtqbETd.js +38 -0
  12. package/dist/diagnostic-model-DxthUZBT.d.ts +31 -0
  13. package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +2 -2
  14. package/dist/diagnostics.d.ts +406 -27
  15. package/dist/diagnostics.js +402 -34
  16. package/dist/edit.d.ts +234 -0
  17. package/dist/{channel-edit.js → edit.js} +341 -3
  18. package/dist/email/jsx-dev-runtime.d.ts +1 -1
  19. package/dist/email/jsx-dev-runtime.js +1 -2
  20. package/dist/email/jsx-runtime.d.ts +1 -1
  21. package/dist/email/jsx-runtime.js +1 -85
  22. package/dist/{email/components.d.ts → email.d.ts} +2 -2
  23. package/dist/email.js +2 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/{cascade-t6kxlgEI.js → ir-CLhlklKV.js} +8 -4
  27. package/dist/jsx-runtime-CBlexoeR.js +98 -0
  28. package/dist/{elements-cqldpaJt.d.ts → jsx-runtime-ylIOnaSf.d.ts} +74 -74
  29. package/dist/{profile-BH-OtdDA.js → profile-pWGtWZXF.js} +153 -20
  30. package/dist/render-BPonDpCl.d.ts +539 -0
  31. package/dist/{email/render.js → render-BQmfh94k.js} +102 -8
  32. package/dist/render.d.ts +35 -0
  33. package/dist/render.js +49 -0
  34. package/dist/sms.d.ts +1 -1
  35. package/dist/sms.js +1 -1
  36. package/dist/{template-_QE_rQND.d.ts → template-DMPwxMSq.d.ts} +2 -2
  37. package/dist/{template.js → template-zLUjcwfF.js} +2 -2
  38. package/dist/{theme-css-hFqcPSar.js → theme-css-JtRCm1fb.js} +2 -2
  39. package/dist/whatsapp.d.ts +1 -1
  40. package/dist/whatsapp.js +1 -1
  41. package/generated/contract.json +16 -10
  42. package/generated/reference.md +17 -10
  43. package/package.json +36 -119
  44. package/samva-brand.d.ts +4 -4
  45. package/skills/sml/SKILL.md +2 -2
  46. package/dist/assets-C5duwTya.d.ts +0 -40
  47. package/dist/channel-edit.d.ts +0 -121
  48. package/dist/channel-specs-B6M_HlaV.js +0 -29
  49. package/dist/compatibility-DbISoFIq.js +0 -589
  50. package/dist/css-BVQKICKC.d.ts +0 -109
  51. package/dist/diagnostic-codes.d.ts +0 -403
  52. package/dist/diagnostic-codes.js +0 -399
  53. package/dist/email/assets.d.ts +0 -2
  54. package/dist/email/assets.js +0 -63
  55. package/dist/email/brand-footer.d.ts +0 -34
  56. package/dist/email/brand-footer.js +0 -63
  57. package/dist/email/brand.d.ts +0 -2
  58. package/dist/email/components.js +0 -275
  59. package/dist/email/diagnostics.d.ts +0 -2
  60. package/dist/email/elements.d.ts +0 -2
  61. package/dist/email/elements.js +0 -299
  62. package/dist/email/html.d.ts +0 -2
  63. package/dist/email/html.js +0 -321
  64. package/dist/email/render.d.ts +0 -40
  65. package/dist/email/text.d.ts +0 -7
  66. package/dist/email/text.js +0 -100
  67. package/dist/html-BJepOwZ4.d.ts +0 -56
  68. package/dist/ir-aaqNE987.d.ts +0 -252
  69. package/dist/ir.d.ts +0 -2
  70. package/dist/ir.js +0 -7
  71. package/dist/preview-Ekdg1dXG.d.ts +0 -100
  72. package/dist/preview.d.ts +0 -2
  73. package/dist/preview.js +0 -160
  74. package/dist/render-ir.d.ts +0 -19
  75. package/dist/render-ir.js +0 -826
  76. package/dist/sms-segments.d.ts +0 -13
  77. package/dist/sms-segments.js +0 -46
  78. package/dist/source-edit.d.ts +0 -116
  79. package/dist/source-edit.js +0 -343
  80. package/dist/source-locations.d.ts +0 -2
  81. package/dist/source-locations.js +0 -16
  82. package/dist/static.d.ts +0 -57
  83. package/dist/static.js +0 -69
  84. package/dist/tailwind-CMYpZf1v.js +0 -614
  85. package/dist/template.d.ts +0 -2
  86. package/dist/theme-css.js +0 -2
  87. package/dist/theme-imports-lvBw3AD1.js +0 -109
  88. package/dist/transform-Ct2OMkFN.d.ts +0 -153
@@ -0,0 +1,539 @@
1
+ import { r as JsxSourceLocation } from "./source-locations-C8wCsiAy.js";
2
+ import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
3
+ import { r as EmailNode } from "./jsx-runtime-ylIOnaSf.js";
4
+ import { n as EmailContent } from "./template-DMPwxMSq.js";
5
+ import "css-tree";
6
+ //#region src/ir.d.ts
7
+ /** The IR version this package writes. A publication pins the version it was built with. */
8
+ declare const SML_IR_VERSION = 1;
9
+ /**
10
+ * Where a node came from: `[line, column]` in the entry file, or `[line, column, file]` with an
11
+ * index into `TemplateIr.sources` when the node came from an imported project file. Published
12
+ * sends strip these.
13
+ */
14
+ type IrSource = readonly [line: number, column: number, file?: number];
15
+ /**
16
+ * A value read at render time.
17
+ *
18
+ * - a string, number, boolean or `null` is a literal;
19
+ * - `bind` reads the input: `"order.id"` is a path from the input root, `"$item.title"` a path
20
+ * from the enclosing `each` variable named `item` (the `$` prefix keeps loop variables from
21
+ * colliding with input fields);
22
+ * - `lit` is a literal object or array, for formatter options;
23
+ * - `concat` joins the text of its parts (a template string);
24
+ * - `format` calls one formatter from `@samva/markup/fmt`;
25
+ * - `if` selects between two values (a conditional attribute or class);
26
+ * - `op` is arithmetic, allowed inside formatter arguments and conditions;
27
+ * - `length` is `.length` of a bound array or string;
28
+ * - `count` is `.filter(...).length`: the items of an array that satisfy a condition.
29
+ */
30
+ type IrValue = string | number | boolean | null | {
31
+ readonly bind: string;
32
+ } | {
33
+ readonly lit: unknown;
34
+ } | {
35
+ readonly concat: readonly IrValue[];
36
+ } | {
37
+ readonly format: IrFormatter;
38
+ readonly args: readonly IrValue[];
39
+ } | {
40
+ readonly if: IrPredicate;
41
+ readonly then: IrValue;
42
+ readonly else: IrValue;
43
+ } | {
44
+ readonly op: IrArithmetic;
45
+ readonly args: readonly [IrValue, IrValue];
46
+ } | {
47
+ readonly length: IrValue;
48
+ } |
49
+ /** How many items of a bound array satisfy `where`, with each item bound as `$as` while it runs. */
50
+ {
51
+ readonly count: IrValue;
52
+ readonly as: string;
53
+ readonly where: IrPredicate;
54
+ };
55
+ type IrFormatter = "money" | "number" | "date" | "time" | "plural" | "list";
56
+ type IrArithmetic = "+" | "-" | "*" | "/" | "%";
57
+ type IrComparison = "eq" | "ne" | "lt" | "gt" | "le" | "ge";
58
+ /** A condition over the input. Truthiness follows JavaScript: `""`, `0`, `null` and absent are false. */
59
+ type IrPredicate =
60
+ /** The bound value at this path is truthy. */
61
+ {
62
+ readonly present: string;
63
+ } |
64
+ /** Any value is truthy. */
65
+ {
66
+ readonly truthy: IrValue;
67
+ } | {
68
+ readonly not: IrPredicate;
69
+ } | {
70
+ readonly and: readonly IrPredicate[];
71
+ } | {
72
+ readonly or: readonly IrPredicate[];
73
+ } | {
74
+ readonly cmp: IrComparison;
75
+ readonly args: readonly [IrValue, IrValue];
76
+ };
77
+ /**
78
+ * An element's inline style. A string is fully static and was checked at compile time; a list
79
+ * carries declarations whose values are read at render time; `if` selects between two styles.
80
+ */
81
+ type IrStyle = string | readonly (readonly [property: string, value: IrValue])[] | {
82
+ readonly if: IrPredicate;
83
+ readonly then: IrStyle;
84
+ readonly else: IrStyle;
85
+ };
86
+ /** A part of compatibility markup the primitives emit verbatim. */
87
+ type IrRawPart = string | {
88
+ readonly attr: IrValue;
89
+ } | {
90
+ readonly text: IrValue;
91
+ };
92
+ interface IrElement {
93
+ readonly el: string;
94
+ readonly attrs?: Readonly<Record<string, IrValue>>;
95
+ readonly style?: IrStyle;
96
+ readonly children?: readonly IrNode[];
97
+ /**
98
+ * Set by the compiler on the elements `BrandFooter` writes: only they may carry the reserved
99
+ * footer markers, so an element the author wrote cannot claim a footer the send path would
100
+ * then not append.
101
+ */
102
+ readonly reserved?: true;
103
+ readonly src?: IrSource;
104
+ }
105
+ /** Content that appears when the predicate holds, or else `else`. */
106
+ interface IrIf {
107
+ readonly if: IrPredicate;
108
+ readonly then: readonly IrNode[];
109
+ readonly else?: readonly IrNode[];
110
+ readonly src?: IrSource;
111
+ }
112
+ /**
113
+ * Repeat `children` for each item of a bound array.
114
+ *
115
+ * `where` skips the items that fail a condition (`.filter(...)`), with the item bound as `$as`.
116
+ * `chunk` groups the items into consecutive slices of that size instead: the loop variable `$as`
117
+ * then holds one slice, an array, and the last slice may be shorter. Filtering applies first.
118
+ */
119
+ interface IrEach {
120
+ readonly each: IrValue;
121
+ /** The loop variable, referenced as `$as.field`. */
122
+ readonly as: string;
123
+ /** The zero-based index variable, referenced as `$index`. */
124
+ readonly index?: string;
125
+ readonly where?: IrPredicate;
126
+ readonly chunk?: number;
127
+ readonly children: readonly IrNode[];
128
+ readonly src?: IrSource;
129
+ }
130
+ /**
131
+ * Compatibility markup the email primitives emit verbatim (conditional comments, VML). Bound
132
+ * parts are escaped for the context they sit in. `text` is what the plain-text part reads.
133
+ */
134
+ interface IrRaw {
135
+ readonly raw: readonly IrRawPart[];
136
+ readonly text?: IrValue;
137
+ readonly src?: IrSource;
138
+ }
139
+ /** Where the hidden inbox-preview block goes; it renders the channel's `preheader`. */
140
+ interface IrPreheader {
141
+ readonly preheader: true;
142
+ }
143
+ /** A node in an email body. A bare value in child position is rendered as escaped text. */
144
+ type IrNode = IrValue | IrElement | IrIf | IrEach | IrRaw | IrPreheader;
145
+ interface IrEmail {
146
+ readonly subject: IrValue;
147
+ readonly preheader?: IrValue;
148
+ readonly body: IrNode;
149
+ }
150
+ /** A plain-text channel body: text pieces, conditionals and loops. */
151
+ interface IrSms {
152
+ readonly category?: string;
153
+ readonly body: readonly IrNode[];
154
+ }
155
+ type IrWhatsAppButton = {
156
+ readonly type: "quick-reply";
157
+ readonly text: IrValue;
158
+ } | {
159
+ readonly type: "url";
160
+ readonly text: IrValue;
161
+ readonly url: IrValue;
162
+ } | {
163
+ readonly type: "phone";
164
+ readonly text: IrValue;
165
+ readonly phone: IrValue;
166
+ } | {
167
+ readonly type: "copy-code";
168
+ readonly code: IrValue;
169
+ };
170
+ /** A button, or buttons that appear when a condition over the input holds. */
171
+ type IrWhatsAppButtonNode = IrWhatsAppButton | {
172
+ readonly if: IrPredicate;
173
+ readonly then: readonly IrWhatsAppButtonNode[];
174
+ readonly else?: readonly IrWhatsAppButtonNode[];
175
+ readonly src?: IrSource;
176
+ };
177
+ interface IrWhatsApp {
178
+ readonly name?: string;
179
+ readonly category: string;
180
+ readonly language: string;
181
+ /** For a media header, `content` is the media URL. */
182
+ readonly header?: {
183
+ readonly type: "text" | "image" | "video" | "document";
184
+ readonly content: readonly IrNode[];
185
+ };
186
+ readonly body: readonly IrNode[];
187
+ readonly footer?: readonly IrNode[];
188
+ readonly buttons?: readonly IrWhatsAppButtonNode[];
189
+ }
190
+ interface TemplateIr {
191
+ readonly sml: typeof SML_IR_VERSION;
192
+ readonly template: string;
193
+ readonly locale?: string;
194
+ /** Project paths that `IrSource` file indexes name; index 0 is the entry. */
195
+ readonly sources?: readonly string[];
196
+ readonly email?: IrEmail;
197
+ readonly sms?: IrSms;
198
+ readonly whatsapp?: IrWhatsApp;
199
+ /** The portable JSON Schema every send's input is validated against. */
200
+ readonly schema: Readonly<Record<string, unknown>>;
201
+ }
202
+ /** How the renderer resolves formatters that take no explicit locale or time zone. */
203
+ interface RenderOptions {
204
+ /** Overrides the template's `locale`. */
205
+ readonly locale?: string;
206
+ /** IANA time zone; defaults to UTC. */
207
+ readonly timeZone?: string;
208
+ /** Also return where each element landed in the HTML, for mapping findings back to source. */
209
+ readonly positions?: boolean;
210
+ }
211
+ /** One rendered element's offset range in the HTML, and the source it was lowered from. */
212
+ interface RenderedPosition {
213
+ readonly start: number;
214
+ readonly end: number;
215
+ readonly tag: string;
216
+ readonly src?: IrSource;
217
+ }
218
+ interface RenderedIrEmail {
219
+ readonly subject: string;
220
+ readonly preheader: string | undefined;
221
+ readonly html: string;
222
+ readonly text: string;
223
+ /** Present when the render asked for `positions`. */
224
+ readonly positions?: readonly RenderedPosition[];
225
+ }
226
+ type RenderedWhatsAppButton = {
227
+ readonly type: "quick-reply";
228
+ readonly text: string;
229
+ } | {
230
+ readonly type: "url";
231
+ readonly text: string;
232
+ readonly url: string;
233
+ } | {
234
+ readonly type: "phone";
235
+ readonly text: string;
236
+ readonly phone: string;
237
+ } | {
238
+ readonly type: "copy-code";
239
+ readonly code: string;
240
+ };
241
+ /** A WhatsApp message with every text rendered and every conditional button resolved. */
242
+ interface RenderedIrWhatsApp {
243
+ readonly name?: string;
244
+ readonly language: string;
245
+ readonly category: string;
246
+ /** For a media header, `text` is the media URL. */
247
+ readonly header?: {
248
+ readonly type: "text" | "image" | "video" | "document";
249
+ readonly text: string;
250
+ };
251
+ readonly body: string;
252
+ readonly footer?: string;
253
+ readonly buttons: readonly RenderedWhatsAppButton[];
254
+ }
255
+ //#endregion
256
+ //#region src/preview.d.ts
257
+ /**
258
+ * The attribute a preview stamps so a click in a rendered document resolves to exactly one
259
+ * rendered element. A delivered message never carries it.
260
+ */
261
+ declare const INSTANCE_PATH_ATTRIBUTE = "data-samva-instance";
262
+ /** Where one element sits in the emitted document, with the authoring that produced it. */
263
+ interface EmittedPosition {
264
+ readonly start: number;
265
+ readonly end: number;
266
+ readonly origins: ReadonlyArray<JsxSourceLocation>;
267
+ /**
268
+ * Dot-separated element indices from the document root, assigned in document order. It is the
269
+ * identity of one rendered instance.
270
+ */
271
+ readonly instancePath: string;
272
+ readonly tag: string;
273
+ /** False when a compiler primitive generated this element. */
274
+ readonly authored: boolean;
275
+ }
276
+ /** One rendered element, and the authoring it came from. */
277
+ interface EmailElementSelection {
278
+ /**
279
+ * Dot-separated element indices from the document root. It is the identity of one rendered
280
+ * instance, which is what tells two iterations of the same authored element apart: their
281
+ * origins are identical.
282
+ */
283
+ readonly instancePath: string;
284
+ readonly tag: string;
285
+ /**
286
+ * The authored JSX element. Empty when the compiler generated this element with no authoring
287
+ * behind it, the hidden preheader block for instance.
288
+ */
289
+ readonly origins: ReadonlyArray<JsxSourceLocation>;
290
+ /**
291
+ * False when a compiler primitive generated this element. `origins[0]` is then the authoring
292
+ * element that asked for it rather than this element itself, so the wrapper selects the
293
+ * primitive the author wrote.
294
+ */
295
+ readonly authored: boolean;
296
+ /** 1-based position among the rendered elements sharing this origin and tag. */
297
+ readonly occurrence: number;
298
+ /**
299
+ * How many rendered elements share that origin and tag. Above one, one piece of authoring
300
+ * produced several rendered elements (a loop, or a primitive that generates a repeated cell),
301
+ * so an edit there changes all of them.
302
+ */
303
+ readonly occurrences: number;
304
+ /** Absent for the document root. */
305
+ readonly parentPath?: string;
306
+ /** Offset range in the rendered HTML this selection was taken from. */
307
+ readonly start: number;
308
+ readonly end: number;
309
+ }
310
+ /** A render of one fixture for an editor: the delivered content plus the identity of every element. */
311
+ interface IrPreview {
312
+ readonly subject: string;
313
+ readonly preheader: string | undefined;
314
+ /** Carries `INSTANCE_PATH_ATTRIBUTE` on every element. Preview-only. */
315
+ readonly html: string;
316
+ readonly text: string;
317
+ /** Every rendered element in document order. */
318
+ readonly selections: readonly EmailElementSelection[];
319
+ }
320
+ /**
321
+ * Turn one render's positions into selections, in document order. `occurrence` and `occurrences`
322
+ * count the rendered elements that share an authored origin and tag.
323
+ */
324
+ declare const emailSelections: (positions: readonly EmittedPosition[]) => readonly EmailElementSelection[];
325
+ /**
326
+ * Render one fixture with an instance path stamped on every element, and the selection each path
327
+ * resolves to. The selections come from the spans the compiler recorded, so a click on the canvas
328
+ * lands on the exact node of the source that produced it.
329
+ */
330
+ declare const renderIrPreview: (ir: TemplateIr, input: unknown, options?: Omit<RenderOptions, "positions">) => IrPreview;
331
+ /** Look selections up by the instance path a rendered document carries. */
332
+ declare const emailSelectionIndex: (selections: readonly EmailElementSelection[]) => ReadonlyMap<string, EmailElementSelection>;
333
+ /**
334
+ * The authoring element a selection edits. For an authored element that is the element itself; for
335
+ * a generated wrapper it is the primitive that asked for it. Undefined only where the compiler
336
+ * generated markup with no authoring behind it, which nothing can edit in place.
337
+ */
338
+ declare const emailAuthoringTarget: (selection: EmailElementSelection) => JsxSourceLocation | undefined;
339
+ /** Root-first ancestry of a selection, itself last. */
340
+ declare const emailSelectionAncestry: (index: ReadonlyMap<string, EmailElementSelection>, instancePath: string) => readonly EmailElementSelection[];
341
+ /**
342
+ * Every rendered instance produced by the same authored element, in document order. One entry
343
+ * means it rendered once; several mean it repeated.
344
+ */
345
+ declare const emailSelectionSiblings: (selections: readonly EmailElementSelection[], selection: EmailElementSelection) => readonly EmailElementSelection[];
346
+ /**
347
+ * Every rendered instance of the same authored element. A partial edited at its definition changes
348
+ * all of these, which is the scope a control has to name before it applies.
349
+ */
350
+ declare const emailDefinitionInstances: (selections: readonly EmailElementSelection[], selection: EmailElementSelection) => readonly EmailElementSelection[];
351
+ //#endregion
352
+ //#region src/email/font-stacks.d.ts
353
+ /** One `@font-face` rule, its `src` already resolved to served URLs. */
354
+ interface FontFaceRule {
355
+ /** The family the rule declares, unquoted. */
356
+ readonly family: string;
357
+ /** The rule as it goes into the email's `<head>`. */
358
+ readonly css: string;
359
+ }
360
+ //#endregion
361
+ //#region src/email/fonts.d.ts
362
+ /**
363
+ * Maps a relative `src` URL, as authored, to the URL its project file is served
364
+ * from, or `undefined` when the project has no such file. The stylesheet's host
365
+ * owns this: it knows which file the stylesheet came from and the asset base.
366
+ */
367
+ type FontFileResolver = (reference: string) => string | undefined;
368
+ //#endregion
369
+ //#region src/email/selectors.d.ts
370
+ type EmailElementNode = Extract<EmailNode, {
371
+ type: "Element";
372
+ }>;
373
+ interface AttributeTest {
374
+ readonly name: string;
375
+ readonly operator?: string | undefined;
376
+ readonly value?: string | undefined;
377
+ }
378
+ interface Compound {
379
+ readonly tag?: string | undefined;
380
+ readonly id?: string | undefined;
381
+ readonly classes: readonly string[];
382
+ readonly attributes: readonly AttributeTest[];
383
+ /** A compound the matcher cannot evaluate — a pseudo-class, say — never matches. */
384
+ readonly opaque: boolean;
385
+ }
386
+ type Combinator = "descendant" | "child" | "adjacent" | "sibling";
387
+ interface CompiledSelector {
388
+ /** Document order, left to right; each part names the combinator that follows it. */
389
+ readonly parts: readonly {
390
+ readonly compound: Compound;
391
+ readonly after?: Combinator;
392
+ }[];
393
+ }
394
+ /** An element together with the ancestors and preceding siblings a combinator needs. */
395
+ interface Ancestry {
396
+ readonly element: EmailElementNode;
397
+ readonly parent: Ancestry | undefined;
398
+ readonly previous: readonly EmailElementNode[];
399
+ }
400
+ declare const classListOf: (element: EmailElementNode) => readonly string[];
401
+ /** Match a compiled selector against one element in its tree position. */
402
+ declare const matchesSelector: (selector: CompiledSelector | undefined, ancestry: Ancestry) => boolean;
403
+ //#endregion
404
+ //#region src/email/css.d.ts
405
+ interface CssDeclaration {
406
+ /** Kebab-case property name, as authored. */
407
+ readonly property: string;
408
+ readonly value: string;
409
+ readonly important: boolean;
410
+ }
411
+ /** Specificity as the CSS triple (id, class/attribute/pseudo-class, type). */
412
+ type Specificity = readonly [number, number, number];
413
+ interface CssRule {
414
+ /** Exactly one selector; a selector list is split into one rule each. */
415
+ readonly selector: string;
416
+ /** Decoded class identifiers mentioned by this selector, including nested pseudo selectors. */
417
+ readonly classes: readonly string[];
418
+ readonly specificity: Specificity;
419
+ readonly declarations: readonly CssDeclaration[];
420
+ /** Position across every stylesheet, in the order they were imported. */
421
+ readonly order: number;
422
+ /** Declared `@layer` index; unlayered declarations sort above every layer. */
423
+ readonly layer: number;
424
+ /** Enclosing conditional at-rule preludes, outermost first. */
425
+ readonly conditions: readonly string[];
426
+ /** Unconditional and pseudo-free rules are the only ones that can become an inline style. */
427
+ readonly inlinable: boolean;
428
+ /** Compiled at build time so the render lane needs no CSS parser. */
429
+ readonly matcher: CompiledSelector | undefined;
430
+ }
431
+ interface Stylesheet {
432
+ readonly rules: readonly CssRule[];
433
+ /** `@page`, `@keyframes` and other at-rules kept verbatim in `<head>`. */
434
+ readonly headAtRules: readonly string[];
435
+ /** Validated `@font-face` rules, `src` resolved; the render hides them from classic Outlook. */
436
+ readonly fontFaces: readonly FontFaceRule[];
437
+ /** Unconditional custom properties, for resolving `var()` before inlining. */
438
+ readonly variables: Readonly<Record<string, string>>;
439
+ readonly diagnostics: readonly EmailDiagnostic[];
440
+ }
441
+ /** Compile a selector into the plain-data matcher the render lane uses. */
442
+ declare const compileSelector: (selector: string) => CompiledSelector | undefined;
443
+ /** Parse one stylesheet into rules, keeping layer, order and conditions. */
444
+ declare const parseStylesheet: (source: string, options?: {
445
+ readonly origin?: string;
446
+ readonly startOrder?: number;
447
+ /** Resolves a relative `@font-face` src to the URL its project file is served from. */
448
+ readonly resolveFontFile?: FontFileResolver | undefined;
449
+ /**
450
+ * The sheet belongs to the project theme, whose `@import`s the Tailwind
451
+ * compile resolves; they are layering, not something to report here.
452
+ */
453
+ readonly themeImports?: boolean | undefined;
454
+ }) => Stylesheet;
455
+ //#endregion
456
+ //#region src/email/html.d.ts
457
+ /** Escape a value for element content. */
458
+ declare const escapeText: (value: string) => string;
459
+ /** Escape a value for a double-quoted attribute. */
460
+ declare const escapeAttribute: (value: string) => string;
461
+ /** camelCase to kebab-case; already-kebab names pass through unchanged. */
462
+ declare const kebab: (property: string) => string;
463
+ /** A bare number is pixels for every property that takes a length. */
464
+ declare const declarationValue: (property: string, value: string | number) => string;
465
+ interface EmailHtmlOutput {
466
+ readonly html: string;
467
+ readonly diagnostics: readonly EmailDiagnostic[];
468
+ /**
469
+ * Each emitted element's offset range and origins. A compatibility finding
470
+ * carries a position in the emitted document, and this is what turns that
471
+ * back into the authoring element the author can edit.
472
+ */
473
+ readonly positions: readonly EmittedPosition[];
474
+ }
475
+ /** How this document will be consumed. Delivery is the default; export is opt-in. */
476
+ interface SerializeEmailOptions {
477
+ /**
478
+ * Accept relative references. A local export serves assets from a relative
479
+ * base beside the file; a delivered message has no directory to resolve one
480
+ * against, so this stays off for anything that will be sent.
481
+ */
482
+ readonly relativeUrls?: boolean | undefined;
483
+ /**
484
+ * Stamp every element with its instance path, so an editor preview can
485
+ * resolve a click in the rendered document to exactly one rendered element.
486
+ * A delivered message never carries it.
487
+ */
488
+ readonly instancePaths?: boolean | undefined;
489
+ }
490
+ /** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
491
+ declare const serializeEmailHtml: (node: EmailNode, options?: SerializeEmailOptions) => EmailHtmlOutput;
492
+ /** An authored style object as a declaration list, checked by the same rules as a serialized tree. */
493
+ declare const serializeStyle: (style: Readonly<Record<string, unknown>>, origins: readonly JsxSourceLocation[]) => {
494
+ readonly css: string;
495
+ readonly diagnostics: readonly EmailDiagnostic[];
496
+ };
497
+ /**
498
+ * Check one static attribute value against its element's spec, as serialization does. Returns
499
+ * the text that would be emitted, or `undefined` when the attribute is dropped or invalid.
500
+ */
501
+ declare const checkAttribute: (tag: string, name: string, value: unknown, origins: readonly JsxSourceLocation[]) => {
502
+ readonly text: string | undefined;
503
+ readonly diagnostics: readonly EmailDiagnostic[];
504
+ };
505
+ //#endregion
506
+ //#region src/email/render.d.ts
507
+ /** Classic Outlook lays a document out correctly only under a transitional doctype. */
508
+ declare const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
509
+ interface EmailCompileOutput {
510
+ readonly html: string;
511
+ readonly text: string;
512
+ /** Findings that did not block the compile; blocking findings throw. */
513
+ readonly diagnostics: readonly EmailDiagnostic[];
514
+ /**
515
+ * Each emitted element's offset range and origins. Compatibility checks run
516
+ * in the host over the emitted document, and this is what lets a finding name
517
+ * the authoring element rather than a character offset.
518
+ */
519
+ readonly positions: readonly EmittedPosition[];
520
+ }
521
+ /** Lower the derived tree to HTML and its plain-text alternative. */
522
+ declare const compileEmail: (node: EmailNode, options?: SerializeEmailOptions) => EmailCompileOutput;
523
+ interface RenderedEmail {
524
+ readonly subject: string;
525
+ readonly preheader: string | undefined;
526
+ readonly html: string;
527
+ readonly text: string;
528
+ readonly tree: EmailNode;
529
+ readonly diagnostics: readonly EmailDiagnostic[];
530
+ readonly positions: readonly EmittedPosition[];
531
+ }
532
+ /**
533
+ * Render authored content against a stylesheet: inline what a client honors,
534
+ * keep the rest for `<head>`, and lower to HTML and text.
535
+ */
536
+ declare const renderEmailContent: (stylesheet: Stylesheet | undefined, render: () => EmailContent, options?: SerializeEmailOptions) => RenderedEmail;
537
+ //#endregion
538
+ export { IrWhatsApp as $, emailAuthoringTarget as A, IrElement as B, Compound as C, EmittedPosition as D, EmailElementSelection as E, emailSelections as F, IrPredicate as G, IrFormatter as H, renderIrPreview as I, IrRawPart as J, IrPreheader as K, IrArithmetic as L, emailSelectionAncestry as M, emailSelectionIndex as N, INSTANCE_PATH_ATTRIBUTE as O, emailSelectionSiblings as P, IrValue as Q, IrComparison as R, CompiledSelector as S, matchesSelector as T, IrIf as U, IrEmail as V, IrNode as W, IrSource as X, IrSms as Y, IrStyle as Z, Specificity as _, renderEmailContent as a, RenderedPosition as at, parseStylesheet as b, checkAttribute as c, TemplateIr as ct, escapeText as d, IrWhatsAppButton as et, kebab as f, CssRule as g, CssDeclaration as h, compileEmail as i, RenderedIrWhatsApp as it, emailDefinitionInstances as j, IrPreview as k, declarationValue as l, serializeStyle as m, EmailCompileOutput as n, RenderOptions as nt, EmailHtmlOutput as o, RenderedWhatsAppButton as ot, serializeEmailHtml as p, IrRaw as q, RenderedEmail as r, RenderedIrEmail as rt, SerializeEmailOptions as s, SML_IR_VERSION as st, EMAIL_DOCTYPE as t, IrWhatsAppButtonNode as tt, escapeAttribute as u, Stylesheet as v, classListOf as w, Ancestry as x, compileSelector as y, IrEach as z };
539
+ //# sourceMappingURL=render-BPonDpCl.d.ts.map
@@ -1,9 +1,103 @@
1
- import { EmailCompileError, hasBlockingDiagnostic } from "./diagnostics.js";
2
- import { EmailNode, drainEmailDiagnostics } from "./jsx-runtime.js";
3
- import { t as applyStylesheet } from "../cascade-t6kxlgEI.js";
4
- import { serializeEmailHtml } from "./html.js";
5
- import { preheaderNode } from "./components.js";
6
- import { deriveEmailText } from "./text.js";
1
+ import { P as emailElement, f as preheaderNode, v as serializeEmailHtml } from "./components-BZCqegS8.js";
2
+ import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
3
+ import { n as applyStylesheet } from "./ir-CLhlklKV.js";
4
+ import { r as hasBlockingDiagnostic, t as EmailCompileError } from "./diagnostics-CnKFefKz.js";
5
+ //#region src/email/text.ts
6
+ const PARAGRAPH = "\n\n";
7
+ const LINE = "\n";
8
+ const INVISIBLE = /\u200b|\u200c|\u200d|\u200e|\u200f|\u2060|\ufeff/g;
9
+ const isLayoutTable = (props) => props.role === "presentation" || props.role === "none";
10
+ /**
11
+ * A data-table cell that reads as nothing and holds only imagery with empty alt text carries no
12
+ * column of its own; an empty cell without such an image is a positional placeholder.
13
+ */
14
+ const isDecorativeCell = (cell, context) => {
15
+ const images = collect(cell.children, (tag) => tag === "img", () => true);
16
+ return images.length > 0 && images.every((image) => image.props.alt === "") && childText(cell, context).trim() === "";
17
+ };
18
+ /** Wrap a block's content in separators; empty blocks contribute nothing. */
19
+ const block = (inner, separator) => {
20
+ const trimmed = inner.replace(/^\s+/, "").replace(/\s+$/, "");
21
+ return trimmed === "" ? "" : `${separator}${trimmed}${separator}`;
22
+ };
23
+ const collect = (nodes, keep, descend) => nodes.flatMap((child) => {
24
+ if (child.type === "Fragment") return collect(child.children, keep, descend);
25
+ if (child.type !== "Element") return [];
26
+ if (keep(child.tag)) return [child];
27
+ return descend(child.tag) ? collect(child.children, keep, descend) : [];
28
+ });
29
+ const TABLE_SECTIONS = /* @__PURE__ */ new Set([
30
+ "thead",
31
+ "tbody",
32
+ "tfoot"
33
+ ]);
34
+ const childText = (node, context) => node.children.map((child) => derive(child, context)).join("");
35
+ const derive = (node, context) => {
36
+ switch (node.type) {
37
+ case "Text": return context.preformatted ? node.value : node.value.replace(INVISIBLE, "").replace(/[^\S\n]+/g, " ");
38
+ case "Raw": return node.text;
39
+ case "Fragment": return node.children.map((child) => derive(child, context)).join("");
40
+ case "Element": {
41
+ const spec = emailElement(node.tag);
42
+ if (spec === void 0) return childText(node, context);
43
+ switch (spec.layout) {
44
+ case "metadata": return "";
45
+ case "document":
46
+ case "inline":
47
+ case "list-item": return childText(node, context);
48
+ case "break": return LINE;
49
+ case "rule": return `${PARAGRAPH}---${PARAGRAPH}`;
50
+ case "image": {
51
+ const alt = typeof node.props.alt === "string" ? node.props.alt.trim() : "";
52
+ return alt === "" ? "" : `[${alt}]`;
53
+ }
54
+ case "link": {
55
+ const label = childText(node, context).replace(/\s+/g, " ").trim();
56
+ const href = typeof node.props.href === "string" ? node.props.href : "";
57
+ if (href === "" || href === label) return label;
58
+ return label === "" ? href : `${label} (${href})`;
59
+ }
60
+ case "list": {
61
+ const indent = " ".repeat(context.listDepth);
62
+ const start = Number(node.props.start ?? 1);
63
+ const first = Number.isFinite(start) ? start : 1;
64
+ const nested = {
65
+ ...context,
66
+ listDepth: context.listDepth + 1
67
+ };
68
+ const lines = collect(node.children, (tag) => tag === "li", () => false).map((item, index) => {
69
+ const marker = node.tag === "ol" ? `${first + index}.` : "-";
70
+ const continuation = `${indent}${" ".repeat(marker.length + 1)}`;
71
+ const [head = "", ...rest] = derive(item, nested).replace(/^\s+/, "").replace(/\s+$/, "").replace(/\n{2,}/g, LINE).split("\n");
72
+ return [`${indent}${marker} ${head}`, ...rest.map((line) => line.trim() === "" ? "" : /^\s/.test(line) ? line : `${continuation}${line}`)].join(LINE);
73
+ });
74
+ return block(lines.join(LINE), PARAGRAPH);
75
+ }
76
+ case "table": {
77
+ if (isLayoutTable(node.props)) return childText(node, context);
78
+ const rows = collect(node.children, (tag) => tag === "tr", (tag) => TABLE_SECTIONS.has(tag)).map((row) => collect(row.children, (tag) => tag === "td" || tag === "th", () => false).filter((cell) => !isDecorativeCell(cell, context)).map((cell) => childText(cell, context).replace(/\s+/g, " ").trim()).join(" | "));
79
+ return block(rows.join(LINE), PARAGRAPH);
80
+ }
81
+ case "row": return block(collect(node.children, (tag) => tag === "td" || tag === "th", () => false).map((cell) => childText(cell, context).replace(/^\s+|\s+$/g, "")).filter((cell) => cell !== "").join(LINE), LINE);
82
+ case "table-section":
83
+ case "cell": return block(childText(node, context), LINE);
84
+ case "preformatted": return block(childText(node, {
85
+ ...context,
86
+ preformatted: true
87
+ }), PARAGRAPH);
88
+ case "block": return block(childText(node, context), PARAGRAPH);
89
+ }
90
+ }
91
+ }
92
+ };
93
+ /** Collapse the separators the walk emits into readable plain text. */
94
+ const normalize = (value) => value.replace(/\r\n?/g, "\n").split("\n").map((line) => line.replace(/[^\S\n]+$/, "")).join("\n").replace(/\n{3,}/g, PARAGRAPH).trim();
95
+ /** Derive the plain-text alternative from the semantic tree in reading order. */
96
+ const deriveEmailText = (node) => normalize(derive(node, {
97
+ preformatted: false,
98
+ listDepth: 0
99
+ }));
100
+ //#endregion
7
101
  //#region src/email/render.ts
8
102
  /** Classic Outlook lays a document out correctly only under a transitional doctype. */
9
103
  const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
@@ -158,6 +252,6 @@ const renderEmailContent = (stylesheet, render, options = {}) => {
158
252
  };
159
253
  };
160
254
  //#endregion
161
- export { EMAIL_DOCTYPE, compileEmail, renderEmailContent };
255
+ export { deriveEmailText as i, compileEmail as n, renderEmailContent as r, EMAIL_DOCTYPE as t };
162
256
 
163
- //# sourceMappingURL=render.js.map
257
+ //# sourceMappingURL=render-BQmfh94k.js.map