@samva/markup 0.7.0 → 0.8.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.
- package/AGENTS.md +14 -0
- package/dist/brand-DcoVkh_S.d.ts +88 -0
- package/dist/{catalog-60eaYC7b.d.ts → catalog-CKuOooB8.d.ts} +13 -15
- package/dist/{checks-DBweSIPN.js → checks-D73Xizbw.js} +414 -2
- package/dist/checks.js +1 -1
- package/dist/classes.d.ts +2 -3
- package/dist/compiler.d.ts +62 -45
- package/dist/compiler.js +179 -857
- package/dist/{corpus-BjYMqOoY.d.ts → corpus-CPPfgAHN.d.ts} +14 -14
- package/dist/{css-COcFOT1y.d.ts → css-CS2jLID6.d.ts} +28 -2
- package/dist/email/assets.d.ts +40 -0
- package/dist/email/assets.js +63 -0
- package/dist/email/brand-footer.d.ts +34 -0
- package/dist/email/brand-footer.js +62 -0
- package/dist/email/brand.d.ts +2 -0
- package/dist/email/brand.js +148 -0
- package/dist/email/html.d.ts +2 -42
- package/dist/email/html.js +11 -0
- package/dist/email/render.d.ts +12 -5
- package/dist/email/render.js +2 -5
- package/dist/email/selection.d.ts +2 -91
- package/dist/email/stylesheets.d.ts +1 -1
- package/dist/email/stylesheets.js +3 -0
- package/dist/html-Bl9BAdqc.d.ts +42 -0
- package/dist/{ops-2aiiYO7m.js → ops-BvwqHl7V.js} +2 -2
- package/dist/ops.js +1 -1
- package/dist/registry-Dcp6QEK_.d.ts +73 -0
- package/dist/registry.d.ts +2 -42
- package/dist/{render-aaJBnsXK.js → render-vAv_4r9v.js} +174 -17
- package/dist/selection-CCArx9lU.d.ts +91 -0
- package/dist/{showcase-DGZAWn4Z.d.ts → showcase-egkusQBf.d.ts} +15 -56
- package/dist/{presets → starters}/category-mapping.d.ts +5 -5
- package/dist/{presets → starters}/category-mapping.js +5 -5
- package/dist/starters/corpus.d.ts +2 -0
- package/dist/{presets → starters}/corpus.js +15 -15
- package/dist/starters/library.d.ts +18 -0
- package/dist/starters/library.js +692 -0
- package/dist/{presets → starters}/public-gallery.d.ts +47 -47
- package/dist/{presets → starters}/public-gallery.js +92 -92
- package/dist/{presets → starters}/public-handoff.d.ts +14 -14
- package/dist/{presets → starters}/public-handoff.js +40 -40
- package/dist/{presets → starters}/public-preview-artifacts.d.ts +4 -4
- package/dist/{presets → starters}/public-preview-artifacts.js +15 -15
- package/dist/{presets → starters}/public-preview.d.ts +1 -1
- package/dist/{presets → starters}/public-preview.js +3 -3
- package/dist/starters/showcase.d.ts +2 -0
- package/dist/starters/showcase.js +335 -0
- package/dist/starters/sources.d.ts +45 -0
- package/dist/{presets → starters}/sources.js +1951 -272
- package/dist/starters/theme.d.ts +45 -0
- package/dist/starters/theme.js +77 -0
- package/dist/starters.d.ts +2 -0
- package/dist/{presets.js → starters.js} +4 -11
- package/dist/tailwind-BYq7Lo6u.js +727 -0
- package/dist/theme-css-hFqcPSar.js +587 -0
- package/dist/theme-css.d.ts +56 -15
- package/dist/theme-css.js +2 -509
- package/dist/tsx-tracer.js +1 -1
- package/dist/unsubscribe-link-CoesnQDU.js +26 -0
- package/package.json +53 -41
- package/samva-brand.d.ts +14 -0
- package/dist/footer.d.ts +0 -66
- package/dist/footer.js +0 -93
- package/dist/presets/corpus.d.ts +0 -2
- package/dist/presets/showcase.d.ts +0 -2
- package/dist/presets/showcase.js +0 -627
- package/dist/presets/sources.d.ts +0 -38
- package/dist/presets.d.ts +0 -2
- package/dist/theme-6ApjjiBo.d.ts +0 -116
- package/dist/theme.d.ts +0 -2
- package/dist/theme.js +0 -118
- package/dist/tokens.d.ts +0 -6
- package/dist/tokens.js +0 -417
|
@@ -1,91 +1,2 @@
|
|
|
1
|
-
import { i as
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The attribute a preview stamps so a click in a rendered document resolves to
|
|
5
|
-
* exactly one rendered element. A delivered message never carries it.
|
|
6
|
-
*/
|
|
7
|
-
declare const INSTANCE_PATH_ATTRIBUTE = "data-samva-instance";
|
|
8
|
-
/** Where one element sits in the emitted document, with the authoring that produced it. */
|
|
9
|
-
interface EmittedPosition {
|
|
10
|
-
readonly start: number;
|
|
11
|
-
readonly end: number;
|
|
12
|
-
readonly origins: ReadonlyArray<JsxSourceLocation>;
|
|
13
|
-
/**
|
|
14
|
-
* Dot-separated element indices from the document root, assigned as the
|
|
15
|
-
* document is written. It is the identity of one rendered instance.
|
|
16
|
-
*/
|
|
17
|
-
readonly instancePath: string;
|
|
18
|
-
readonly tag: string;
|
|
19
|
-
/** False when a compiler primitive generated this element. */
|
|
20
|
-
readonly authored: boolean;
|
|
21
|
-
}
|
|
22
|
-
/** One rendered element, and the authoring it came from. */
|
|
23
|
-
interface EmailElementSelection {
|
|
24
|
-
/**
|
|
25
|
-
* Dot-separated element indices from the document root. It is the identity
|
|
26
|
-
* of one rendered instance, which is what tells two iterations of the same
|
|
27
|
-
* authored element apart: their origins are identical.
|
|
28
|
-
*/
|
|
29
|
-
readonly instancePath: string;
|
|
30
|
-
readonly tag: string;
|
|
31
|
-
/**
|
|
32
|
-
* The authored JSX element first, then each enclosing component call site,
|
|
33
|
-
* innermost first. Empty when the compiler generated this element with no
|
|
34
|
-
* authoring on the stack — the hidden preheader block, for instance.
|
|
35
|
-
*/
|
|
36
|
-
readonly origins: ReadonlyArray<JsxSourceLocation>;
|
|
37
|
-
/**
|
|
38
|
-
* False when a compiler primitive generated this element. `origins[0]` is
|
|
39
|
-
* then the authoring element that asked for it rather than this element
|
|
40
|
-
* itself, so the wrapper selects the primitive the author wrote.
|
|
41
|
-
*/
|
|
42
|
-
readonly authored: boolean;
|
|
43
|
-
/** 1-based position among the rendered elements sharing this origin chain and tag. */
|
|
44
|
-
readonly occurrence: number;
|
|
45
|
-
/**
|
|
46
|
-
* How many rendered elements share that chain and tag. Above one, one piece
|
|
47
|
-
* of authoring produced several rendered elements — a loop over a component,
|
|
48
|
-
* or a primitive that generates a repeated cell — so an edit there changes
|
|
49
|
-
* all of them.
|
|
50
|
-
*/
|
|
51
|
-
readonly occurrences: number;
|
|
52
|
-
/** Absent for the document root. */
|
|
53
|
-
readonly parentPath?: string;
|
|
54
|
-
/** Offset range in the rendered HTML this selection was taken from. */
|
|
55
|
-
readonly start: number;
|
|
56
|
-
readonly end: number;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Turn one render's emitted positions into selections, in document order.
|
|
60
|
-
*
|
|
61
|
-
* The serializer records an element once its content is written, so positions
|
|
62
|
-
* arrive innermost-first; document order is what an outline and an ancestry
|
|
63
|
-
* walk both need.
|
|
64
|
-
*/
|
|
65
|
-
declare const emailSelections: (positions: readonly EmittedPosition[]) => readonly EmailElementSelection[];
|
|
66
|
-
/** Look selections up by the instance path a rendered document carries. */
|
|
67
|
-
declare const emailSelectionIndex: (selections: readonly EmailElementSelection[]) => ReadonlyMap<string, EmailElementSelection>;
|
|
68
|
-
/**
|
|
69
|
-
* The authoring element a selection edits. For an authored element that is the
|
|
70
|
-
* element itself; for a generated wrapper it is the primitive that asked for
|
|
71
|
-
* it. Undefined only where the compiler generated markup with no authoring on
|
|
72
|
-
* the stack, which nothing can edit in place.
|
|
73
|
-
*/
|
|
74
|
-
declare const emailAuthoringTarget: (selection: EmailElementSelection) => JsxSourceLocation | undefined;
|
|
75
|
-
/** Root-first ancestry of a selection, itself last. */
|
|
76
|
-
declare const emailSelectionAncestry: (index: ReadonlyMap<string, EmailElementSelection>, instancePath: string) => readonly EmailElementSelection[];
|
|
77
|
-
/**
|
|
78
|
-
* Every rendered instance produced through the same call site, in document
|
|
79
|
-
* order. One entry means the call ran once; several mean it repeated.
|
|
80
|
-
*/
|
|
81
|
-
declare const emailSelectionSiblings: (selections: readonly EmailElementSelection[], selection: EmailElementSelection) => readonly EmailElementSelection[];
|
|
82
|
-
/**
|
|
83
|
-
* Every rendered instance of the same authored element, across every call site
|
|
84
|
-
* that reaches it. A shared component edited at its definition changes all of
|
|
85
|
-
* these; edited at one call site it changes only `emailSelectionSiblings`. A
|
|
86
|
-
* control has to say which of the two it is doing before it applies.
|
|
87
|
-
*/
|
|
88
|
-
declare const emailDefinitionInstances: (selections: readonly EmailElementSelection[], selection: EmailElementSelection) => readonly EmailElementSelection[];
|
|
89
|
-
//#endregion
|
|
90
|
-
export { EmailElementSelection, EmittedPosition, INSTANCE_PATH_ATTRIBUTE, emailAuthoringTarget, emailDefinitionInstances, emailSelectionAncestry, emailSelectionIndex, emailSelectionSiblings, emailSelections };
|
|
91
|
-
//# sourceMappingURL=selection.d.ts.map
|
|
1
|
+
import { a as emailDefinitionInstances, c as emailSelectionSiblings, i as emailAuthoringTarget, l as emailSelections, n as EmittedPosition, o as emailSelectionAncestry, r as INSTANCE_PATH_ATTRIBUTE, s as emailSelectionIndex, t as EmailElementSelection } from "../selection-CCArx9lU.js";
|
|
2
|
+
export { EmailElementSelection, EmittedPosition, INSTANCE_PATH_ATTRIBUTE, emailAuthoringTarget, emailDefinitionInstances, emailSelectionAncestry, emailSelectionIndex, emailSelectionSiblings, emailSelections };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as Stylesheet } from "../css-
|
|
1
|
+
import { i as Stylesheet } from "../css-CS2jLID6.js";
|
|
2
2
|
//#region src/email/stylesheets.d.ts
|
|
3
3
|
/** Called by a compiler-generated stylesheet module at import time. */
|
|
4
4
|
declare const registerStylesheet: (sheet: Stylesheet) => void;
|
|
@@ -23,14 +23,17 @@ const collectStylesheets = () => {
|
|
|
23
23
|
})));
|
|
24
24
|
const variables = {};
|
|
25
25
|
const headAtRules = [];
|
|
26
|
+
const fontFaces = /* @__PURE__ */ new Map();
|
|
26
27
|
const diagnostics = registry.flatMap(({ sheet }) => sheet.diagnostics);
|
|
27
28
|
for (const { sheet } of registry) {
|
|
28
29
|
Object.assign(variables, sheet.variables);
|
|
29
30
|
headAtRules.push(...sheet.headAtRules);
|
|
31
|
+
for (const face of sheet.fontFaces) fontFaces.set(face.css, face);
|
|
30
32
|
}
|
|
31
33
|
return {
|
|
32
34
|
rules,
|
|
33
35
|
headAtRules,
|
|
36
|
+
fontFaces: [...fontFaces.values()],
|
|
34
37
|
variables,
|
|
35
38
|
diagnostics
|
|
36
39
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
//#region src/email/html.d.ts
|
|
5
|
+
/** Escape a value for element content. */
|
|
6
|
+
declare const escapeText: (value: string) => string;
|
|
7
|
+
/** Escape a value for a double-quoted attribute. */
|
|
8
|
+
declare const escapeAttribute: (value: string) => string;
|
|
9
|
+
/** camelCase to kebab-case; already-kebab names pass through unchanged. */
|
|
10
|
+
declare const kebab: (property: string) => string;
|
|
11
|
+
/** A bare number is pixels for every property that takes a length. */
|
|
12
|
+
declare const declarationValue: (property: string, value: string | number) => string;
|
|
13
|
+
interface EmailHtmlOutput {
|
|
14
|
+
readonly html: string;
|
|
15
|
+
readonly diagnostics: readonly EmailDiagnostic[];
|
|
16
|
+
/**
|
|
17
|
+
* Each emitted element's offset range and origins. A compatibility finding
|
|
18
|
+
* carries a position in the emitted document, and this is what turns that
|
|
19
|
+
* back into the authoring element the author can edit.
|
|
20
|
+
*/
|
|
21
|
+
readonly positions: readonly EmittedPosition[];
|
|
22
|
+
}
|
|
23
|
+
/** How this document will be consumed. Delivery is the default; export is opt-in. */
|
|
24
|
+
interface SerializeEmailOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Accept relative references. A local export serves assets from a relative
|
|
27
|
+
* base beside the file; a delivered message has no directory to resolve one
|
|
28
|
+
* against, so this stays off for anything that will be sent.
|
|
29
|
+
*/
|
|
30
|
+
readonly relativeUrls?: boolean | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Stamp every element with its instance path, so an editor preview can
|
|
33
|
+
* resolve a click in the rendered document to exactly one rendered element.
|
|
34
|
+
* A delivered message never carries it.
|
|
35
|
+
*/
|
|
36
|
+
readonly instancePaths?: boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
|
|
39
|
+
declare const serializeEmailHtml: (node: EmailNode, options?: SerializeEmailOptions) => EmailHtmlOutput;
|
|
40
|
+
//#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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { documentChannel } from "./ast.js";
|
|
2
2
|
import { CHANNEL_TAG_SPECS, allowedChildren, parseSml } from "./parser.js";
|
|
3
3
|
import { compileSml } from "./compile.js";
|
|
4
|
-
import { a as isBreakingCompatibilityCheck, r as checkSmlCompatibility } from "./checks-
|
|
4
|
+
import { a as isBreakingCompatibilityCheck, r as checkSmlCompatibility } from "./checks-D73Xizbw.js";
|
|
5
5
|
import { printSml } from "./printer.js";
|
|
6
6
|
import { Result, TaggedError } from "better-result";
|
|
7
7
|
//#region src/internal/sha256.ts
|
|
@@ -627,4 +627,4 @@ const applyOp = (source, op, options) => applyOps(source, [op], options);
|
|
|
627
627
|
//#endregion
|
|
628
628
|
export { sourceRevision as a, normalizeSml as i, applyOp as n, applyOps as r, SmlOpError as t };
|
|
629
629
|
|
|
630
|
-
//# sourceMappingURL=ops-
|
|
630
|
+
//# sourceMappingURL=ops-BvwqHl7V.js.map
|
package/dist/ops.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as sourceRevision, i as normalizeSml, n as applyOp, r as applyOps, t as SmlOpError } from "./ops-
|
|
1
|
+
import { a as sourceRevision, i as normalizeSml, n as applyOp, r as applyOps, t as SmlOpError } from "./ops-BvwqHl7V.js";
|
|
2
2
|
export { SmlOpError, applyOp, applyOps, normalizeSml, sourceRevision };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/theme.d.ts
|
|
2
|
+
interface TextToken {
|
|
3
|
+
readonly sizePx: number;
|
|
4
|
+
readonly lineHeightPx: number;
|
|
5
|
+
}
|
|
6
|
+
interface UtilityScale {
|
|
7
|
+
/** Flattened color names ("red-500", "white", "brand") → 6-digit lowercase hex. */
|
|
8
|
+
readonly colors: Readonly<Record<string, string>>;
|
|
9
|
+
/**
|
|
10
|
+
* Dark counterparts, colors namespace only (dark radius/fonts are not a
|
|
11
|
+
* thing). Only tokens with a dark pair appear; a color-family utility that
|
|
12
|
+
* resolves a paired token also emits the dark head rule automatically.
|
|
13
|
+
*/
|
|
14
|
+
readonly colorsDark: Readonly<Record<string, string>>;
|
|
15
|
+
/** px per spacing unit (`p-4` → 4 × spacingPx). */
|
|
16
|
+
readonly spacingPx: number;
|
|
17
|
+
/** text-{size} → font-size / line-height px pair. */
|
|
18
|
+
readonly text: Readonly<Record<string, TextToken>>;
|
|
19
|
+
/** leading-{name} → unitless line-height ratio. */
|
|
20
|
+
readonly leading: Readonly<Record<string, number>>;
|
|
21
|
+
/** tracking-{name} → letter-spacing em. */
|
|
22
|
+
readonly trackingEm: Readonly<Record<string, number>>;
|
|
23
|
+
/** rounded-{name} → border-radius px. */
|
|
24
|
+
readonly radiusPx: Readonly<Record<string, number>>;
|
|
25
|
+
/** font-{name} → font-weight number. */
|
|
26
|
+
readonly fontWeights: Readonly<Record<string, number>>;
|
|
27
|
+
/** font-{name} → the inline font-family stack, ending in a generic family. */
|
|
28
|
+
readonly fontFamilies: Readonly<Record<string, string>>;
|
|
29
|
+
/** max-w-{name} → px. */
|
|
30
|
+
readonly containerPx: Readonly<Record<string, number>>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/registry.d.ts
|
|
34
|
+
interface Declaration {
|
|
35
|
+
readonly property: string;
|
|
36
|
+
readonly value: string;
|
|
37
|
+
}
|
|
38
|
+
interface UtilityFamily {
|
|
39
|
+
readonly root: string;
|
|
40
|
+
/** Accepts `root` with no value ("border", "rounded", "block"). */
|
|
41
|
+
readonly bare?: ReadonlyArray<Declaration> | undefined;
|
|
42
|
+
/** Resolve a named value; null = this family does not accept it. */
|
|
43
|
+
readonly named?: ((value: string, theme: UtilityScale) => ReadonlyArray<Declaration> | null) | undefined;
|
|
44
|
+
/** Resolve an arbitrary `[...]` value; null = malformed for this family. */
|
|
45
|
+
readonly arbitrary?: ((value: string, theme: UtilityScale) => ReadonlyArray<Declaration> | null) | undefined;
|
|
46
|
+
/** dark: variant is only legal on color families. */
|
|
47
|
+
readonly colorFamily?: boolean | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Base-variant only: sm:/dark: are rejected. Set on the gradient family —
|
|
50
|
+
* its declarations are compiler-internal placeholders that must be combined
|
|
51
|
+
* per node, which head rules cannot express.
|
|
52
|
+
*/
|
|
53
|
+
readonly baseOnly?: boolean | undefined;
|
|
54
|
+
/** "warn" families compile but surface a lint warning. */
|
|
55
|
+
readonly warn?: string | undefined;
|
|
56
|
+
readonly description: string;
|
|
57
|
+
readonly caniemailSlugs: ReadonlyArray<string>;
|
|
58
|
+
}
|
|
59
|
+
interface ForbiddenUtility {
|
|
60
|
+
/** Exact class or `prefix-` match against the candidate base. */
|
|
61
|
+
readonly match: string;
|
|
62
|
+
readonly message: string;
|
|
63
|
+
}
|
|
64
|
+
declare const GRADIENT_DIRECTION_PROPERTY = "-sml-gradient-direction";
|
|
65
|
+
declare const GRADIENT_FROM_PROPERTY = "-sml-gradient-from";
|
|
66
|
+
declare const GRADIENT_TO_PROPERTY = "-sml-gradient-to";
|
|
67
|
+
declare const utilityRegistry: ReadonlyArray<UtilityFamily>;
|
|
68
|
+
declare const forbiddenUtilities: ReadonlyArray<ForbiddenUtility>;
|
|
69
|
+
/** Every root the resolver may try to split on, longest-match first (candidate.ts). */
|
|
70
|
+
declare const registryRoots: ReadonlySet<string>;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { GRADIENT_TO_PROPERTY as a, registryRoots as c, GRADIENT_FROM_PROPERTY as i, utilityRegistry as l, ForbiddenUtility as n, UtilityFamily as o, GRADIENT_DIRECTION_PROPERTY as r, forbiddenUtilities as s, Declaration as t, UtilityScale as u };
|
|
73
|
+
//# sourceMappingURL=registry-Dcp6QEK_.d.ts.map
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,42 +1,2 @@
|
|
|
1
|
-
import { s as
|
|
2
|
-
|
|
3
|
-
interface Declaration {
|
|
4
|
-
readonly property: string;
|
|
5
|
-
readonly value: string;
|
|
6
|
-
}
|
|
7
|
-
interface UtilityFamily {
|
|
8
|
-
readonly root: string;
|
|
9
|
-
/** Accepts `root` with no value ("border", "rounded", "block"). */
|
|
10
|
-
readonly bare?: ReadonlyArray<Declaration> | undefined;
|
|
11
|
-
/** Resolve a named value; null = this family does not accept it. */
|
|
12
|
-
readonly named?: ((value: string, theme: TokenTable) => ReadonlyArray<Declaration> | null) | undefined;
|
|
13
|
-
/** Resolve an arbitrary `[...]` value; null = malformed for this family. */
|
|
14
|
-
readonly arbitrary?: ((value: string, theme: TokenTable) => ReadonlyArray<Declaration> | null) | undefined;
|
|
15
|
-
/** dark: variant is only legal on color families. */
|
|
16
|
-
readonly colorFamily?: boolean | undefined;
|
|
17
|
-
/**
|
|
18
|
-
* Base-variant only: sm:/dark: are rejected. Set on the gradient family —
|
|
19
|
-
* its declarations are compiler-internal placeholders that must be combined
|
|
20
|
-
* per node, which head rules cannot express.
|
|
21
|
-
*/
|
|
22
|
-
readonly baseOnly?: boolean | undefined;
|
|
23
|
-
/** "warn" families compile but surface a lint warning. */
|
|
24
|
-
readonly warn?: string | undefined;
|
|
25
|
-
readonly description: string;
|
|
26
|
-
readonly caniemailSlugs: ReadonlyArray<string>;
|
|
27
|
-
}
|
|
28
|
-
interface ForbiddenUtility {
|
|
29
|
-
/** Exact class or `prefix-` match against the candidate base. */
|
|
30
|
-
readonly match: string;
|
|
31
|
-
readonly message: string;
|
|
32
|
-
}
|
|
33
|
-
declare const GRADIENT_DIRECTION_PROPERTY = "-sml-gradient-direction";
|
|
34
|
-
declare const GRADIENT_FROM_PROPERTY = "-sml-gradient-from";
|
|
35
|
-
declare const GRADIENT_TO_PROPERTY = "-sml-gradient-to";
|
|
36
|
-
declare const utilityRegistry: ReadonlyArray<UtilityFamily>;
|
|
37
|
-
declare const forbiddenUtilities: ReadonlyArray<ForbiddenUtility>;
|
|
38
|
-
/** Every root the resolver may try to split on, longest-match first (candidate.ts). */
|
|
39
|
-
declare const registryRoots: ReadonlySet<string>;
|
|
40
|
-
//#endregion
|
|
41
|
-
export { Declaration, ForbiddenUtility, GRADIENT_DIRECTION_PROPERTY, GRADIENT_FROM_PROPERTY, GRADIENT_TO_PROPERTY, UtilityFamily, forbiddenUtilities, registryRoots, utilityRegistry };
|
|
42
|
-
//# sourceMappingURL=registry.d.ts.map
|
|
1
|
+
import { a as GRADIENT_TO_PROPERTY, c as registryRoots, i as GRADIENT_FROM_PROPERTY, l as utilityRegistry, n as ForbiddenUtility, o as UtilityFamily, r as GRADIENT_DIRECTION_PROPERTY, s as forbiddenUtilities, t as Declaration } from "./registry-Dcp6QEK_.js";
|
|
2
|
+
export { Declaration, ForbiddenUtility, GRADIENT_DIRECTION_PROPERTY, GRADIENT_FROM_PROPERTY, GRADIENT_TO_PROPERTY, UtilityFamily, forbiddenUtilities, registryRoots, utilityRegistry };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
2
|
import { EmailNode, drainEmailDiagnostics } from "./email/jsx-runtime.js";
|
|
3
|
-
import { declarationValue, kebab, serializeEmailHtml } from "./email/html.js";
|
|
3
|
+
import { declarationValue, escapeAttribute, kebab, serializeEmailHtml } from "./email/html.js";
|
|
4
4
|
import { PREHEADER_ATTRIBUTE, preheaderNode } from "./email/components.js";
|
|
5
5
|
import { EmailCompileError, hasBlockingDiagnostic } from "./email/diagnostics.js";
|
|
6
6
|
import { snapshotStylesheets } from "./email/stylesheets.js";
|
|
@@ -192,6 +192,95 @@ const renderHeadCss = (rules, headAtRules, variables) => {
|
|
|
192
192
|
return blocks.join("");
|
|
193
193
|
};
|
|
194
194
|
//#endregion
|
|
195
|
+
//#region src/email/font-stacks.ts
|
|
196
|
+
/** Generic families every email client understands as the last resort in a stack. */
|
|
197
|
+
const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
198
|
+
"serif",
|
|
199
|
+
"sans-serif",
|
|
200
|
+
"monospace",
|
|
201
|
+
"cursive",
|
|
202
|
+
"fantasy"
|
|
203
|
+
]);
|
|
204
|
+
/** One family name as written, without its quotes and with runs of spaces collapsed. */
|
|
205
|
+
const unquote = (value) => {
|
|
206
|
+
const trimmed = value.trim();
|
|
207
|
+
const quote = trimmed[0];
|
|
208
|
+
return (quote === "\"" || quote === "'") && trimmed.endsWith(quote) && trimmed.length >= 2 ? trimmed.slice(1, -1) : trimmed.replace(/\s+/g, " ");
|
|
209
|
+
};
|
|
210
|
+
/** The families of a `font-family` value, unquoted, in stack order. */
|
|
211
|
+
const fontStackFamilies = (value) => {
|
|
212
|
+
const families = [];
|
|
213
|
+
let start = 0;
|
|
214
|
+
let quote;
|
|
215
|
+
for (let index = 0; index < value.length; index++) {
|
|
216
|
+
const character = value[index];
|
|
217
|
+
if (quote !== void 0) {
|
|
218
|
+
if (character === quote) quote = void 0;
|
|
219
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
220
|
+
else if (character === ",") {
|
|
221
|
+
families.push(unquote(value.slice(start, index)));
|
|
222
|
+
start = index + 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
families.push(unquote(value.slice(start)));
|
|
226
|
+
return families.filter((family) => family !== "");
|
|
227
|
+
};
|
|
228
|
+
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;
|
|
229
|
+
/** Whitespace-separated tokens at the top level, keeping parentheses and quotes whole. */
|
|
230
|
+
const shorthandTokens = (value) => {
|
|
231
|
+
const tokens = [];
|
|
232
|
+
let current = "";
|
|
233
|
+
let depth = 0;
|
|
234
|
+
let quote;
|
|
235
|
+
for (const character of value.trim()) {
|
|
236
|
+
if (quote !== void 0) {
|
|
237
|
+
if (character === quote) quote = void 0;
|
|
238
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
239
|
+
else if (character === "(") depth++;
|
|
240
|
+
else if (character === ")") depth--;
|
|
241
|
+
else if (/\s/.test(character) && depth === 0) {
|
|
242
|
+
if (current !== "") tokens.push(current);
|
|
243
|
+
current = "";
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
current += character;
|
|
247
|
+
}
|
|
248
|
+
if (current !== "") tokens.push(current);
|
|
249
|
+
return tokens;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* The family list of a `font` shorthand: everything after the size (and its
|
|
253
|
+
* optional line height), or `undefined` for a system-font keyword that names
|
|
254
|
+
* no family.
|
|
255
|
+
*/
|
|
256
|
+
const fontShorthandFamilies = (value) => {
|
|
257
|
+
const tokens = shorthandTokens(value);
|
|
258
|
+
const size = tokens.findIndex((token) => FONT_SIZE.test(token.replace(/\/.*$/, "")));
|
|
259
|
+
if (size < 0) return void 0;
|
|
260
|
+
let next = size + 1;
|
|
261
|
+
const sizeToken = tokens[size];
|
|
262
|
+
if (sizeToken.endsWith("/")) next += 1;
|
|
263
|
+
else if (!sizeToken.includes("/") && tokens[next]?.startsWith("/")) next += tokens[next] === "/" ? 2 : 1;
|
|
264
|
+
const family = tokens.slice(next).join(" ");
|
|
265
|
+
return family === "" ? void 0 : family;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* True when a stack names a declared project family without ending in a
|
|
269
|
+
* generic. `declared` holds lowercased family names. A quoted `"sans-serif"`
|
|
270
|
+
* names a font called sans-serif, not the generic, so it does not count.
|
|
271
|
+
*/
|
|
272
|
+
const lacksGenericFallback = (value, declared) => {
|
|
273
|
+
const stack = value.replace(/\s*!important\s*$/i, "");
|
|
274
|
+
if (!fontStackFamilies(stack).some((family) => declared.has(family.toLowerCase()))) return false;
|
|
275
|
+
const last = stack.slice(stack.lastIndexOf(",") + 1).trim();
|
|
276
|
+
return !GENERIC_FAMILIES.has(last.toLowerCase());
|
|
277
|
+
};
|
|
278
|
+
const fontFallbackDiagnostic = (value) => ({
|
|
279
|
+
code: "email-font-fallback",
|
|
280
|
+
severity: "error",
|
|
281
|
+
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.`
|
|
282
|
+
});
|
|
283
|
+
//#endregion
|
|
195
284
|
//#region src/email/selectors.ts
|
|
196
285
|
const classListOf = (element) => {
|
|
197
286
|
const value = element.props.class ?? element.props.className;
|
|
@@ -260,6 +349,17 @@ const matchesFrom = (selector, index, ancestry) => {
|
|
|
260
349
|
const matchesSelector = (selector, ancestry) => selector === void 0 || selector.parts.length === 0 ? false : matchesFrom(selector, selector.parts.length - 1, ancestry);
|
|
261
350
|
//#endregion
|
|
262
351
|
//#region src/email/cascade.ts
|
|
352
|
+
const VARIABLE_REFERENCE = /var\(\s*--[\w-]+\s*(?:,[^()]*)?\)/g;
|
|
353
|
+
/**
|
|
354
|
+
* Compatibility markup a primitive emits (a Button's VML) and a presentational
|
|
355
|
+
* `bgcolor` attribute are not CSS, so no client resolves `var()` in them. Their
|
|
356
|
+
* theme variables resolve here, in the scope of the element they sit in, as a
|
|
357
|
+
* declaration on that element would.
|
|
358
|
+
*/
|
|
359
|
+
const resolveMarkupVariables = (markup, variables) => markup.replace(VARIABLE_REFERENCE, (reference) => {
|
|
360
|
+
const value = finalizeValue(reference, variables);
|
|
361
|
+
return value === reference ? reference : escapeAttribute(value);
|
|
362
|
+
});
|
|
263
363
|
/**
|
|
264
364
|
* Cascade rank as the tuple CSS actually sorts by: importance first, then
|
|
265
365
|
* whether the declaration is attached to the element itself, then layer, then
|
|
@@ -353,6 +453,14 @@ const applyStylesheet = (root, sheet, options = {}) => {
|
|
|
353
453
|
}
|
|
354
454
|
const conditionalWritingRules = retained.filter((rule) => rule.declarations.some((declaration) => declaration.property === "direction" || declaration.property === "writing-mode" || declaration.property === "all"));
|
|
355
455
|
const unmatched = /* @__PURE__ */ new Map();
|
|
456
|
+
const declaredFamilies = new Set(sheet.fontFaces.map((face) => face.family.toLowerCase()));
|
|
457
|
+
const fallbackFindings = /* @__PURE__ */ new Map();
|
|
458
|
+
const checkFallback = (property, value, origins) => {
|
|
459
|
+
if (declaredFamilies.size === 0) return;
|
|
460
|
+
const stack = property === "font-family" ? value : property === "font" ? fontShorthandFamilies(value) : void 0;
|
|
461
|
+
if (stack === void 0 || fallbackFindings.has(stack)) return;
|
|
462
|
+
if (lacksGenericFallback(stack, declaredFamilies)) fallbackFindings.set(stack, origins);
|
|
463
|
+
};
|
|
356
464
|
/**
|
|
357
465
|
* Children are visited with their own preceding-sibling list so `+`/`~` work.
|
|
358
466
|
* A fragment is not an element in the serialized document, so its children
|
|
@@ -371,6 +479,10 @@ const applyStylesheet = (root, sheet, options = {}) => {
|
|
|
371
479
|
return result;
|
|
372
480
|
}
|
|
373
481
|
if (child.type === "Fragment") return EmailNode.Fragment({ children: visit(child.children) });
|
|
482
|
+
if (child.type === "Raw" && child.html.includes("var(")) return EmailNode.Raw({
|
|
483
|
+
...child,
|
|
484
|
+
html: resolveMarkupVariables(child.html, variables)
|
|
485
|
+
});
|
|
374
486
|
return child;
|
|
375
487
|
});
|
|
376
488
|
return visit(children);
|
|
@@ -422,15 +534,21 @@ const applyStylesheet = (root, sheet, options = {}) => {
|
|
|
422
534
|
writingMode: winners.has("all") ? "unknown" : inheritedValue("writing-mode", inheritedWriting.writingMode, "horizontal-tb")
|
|
423
535
|
};
|
|
424
536
|
const emitted = [...winners].filter(([property]) => !property.startsWith("--")).sort(([, left], [, right]) => compareRank(left.key, right.key)).map(([property, entry]) => [property, finalizeValue(entry.value, variables)]);
|
|
537
|
+
for (const [property, value] of emitted) checkFallback(property, value, element.origins.slice(0, 1));
|
|
425
538
|
const physical = /* @__PURE__ */ new Map();
|
|
426
539
|
for (const [property, value] of emitted) for (const [name, lowered] of conditionalWriting ? [[property, value]] : physicalPadding(property, value, writing)) {
|
|
427
540
|
physical.delete(name);
|
|
428
541
|
physical.set(name, lowered);
|
|
429
542
|
}
|
|
543
|
+
const bgcolor = element.props.bgcolor;
|
|
544
|
+
const props = typeof bgcolor === "string" && bgcolor.includes("var(") ? {
|
|
545
|
+
...element.props,
|
|
546
|
+
bgcolor: finalizeValue(bgcolor, variables)
|
|
547
|
+
} : element.props;
|
|
430
548
|
return EmailNode.Element({
|
|
431
549
|
...element,
|
|
432
|
-
props: emitted.length === 0 ?
|
|
433
|
-
...
|
|
550
|
+
props: emitted.length === 0 ? props : {
|
|
551
|
+
...props,
|
|
434
552
|
style: Object.fromEntries(physical)
|
|
435
553
|
},
|
|
436
554
|
children: visitChildren(element.children, ancestry, variables, writing)
|
|
@@ -441,6 +559,11 @@ const applyStylesheet = (root, sheet, options = {}) => {
|
|
|
441
559
|
parent: void 0,
|
|
442
560
|
previous: []
|
|
443
561
|
}, sheet.variables, horizontalWriting) : root.type === "Fragment" ? EmailNode.Fragment({ children: visitChildren(root.children, void 0, sheet.variables, horizontalWriting) }) : root;
|
|
562
|
+
for (const rule of retained) for (const declaration of rule.declarations) checkFallback(declaration.property, finalizeValue(declaration.value, sheet.variables), []);
|
|
563
|
+
for (const [value, origins] of fallbackFindings) diagnostics.push({
|
|
564
|
+
...fontFallbackDiagnostic(value),
|
|
565
|
+
origins
|
|
566
|
+
});
|
|
444
567
|
for (const [name, origins] of unmatched) diagnostics.push({
|
|
445
568
|
code: "unmatched-class",
|
|
446
569
|
severity: "warning",
|
|
@@ -450,6 +573,7 @@ const applyStylesheet = (root, sheet, options = {}) => {
|
|
|
450
573
|
return {
|
|
451
574
|
tree,
|
|
452
575
|
headCss: renderHeadCss(retained, sheet.headAtRules, sheet.variables),
|
|
576
|
+
fontFaceCss: sheet.fontFaces.map((face) => face.css).join(""),
|
|
453
577
|
diagnostics
|
|
454
578
|
};
|
|
455
579
|
};
|
|
@@ -510,8 +634,21 @@ const withPreheader = (node, text) => {
|
|
|
510
634
|
};
|
|
511
635
|
return insert(node) ?? EmailNode.Fragment({ children: [preheaderNode(text), node] });
|
|
512
636
|
};
|
|
513
|
-
|
|
514
|
-
|
|
637
|
+
const headStyle = (css, origins) => EmailNode.Element({
|
|
638
|
+
tag: "style",
|
|
639
|
+
props: {},
|
|
640
|
+
children: [EmailNode.Text({ value: css })],
|
|
641
|
+
origins,
|
|
642
|
+
authored: false
|
|
643
|
+
});
|
|
644
|
+
/**
|
|
645
|
+
* Put the retained rules in a `<style>` at the end of `<head>`, and project
|
|
646
|
+
* `@font-face` rules in a sibling classic Outlook cannot see. Outlook for
|
|
647
|
+
* Windows registers a face it cannot load and then renders the family in Times
|
|
648
|
+
* New Roman, ignoring the stack; hidden, the family is merely missing and the
|
|
649
|
+
* stack's fallback applies.
|
|
650
|
+
*/
|
|
651
|
+
const withHeadCss = (node, css, fontFaceCss) => {
|
|
515
652
|
const insert = (current) => {
|
|
516
653
|
if (current.type === "Fragment") {
|
|
517
654
|
for (const [index, child] of current.children.entries()) {
|
|
@@ -523,13 +660,23 @@ const withHeadCss = (node, css) => {
|
|
|
523
660
|
if (current.type !== "Element") return void 0;
|
|
524
661
|
if (current.tag === "head") return EmailNode.Element({
|
|
525
662
|
...current,
|
|
526
|
-
children: [
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
663
|
+
children: [
|
|
664
|
+
...current.children,
|
|
665
|
+
...css === "" ? [] : [headStyle(css, current.origins)],
|
|
666
|
+
...fontFaceCss === "" ? [] : [
|
|
667
|
+
EmailNode.Raw({
|
|
668
|
+
html: "<!--[if !mso]><!-->",
|
|
669
|
+
text: "",
|
|
670
|
+
origins: []
|
|
671
|
+
}),
|
|
672
|
+
headStyle(fontFaceCss, current.origins),
|
|
673
|
+
EmailNode.Raw({
|
|
674
|
+
html: "<!--<![endif]-->",
|
|
675
|
+
text: "",
|
|
676
|
+
origins: []
|
|
677
|
+
})
|
|
678
|
+
]
|
|
679
|
+
]
|
|
533
680
|
});
|
|
534
681
|
for (const [index, child] of current.children.entries()) {
|
|
535
682
|
const replaced = insert(child);
|
|
@@ -551,8 +698,17 @@ const renderEmail = (template, data, options = {}) => renderEmailWith(snapshotSt
|
|
|
551
698
|
const bindStylesheets = (stylesheet = snapshotStylesheets()) => (template, data, options = {}) => renderEmailWith(stylesheet, template, data, options);
|
|
552
699
|
const renderEmailWith = (stylesheet, template, data, options) => {
|
|
553
700
|
if (!template.schema.validate(data)) throw new TypeError(`Invalid template input: ${JSON.stringify(template.schema.validate.errors)}`);
|
|
701
|
+
return renderEmailContent(stylesheet, () => template.render(data), options);
|
|
702
|
+
};
|
|
703
|
+
/**
|
|
704
|
+
* Render authored content against a stylesheet: inline what a client honors,
|
|
705
|
+
* keep the rest for `<head>`, and lower to HTML and text. `renderEmail` runs a
|
|
706
|
+
* validated template through it; a host that composes content without a
|
|
707
|
+
* template definition calls it directly.
|
|
708
|
+
*/
|
|
709
|
+
const renderEmailContent = (stylesheet, render, options = {}) => {
|
|
554
710
|
drainEmailDiagnostics();
|
|
555
|
-
const content =
|
|
711
|
+
const content = render();
|
|
556
712
|
const invalid = [];
|
|
557
713
|
const fail = (code, message) => invalid.push({
|
|
558
714
|
code,
|
|
@@ -570,12 +726,13 @@ const renderEmailWith = (stylesheet, template, data, options) => {
|
|
|
570
726
|
const styled = stylesheet === void 0 ? {
|
|
571
727
|
tree: base,
|
|
572
728
|
headCss: "",
|
|
729
|
+
fontFaceCss: "",
|
|
573
730
|
diagnostics: []
|
|
574
731
|
} : applyStylesheet(base, stylesheet);
|
|
575
732
|
const style = [...styled.diagnostics];
|
|
576
733
|
let tree = styled.tree;
|
|
577
|
-
if (styled.headCss !== "") {
|
|
578
|
-
const placed = withHeadCss(tree, styled.headCss);
|
|
734
|
+
if (styled.headCss !== "" || styled.fontFaceCss !== "") {
|
|
735
|
+
const placed = withHeadCss(tree, styled.headCss, styled.fontFaceCss);
|
|
579
736
|
if (placed === void 0) style.push({
|
|
580
737
|
code: "missing-head",
|
|
581
738
|
severity: "error",
|
|
@@ -604,6 +761,6 @@ const renderEmailWith = (stylesheet, template, data, options) => {
|
|
|
604
761
|
};
|
|
605
762
|
};
|
|
606
763
|
//#endregion
|
|
607
|
-
export {
|
|
764
|
+
export { renderEmailContent as a, matchesSelector as c, finalizeValue as d, foldCalc as f, renderEmail as i, unquote as l, resolveVariables as m, bindStylesheets as n, applyStylesheet as o, renderHeadCss as p, compileEmail as r, classListOf as s, EMAIL_DOCTYPE as t, downlevelMediaQuery as u };
|
|
608
765
|
|
|
609
|
-
//# sourceMappingURL=render-
|
|
766
|
+
//# sourceMappingURL=render-vAv_4r9v.js.map
|