@samva/markup 0.8.0 → 0.9.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 (171) hide show
  1. package/AGENTS.md +90 -37
  2. package/README.md +188 -52
  3. package/dist/assets-C5duwTya.d.ts +40 -0
  4. package/dist/{brand-DcoVkh_S.d.ts → brand-B-0bN_Ir.d.ts} +2 -2
  5. package/dist/{render-vAv_4r9v.js → cascade-t6kxlgEI.js} +93 -280
  6. package/dist/channel-edit.d.ts +121 -0
  7. package/dist/channel-edit.js +609 -0
  8. package/dist/channel-specs-B6M_HlaV.js +29 -0
  9. package/dist/channel-specs-C2feJwuP.d.ts +13 -0
  10. package/dist/compatibility-DbISoFIq.js +589 -0
  11. package/dist/compiler.d.ts +10 -257
  12. package/dist/compiler.js +10 -730
  13. package/dist/{css-CS2jLID6.d.ts → css-BVQKICKC.d.ts} +3 -3
  14. package/dist/diagnostic-codes.d.ts +403 -0
  15. package/dist/diagnostic-codes.js +399 -0
  16. package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
  17. package/dist/diagnostics.d.ts +31 -2
  18. package/dist/diagnostics.js +1 -19
  19. package/dist/{elements-BzjjnoCS.d.ts → elements-cqldpaJt.d.ts} +3 -3
  20. package/dist/email/assets.d.ts +2 -40
  21. package/dist/email/brand-footer.d.ts +2 -2
  22. package/dist/email/brand-footer.js +4 -3
  23. package/dist/email/brand.d.ts +1 -1
  24. package/dist/email/brand.js +1 -1
  25. package/dist/email/components.d.ts +21 -10
  26. package/dist/email/components.js +46 -11
  27. package/dist/email/diagnostics.d.ts +2 -2
  28. package/dist/email/diagnostics.js +2 -1
  29. package/dist/email/elements.d.ts +1 -1
  30. package/dist/email/html.d.ts +2 -2
  31. package/dist/email/html.js +45 -2
  32. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  33. package/dist/email/jsx-runtime.d.ts +1 -1
  34. package/dist/email/render.d.ts +8 -18
  35. package/dist/email/render.js +163 -3
  36. package/dist/email/text.d.ts +1 -1
  37. package/dist/fmt.d.ts +32 -0
  38. package/dist/fmt.js +174 -0
  39. package/dist/{html-Bl9BAdqc.d.ts → html-BJepOwZ4.d.ts} +19 -5
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +2 -0
  42. package/dist/ir-aaqNE987.d.ts +252 -0
  43. package/dist/ir.d.ts +2 -0
  44. package/dist/ir.js +7 -0
  45. package/dist/preview-Ekdg1dXG.d.ts +100 -0
  46. package/dist/preview.d.ts +2 -0
  47. package/dist/preview.js +160 -0
  48. package/dist/profile-BH-OtdDA.js +3670 -0
  49. package/dist/render-ir.d.ts +19 -0
  50. package/dist/render-ir.js +826 -0
  51. package/dist/sms-segments.d.ts +13 -2
  52. package/dist/sms.d.ts +16 -0
  53. package/dist/sms.js +12 -0
  54. package/dist/{email/source-edit.d.ts → source-edit.d.ts} +55 -6
  55. package/dist/source-edit.js +343 -0
  56. package/dist/source-locations-C8wCsiAy.d.ts +23 -0
  57. package/dist/source-locations.d.ts +2 -2
  58. package/dist/source-locations.js +3 -89
  59. package/dist/static.d.ts +57 -0
  60. package/dist/static.js +69 -0
  61. package/dist/tailwind-CMYpZf1v.js +614 -0
  62. package/dist/template-_QE_rQND.d.ts +65 -0
  63. package/dist/template.d.ts +2 -2
  64. package/dist/template.js +15 -10
  65. package/dist/theme-css.d.ts +1 -1
  66. package/dist/theme-imports-lvBw3AD1.js +109 -0
  67. package/dist/transform-Ct2OMkFN.d.ts +153 -0
  68. package/dist/whatsapp.d.ts +57 -0
  69. package/dist/whatsapp.js +22 -0
  70. package/generated/contract.json +1782 -0
  71. package/generated/reference.md +353 -0
  72. package/package.json +41 -151
  73. package/skills/sml/SKILL.md +123 -0
  74. package/dist/ast-BrDtApwS.d.ts +0 -107
  75. package/dist/ast.d.ts +0 -2
  76. package/dist/ast.js +0 -124
  77. package/dist/candidate.d.ts +0 -29
  78. package/dist/candidate.js +0 -88
  79. package/dist/catalog-CKuOooB8.d.ts +0 -354
  80. package/dist/checks-D73Xizbw.js +0 -59366
  81. package/dist/checks.d.ts +0 -33
  82. package/dist/checks.js +0 -2
  83. package/dist/classes.d.ts +0 -26
  84. package/dist/classes.js +0 -184
  85. package/dist/compile-sms.d.ts +0 -28
  86. package/dist/compile-sms.js +0 -44
  87. package/dist/compile-whatsapp.d.ts +0 -56
  88. package/dist/compile-whatsapp.js +0 -186
  89. package/dist/compile.d.ts +0 -18
  90. package/dist/compile.js +0 -29
  91. package/dist/components.d.ts +0 -289
  92. package/dist/components.js +0 -106
  93. package/dist/conditionals.d.ts +0 -10
  94. package/dist/conditionals.js +0 -74
  95. package/dist/corpus-CPPfgAHN.d.ts +0 -106
  96. package/dist/diagnostics-OcQKQYSY.d.ts +0 -49
  97. package/dist/email/selection.d.ts +0 -2
  98. package/dist/email/selection.js +0 -91
  99. package/dist/email/source-edit.js +0 -185
  100. package/dist/email/stylesheets.d.ts +0 -15
  101. package/dist/email/stylesheets.js +0 -48
  102. package/dist/emit.d.ts +0 -51
  103. package/dist/emit.js +0 -151
  104. package/dist/interpolate.d.ts +0 -24
  105. package/dist/interpolate.js +0 -43
  106. package/dist/jsx-dev-runtime.d.ts +0 -11
  107. package/dist/jsx-dev-runtime.js +0 -15
  108. package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
  109. package/dist/jsx-runtime.d.ts +0 -2
  110. package/dist/jsx-runtime.js +0 -121
  111. package/dist/lexer.d.ts +0 -60
  112. package/dist/lexer.js +0 -281
  113. package/dist/model.d.ts +0 -24
  114. package/dist/model.js +0 -77
  115. package/dist/ops-BvwqHl7V.js +0 -630
  116. package/dist/ops.d.ts +0 -114
  117. package/dist/ops.js +0 -2
  118. package/dist/parser-t29cHLVL.d.ts +0 -396
  119. package/dist/parser.d.ts +0 -2
  120. package/dist/parser.js +0 -859
  121. package/dist/predicate-BI6uRRM-.js +0 -549
  122. package/dist/predicate-M-05Nd7R.d.ts +0 -56
  123. package/dist/predicate.d.ts +0 -2
  124. package/dist/predicate.js +0 -2
  125. package/dist/printer.d.ts +0 -6
  126. package/dist/printer.js +0 -122
  127. package/dist/registry-Dcp6QEK_.d.ts +0 -73
  128. package/dist/registry.d.ts +0 -2
  129. package/dist/registry.js +0 -646
  130. package/dist/render-template.d.ts +0 -45
  131. package/dist/render-template.js +0 -76
  132. package/dist/selection-CCArx9lU.d.ts +0 -91
  133. package/dist/showcase-egkusQBf.d.ts +0 -415
  134. package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
  135. package/dist/source-locations-DrCM82Ym.d.ts +0 -67
  136. package/dist/starters/category-mapping.d.ts +0 -13
  137. package/dist/starters/category-mapping.js +0 -49
  138. package/dist/starters/corpus.d.ts +0 -2
  139. package/dist/starters/corpus.js +0 -194
  140. package/dist/starters/library.d.ts +0 -18
  141. package/dist/starters/library.js +0 -692
  142. package/dist/starters/public-gallery.d.ts +0 -266
  143. package/dist/starters/public-gallery.js +0 -916
  144. package/dist/starters/public-handoff.d.ts +0 -164
  145. package/dist/starters/public-handoff.js +0 -361
  146. package/dist/starters/public-preview-artifacts.d.ts +0 -43
  147. package/dist/starters/public-preview-artifacts.js +0 -104
  148. package/dist/starters/public-preview.d.ts +0 -61
  149. package/dist/starters/public-preview.js +0 -83
  150. package/dist/starters/showcase.d.ts +0 -2
  151. package/dist/starters/showcase.js +0 -335
  152. package/dist/starters/sources.d.ts +0 -45
  153. package/dist/starters/sources.js +0 -7762
  154. package/dist/starters/theme.d.ts +0 -45
  155. package/dist/starters/theme.js +0 -77
  156. package/dist/starters.d.ts +0 -2
  157. package/dist/starters.js +0 -874
  158. package/dist/tailwind-BYq7Lo6u.js +0 -727
  159. package/dist/template-DWftqh4x.d.ts +0 -60
  160. package/dist/tsx-source-instrumentation.d.ts +0 -30
  161. package/dist/tsx-source-instrumentation.js +0 -115
  162. package/dist/tsx-tracer.d.ts +0 -112
  163. package/dist/tsx-tracer.js +0 -985
  164. package/dist/variables-J_L3qxRa.d.ts +0 -77
  165. package/dist/variables.d.ts +0 -2
  166. package/dist/variables.js +0 -141
  167. package/dist/version-BITr58kk.js +0 -96
  168. package/generated/sml-agent-contract.json +0 -709
  169. package/generated/sml-email-agent-contract.json +0 -615
  170. package/generated/sml-email-language-reference.md +0 -94
  171. package/generated/sml-language-reference.md +0 -195
@@ -1,4 +1,4 @@
1
- import { jsx, rawMarkup, reportEmailDiagnostic } from "./jsx-runtime.js";
1
+ import { EmailNode, Fragment, jsx, normalizeChildren, rawMarkup, reportEmailDiagnostic } from "./jsx-runtime.js";
2
2
  import { escapeAttribute, escapeText } from "./html.js";
3
3
  //#region src/email/components.ts
4
4
  /** Marks the hidden preview block so the renderer can read it back and the text lane can keep it. */
@@ -34,7 +34,7 @@ const preheaderNode = (text) => jsx("div", {
34
34
  })]
35
35
  });
36
36
  /** Document shell: the head every target client needs, plus the body content cell. */
37
- const Email = ({ className, lang = "en", dir = "ltr", title, preheader, backgroundColor, style, children }) => jsx("html", {
37
+ const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor, style, children }) => jsx("html", {
38
38
  lang,
39
39
  dir,
40
40
  xmlns: "http://www.w3.org/1999/xhtml",
@@ -66,7 +66,7 @@ const Email = ({ className, lang = "en", dir = "ltr", title, preheader, backgrou
66
66
  padding: 0,
67
67
  ...backgroundColor === void 0 ? {} : { backgroundColor }
68
68
  },
69
- children: [preheader === void 0 ? null : preheaderNode(preheader), presentation({
69
+ children: [presentation({
70
70
  align: "center",
71
71
  width: "100%",
72
72
  ...backgroundColor === void 0 ? {} : { bgcolor: backgroundColor }
@@ -88,13 +88,48 @@ const Section = ({ className, align = "center", width = "100%", bgcolor, style,
88
88
  ...style === void 0 ? {} : { style },
89
89
  children
90
90
  }));
91
- /** A row of side-by-side `Column`s. No flex or grid conversion happens here. */
92
- const Columns = ({ className, align = "center", width = "100%", style, children }) => presentation({
93
- className,
94
- align,
95
- width,
96
- ...style === void 0 ? {} : { style }
97
- }, children, { style: { width: "100%" } });
91
+ /** The equal share of a row that each of `count` cells takes, whole percent rounded down. */
92
+ const equalColumnWidth = (count) => `${Math.floor(100 / count)}%`;
93
+ /** Give a `Column` that states no width its equal share, keeping the primitive's attribute order. */
94
+ const withColumnWidth = (node, width) => {
95
+ if (node.type === "Fragment") return EmailNode.Fragment({ children: node.children.map((child) => withColumnWidth(child, width)) });
96
+ if (node.type !== "Element" || node.tag !== "td" || node.props.width !== void 0) return node;
97
+ const props = {};
98
+ for (const [key, value] of Object.entries(node.props)) {
99
+ if (key === "style") props.width = width;
100
+ props[key] = value;
101
+ }
102
+ if (props.width === void 0) props.width = width;
103
+ return EmailNode.Element({
104
+ ...node,
105
+ props
106
+ });
107
+ };
108
+ /**
109
+ * A row of side-by-side `Column`s. No flex or grid conversion happens here. With `each` it repeats
110
+ * its cells over a list, `per` to a row, and splits the row equally between cells that state no
111
+ * width.
112
+ */
113
+ const Columns = ({ className, align = "center", width = "100%", style, each, per, children }) => {
114
+ const table = {
115
+ className,
116
+ align,
117
+ width,
118
+ ...style === void 0 ? {} : { style }
119
+ };
120
+ if (each === void 0 || typeof children !== "function") return presentation(table, children, { style: { width: "100%" } });
121
+ if (per !== void 0 && (!Number.isInteger(per) || per < 1)) reportEmailDiagnostic({
122
+ code: "invalid-columns-per",
123
+ severity: "error",
124
+ message: "<Columns per> must be a positive integer."
125
+ });
126
+ const size = per !== void 0 && Number.isInteger(per) && per >= 1 ? per : void 0;
127
+ const chunks = [];
128
+ if (size === void 0) chunks.push(each);
129
+ else for (let at = 0; at < each.length; at += size) chunks.push(each.slice(at, at + size));
130
+ const share = equalColumnWidth(size ?? Math.max(each.length, 1));
131
+ return jsx(Fragment, { children: chunks.map((chunk) => presentation(table, chunk.flatMap((item, index) => normalizeChildren(children(item, index)).map((cell) => withColumnWidth(cell, share))), { style: { width: "100%" } })) });
132
+ };
98
133
  /** One cell of a `Columns` row. */
99
134
  const Column = ({ className, align, valign, width, style, children }) => jsx("td", {
100
135
  className,
@@ -235,6 +270,6 @@ const Link = ({ className, href, target = "_blank", rel = "noopener noreferrer",
235
270
  children
236
271
  });
237
272
  //#endregion
238
- export { Button, Column, Columns, Divider, Email, Image, Link, PREHEADER_ATTRIBUTE, Section, Spacer, preheaderNode };
273
+ export { Button, Column, Columns, Divider, Email, Image, Link, PREHEADER_ATTRIBUTE, Section, Spacer, equalColumnWidth, preheaderNode };
239
274
 
240
275
  //# sourceMappingURL=components.js.map
@@ -1,2 +1,2 @@
1
- import { a as formatEmailDiagnostic, i as EmailDiagnosticSeverity, n as EmailCompileError, o as hasBlockingDiagnostic, r as EmailDiagnostic, t as EmailCompatibilityAssessment } from "../diagnostics-BMYtBCQk.js";
2
- export { EmailCompatibilityAssessment, EmailCompileError, EmailDiagnostic, EmailDiagnosticSeverity, formatEmailDiagnostic, hasBlockingDiagnostic };
1
+ import { a as TemplateDiagnostic, i as EmailDiagnosticSeverity, n as EmailCompileError, o as formatEmailDiagnostic, r as EmailDiagnostic, s as hasBlockingDiagnostic, t as EmailCompatibilityAssessment } from "../diagnostics-B-Gym34R.js";
2
+ export { EmailCompatibilityAssessment, EmailCompileError, EmailDiagnostic, EmailDiagnosticSeverity, TemplateDiagnostic, formatEmailDiagnostic, hasBlockingDiagnostic };
@@ -11,7 +11,8 @@ var EmailCompileError = class extends TaggedError("EmailCompileError") {
11
11
  /** `emails/welcome.tsx:12:3 unsupported-element: …` — the shape agents iterate on. */
12
12
  const formatEmailDiagnostic = (diagnostic) => {
13
13
  const origin = diagnostic.origins[0];
14
- return `${origin === void 0 ? "<unknown source>" : `${origin.fileName}:${origin.lineNumber}:${origin.columnNumber}`} ${diagnostic.code}: ${diagnostic.message}`;
14
+ const head = `${origin === void 0 ? "<unknown source>" : `${origin.fileName}:${origin.lineNumber}:${origin.columnNumber}`} ${diagnostic.code}: ${diagnostic.message}`;
15
+ return diagnostic.fix === void 0 ? head : `${head}\n Fix: ${diagnostic.fix}`;
15
16
  };
16
17
  /** True when a set of findings must stop the compile or refuse a publication. */
17
18
  const hasBlockingDiagnostic = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error");
@@ -1,2 +1,2 @@
1
- import { a as EmailGlobalProps, c as EmailStyle, d as isUrlAttribute, i as EmailElementSpec, l as EmailTag, n as EMAIL_GLOBAL_ATTRIBUTES, o as EmailIntrinsicElements, r as EmailAttributeSpec, s as EmailLayout, t as EMAIL_ELEMENTS, u as emailElement } from "../elements-BzjjnoCS.js";
1
+ import { a as EmailGlobalProps, c as EmailStyle, d as isUrlAttribute, i as EmailElementSpec, l as EmailTag, n as EMAIL_GLOBAL_ATTRIBUTES, o as EmailIntrinsicElements, r as EmailAttributeSpec, s as EmailLayout, t as EMAIL_ELEMENTS, u as emailElement } from "../elements-cqldpaJt.js";
2
2
  export { EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, EmailAttributeSpec, EmailElementSpec, EmailGlobalProps, EmailIntrinsicElements, EmailLayout, EmailStyle, EmailTag, emailElement, isUrlAttribute };
@@ -1,2 +1,2 @@
1
- import { a as escapeText, i as escapeAttribute, n as SerializeEmailOptions, o as kebab, r as declarationValue, s as serializeEmailHtml, t as EmailHtmlOutput } from "../html-Bl9BAdqc.js";
2
- export { EmailHtmlOutput, SerializeEmailOptions, declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml };
1
+ import { a as escapeAttribute, c as serializeEmailHtml, i as declarationValue, l as serializeStyle, n as SerializeEmailOptions, o as escapeText, r as checkAttribute, s as kebab, t as EmailHtmlOutput } from "../html-BJepOwZ4.js";
2
+ export { EmailHtmlOutput, SerializeEmailOptions, checkAttribute, declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml, serializeStyle };
@@ -1,7 +1,7 @@
1
1
  import { n as isObject } from "../guards-eV1qLbf2.js";
2
2
  import { i as UNSUBSCRIBE_URL_PLACEHOLDER, n as RESERVED_FOOTER_ATTRIBUTES, r as UNSUBSCRIBE_LINK, t as BRAND_FOOTER_MARKER } from "../unsubscribe-link-CoesnQDU.js";
3
3
  import { EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, emailElement } from "./elements.js";
4
- import { INSTANCE_PATH_ATTRIBUTE } from "./selection.js";
4
+ import { INSTANCE_PATH_ATTRIBUTE } from "../preview.js";
5
5
  //#region src/email/html.ts
6
6
  /** Escape a value for element content. */
7
7
  const escapeText = (value) => value.replace(/[&<>]/g, (character) => ({
@@ -272,7 +272,50 @@ const serializeEmailHtml = (node, options = {}) => {
272
272
  positions: sink.positions
273
273
  };
274
274
  };
275
+ /** An authored style object as a declaration list, checked by the same rules as a serialized tree. */
276
+ const serializeStyle = (style, origins) => {
277
+ const sink = {
278
+ diagnostics: [],
279
+ positions: [],
280
+ written: 0,
281
+ relativeUrls: false,
282
+ instancePaths: false
283
+ };
284
+ return {
285
+ css: declarations(sink, style, origins),
286
+ diagnostics: sink.diagnostics
287
+ };
288
+ };
289
+ /**
290
+ * Check one static attribute value against its element's spec, as serialization does. Returns
291
+ * the text that would be emitted, or `undefined` when the attribute is dropped or invalid.
292
+ */
293
+ const checkAttribute = (tag, name, value, origins) => {
294
+ const sink = {
295
+ diagnostics: [],
296
+ positions: [],
297
+ written: 0,
298
+ relativeUrls: false,
299
+ instancePaths: false
300
+ };
301
+ const element = emailElement(tag);
302
+ const globals = EMAIL_GLOBAL_ATTRIBUTES;
303
+ const attributeSpec = () => {
304
+ if (/^(data|aria)-[a-z0-9-]+$/.test(name)) return { type: "string" };
305
+ if (Object.hasOwn(globals, name)) return globals[name];
306
+ if (element !== void 0 && Object.hasOwn(element.attributes, name)) return element.attributes[name];
307
+ };
308
+ const spec = attributeSpec();
309
+ if (spec === void 0) return {
310
+ text: void 0,
311
+ diagnostics: sink.diagnostics
312
+ };
313
+ return {
314
+ text: attributeValue(sink, tag, name, spec, value, origins),
315
+ diagnostics: sink.diagnostics
316
+ };
317
+ };
275
318
  //#endregion
276
- export { declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml };
319
+ export { checkAttribute, declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml, serializeStyle };
277
320
 
278
321
  //# sourceMappingURL=html.js.map
@@ -1,5 +1,5 @@
1
- import { r as JsxSource } from "../source-locations-DrCM82Ym.js";
2
- import { g as JSX, h as Fragment, m as EmailNode, p as EmailComponent } from "../elements-BzjjnoCS.js";
1
+ import { n as JsxSource } from "../source-locations-C8wCsiAy.js";
2
+ import { g as JSX, h as Fragment, m as EmailNode, p as EmailComponent } from "../elements-cqldpaJt.js";
3
3
  //#region src/email/jsx-dev-runtime.d.ts
4
4
  /** Development JSX retains compiler-provided origins directly on the semantic tree. */
5
5
  declare const jsxDEV: (type: string | EmailComponent<Record<string, unknown>> | typeof Fragment, props: Record<string, unknown> | null, _key?: unknown, _staticChildren?: boolean, source?: JsxSource) => EmailNode;
@@ -1,2 +1,2 @@
1
- import { C as reportEmailDiagnostic, S as rawMarkup, _ as currentEmailOrigins, b as jsxs, f as EmailChild, g as JSX, h as Fragment, m as EmailNode, p as EmailComponent, v as drainEmailDiagnostics, x as normalizeChildren, y as jsx } from "../elements-BzjjnoCS.js";
1
+ import { C as reportEmailDiagnostic, S as rawMarkup, _ as currentEmailOrigins, b as jsxs, f as EmailChild, g as JSX, h as Fragment, m as EmailNode, p as EmailComponent, v as drainEmailDiagnostics, x as normalizeChildren, y as jsx } from "../elements-cqldpaJt.js";
2
2
  export { EmailChild, EmailComponent, EmailNode, Fragment, JSX, currentEmailOrigins, drainEmailDiagnostics, jsx, jsxs, normalizeChildren, rawMarkup, reportEmailDiagnostic };
@@ -1,9 +1,9 @@
1
- import { r as EmailDiagnostic } from "../diagnostics-BMYtBCQk.js";
2
- import { m as EmailNode } from "../elements-BzjjnoCS.js";
3
- import { n as EmittedPosition } from "../selection-CCArx9lU.js";
4
- import { n as SerializeEmailOptions } from "../html-Bl9BAdqc.js";
5
- import { r as TemplateDefinition, t as EmailContent } from "../template-DWftqh4x.js";
6
- import { i as Stylesheet } from "../css-CS2jLID6.js";
1
+ import { r as EmailDiagnostic } from "../diagnostics-B-Gym34R.js";
2
+ import { m as EmailNode } from "../elements-cqldpaJt.js";
3
+ import { n as EmittedPosition } from "../preview-Ekdg1dXG.js";
4
+ import { n as SerializeEmailOptions } from "../html-BJepOwZ4.js";
5
+ import { n as EmailContent } from "../template-_QE_rQND.js";
6
+ import { i as Stylesheet } from "../css-BVQKICKC.js";
7
7
  //#region src/email/render.d.ts
8
8
  /** Classic Outlook lays a document out correctly only under a transitional doctype. */
9
9
  declare const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
@@ -30,21 +30,11 @@ interface RenderedEmail {
30
30
  readonly diagnostics: readonly EmailDiagnostic[];
31
31
  readonly positions: readonly EmittedPosition[];
32
32
  }
33
- /** Render one validated input through the template's only content function. Host isolation is required for untrusted source. */
34
- declare const renderEmail: <Input>(template: TemplateDefinition<Input>, data: unknown, options?: SerializeEmailOptions) => RenderedEmail;
35
- /**
36
- * A render bound to the sheets registered so far. A compiled project's wrapper
37
- * calls this once its own stylesheet imports have run, so a later project
38
- * loaded into the same process cannot restyle an entry that is rendered again.
39
- */
40
- declare const bindStylesheets: (stylesheet?: Stylesheet | undefined) => typeof renderEmail;
41
33
  /**
42
34
  * Render authored content against a stylesheet: inline what a client honors,
43
- * keep the rest for `<head>`, and lower to HTML and text. `renderEmail` runs a
44
- * validated template through it; a host that composes content without a
45
- * template definition calls it directly.
35
+ * keep the rest for `<head>`, and lower to HTML and text.
46
36
  */
47
37
  declare const renderEmailContent: (stylesheet: Stylesheet | undefined, render: () => EmailContent, options?: SerializeEmailOptions) => RenderedEmail;
48
38
  //#endregion
49
- export { EMAIL_DOCTYPE, EmailCompileOutput, RenderedEmail, bindStylesheets, compileEmail, renderEmail, renderEmailContent };
39
+ export { EMAIL_DOCTYPE, EmailCompileOutput, RenderedEmail, compileEmail, renderEmailContent };
50
40
  //# sourceMappingURL=render.d.ts.map
@@ -1,3 +1,163 @@
1
- import { a as renderEmailContent, i as renderEmail, n as bindStylesheets, r as compileEmail, t as EMAIL_DOCTYPE } from "../render-vAv_4r9v.js";
2
- import "./diagnostics.js";
3
- export { EMAIL_DOCTYPE, bindStylesheets, compileEmail, renderEmail, renderEmailContent };
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";
7
+ //#region src/email/render.ts
8
+ /** Classic Outlook lays a document out correctly only under a transitional doctype. */
9
+ const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
10
+ const settle = (diagnostics) => {
11
+ if (hasBlockingDiagnostic(diagnostics)) throw new EmailCompileError({ diagnostics });
12
+ return diagnostics;
13
+ };
14
+ /** Lower the derived tree to HTML and its plain-text alternative. */
15
+ const compileEmail = (node, options = {}) => {
16
+ const serialized = serializeEmailHtml(node, options);
17
+ const diagnostics = settle([...drainEmailDiagnostics(), ...serialized.diagnostics]);
18
+ return {
19
+ html: serialized.html,
20
+ text: deriveEmailText(node),
21
+ diagnostics,
22
+ positions: serialized.positions
23
+ };
24
+ };
25
+ /** Put the hidden preview block first inside `<body>`, or first overall when there is no shell. */
26
+ const withPreheader = (node, text) => {
27
+ const insert = (current) => {
28
+ if (current.type === "Fragment") {
29
+ for (const [index, child] of current.children.entries()) {
30
+ const replaced = insert(child);
31
+ if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
32
+ }
33
+ return;
34
+ }
35
+ if (current.type !== "Element") return void 0;
36
+ if (current.tag === "body") return EmailNode.Element({
37
+ ...current,
38
+ children: [preheaderNode(text), ...current.children]
39
+ });
40
+ for (const [index, child] of current.children.entries()) {
41
+ const replaced = insert(child);
42
+ if (replaced !== void 0) return EmailNode.Element({
43
+ ...current,
44
+ children: current.children.with(index, replaced)
45
+ });
46
+ }
47
+ };
48
+ return insert(node) ?? EmailNode.Fragment({ children: [preheaderNode(text), node] });
49
+ };
50
+ const headStyle = (css, origins) => EmailNode.Element({
51
+ tag: "style",
52
+ props: {},
53
+ children: [EmailNode.Text({ value: css })],
54
+ origins,
55
+ authored: false
56
+ });
57
+ /**
58
+ * Put the retained rules in a `<style>` at the end of `<head>`, and project
59
+ * `@font-face` rules in a sibling classic Outlook cannot see. Outlook for
60
+ * Windows registers a face it cannot load and then renders the family in Times
61
+ * New Roman, ignoring the stack; hidden, the family is merely missing and the
62
+ * stack's fallback applies.
63
+ */
64
+ const withHeadCss = (node, css, fontFaceCss) => {
65
+ const insert = (current) => {
66
+ if (current.type === "Fragment") {
67
+ for (const [index, child] of current.children.entries()) {
68
+ const replaced = insert(child);
69
+ if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
70
+ }
71
+ return;
72
+ }
73
+ if (current.type !== "Element") return void 0;
74
+ if (current.tag === "head") return EmailNode.Element({
75
+ ...current,
76
+ children: [
77
+ ...current.children,
78
+ ...css === "" ? [] : [headStyle(css, current.origins)],
79
+ ...fontFaceCss === "" ? [] : [
80
+ EmailNode.Raw({
81
+ html: "<!--[if !mso]><!-->",
82
+ text: "",
83
+ origins: []
84
+ }),
85
+ headStyle(fontFaceCss, current.origins),
86
+ EmailNode.Raw({
87
+ html: "<!--<![endif]-->",
88
+ text: "",
89
+ origins: []
90
+ })
91
+ ]
92
+ ]
93
+ });
94
+ for (const [index, child] of current.children.entries()) {
95
+ const replaced = insert(child);
96
+ if (replaced !== void 0) return EmailNode.Element({
97
+ ...current,
98
+ children: current.children.with(index, replaced)
99
+ });
100
+ }
101
+ };
102
+ return insert(node);
103
+ };
104
+ /**
105
+ * Render authored content against a stylesheet: inline what a client honors,
106
+ * keep the rest for `<head>`, and lower to HTML and text.
107
+ */
108
+ const renderEmailContent = (stylesheet, render, options = {}) => {
109
+ drainEmailDiagnostics();
110
+ const content = render();
111
+ const invalid = [];
112
+ const fail = (code, message) => invalid.push({
113
+ code,
114
+ severity: "error",
115
+ message,
116
+ origins: []
117
+ });
118
+ if (typeof content.subject !== "string" || /[\r\n\u0000]/.test(content.subject)) fail("invalid-subject", "Invalid email subject: it must be one line of text.");
119
+ if (content.preheader !== void 0 && typeof content.preheader !== "string") fail("invalid-preheader", "Invalid email preheader: it must be a string.");
120
+ if (content.text !== void 0 && typeof content.text !== "string") fail("invalid-text", "Invalid plain text: it must be a string.");
121
+ settle(invalid);
122
+ const base = content.preheader === void 0 ? content.body : withPreheader(content.body, content.preheader);
123
+ const styled = stylesheet === void 0 ? {
124
+ tree: base,
125
+ headCss: "",
126
+ fontFaceCss: "",
127
+ diagnostics: []
128
+ } : applyStylesheet(base, stylesheet);
129
+ const style = [...styled.diagnostics];
130
+ let tree = styled.tree;
131
+ if (styled.headCss !== "" || styled.fontFaceCss !== "") {
132
+ const placed = withHeadCss(tree, styled.headCss, styled.fontFaceCss);
133
+ if (placed === void 0) style.push({
134
+ code: "missing-head",
135
+ severity: "error",
136
+ 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.",
137
+ origins: []
138
+ });
139
+ else tree = placed;
140
+ }
141
+ settle(style);
142
+ const compiled = compileEmail(tree, options);
143
+ const prefix = compiled.html.startsWith("<html") ? EMAIL_DOCTYPE : "";
144
+ const html = `${prefix}${compiled.html}`;
145
+ const positions = prefix === "" ? compiled.positions : compiled.positions.map((position) => ({
146
+ ...position,
147
+ start: position.start + prefix.length,
148
+ end: position.end + prefix.length
149
+ }));
150
+ return {
151
+ subject: content.subject,
152
+ preheader: content.preheader,
153
+ html,
154
+ text: content.text ?? compiled.text,
155
+ tree,
156
+ diagnostics: [...style, ...compiled.diagnostics],
157
+ positions
158
+ };
159
+ };
160
+ //#endregion
161
+ export { EMAIL_DOCTYPE, compileEmail, renderEmailContent };
162
+
163
+ //# sourceMappingURL=render.js.map
@@ -1,4 +1,4 @@
1
- import { m as EmailNode } from "../elements-BzjjnoCS.js";
1
+ import { m as EmailNode } from "../elements-cqldpaJt.js";
2
2
  //#region src/email/text.d.ts
3
3
  /** Derive the plain-text alternative from the semantic tree in reading order. */
4
4
  declare const deriveEmailText: (node: EmailNode) => string;
package/dist/fmt.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ //#region src/fmt.d.ts
2
+ declare const DEFAULT_LOCALE = "en-US";
3
+ declare const DEFAULT_TIME_ZONE = "UTC";
4
+ interface FormatContext {
5
+ readonly locale?: string | undefined;
6
+ readonly timeZone?: string | undefined;
7
+ }
8
+ type DateStyle = "short" | "medium" | "long" | "full";
9
+ interface PluralForms {
10
+ readonly zero?: string;
11
+ readonly one?: string;
12
+ readonly two?: string;
13
+ readonly few?: string;
14
+ readonly many?: string;
15
+ readonly other: string;
16
+ }
17
+ interface Fmt {
18
+ money(amount: number, currency: string, locale?: string): string;
19
+ number(value: number, options?: Intl.NumberFormatOptions, locale?: string): string;
20
+ date(iso: string, style?: DateStyle, locale?: string): string;
21
+ time(iso: string, style?: DateStyle, locale?: string): string;
22
+ plural(count: number, forms: PluralForms, locale?: string): string;
23
+ list(values: readonly string[], locale?: string): string;
24
+ }
25
+ declare const FORMATTER_NAMES: readonly ["money", "number", "date", "time", "plural", "list"];
26
+ declare const createFmt: (context?: FormatContext) => Fmt;
27
+ declare const withFormatContext: <T>(context: FormatContext, run: () => T) => T;
28
+ declare const fmt: Fmt;
29
+ declare const callFormatter: (formatter: Fmt, name: string, args: readonly unknown[]) => string;
30
+ //#endregion
31
+ export { DEFAULT_LOCALE, DEFAULT_TIME_ZONE, DateStyle, FORMATTER_NAMES, Fmt, FormatContext, PluralForms, callFormatter, createFmt, fmt, withFormatContext };
32
+ //# sourceMappingURL=fmt.d.ts.map
package/dist/fmt.js ADDED
@@ -0,0 +1,174 @@
1
+ //#region src/fmt.ts
2
+ const DEFAULT_LOCALE = "en-US";
3
+ const DEFAULT_TIME_ZONE = "UTC";
4
+ const FORMATTER_NAMES = [
5
+ "money",
6
+ "number",
7
+ "date",
8
+ "time",
9
+ "plural",
10
+ "list"
11
+ ];
12
+ const CACHE_LIMIT = 200;
13
+ const cache = /* @__PURE__ */ new Map();
14
+ const cached = (kind, locale, options, make) => {
15
+ const key = `${kind}\u0000${locale}\u0000${options === void 0 ? "" : JSON.stringify(options)}`;
16
+ const hit = cache.get(key);
17
+ if (hit !== void 0) return hit;
18
+ const value = make();
19
+ if (cache.size >= CACHE_LIMIT) cache.clear();
20
+ cache.set(key, value);
21
+ return value;
22
+ };
23
+ const assertFinite = (value, what) => {
24
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${what} must be a finite number.`);
25
+ };
26
+ const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
27
+ const parseIso = (iso) => {
28
+ if (typeof iso !== "string") throw new TypeError("ISO date must be a string.");
29
+ const date = new Date(DATE_ONLY.test(iso) ? `${iso}T00:00:00Z` : iso);
30
+ if (Number.isNaN(date.getTime())) throw new RangeError(`Cannot parse "${iso}" as an ISO 8601 date.`);
31
+ return date;
32
+ };
33
+ const createFmt = (context = {}) => {
34
+ const defaultLocale = context.locale ?? "en-US";
35
+ const timeZone = context.timeZone ?? "UTC";
36
+ const pick = (locale) => locale ?? defaultLocale;
37
+ return {
38
+ money: (amount, currency, locale) => {
39
+ assertFinite(amount, "money amount");
40
+ const l = pick(locale);
41
+ return cached("money", l, currency, () => new Intl.NumberFormat(l, {
42
+ style: "currency",
43
+ currency
44
+ })).format(amount);
45
+ },
46
+ number: (value, options, locale) => {
47
+ assertFinite(value, "number value");
48
+ const l = pick(locale);
49
+ return cached("number", l, options, () => new Intl.NumberFormat(l, options)).format(value);
50
+ },
51
+ date: (iso, style = "medium", locale) => {
52
+ const d = parseIso(iso);
53
+ const l = pick(locale);
54
+ return cached("date", l, {
55
+ style,
56
+ timeZone
57
+ }, () => new Intl.DateTimeFormat(l, {
58
+ dateStyle: style,
59
+ timeZone
60
+ })).format(d);
61
+ },
62
+ time: (iso, style = "short", locale) => {
63
+ const d = parseIso(iso);
64
+ const l = pick(locale);
65
+ return cached("time", l, {
66
+ style,
67
+ timeZone
68
+ }, () => new Intl.DateTimeFormat(l, {
69
+ timeStyle: style,
70
+ timeZone
71
+ })).format(d);
72
+ },
73
+ plural: (count, forms, locale) => {
74
+ assertFinite(count, "plural count");
75
+ const l = pick(locale);
76
+ return forms[cached("plural", l, void 0, () => new Intl.PluralRules(l)).select(count)] ?? forms.other;
77
+ },
78
+ list: (values, locale) => {
79
+ const l = pick(locale);
80
+ return cached("list", l, void 0, () => new Intl.ListFormat(l, {
81
+ type: "conjunction",
82
+ style: "long"
83
+ })).format(values);
84
+ }
85
+ };
86
+ };
87
+ let ambient = {};
88
+ const withFormatContext = (context, run) => {
89
+ const previous = ambient;
90
+ ambient = context;
91
+ try {
92
+ return run();
93
+ } finally {
94
+ ambient = previous;
95
+ }
96
+ };
97
+ const fmt = {
98
+ money: (a, c, l) => createFmt(ambient).money(a, c, l),
99
+ number: (v, o, l) => createFmt(ambient).number(v, o, l),
100
+ date: (i, s, l) => createFmt(ambient).date(i, s, l),
101
+ time: (i, s, l) => createFmt(ambient).time(i, s, l),
102
+ plural: (c, f, l) => createFmt(ambient).plural(c, f, l),
103
+ list: (v, l) => createFmt(ambient).list(v, l)
104
+ };
105
+ const STYLES = [
106
+ "short",
107
+ "medium",
108
+ "long",
109
+ "full"
110
+ ];
111
+ const PLURAL_KEYS = [
112
+ "zero",
113
+ "one",
114
+ "two",
115
+ "few",
116
+ "many",
117
+ "other"
118
+ ];
119
+ const arity = (name, args, min, max) => {
120
+ if (args.length < min || args.length > max) {
121
+ const expected = min === max ? `${min}` : `${min} to ${max}`;
122
+ throw new TypeError(`${name} takes ${expected} arguments but received ${args.length}.`);
123
+ }
124
+ };
125
+ const str = (name, index, value, optional = false) => {
126
+ if (value === void 0 && optional) return void 0;
127
+ if (typeof value !== "string") throw new TypeError(`${name} argument ${index + 1} must be a string.`);
128
+ return value;
129
+ };
130
+ const num = (name, index, value) => {
131
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} argument ${index + 1} must be a finite number.`);
132
+ return value;
133
+ };
134
+ const style = (name, index, value) => {
135
+ if (value === void 0) return void 0;
136
+ if (typeof value !== "string" || !STYLES.includes(value)) throw new TypeError(`${name} argument ${index + 1} must be one of ${STYLES.join(", ")}.`);
137
+ return value;
138
+ };
139
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
140
+ const callFormatter = (formatter, name, args) => {
141
+ switch (name) {
142
+ case "money":
143
+ arity(name, args, 2, 3);
144
+ return formatter.money(num(name, 0, args[0]), str(name, 1, args[1]), str(name, 2, args[2], true));
145
+ case "number": {
146
+ arity(name, args, 1, 3);
147
+ const options = args[1];
148
+ if (options !== void 0 && !isRecord(options)) throw new TypeError("number argument 2 must be an options object.");
149
+ return formatter.number(num(name, 0, args[0]), options, str(name, 2, args[2], true));
150
+ }
151
+ case "date":
152
+ case "time":
153
+ arity(name, args, 1, 3);
154
+ return formatter[name](str(name, 0, args[0]), style(name, 1, args[1]), str(name, 2, args[2], true));
155
+ case "plural": {
156
+ arity(name, args, 2, 3);
157
+ const forms = args[1];
158
+ if (!isRecord(forms) || typeof forms.other !== "string") throw new TypeError("plural argument 2 must be an object with a string `other` form.");
159
+ for (const key of PLURAL_KEYS) if (forms[key] !== void 0 && typeof forms[key] !== "string") throw new TypeError(`plural argument 2 form \`${key}\` must be a string.`);
160
+ return formatter.plural(num(name, 0, args[0]), forms, str(name, 2, args[2], true));
161
+ }
162
+ case "list": {
163
+ arity(name, args, 1, 2);
164
+ const values = args[0];
165
+ if (!Array.isArray(values) || values.some((v) => typeof v !== "string")) throw new TypeError("list argument 1 must be an array of strings.");
166
+ return formatter.list(values, str(name, 1, args[1], true));
167
+ }
168
+ default: throw new TypeError(`Unknown formatter "${name}".`);
169
+ }
170
+ };
171
+ //#endregion
172
+ export { DEFAULT_LOCALE, DEFAULT_TIME_ZONE, FORMATTER_NAMES, callFormatter, createFmt, fmt, withFormatContext };
173
+
174
+ //# sourceMappingURL=fmt.js.map
@@ -1,6 +1,7 @@
1
- import { r as EmailDiagnostic } from "./diagnostics-BMYtBCQk.js";
2
- import { m as EmailNode } from "./elements-BzjjnoCS.js";
3
- import { n as EmittedPosition } from "./selection-CCArx9lU.js";
1
+ import { r as JsxSourceLocation } from "./source-locations-C8wCsiAy.js";
2
+ import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
3
+ import { m as EmailNode } from "./elements-cqldpaJt.js";
4
+ import { n as EmittedPosition } from "./preview-Ekdg1dXG.js";
4
5
  //#region src/email/html.d.ts
5
6
  /** Escape a value for element content. */
6
7
  declare const escapeText: (value: string) => string;
@@ -37,6 +38,19 @@ interface SerializeEmailOptions {
37
38
  }
38
39
  /** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
39
40
  declare const serializeEmailHtml: (node: EmailNode, options?: SerializeEmailOptions) => EmailHtmlOutput;
41
+ /** An authored style object as a declaration list, checked by the same rules as a serialized tree. */
42
+ declare const serializeStyle: (style: Readonly<Record<string, unknown>>, origins: readonly JsxSourceLocation[]) => {
43
+ readonly css: string;
44
+ readonly diagnostics: readonly EmailDiagnostic[];
45
+ };
46
+ /**
47
+ * Check one static attribute value against its element's spec, as serialization does. Returns
48
+ * the text that would be emitted, or `undefined` when the attribute is dropped or invalid.
49
+ */
50
+ declare const checkAttribute: (tag: string, name: string, value: unknown, origins: readonly JsxSourceLocation[]) => {
51
+ readonly text: string | undefined;
52
+ readonly diagnostics: readonly EmailDiagnostic[];
53
+ };
40
54
  //#endregion
41
- export { escapeText as a, escapeAttribute as i, SerializeEmailOptions as n, kebab as o, declarationValue as r, serializeEmailHtml as s, EmailHtmlOutput as t };
42
- //# sourceMappingURL=html-Bl9BAdqc.d.ts.map
55
+ export { escapeAttribute as a, serializeEmailHtml as c, declarationValue as i, serializeStyle as l, SerializeEmailOptions as n, escapeText as o, checkAttribute as r, kebab as s, EmailHtmlOutput as t };
56
+ //# sourceMappingURL=html-BJepOwZ4.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { i as TEMPLATE_ID_PATTERN, o as TemplateInput, r as SmlTemplate, s as defineTemplate, t as EmailChannel } from "./template-_QE_rQND.js";
2
+ export { type EmailChannel, type SmlTemplate, TEMPLATE_ID_PATTERN, type TemplateInput, defineTemplate };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { TEMPLATE_ID_PATTERN, defineTemplate } from "./template.js";
2
+ export { TEMPLATE_ID_PATTERN, defineTemplate };