@samva/markup 0.16.0 → 0.17.1
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.
- package/AGENTS.md +42 -40
- package/CHANGELOG.md +72 -0
- package/README.md +10 -9
- package/dist/{brand-jFV6YtSV.d.ts → assets-CS5Xa_mr.d.ts} +41 -3
- package/dist/{brand-Ci05p37c.js → brand-CIiEqkU7.js} +64 -3
- package/dist/{brand-plugin-CHhcei-j.d.ts → brand-plugin-Dj9SMpfS.d.ts} +3 -1
- package/dist/brand.d.ts +2 -3
- package/dist/brand.js +3 -4
- package/dist/compatibility.d.ts +76 -0
- package/dist/compatibility.js +807 -0
- package/dist/compiled-template-Bc4Dq9zy.d.ts +268 -0
- package/dist/compiler.d.ts +59 -193
- package/dist/compiler.js +473 -775
- package/dist/{components-3XHjYD3i.js → components-C5850-Qg.js} +37 -29
- package/dist/css-B_aVKrDo.js +436 -0
- package/dist/css-color-DLM12Osu.js +315 -0
- package/dist/{diagnostic-codes-8XDLR9ef.js → diagnostic-codes-DZKb0Xcg.js} +40 -1
- package/dist/{diagnostics-CUYlQ_6x.d.ts → diagnostics-D4vSLQrO.d.ts} +1 -10
- package/dist/diagnostics.d.ts +6 -2
- package/dist/diagnostics.js +3 -4
- package/dist/edit.d.ts +10 -9
- package/dist/edit.js +12 -11
- package/dist/email/jsx-dev-runtime.d.ts +1 -1
- package/dist/email/jsx-dev-runtime.js +1 -1
- package/dist/email/jsx-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.js +2 -2
- package/dist/email.d.ts +1 -1
- package/dist/email.js +1 -1
- package/dist/html-DJUK2YJF.js +483 -0
- package/dist/index.d.ts +1 -1
- package/dist/{jsx-runtime-DC7xG0aA.d.ts → jsx-runtime-DL8NVF1f.d.ts} +12 -6
- package/dist/{jsx-runtime-CBlexoeR.js → jsx-runtime-qL19u3Fy.js} +17 -10
- package/dist/preview-rNoJBEIY.d.ts +104 -0
- package/dist/{profile-BsY4dkan.js → profile-JTvgCD6X.js} +714 -29
- package/dist/{html-BvRFepeO.js → render-template-DwPDm3Hq.js} +155 -572
- package/dist/render.d.ts +40 -28
- package/dist/render.js +3 -47
- package/dist/selectors-CYWCoxZr.js +157 -0
- package/dist/sms.d.ts +1 -1
- package/dist/sms.js +1 -1
- package/dist/{template-B-UDXK8a.d.ts → template-DmNcoH4Q.d.ts} +2 -9
- package/dist/{theme-css-s9QwD5Ko.js → theme-css-P9F5S7Ic.js} +3 -316
- package/dist/whatsapp.d.ts +1 -1
- package/dist/whatsapp.js +1 -1
- package/generated/contract.json +7 -2
- package/generated/reference.md +3 -2
- package/package.json +5 -5
- package/skills/sml/SKILL.md +2 -2
- package/dist/assets-BSpgZOSR.js +0 -61
- package/dist/assets-BSuNT9gr.d.ts +0 -39
- package/dist/brand-footer-ClRtWnIc.d.ts +0 -40
- package/dist/brand-footer-DXVGsKE5.js +0 -746
- package/dist/brand-sample.d.ts +0 -4
- package/dist/brand-sample.js +0 -2
- package/dist/cascade-DoLWZ66g.js +0 -701
- package/dist/diagnostic-model-DJtqbETd.js +0 -36
- package/dist/ir-KJLFW08u.js +0 -5
- package/dist/render-CVnXZMyK.js +0 -258
- package/dist/render-CuZej25A.d.ts +0 -564
- package/dist/theme-imports-u_P0ygvQ.js +0 -102
package/dist/render.d.ts
CHANGED
|
@@ -1,34 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
/**
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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 {
|
|
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 {
|
|
2
|
-
import { i as
|
|
3
|
-
|
|
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
package/dist/sms.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
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 };
|
package/dist/whatsapp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|