@samva/markup 0.16.0 → 0.17.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 (60) hide show
  1. package/AGENTS.md +42 -40
  2. package/CHANGELOG.md +72 -0
  3. package/README.md +10 -9
  4. package/dist/{brand-jFV6YtSV.d.ts → assets-CS5Xa_mr.d.ts} +41 -3
  5. package/dist/{brand-Ci05p37c.js → brand-CIiEqkU7.js} +64 -3
  6. package/dist/{brand-plugin-CHhcei-j.d.ts → brand-plugin-Dj9SMpfS.d.ts} +3 -1
  7. package/dist/brand.d.ts +2 -3
  8. package/dist/brand.js +3 -4
  9. package/dist/compatibility.d.ts +76 -0
  10. package/dist/compatibility.js +807 -0
  11. package/dist/compiled-template-Bc4Dq9zy.d.ts +268 -0
  12. package/dist/compiler.d.ts +59 -193
  13. package/dist/compiler.js +473 -775
  14. package/dist/{components-3XHjYD3i.js → components-C5850-Qg.js} +37 -29
  15. package/dist/css-B_aVKrDo.js +436 -0
  16. package/dist/css-color-DLM12Osu.js +315 -0
  17. package/dist/{diagnostic-codes-8XDLR9ef.js → diagnostic-codes-DZKb0Xcg.js} +40 -1
  18. package/dist/{diagnostics-CUYlQ_6x.d.ts → diagnostics-D4vSLQrO.d.ts} +1 -10
  19. package/dist/diagnostics.d.ts +6 -2
  20. package/dist/diagnostics.js +3 -4
  21. package/dist/edit.d.ts +10 -9
  22. package/dist/edit.js +12 -11
  23. package/dist/email/jsx-dev-runtime.d.ts +1 -1
  24. package/dist/email/jsx-dev-runtime.js +1 -1
  25. package/dist/email/jsx-runtime.d.ts +2 -2
  26. package/dist/email/jsx-runtime.js +2 -2
  27. package/dist/email.d.ts +1 -1
  28. package/dist/email.js +1 -1
  29. package/dist/html-DJUK2YJF.js +483 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/{jsx-runtime-DC7xG0aA.d.ts → jsx-runtime-DL8NVF1f.d.ts} +12 -6
  32. package/dist/{jsx-runtime-CBlexoeR.js → jsx-runtime-qL19u3Fy.js} +17 -10
  33. package/dist/preview-rNoJBEIY.d.ts +104 -0
  34. package/dist/{profile-BsY4dkan.js → profile-JTvgCD6X.js} +714 -29
  35. package/dist/{html-BvRFepeO.js → render-template-DwPDm3Hq.js} +155 -572
  36. package/dist/render.d.ts +40 -28
  37. package/dist/render.js +3 -47
  38. package/dist/selectors-CYWCoxZr.js +157 -0
  39. package/dist/sms.d.ts +1 -1
  40. package/dist/sms.js +1 -1
  41. package/dist/{template-B-UDXK8a.d.ts → template-DmNcoH4Q.d.ts} +2 -9
  42. package/dist/{theme-css-s9QwD5Ko.js → theme-css-P9F5S7Ic.js} +3 -316
  43. package/dist/whatsapp.d.ts +1 -1
  44. package/dist/whatsapp.js +1 -1
  45. package/generated/contract.json +7 -2
  46. package/generated/reference.md +3 -2
  47. package/package.json +5 -5
  48. package/skills/sml/SKILL.md +2 -2
  49. package/dist/assets-BSpgZOSR.js +0 -61
  50. package/dist/assets-BSuNT9gr.d.ts +0 -39
  51. package/dist/brand-footer-ClRtWnIc.d.ts +0 -40
  52. package/dist/brand-footer-DXVGsKE5.js +0 -746
  53. package/dist/brand-sample.d.ts +0 -4
  54. package/dist/brand-sample.js +0 -2
  55. package/dist/cascade-DoLWZ66g.js +0 -701
  56. package/dist/diagnostic-model-DJtqbETd.js +0 -36
  57. package/dist/ir-KJLFW08u.js +0 -5
  58. package/dist/render-CVnXZMyK.js +0 -258
  59. package/dist/render-CuZej25A.d.ts +0 -564
  60. package/dist/theme-imports-u_P0ygvQ.js +0 -102
package/dist/render.d.ts CHANGED
@@ -1,34 +1,46 @@
1
- import { C as isUrlAttribute, S as emailElement, _ as EmailGlobalProps, b as EmailStyle, g as EmailElementSpec, h as EmailAttributeSpec, m as EMAIL_GLOBAL_ATTRIBUTES, p as EMAIL_ELEMENTS, r as EmailNode, v as EmailIntrinsicElements, x as EmailTag, y as EmailLayout } from "./jsx-runtime-DC7xG0aA.js";
2
- import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-CuZej25A.js";
3
- //#region src/render-ir.d.ts
4
- interface IrRenderDiagnostic {
1
+ import { t as BrandPlugin } from "./brand-plugin-Dj9SMpfS.js";
2
+ import { C as emailElement, S as EmailTag, _ as EmailElementSpec, b as EmailLayout, g as EmailAttributeSpec, h as EMAIL_GLOBAL_ATTRIBUTES, m as EMAIL_ELEMENTS, v as EmailGlobalProps, w as isUrlAttribute, x as EmailStyle, y as EmailIntrinsicElements } from "./jsx-runtime-DL8NVF1f.js";
3
+ import { A as smsSegmentInfo, C as RenderedEmail, D as RenderedWhatsAppButton, E as RenderedWhatsApp, O as SmsEncoding, S as CompiledWhatsAppButtonNode, T as RenderedSms, _ as CompiledStyle, a as CompiledEach, b as CompiledWhatsApp, c as CompiledFormatter, d as CompiledPredicate, f as CompiledPreheader, g as CompiledSource, h as CompiledSms, i as CompiledComparison, k as SmsSegmentInfo, l as CompiledIf, m as CompiledRawPart, n as Channel, o as CompiledElement, p as CompiledRaw, r as CompiledArithmetic, s as CompiledEmail, t as COMPILED_TEMPLATE_VERSION, u as CompiledNode, v as CompiledTemplate, w as RenderedMessage, x as CompiledWhatsAppButton, y as CompiledValue } from "./compiled-template-Bc4Dq9zy.js";
4
+ //#region src/render-template.d.ts
5
+ interface RenderDiagnostic {
5
6
  readonly code: string;
6
7
  readonly message: string;
7
8
  }
8
- declare class IrRenderError extends Error {
9
- readonly diagnostics: readonly IrRenderDiagnostic[];
10
- constructor(diagnostics: readonly IrRenderDiagnostic[]);
9
+ declare const RenderError_base: import("better-result").TaggedErrorClass<"RenderError">;
10
+ declare class RenderError extends RenderError_base<{
11
+ readonly diagnostics: readonly RenderDiagnostic[];
12
+ readonly message: string;
13
+ }> {
14
+ constructor(args: {
15
+ readonly diagnostics: readonly RenderDiagnostic[];
16
+ });
11
17
  }
12
- /** Render the email channel of a compiled template against one validated input. */
13
- declare const renderIr: (ir: TemplateIr, input: unknown, options?: RenderOptions) => RenderedIrEmail;
14
- /** Render the SMS channel of a compiled template to plain text. */
15
- declare const renderIrSms: (ir: TemplateIr, input: unknown, options?: RenderOptions) => string;
16
- /** Render the WhatsApp channel of a compiled template: every text rendered, conditional buttons resolved. */
17
- declare const renderIrWhatsApp: (ir: TemplateIr, input: unknown, options?: RenderOptions) => RenderedIrWhatsApp;
18
- //#endregion
19
- //#region src/email/text.d.ts
20
- /** Derive the plain-text alternative from the semantic tree in reading order. */
21
- declare const deriveEmailText: (node: EmailNode) => string;
22
- //#endregion
23
- //#region src/sms-segments.d.ts
24
- /** The encoding a message needs: GSM-7 when every character fits, UCS-2 otherwise. */
25
- type SmsEncoding = "gsm7" | "ucs2";
26
- interface SmsSegmentInfo {
27
- readonly encoding: SmsEncoding;
28
- readonly characterCount: number;
29
- readonly segmentCount: number;
18
+ /** What every channel's render reads besides the template and the input. */
19
+ interface RenderSettings {
20
+ /** Overrides the template's `locale`. */
21
+ readonly locale?: string | undefined;
22
+ /** IANA time zone; defaults to UTC. */
23
+ readonly timeZone?: string | undefined;
24
+ /**
25
+ * The brand plugin the template compiled against: its unsubscribe placeholder gets the
26
+ * no-track marker, and only the elements `BrandFooter` wrote may carry its footer markers.
27
+ * `samvaBrandPlugin` when omitted.
28
+ */
29
+ readonly brandPlugin?: BrandPlugin | undefined;
30
30
  }
31
- declare const smsSegmentInfo: (text: string) => SmsSegmentInfo;
32
- declare const hasSmsOptOut: (text: string) => boolean;
31
+ /**
32
+ * Render one channel of a compiled template against one validated input. It throws only
33
+ * `RenderError`: a template compiled by another version, a channel the template does not declare,
34
+ * or input or markup the renderer refuses.
35
+ */
36
+ declare const render: <C extends Channel>(template: CompiledTemplate, input: unknown, options: RenderSettings & {
37
+ readonly channel: C;
38
+ }) => RenderedMessage[C];
39
+ //#endregion
40
+ //#region src/email/html.d.ts
41
+ /** Escape a value for element content. */
42
+ declare const escapeText: (value: string) => string;
43
+ /** Escape a value for a double-quoted attribute. */
44
+ declare const escapeAttribute: (value: string) => string;
33
45
  //#endregion
34
- export { EMAIL_DOCTYPE, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, EmailAttributeSpec, EmailCompileOutput, EmailElementSelection, EmailElementSpec, EmailGlobalProps, EmailHtmlOutput, EmailIntrinsicElements, EmailLayout, EmailStyle, EmailTag, EmittedPosition, INSTANCE_PATH_ATTRIBUTE, IrArithmetic, IrComparison, IrEach, IrElement, IrEmail, IrFormatter, IrIf, IrNode, IrPredicate, IrPreheader, IrPreview, IrRaw, IrRawPart, IrRenderDiagnostic, IrRenderError, IrSms, IrSource, IrStyle, IrValue, IrWhatsApp, IrWhatsAppButton, IrWhatsAppButtonNode, RenderOptions, RenderedEmail, RenderedIrEmail, RenderedIrWhatsApp, RenderedPosition, RenderedWhatsAppButton, SML_IR_VERSION, SerializeEmailOptions, SmsEncoding, SmsSegmentInfo, TemplateIr, checkAttribute, compileEmail, declarationValue, deriveEmailText, emailAuthoringTarget, emailDefinitionInstances, emailElement, emailSelectionAncestry, emailSelectionIndex, emailSelectionSiblings, emailSelections, escapeAttribute, escapeText, hasSmsOptOut, isUrlAttribute, kebab, renderEmailContent, renderIr, renderIrPreview, renderIrSms, renderIrWhatsApp, serializeEmailHtml, serializeStyle, smsSegmentInfo };
46
+ export { COMPILED_TEMPLATE_VERSION, type Channel, type CompiledArithmetic, type CompiledComparison, type CompiledEach, type CompiledElement, type CompiledEmail, type CompiledFormatter, type CompiledIf, type CompiledNode, type CompiledPredicate, type CompiledPreheader, type CompiledRaw, type CompiledRawPart, type CompiledSms, type CompiledSource, type CompiledStyle, type CompiledTemplate, type CompiledValue, type CompiledWhatsApp, type CompiledWhatsAppButton, type CompiledWhatsAppButtonNode, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, type EmailAttributeSpec, type EmailElementSpec, type EmailGlobalProps, type EmailIntrinsicElements, type EmailLayout, type EmailStyle, type EmailTag, type RenderDiagnostic, RenderError, type RenderedEmail, type RenderedMessage, type RenderedSms, type RenderedWhatsApp, type RenderedWhatsAppButton, type SmsEncoding, type SmsSegmentInfo, emailElement, escapeAttribute, escapeText, isUrlAttribute, render, smsSegmentInfo };
package/dist/render.js CHANGED
@@ -1,47 +1,3 @@
1
- import { C as isUrlAttribute, S as emailElement, _ as renderIr, a as kebab, b as EMAIL_ELEMENTS, c as INSTANCE_PATH_ATTRIBUTE, d as emailSelectionAncestry, f as emailSelectionIndex, g as IrRenderError, h as renderIrPreview, i as escapeText, l as emailAuthoringTarget, m as emailSelections, n as declarationValue, o as serializeEmailHtml, p as emailSelectionSiblings, r as escapeAttribute, s as serializeStyle, t as checkAttribute, u as emailDefinitionInstances, v as renderIrSms, x as EMAIL_GLOBAL_ATTRIBUTES, y as renderIrWhatsApp } from "./html-BvRFepeO.js";
2
- import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-CVnXZMyK.js";
3
- import { t as SML_IR_VERSION } from "./ir-KJLFW08u.js";
4
- //#region src/sms-segments.ts
5
- const GSM7_EXTENDED = /* @__PURE__ */ new Set([
6
- "[",
7
- "]",
8
- "{",
9
- "}",
10
- "\\",
11
- "~",
12
- "|",
13
- "€",
14
- "^",
15
- "\f"
16
- ]);
17
- const GSM7_BASIC_PATTERN = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./:;<=>?¡0-9ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$/;
18
- const containsNonGsmCharacters = (text) => {
19
- for (const char of text) {
20
- if (GSM7_EXTENDED.has(char)) continue;
21
- if (!GSM7_BASIC_PATTERN.test(char)) return true;
22
- }
23
- return false;
24
- };
25
- const smsSegmentInfo = (text) => {
26
- if (containsNonGsmCharacters(text)) {
27
- const codePoints = [...text].length;
28
- const segmentCount = codePoints <= 70 ? 1 : Math.ceil(codePoints / 67);
29
- return {
30
- encoding: "ucs2",
31
- characterCount: text.length,
32
- segmentCount
33
- };
34
- }
35
- let encodedLength = 0;
36
- for (const char of text) encodedLength += GSM7_EXTENDED.has(char) ? 2 : 1;
37
- const segmentCount = encodedLength <= 160 ? 1 : Math.ceil(encodedLength / 153);
38
- return {
39
- encoding: "gsm7",
40
- characterCount: text.length,
41
- segmentCount
42
- };
43
- };
44
- const SMS_OPT_OUT_PATTERN = /\b(stop|unsubscribe|opt[\s-]?out|cancel|quit)\b/i;
45
- const hasSmsOptOut = (text) => SMS_OPT_OUT_PATTERN.test(text);
46
- //#endregion
47
- export { EMAIL_DOCTYPE, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, INSTANCE_PATH_ATTRIBUTE, IrRenderError, SML_IR_VERSION, checkAttribute, compileEmail, declarationValue, deriveEmailText, emailAuthoringTarget, emailDefinitionInstances, emailElement, emailSelectionAncestry, emailSelectionIndex, emailSelectionSiblings, emailSelections, escapeAttribute, escapeText, hasSmsOptOut, isUrlAttribute, kebab, renderEmailContent, renderIr, renderIrPreview, renderIrSms, renderIrWhatsApp, serializeEmailHtml, serializeStyle, smsSegmentInfo };
1
+ import { i as escapeText, r as escapeAttribute } from "./html-DJUK2YJF.js";
2
+ import { a as EMAIL_ELEMENTS, c as isUrlAttribute, i as smsSegmentInfo, n as render, o as EMAIL_GLOBAL_ATTRIBUTES, p as COMPILED_TEMPLATE_VERSION, s as emailElement, t as RenderError } from "./render-template-DwPDm3Hq.js";
3
+ export { COMPILED_TEMPLATE_VERSION, EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, RenderError, emailElement, escapeAttribute, escapeText, isUrlAttribute, render, smsSegmentInfo };
@@ -0,0 +1,157 @@
1
+ //#region src/email/font-stacks.ts
2
+ /** Generic families every email client understands as the last resort in a stack. */
3
+ const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
4
+ "serif",
5
+ "sans-serif",
6
+ "monospace",
7
+ "cursive",
8
+ "fantasy"
9
+ ]);
10
+ /** One family name as written, without its quotes and with runs of spaces collapsed. */
11
+ const unquote = (value) => {
12
+ const trimmed = value.trim();
13
+ const quote = trimmed[0];
14
+ return (quote === "\"" || quote === "'") && trimmed.endsWith(quote) && trimmed.length >= 2 ? trimmed.slice(1, -1) : trimmed.replace(/\s+/g, " ");
15
+ };
16
+ /** The families of a `font-family` value, unquoted, in stack order. */
17
+ const fontStackFamilies = (value) => {
18
+ const families = [];
19
+ let start = 0;
20
+ let quote;
21
+ for (let index = 0; index < value.length; index++) {
22
+ const character = value[index];
23
+ if (quote !== void 0) {
24
+ if (character === quote) quote = void 0;
25
+ } else if (character === "'" || character === "\"") quote = character;
26
+ else if (character === ",") {
27
+ families.push(unquote(value.slice(start, index)));
28
+ start = index + 1;
29
+ }
30
+ }
31
+ families.push(unquote(value.slice(start)));
32
+ return families.filter((family) => family !== "");
33
+ };
34
+ const FONT_SIZE = /^(?:xx-small|x-small|small|medium|large|x-large|xx-large|xxx-large|larger|smaller|[+-]?(?:\d+\.?\d*|\.\d+)(?:[a-z]+|%)|(?:calc|clamp|min|max|var)\(.*\))$/i;
35
+ /** Whitespace-separated tokens at the top level, keeping parentheses and quotes whole. */
36
+ const shorthandTokens = (value) => {
37
+ const tokens = [];
38
+ let current = "";
39
+ let depth = 0;
40
+ let quote;
41
+ for (const character of value.trim()) {
42
+ if (quote !== void 0) {
43
+ if (character === quote) quote = void 0;
44
+ } else if (character === "'" || character === "\"") quote = character;
45
+ else if (character === "(") depth++;
46
+ else if (character === ")") depth--;
47
+ else if (/\s/.test(character) && depth === 0) {
48
+ if (current !== "") tokens.push(current);
49
+ current = "";
50
+ continue;
51
+ }
52
+ current += character;
53
+ }
54
+ if (current !== "") tokens.push(current);
55
+ return tokens;
56
+ };
57
+ /**
58
+ * The family list of a `font` shorthand: everything after the size (and its
59
+ * optional line height), or `undefined` for a system-font keyword that names
60
+ * no family.
61
+ */
62
+ const fontShorthandFamilies = (value) => {
63
+ const tokens = shorthandTokens(value);
64
+ const size = tokens.findIndex((token) => FONT_SIZE.test(token.replace(/\/.*$/, "")));
65
+ if (size < 0) return void 0;
66
+ let next = size + 1;
67
+ const sizeToken = tokens[size];
68
+ if (sizeToken.endsWith("/")) next += 1;
69
+ else if (!sizeToken.includes("/") && tokens[next]?.startsWith("/")) next += tokens[next] === "/" ? 2 : 1;
70
+ const family = tokens.slice(next).join(" ");
71
+ return family === "" ? void 0 : family;
72
+ };
73
+ /**
74
+ * True when a stack names a declared project family without ending in a
75
+ * generic. `declared` holds lowercased family names. A quoted `"sans-serif"`
76
+ * names a font called sans-serif, not the generic, so it does not count.
77
+ */
78
+ const lacksGenericFallback = (value, declared) => {
79
+ const stack = value.replace(/\s*!important\s*$/i, "");
80
+ if (!fontStackFamilies(stack).some((family) => declared.has(family.toLowerCase()))) return false;
81
+ const last = stack.slice(stack.lastIndexOf(",") + 1).trim();
82
+ return !GENERIC_FAMILIES.has(last.toLowerCase());
83
+ };
84
+ const fontFallbackDiagnostic = (value) => ({
85
+ code: "email-font-fallback",
86
+ severity: "error",
87
+ message: `font-family ${JSON.stringify(value)} uses a project font without a generic fallback. Clients that drop webfonts (Gmail, classic Outlook) render this stack as written, so end it with sans-serif, serif or monospace.`
88
+ });
89
+ //#endregion
90
+ //#region src/email/selectors.ts
91
+ const classListOf = (element) => {
92
+ const value = element.props.class ?? element.props.className;
93
+ return typeof value === "string" ? value.split(/\s+/).filter((name) => name !== "") : [];
94
+ };
95
+ const attributeOf = (element, name) => {
96
+ const value = element.props[name];
97
+ if (value === void 0 || value === null || value === false) return void 0;
98
+ if (value === true) return "";
99
+ return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
100
+ };
101
+ const matchesAttribute = (element, test) => {
102
+ const actual = test.name === "class" ? classListOf(element).join(" ") : attributeOf(element, test.name);
103
+ if (actual === void 0) return false;
104
+ if (test.operator === void 0 || test.value === void 0) return true;
105
+ switch (test.operator) {
106
+ case "=": return actual === test.value;
107
+ case "~=": return actual.split(/\s+/).includes(test.value);
108
+ case "^=": return actual.startsWith(test.value);
109
+ case "$=": return actual.endsWith(test.value);
110
+ case "*=": return actual.includes(test.value);
111
+ case "|=": return actual === test.value || actual.startsWith(`${test.value}-`);
112
+ default: return false;
113
+ }
114
+ };
115
+ const matchesCompound = (element, compound) => {
116
+ if (compound.opaque) return false;
117
+ if (compound.tag !== void 0 && compound.tag !== element.tag) return false;
118
+ if (compound.id !== void 0 && attributeOf(element, "id") !== compound.id) return false;
119
+ if (compound.classes.length > 0) {
120
+ const classes = classListOf(element);
121
+ if (!compound.classes.every((name) => classes.includes(name))) return false;
122
+ }
123
+ return compound.attributes.every((test) => matchesAttribute(element, test));
124
+ };
125
+ const matchesFrom = (selector, index, ancestry) => {
126
+ if (ancestry === void 0) return false;
127
+ const part = selector.parts[index];
128
+ if (part === void 0) return false;
129
+ if (!matchesCompound(ancestry.element, part.compound)) return false;
130
+ const left = selector.parts[index - 1];
131
+ if (left === void 0) return true;
132
+ switch (left.after ?? "descendant") {
133
+ case "child": return matchesFrom(selector, index - 1, ancestry.parent);
134
+ case "descendant":
135
+ for (let parent = ancestry.parent; parent !== void 0; parent = parent.parent) if (matchesFrom(selector, index - 1, parent)) return true;
136
+ return false;
137
+ case "adjacent": {
138
+ const sibling = ancestry.previous.at(-1);
139
+ return sibling === void 0 ? false : matchesFrom(selector, index - 1, {
140
+ element: sibling,
141
+ parent: ancestry.parent,
142
+ previous: ancestry.previous.slice(0, -1)
143
+ });
144
+ }
145
+ case "sibling":
146
+ for (let position = ancestry.previous.length - 1; position >= 0; position--) if (matchesFrom(selector, index - 1, {
147
+ element: ancestry.previous[position],
148
+ parent: ancestry.parent,
149
+ previous: ancestry.previous.slice(0, position)
150
+ })) return true;
151
+ return false;
152
+ }
153
+ };
154
+ /** Match a compiled selector against one element in its tree position. */
155
+ const matchesSelector = (selector, ancestry) => selector === void 0 || selector.parts.length === 0 ? false : matchesFrom(selector, selector.parts.length - 1, ancestry);
156
+ //#endregion
157
+ export { lacksGenericFallback as a, fontShorthandFamilies as i, matchesSelector as n, unquote as o, fontFallbackDiagnostic as r, classListOf as t };
package/dist/sms.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as EmailNode, t as EmailChild } from "./jsx-runtime-DC7xG0aA.js";
1
+ import { i as EmailNode, t as EmailChild } from "./jsx-runtime-DL8NVF1f.js";
2
2
  import { t as SmsCategory } from "./channel-specs-C2feJwuP.js";
3
3
  //#region src/sms.d.ts
4
4
  interface SmsProps {
package/dist/sms.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as jsx, n as Fragment } from "./jsx-runtime-CBlexoeR.js";
1
+ import { a as jsx, n as Fragment } from "./jsx-runtime-qL19u3Fy.js";
2
2
  //#region src/sms.ts
3
3
  /**
4
4
  * The root of an SMS body: plain text with bindings, conditionals and formatters. The compiler
@@ -1,14 +1,7 @@
1
- import { r as EmailNode } from "./jsx-runtime-DC7xG0aA.js";
1
+ import { i as EmailNode } from "./jsx-runtime-DL8NVF1f.js";
2
2
  import { r as InputSchema } from "./input-schema-7dPFVvNE.js";
3
3
  import { StandardJSONSchemaV1 } from "@standard-schema/spec";
4
4
  //#region src/template.d.ts
5
- /** One actual-input render owns all of a message's authored content. */
6
- interface EmailContent {
7
- readonly subject: string;
8
- readonly preheader?: string;
9
- readonly body: EmailNode;
10
- readonly text?: string;
11
- }
12
5
  /**
13
6
  * The one naming contract for a template id and a fixture key: a letter, a digit or `_` first,
14
7
  * then those plus `.`, `_` and `-`.
@@ -61,4 +54,4 @@ declare const defineTemplate: <S extends StandardJSONSchemaV1>(definition: {
61
54
  readonly whatsapp?: ((input: NoInfer<StandardJSONSchemaV1.InferInput<S>>) => EmailNode) | undefined;
62
55
  }) => SmlTemplate<StandardJSONSchemaV1.InferInput<S>>;
63
56
  //#endregion
64
- export { TEMPLATE_NAME_PATTERN as a, TEMPLATE_ID_PATTERN as i, EmailContent as n, TemplateInput as o, SmlTemplate as r, defineTemplate as s, EmailChannel as t };
57
+ export { TemplateInput as a, TEMPLATE_NAME_PATTERN as i, SmlTemplate as n, defineTemplate as o, TEMPLATE_ID_PATTERN as r, EmailChannel as t };
@@ -1,320 +1,7 @@
1
+ import { n as parseColor } from "./css-color-DLM12Osu.js";
1
2
  import { r as isDarkSchemeQuery } from "./dark-scheme-CbCwGDWH.js";
2
- import { generate, parse, walk } from "css-tree";
3
3
  import { Result } from "better-result";
4
- //#region src/email/css-color.ts
5
- const clamp = (value, low = 0, high = 1) => value < low ? low : value > high ? high : value;
6
- const toGamma = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - .055;
7
- const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4;
8
- /** OKLab is the interpolation space Tailwind mixes in, so conversions run both ways. */
9
- const oklabToRgb = (lightness, a, b, alpha) => {
10
- const l = (lightness + .3963377774 * a + .2158037573 * b) ** 3;
11
- const m = (lightness - .1055613458 * a - .0638541728 * b) ** 3;
12
- const s = (lightness - .0894841775 * a - 1.291485548 * b) ** 3;
13
- return {
14
- r: toGamma(4.0767416621 * l - 3.3077115913 * m + .2309699292 * s),
15
- g: toGamma(-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s),
16
- b: toGamma(-.0041960863 * l - .7034186147 * m + 1.707614701 * s),
17
- a: alpha
18
- };
19
- };
20
- const rgbToOklab = (color) => {
21
- const r = toLinear(color.r);
22
- const g = toLinear(color.g);
23
- const b = toLinear(color.b);
24
- const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
25
- const m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
26
- const s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
27
- return {
28
- L: .2104542553 * l + .793617785 * m - .0040720468 * s,
29
- a: 1.9779984951 * l - 2.428592205 * m + .4505937099 * s,
30
- b: .0259040371 * l + .7827717662 * m - .808675766 * s,
31
- alpha: color.a
32
- };
33
- };
34
- const hslToRgb = (hue, saturation, lightness, alpha) => {
35
- const h = (hue % 360 + 360) % 360;
36
- const c = (1 - Math.abs(2 * lightness - 1)) * saturation;
37
- const x = c * (1 - Math.abs(h / 60 % 2 - 1));
38
- const m = lightness - c / 2;
39
- const [r, g, b] = [
40
- [
41
- c,
42
- x,
43
- 0
44
- ],
45
- [
46
- x,
47
- c,
48
- 0
49
- ],
50
- [
51
- 0,
52
- c,
53
- x
54
- ],
55
- [
56
- 0,
57
- x,
58
- c
59
- ],
60
- [
61
- x,
62
- 0,
63
- c
64
- ],
65
- [
66
- c,
67
- 0,
68
- x
69
- ]
70
- ][Math.min(5, Math.floor(h / 60))];
71
- return {
72
- r: r + m,
73
- g: g + m,
74
- b: b + m,
75
- a: alpha
76
- };
77
- };
78
- /** The named colors an email template actually reaches for; anything else stays literal. */
79
- const NAMED = {
80
- transparent: "#00000000",
81
- black: "#000000",
82
- white: "#ffffff",
83
- red: "#ff0000",
84
- green: "#008000",
85
- blue: "#0000ff",
86
- gray: "#808080",
87
- grey: "#808080",
88
- silver: "#c0c0c0",
89
- yellow: "#ffff00",
90
- orange: "#ffa500",
91
- purple: "#800080",
92
- navy: "#000080",
93
- teal: "#008080",
94
- olive: "#808000",
95
- maroon: "#800000",
96
- lime: "#00ff00",
97
- aqua: "#00ffff",
98
- cyan: "#00ffff",
99
- fuchsia: "#ff00ff",
100
- magenta: "#ff00ff"
101
- };
102
- const number = (token, reference = 1) => {
103
- const text = token.trim();
104
- if (text === "none") return 0;
105
- if (text.endsWith("%")) {
106
- const value = Number.parseFloat(text.slice(0, -1));
107
- return Number.isFinite(value) ? value / 100 * reference : void 0;
108
- }
109
- const value = Number.parseFloat(text);
110
- return Number.isFinite(value) ? value : void 0;
111
- };
112
- const angle = (token) => {
113
- const text = token.trim();
114
- const value = Number.parseFloat(text);
115
- if (!Number.isFinite(value)) return void 0;
116
- if (text.endsWith("turn")) return value * 360;
117
- if (text.endsWith("rad")) return value * 180 / Math.PI;
118
- if (text.endsWith("grad")) return value * .9;
119
- return value;
120
- };
121
- /** Split a function's arguments on top-level commas and `/`, keeping nested calls intact. */
122
- const argumentTokens = (body) => {
123
- const parts = [];
124
- let alpha;
125
- let depth = 0;
126
- let current = "";
127
- let afterSlash = false;
128
- for (const character of body) {
129
- if (character === "(") depth += 1;
130
- if (character === ")") depth -= 1;
131
- if (depth === 0 && (character === "," || character === " " || character === "/")) {
132
- if (character === "/") {
133
- if (current.trim() !== "") parts.push(current.trim());
134
- current = "";
135
- afterSlash = true;
136
- continue;
137
- }
138
- if (current.trim() !== "") {
139
- if (afterSlash) alpha = current.trim();
140
- else parts.push(current.trim());
141
- }
142
- current = "";
143
- continue;
144
- }
145
- current += character;
146
- }
147
- if (current.trim() !== "") {
148
- if (afterSlash) alpha = current.trim();
149
- else parts.push(current.trim());
150
- }
151
- return alpha === void 0 ? { parts } : {
152
- parts,
153
- alpha
154
- };
155
- };
156
- /** Read one function call at the start of `value`, returning its name and body. */
157
- const functionCall = (value) => {
158
- const match = /^([a-zA-Z-]+)\(/.exec(value.trim());
159
- if (match === null) return void 0;
160
- const text = value.trim();
161
- let depth = 0;
162
- for (let index = match[0].length - 1; index < text.length; index++) if (text[index] === "(") depth += 1;
163
- else if (text[index] === ")") {
164
- depth -= 1;
165
- if (depth === 0) return {
166
- name: match[1].toLowerCase(),
167
- body: text.slice(match[0].length, index)
168
- };
169
- }
170
- };
171
- /** Parse any supported color notation into extended sRGB. `undefined` means "leave it alone". */
172
- const parseColor = (value) => {
173
- const text = value.trim();
174
- const named = NAMED[text.toLowerCase()];
175
- if (named !== void 0) return parseColor(named);
176
- if (text.startsWith("#")) {
177
- const hex = text.slice(1);
178
- const expand = (part) => Number.parseInt(part.repeat(2 / part.length), 16) / 255;
179
- if (hex.length === 3 || hex.length === 4) return {
180
- r: expand(hex[0]),
181
- g: expand(hex[1]),
182
- b: expand(hex[2]),
183
- a: hex.length === 4 ? expand(hex[3]) : 1
184
- };
185
- if (hex.length === 6 || hex.length === 8) return {
186
- r: Number.parseInt(hex.slice(0, 2), 16) / 255,
187
- g: Number.parseInt(hex.slice(2, 4), 16) / 255,
188
- b: Number.parseInt(hex.slice(4, 6), 16) / 255,
189
- a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1
190
- };
191
- return;
192
- }
193
- const call = functionCall(text);
194
- if (call === void 0) return void 0;
195
- const { parts, alpha } = argumentTokens(call.body);
196
- const opacity = alpha === void 0 ? 1 : number(alpha) ?? 1;
197
- switch (call.name) {
198
- case "rgb":
199
- case "rgba": {
200
- const [r, g, b, legacy] = parts;
201
- if (r === void 0 || g === void 0 || b === void 0) return void 0;
202
- const channel = (token) => {
203
- const parsed = number(token, 255);
204
- return parsed === void 0 ? void 0 : parsed / 255;
205
- };
206
- const [red, green, blue] = [
207
- channel(r),
208
- channel(g),
209
- channel(b)
210
- ];
211
- if (red === void 0 || green === void 0 || blue === void 0) return void 0;
212
- return {
213
- r: red,
214
- g: green,
215
- b: blue,
216
- a: legacy === void 0 ? opacity : number(legacy) ?? 1
217
- };
218
- }
219
- case "hsl":
220
- case "hsla": {
221
- const [h, s, l, legacy] = parts;
222
- if (h === void 0 || s === void 0 || l === void 0) return void 0;
223
- const hue = angle(h);
224
- const saturation = number(s);
225
- const lightness = number(l);
226
- if (hue === void 0 || saturation === void 0 || lightness === void 0) return void 0;
227
- return hslToRgb(hue, saturation, lightness, legacy === void 0 ? opacity : number(legacy) ?? 1);
228
- }
229
- case "oklch": {
230
- const [l, c, h] = parts;
231
- if (l === void 0 || c === void 0 || h === void 0) return void 0;
232
- const lightness = number(l);
233
- const chroma = number(c, .4);
234
- const hue = angle(h);
235
- if (lightness === void 0 || chroma === void 0 || hue === void 0) return void 0;
236
- const radians = hue * Math.PI / 180;
237
- return oklabToRgb(lightness, chroma * Math.cos(radians), chroma * Math.sin(radians), opacity);
238
- }
239
- case "oklab": {
240
- const [l, a, b] = parts;
241
- if (l === void 0 || a === void 0 || b === void 0) return void 0;
242
- const lightness = number(l);
243
- const green = number(a, .4);
244
- const blue = number(b, .4);
245
- if (lightness === void 0 || green === void 0 || blue === void 0) return void 0;
246
- return oklabToRgb(lightness, green, blue, opacity);
247
- }
248
- case "color-mix": return mixColors(call.body);
249
- default: return;
250
- }
251
- };
252
- /** `color-mix(in <space>, <color> <pct>?, <color> <pct>?)` — Tailwind's opacity modifier. */
253
- const mixColors = (body) => {
254
- const stops = body.split(",").map((part) => part.trim());
255
- const [space, first, second] = stops;
256
- if (space === void 0 || first === void 0 || second === void 0 || stops.length !== 3) return void 0;
257
- const method = /^in\s+([a-z-]+)/i.exec(space)?.[1]?.toLowerCase();
258
- if (method === void 0) return void 0;
259
- const stop = (text) => {
260
- const percentage = /\s([\d.]+)%$/.exec(text);
261
- const color = parseColor(percentage === null ? text : text.slice(0, percentage.index));
262
- if (color === void 0) return void 0;
263
- return {
264
- color,
265
- weight: percentage === null ? void 0 : Number.parseFloat(percentage[1]) / 100
266
- };
267
- };
268
- const left = stop(first);
269
- const right = stop(second);
270
- if (left === void 0 || right === void 0) return void 0;
271
- const leftWeight = left.weight ?? (right.weight === void 0 ? .5 : 1 - right.weight);
272
- const total = leftWeight + (right.weight ?? 1 - leftWeight);
273
- if (total === 0) return void 0;
274
- const p = leftWeight / total;
275
- const alpha = left.color.a * p + right.color.a * (1 - p);
276
- const mix = (l, r) => alpha === 0 ? 0 : (l * left.color.a * p + r * right.color.a * (1 - p)) / alpha;
277
- if (method === "srgb") return {
278
- r: mix(left.color.r, right.color.r),
279
- g: mix(left.color.g, right.color.g),
280
- b: mix(left.color.b, right.color.b),
281
- a: alpha
282
- };
283
- const l = rgbToOklab(left.color);
284
- const r = rgbToOklab(right.color);
285
- const channel = (a, b) => alpha === 0 ? 0 : (a * left.color.a * p + b * right.color.a * (1 - p)) / alpha;
286
- return oklabToRgb(channel(l.L, r.L), channel(l.a, r.a), channel(l.b, r.b), alpha);
287
- };
288
- const byte = (channel) => Math.round(clamp(channel) * 255).toString(16).padStart(2, "0");
289
- /** sRGB hex, or `rgba()` when the color is not opaque. */
290
- const formatColor = (color) => color.a >= 1 ? `#${byte(color.r)}${byte(color.g)}${byte(color.b)}` : `rgba(${Math.round(clamp(color.r) * 255)},${Math.round(clamp(color.g) * 255)},${Math.round(clamp(color.b) * 255)},${Math.round(clamp(color.a) * 1e3) / 1e3})`;
291
- const MODERN_COLOR = /\b(oklch|oklab|color-mix|lab|lch)\(/i;
292
- /** Rewrite every modern color function in a declaration value; other text is untouched. */
293
- const downlevelColors = (value) => {
294
- if (!MODERN_COLOR.test(value)) return value;
295
- let result = "";
296
- let rest = value;
297
- while (rest.length > 0) {
298
- const match = MODERN_COLOR.exec(rest);
299
- if (match === null || match.index === void 0) {
300
- result += rest;
301
- break;
302
- }
303
- result += rest.slice(0, match.index);
304
- const remainder = rest.slice(match.index);
305
- const call = functionCall(remainder);
306
- if (call === void 0) {
307
- result += remainder;
308
- break;
309
- }
310
- const consumed = remainder.indexOf("(") + call.body.length + 2;
311
- const color = parseColor(remainder.slice(0, consumed));
312
- result += color === void 0 ? remainder.slice(0, consumed) : formatColor(color);
313
- rest = remainder.slice(consumed);
314
- }
315
- return result;
316
- };
317
- //#endregion
4
+ import { generate, parse, walk } from "css-tree";
318
5
  //#region src/theme-css.ts
319
6
  /** Theme-variable namespaces a brand may set: Tailwind's, minus anything that is not a look. */
320
7
  const BRAND_NAMESPACES = [
@@ -644,4 +331,4 @@ const foregroundDefaults = (light, dark) => {
644
331
  };
645
332
  };
646
333
  //#endregion
647
- export { THEME_COLOR_ROLES as a, foregroundDefaults as c, downlevelColors as d, formatColor as f, THEME_COLORS as i, parseBrandCss as l, DEFAULT_THEME_CSS as n, THEME_TOKENS as o, parseColor as p, FILL_COLORS as r, contrastForeground as s, DEFAULT_THEME as t, printBrandCss as u };
334
+ export { THEME_COLOR_ROLES as a, foregroundDefaults as c, THEME_COLORS as i, parseBrandCss as l, DEFAULT_THEME_CSS as n, THEME_TOKENS as o, FILL_COLORS as r, contrastForeground as s, DEFAULT_THEME as t, printBrandCss as u };
@@ -1,4 +1,4 @@
1
- import { r as EmailNode, t as EmailChild } from "./jsx-runtime-DC7xG0aA.js";
1
+ import { i as EmailNode, t as EmailChild } from "./jsx-runtime-DL8NVF1f.js";
2
2
  import { n as WhatsAppCategory, r as WhatsAppHeaderType } from "./channel-specs-C2feJwuP.js";
3
3
  //#region src/whatsapp.d.ts
4
4
  interface WhatsAppProps {