@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
package/dist/theme-css.js
CHANGED
|
@@ -1,509 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const CHAR_TAB = 9;
|
|
4
|
-
const CHAR_LINE_FEED = 10;
|
|
5
|
-
const CHAR_CARRIAGE_RETURN = 13;
|
|
6
|
-
const CHAR_SPACE = 32;
|
|
7
|
-
const CHAR_STAR = 42;
|
|
8
|
-
const CHAR_HYPHEN = 45;
|
|
9
|
-
const CHAR_SLASH = 47;
|
|
10
|
-
const CHAR_COLON = 58;
|
|
11
|
-
const CHAR_SEMICOLON = 59;
|
|
12
|
-
const CHAR_AT = 64;
|
|
13
|
-
const CHAR_OPEN_PAREN = 40;
|
|
14
|
-
const CHAR_CLOSE_PAREN = 41;
|
|
15
|
-
const CHAR_OPEN_BRACE = 123;
|
|
16
|
-
const CHAR_CLOSE_BRACE = 125;
|
|
17
|
-
const CHAR_0 = 48;
|
|
18
|
-
const CHAR_9 = 57;
|
|
19
|
-
const CHAR_UPPER_A = 65;
|
|
20
|
-
const CHAR_UPPER_Z = 90;
|
|
21
|
-
const CHAR_LOWER_A = 97;
|
|
22
|
-
const CHAR_LOWER_Z = 122;
|
|
23
|
-
const isWhitespace = (code) => code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LINE_FEED || code === CHAR_CARRIAGE_RETURN;
|
|
24
|
-
const isIdentChar = (code) => code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z || code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z || code >= CHAR_0 && code <= CHAR_9 || code === CHAR_HYPHEN;
|
|
25
|
-
const HEX_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
26
|
-
const NUM = String.raw`(?:\d+(?:\.\d+)?|\.\d+)`;
|
|
27
|
-
const PX_PATTERN = new RegExp(`^(${NUM})px$`);
|
|
28
|
-
const TEXT_PATTERN = new RegExp(`^(${NUM})px(?:\\s*/\\s*(${NUM})px)?$`);
|
|
29
|
-
const UNITLESS_PATTERN = new RegExp(`^${NUM}$`);
|
|
30
|
-
const EM_PATTERN = new RegExp(`^(-?${NUM})em$`);
|
|
31
|
-
const INTEGER_PATTERN = /^\d+$/;
|
|
32
|
-
/** #rgb/#rrggbb → 6-digit lowercase, or null. */
|
|
33
|
-
const normalizeHex = (value) => {
|
|
34
|
-
const match = HEX_PATTERN.exec(value);
|
|
35
|
-
if (match === null) return null;
|
|
36
|
-
const digits = match[1].toLowerCase();
|
|
37
|
-
if (digits.length === 6) return `#${digits}`;
|
|
38
|
-
return `#${digits[0]}${digits[0]}${digits[1]}${digits[1]}${digits[2]}${digits[2]}`;
|
|
39
|
-
};
|
|
40
|
-
const pxNumber = (value) => {
|
|
41
|
-
const match = PX_PATTERN.exec(value);
|
|
42
|
-
return match === null ? null : Number.parseFloat(match[1]);
|
|
43
|
-
};
|
|
44
|
-
const NAMESPACE_SUMMARY = "supported namespaces: --color-*, --radius-*, --font-*, --font-weight-*, --text-*, --leading-*, --spacing, --container-*, --tracking-*";
|
|
45
|
-
const parseThemeCss = (css) => {
|
|
46
|
-
const length = css.length;
|
|
47
|
-
let position = 0;
|
|
48
|
-
const diagnostics = [];
|
|
49
|
-
const seen = /* @__PURE__ */ new Set();
|
|
50
|
-
const accumulator = {
|
|
51
|
-
colors: {},
|
|
52
|
-
colorsDark: {},
|
|
53
|
-
text: {},
|
|
54
|
-
leading: {},
|
|
55
|
-
radiusPx: {},
|
|
56
|
-
fontWeights: {},
|
|
57
|
-
fontFamilies: {},
|
|
58
|
-
containerPx: {},
|
|
59
|
-
tracking: {},
|
|
60
|
-
spacingPx: void 0
|
|
61
|
-
};
|
|
62
|
-
let themeBlockCount = 0;
|
|
63
|
-
const report = (code, message, span) => {
|
|
64
|
-
diagnostics.push({
|
|
65
|
-
code,
|
|
66
|
-
message,
|
|
67
|
-
span
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
const skipTrivia = () => {
|
|
71
|
-
while (position < length) {
|
|
72
|
-
const code = css.charCodeAt(position);
|
|
73
|
-
if (isWhitespace(code)) {
|
|
74
|
-
position++;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (code === CHAR_SLASH && css.charCodeAt(position + 1) === CHAR_STAR) {
|
|
78
|
-
const start = position;
|
|
79
|
-
const close = css.indexOf("*/", position + 2);
|
|
80
|
-
if (close === -1) {
|
|
81
|
-
report("theme-unterminated-comment", "unterminated /* comment — add */", {
|
|
82
|
-
start,
|
|
83
|
-
end: length
|
|
84
|
-
});
|
|
85
|
-
position = length;
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
position = close + 2;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
/** `@word` at the current position, not followed by more ident chars. */
|
|
95
|
-
const atKeyword = (word) => css.startsWith(word, position) && !isIdentChar(css.charCodeAt(position + word.length));
|
|
96
|
-
/** Skips a balanced `{ ... }` region (position on or before the `{`). */
|
|
97
|
-
const skipBalancedBlock = () => {
|
|
98
|
-
while (position < length && css.charCodeAt(position) !== CHAR_OPEN_BRACE) position++;
|
|
99
|
-
let depth = 0;
|
|
100
|
-
while (position < length) {
|
|
101
|
-
const code = css.charCodeAt(position);
|
|
102
|
-
position++;
|
|
103
|
-
if (code === CHAR_OPEN_BRACE) depth++;
|
|
104
|
-
else if (code === CHAR_CLOSE_BRACE) {
|
|
105
|
-
depth--;
|
|
106
|
-
if (depth === 0) return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
/** Declaration-level recovery: past the next `;`, or up to the block's `}`. */
|
|
111
|
-
const skipToDeclarationBoundary = () => {
|
|
112
|
-
while (position < length) {
|
|
113
|
-
const code = css.charCodeAt(position);
|
|
114
|
-
if (code === CHAR_SEMICOLON) {
|
|
115
|
-
position++;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (code === CHAR_CLOSE_BRACE) return;
|
|
119
|
-
position++;
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const record = (target, scope, field, name, value, propertySpan, property) => {
|
|
123
|
-
const key = `${scope}:${field}:${name}`;
|
|
124
|
-
if (seen.has(key)) {
|
|
125
|
-
report("theme-duplicate-declaration", `duplicate declaration --${property} — declare each token once`, propertySpan);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
seen.add(key);
|
|
129
|
-
target[name] = value;
|
|
130
|
-
};
|
|
131
|
-
const parseDeclaration = (scope) => {
|
|
132
|
-
const propertyStart = position;
|
|
133
|
-
position += 2;
|
|
134
|
-
while (position < length && isIdentChar(css.charCodeAt(position))) position++;
|
|
135
|
-
const property = css.slice(propertyStart + 2, position);
|
|
136
|
-
const propertySpan = {
|
|
137
|
-
start: propertyStart,
|
|
138
|
-
end: position
|
|
139
|
-
};
|
|
140
|
-
skipTrivia();
|
|
141
|
-
if (css.charCodeAt(position) !== CHAR_COLON) {
|
|
142
|
-
report("theme-expected-declaration", `expected : after --${property}`, {
|
|
143
|
-
start: position,
|
|
144
|
-
end: Math.min(position + 1, length)
|
|
145
|
-
});
|
|
146
|
-
skipToDeclarationBoundary();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
position++;
|
|
150
|
-
skipTrivia();
|
|
151
|
-
const valueStart = position;
|
|
152
|
-
while (position < length) {
|
|
153
|
-
const code = css.charCodeAt(position);
|
|
154
|
-
if (code === CHAR_SEMICOLON || code === CHAR_CLOSE_BRACE || code === CHAR_OPEN_BRACE) break;
|
|
155
|
-
position++;
|
|
156
|
-
}
|
|
157
|
-
let valueEnd = position;
|
|
158
|
-
while (valueEnd > valueStart && isWhitespace(css.charCodeAt(valueEnd - 1))) valueEnd--;
|
|
159
|
-
const value = css.slice(valueStart, valueEnd).replace(/\s+/g, " ");
|
|
160
|
-
const valueSpan = {
|
|
161
|
-
start: valueStart,
|
|
162
|
-
end: Math.max(valueEnd, valueStart + 1)
|
|
163
|
-
};
|
|
164
|
-
if (css.charCodeAt(position) === CHAR_SEMICOLON) position++;
|
|
165
|
-
else report("theme-missing-semicolon", `missing ; after --${property} declaration`, propertySpan);
|
|
166
|
-
routeDeclaration(scope, property, propertySpan, value, valueSpan);
|
|
167
|
-
};
|
|
168
|
-
const invalidValue = (property, value, expected, span) => {
|
|
169
|
-
report("theme-invalid-value", `invalid value "${value}" for --${property} — ${expected}`, span);
|
|
170
|
-
};
|
|
171
|
-
const routeDeclaration = (scope, property, propertySpan, value, valueSpan) => {
|
|
172
|
-
const named = (prefix) => {
|
|
173
|
-
if (!property.startsWith(prefix)) return null;
|
|
174
|
-
const name = property.slice(prefix.length);
|
|
175
|
-
if (name.length === 0) {
|
|
176
|
-
report("theme-invalid-value", `--${property} is missing a token name — write --${prefix}<name>`, propertySpan);
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
if (name.includes("--")) {
|
|
180
|
-
report("theme-invalid-value", `malformed token name in --${property}${prefix === "text-" ? " — use the size/line-height shorthand instead: --text-xl: 20px/28px" : " — token names use single hyphens"}`, propertySpan);
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
return name;
|
|
184
|
-
};
|
|
185
|
-
if (value.length === 0) {
|
|
186
|
-
invalidValue(property, value, "the value is empty", valueSpan);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (property.startsWith("color-")) {
|
|
190
|
-
const name = named("color-");
|
|
191
|
-
if (name === null) return;
|
|
192
|
-
const hex = normalizeHex(value);
|
|
193
|
-
if (hex === null) {
|
|
194
|
-
invalidValue(property, value, "expected a hex color like #4f46e5 or #fff (named colors, oklch(), and rgb() are outside the closed grammar)", valueSpan);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (scope === "dark") record(accumulator.colorsDark, scope, "colors", name, hex, propertySpan, property);
|
|
198
|
-
else record(accumulator.colors, scope, "colors", name, hex, propertySpan, property);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (scope === "dark") {
|
|
202
|
-
report("theme-dark-non-color", `--${property} is not allowed in the dark theme block — only --color-* tokens have dark counterparts`, propertySpan);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (property.startsWith("radius-")) {
|
|
206
|
-
const name = named("radius-");
|
|
207
|
-
if (name === null) return;
|
|
208
|
-
const px = pxNumber(value);
|
|
209
|
-
if (px === null) {
|
|
210
|
-
invalidValue(property, value, "expected a px length like \"12px\"", valueSpan);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
record(accumulator.radiusPx, scope, "radiusPx", name, px, propertySpan, property);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (property.startsWith("font-weight-")) {
|
|
217
|
-
const name = named("font-weight-");
|
|
218
|
-
if (name === null) return;
|
|
219
|
-
const weight = INTEGER_PATTERN.test(value) ? Number.parseInt(value, 10) : null;
|
|
220
|
-
if (weight === null || weight < 100 || weight > 900) {
|
|
221
|
-
invalidValue(property, value, "expected a font weight between 100 and 900", valueSpan);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
record(accumulator.fontWeights, scope, "fontWeights", name, weight, propertySpan, property);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (property.startsWith("font-")) {
|
|
228
|
-
const name = named("font-");
|
|
229
|
-
if (name === null) return;
|
|
230
|
-
if (value.includes("var(") || value.includes("calc(") || value.includes("oklch(")) {
|
|
231
|
-
invalidValue(property, value, "expected a literal font stack — var()/calc() don't exist in compiled email", valueSpan);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const stack = value.replaceAll("\"", "'");
|
|
235
|
-
record(accumulator.fontFamilies, scope, "fontFamilies", name, stack, propertySpan, property);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (property.startsWith("text-")) {
|
|
239
|
-
const name = named("text-");
|
|
240
|
-
if (name === null) return;
|
|
241
|
-
const match = TEXT_PATTERN.exec(value);
|
|
242
|
-
if (match === null) {
|
|
243
|
-
invalidValue(property, value, "expected \"16px\" or \"16px/24px\" (font-size/line-height)", valueSpan);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const sizePx = Number.parseFloat(match[1]);
|
|
247
|
-
const lineHeightPx = match[2] === void 0 ? Math.round(sizePx * 1.5) : Number.parseFloat(match[2]);
|
|
248
|
-
record(accumulator.text, scope, "text", name, {
|
|
249
|
-
sizePx,
|
|
250
|
-
lineHeightPx
|
|
251
|
-
}, propertySpan, property);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (property.startsWith("leading-")) {
|
|
255
|
-
const name = named("leading-");
|
|
256
|
-
if (name === null) return;
|
|
257
|
-
if (!UNITLESS_PATTERN.test(value) || Number.parseFloat(value) <= 0) {
|
|
258
|
-
invalidValue(property, value, "expected a unitless line-height ratio like \"1.5\"", valueSpan);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
record(accumulator.leading, scope, "leading", name, Number.parseFloat(value), propertySpan, property);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (property.startsWith("tracking-")) {
|
|
265
|
-
const name = named("tracking-");
|
|
266
|
-
if (name === null) return;
|
|
267
|
-
const match = EM_PATTERN.exec(value);
|
|
268
|
-
if (match === null) {
|
|
269
|
-
invalidValue(property, value, "expected an em value like \"0.025em\" or \"-0.05em\"", valueSpan);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
record(accumulator.tracking, scope, "tracking", name, Number.parseFloat(match[1]), propertySpan, property);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (property.startsWith("container-")) {
|
|
276
|
-
const name = named("container-");
|
|
277
|
-
if (name === null) return;
|
|
278
|
-
const px = pxNumber(value);
|
|
279
|
-
if (px === null) {
|
|
280
|
-
invalidValue(property, value, "expected a px length like \"600px\"", valueSpan);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
record(accumulator.containerPx, scope, "containerPx", name, px, propertySpan, property);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (property === "spacing") {
|
|
287
|
-
const px = pxNumber(value);
|
|
288
|
-
if (px === null || px <= 0) {
|
|
289
|
-
invalidValue(property, value, "expected a positive px length like \"4px\"", valueSpan);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const key = "light:spacingPx";
|
|
293
|
-
if (seen.has(key)) {
|
|
294
|
-
report("theme-duplicate-declaration", "duplicate declaration --spacing — declare each token once", propertySpan);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
seen.add(key);
|
|
298
|
-
accumulator.spacingPx = px;
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (property.startsWith("spacing-")) {
|
|
302
|
-
report("theme-unknown-namespace", `--${property} is not supported — only the bare --spacing multiplier is (spacing steps derive from it)`, propertySpan);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
report("theme-unknown-namespace", `unknown namespace in --${property} — ${NAMESPACE_SUMMARY}`, propertySpan);
|
|
306
|
-
};
|
|
307
|
-
/** Position after the `@theme` keyword; parses `{ declarations }`. */
|
|
308
|
-
const parseThemeBlock = (scope) => {
|
|
309
|
-
skipTrivia();
|
|
310
|
-
if (css.charCodeAt(position) !== CHAR_OPEN_BRACE) {
|
|
311
|
-
report("theme-expected-block", "expected { after @theme", {
|
|
312
|
-
start: position,
|
|
313
|
-
end: Math.min(position + 1, length)
|
|
314
|
-
});
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
const openBrace = position;
|
|
318
|
-
position++;
|
|
319
|
-
themeBlockCount++;
|
|
320
|
-
while (true) {
|
|
321
|
-
skipTrivia();
|
|
322
|
-
if (position >= length) {
|
|
323
|
-
report("theme-unbalanced-braces", "unclosed @theme block — add }", {
|
|
324
|
-
start: openBrace,
|
|
325
|
-
end: openBrace + 1
|
|
326
|
-
});
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const code = css.charCodeAt(position);
|
|
330
|
-
if (code === CHAR_CLOSE_BRACE) {
|
|
331
|
-
position++;
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (code === CHAR_HYPHEN && css.charCodeAt(position + 1) === CHAR_HYPHEN) {
|
|
335
|
-
parseDeclaration(scope);
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
report("theme-expected-declaration", "expected a --<namespace>-<name> declaration", {
|
|
339
|
-
start: position,
|
|
340
|
-
end: Math.min(position + 1, length)
|
|
341
|
-
});
|
|
342
|
-
skipToDeclarationBoundary();
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
/** Position after the `@media` keyword; parses the dark wrapper block. */
|
|
346
|
-
const parseMediaBlock = () => {
|
|
347
|
-
const mediaStart = position - 6;
|
|
348
|
-
const preludeError = () => {
|
|
349
|
-
report("theme-unsupported-media", "only @media (prefers-color-scheme: dark) is supported — the dark theme wrapper", {
|
|
350
|
-
start: mediaStart,
|
|
351
|
-
end: Math.min(position + 1, length)
|
|
352
|
-
});
|
|
353
|
-
skipBalancedBlock();
|
|
354
|
-
};
|
|
355
|
-
skipTrivia();
|
|
356
|
-
if (css.charCodeAt(position) !== CHAR_OPEN_PAREN) return preludeError();
|
|
357
|
-
position++;
|
|
358
|
-
skipTrivia();
|
|
359
|
-
if (!css.startsWith("prefers-color-scheme", position)) return preludeError();
|
|
360
|
-
position += 20;
|
|
361
|
-
skipTrivia();
|
|
362
|
-
if (css.charCodeAt(position) !== CHAR_COLON) return preludeError();
|
|
363
|
-
position++;
|
|
364
|
-
skipTrivia();
|
|
365
|
-
if (!atKeyword("dark")) return preludeError();
|
|
366
|
-
position += 4;
|
|
367
|
-
skipTrivia();
|
|
368
|
-
if (css.charCodeAt(position) !== CHAR_CLOSE_PAREN) return preludeError();
|
|
369
|
-
position++;
|
|
370
|
-
skipTrivia();
|
|
371
|
-
if (css.charCodeAt(position) !== CHAR_OPEN_BRACE) {
|
|
372
|
-
report("theme-expected-block", "expected { after the dark media query", {
|
|
373
|
-
start: position,
|
|
374
|
-
end: Math.min(position + 1, length)
|
|
375
|
-
});
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
const openBrace = position;
|
|
379
|
-
position++;
|
|
380
|
-
let sawThemeBlock = false;
|
|
381
|
-
while (true) {
|
|
382
|
-
skipTrivia();
|
|
383
|
-
if (position >= length) {
|
|
384
|
-
report("theme-unbalanced-braces", "unclosed @media block — add }", {
|
|
385
|
-
start: openBrace,
|
|
386
|
-
end: openBrace + 1
|
|
387
|
-
});
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
const code = css.charCodeAt(position);
|
|
391
|
-
if (code === CHAR_CLOSE_BRACE) {
|
|
392
|
-
position++;
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
if (code === CHAR_AT && atKeyword("@theme")) {
|
|
396
|
-
position += 6;
|
|
397
|
-
parseThemeBlock("dark");
|
|
398
|
-
sawThemeBlock = true;
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
report("theme-expected-block", "expected an @theme block inside the dark media query", {
|
|
402
|
-
start: position,
|
|
403
|
-
end: Math.min(position + 1, length)
|
|
404
|
-
});
|
|
405
|
-
skipBalancedBlock();
|
|
406
|
-
}
|
|
407
|
-
if (!sawThemeBlock) report("theme-expected-block", "the dark media query contains no @theme block", {
|
|
408
|
-
start: openBrace,
|
|
409
|
-
end: openBrace + 1
|
|
410
|
-
});
|
|
411
|
-
};
|
|
412
|
-
while (position < length) {
|
|
413
|
-
skipTrivia();
|
|
414
|
-
if (position >= length) break;
|
|
415
|
-
if (atKeyword("@theme")) {
|
|
416
|
-
position += 6;
|
|
417
|
-
parseThemeBlock("light");
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
if (atKeyword("@media")) {
|
|
421
|
-
position += 6;
|
|
422
|
-
parseMediaBlock();
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
report("theme-unexpected-content", "expected @theme or @media (prefers-color-scheme: dark) at the top level", {
|
|
426
|
-
start: position,
|
|
427
|
-
end: Math.min(position + 1, length)
|
|
428
|
-
});
|
|
429
|
-
const next = position;
|
|
430
|
-
while (position < length) {
|
|
431
|
-
const code = css.charCodeAt(position);
|
|
432
|
-
if (code === CHAR_AT && position > next) break;
|
|
433
|
-
if (code === CHAR_OPEN_BRACE) {
|
|
434
|
-
skipBalancedBlock();
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
position++;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (themeBlockCount === 0 && diagnostics.length === 0) report("theme-missing-block", "no @theme block found — write @theme { --color-brand: #4f46e5; }", {
|
|
441
|
-
start: 0,
|
|
442
|
-
end: Math.min(1, length)
|
|
443
|
-
});
|
|
444
|
-
if (diagnostics.length > 0) return Result.err(diagnostics);
|
|
445
|
-
const nonEmpty = (entries) => Object.keys(entries).length > 0 ? entries : void 0;
|
|
446
|
-
const colors = nonEmpty(accumulator.colors);
|
|
447
|
-
const colorsDark = nonEmpty(accumulator.colorsDark);
|
|
448
|
-
const text = nonEmpty(accumulator.text);
|
|
449
|
-
const leading = nonEmpty(accumulator.leading);
|
|
450
|
-
const radiusPx = nonEmpty(accumulator.radiusPx);
|
|
451
|
-
const fontWeights = nonEmpty(accumulator.fontWeights);
|
|
452
|
-
const fontFamilies = nonEmpty(accumulator.fontFamilies);
|
|
453
|
-
const containerPx = nonEmpty(accumulator.containerPx);
|
|
454
|
-
const tracking = nonEmpty(accumulator.tracking);
|
|
455
|
-
return Result.ok({
|
|
456
|
-
colors,
|
|
457
|
-
colorsDark,
|
|
458
|
-
spacingPx: accumulator.spacingPx,
|
|
459
|
-
text,
|
|
460
|
-
leading,
|
|
461
|
-
radiusPx,
|
|
462
|
-
fontWeights,
|
|
463
|
-
fontFamilies,
|
|
464
|
-
containerPx,
|
|
465
|
-
tracking
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
|
-
/** Trim a JS number to a stable decimal string (no exponent, no trailing zeros). */
|
|
469
|
-
const numberText = (value) => {
|
|
470
|
-
const text = String(value);
|
|
471
|
-
if (!text.includes("e") && !text.includes("E")) return text;
|
|
472
|
-
const expanded = value.toLocaleString("en-US", {
|
|
473
|
-
useGrouping: false,
|
|
474
|
-
maximumFractionDigits: 20
|
|
475
|
-
});
|
|
476
|
-
if (!expanded.includes(".")) return expanded;
|
|
477
|
-
return expanded.replace(/0+$/, "").replace(/\.$/, "");
|
|
478
|
-
};
|
|
479
|
-
const declaration = (property, value) => ` --${property}: ${value};`;
|
|
480
|
-
const colorDeclarations = (colors) => colors === void 0 ? [] : Object.entries(colors).map(([name, hex]) => declaration(`color-${name}`, hex));
|
|
481
|
-
/**
|
|
482
|
-
* Serialize a ParsedTheme to `@theme` CSS. Light tokens go in the top-level
|
|
483
|
-
* `@theme` block; `colorsDark` (colors-only, matching the parser) goes in the
|
|
484
|
-
* `@media (prefers-color-scheme: dark) { @theme { … } }` wrapper.
|
|
485
|
-
*/
|
|
486
|
-
const printThemeCss = (theme) => {
|
|
487
|
-
const light = [
|
|
488
|
-
...colorDeclarations(theme.colors),
|
|
489
|
-
...theme.spacingPx !== void 0 ? [declaration("spacing", `${numberText(theme.spacingPx)}px`)] : [],
|
|
490
|
-
...theme.radiusPx === void 0 ? [] : Object.entries(theme.radiusPx).map(([name, px]) => declaration(`radius-${name}`, `${numberText(px)}px`)),
|
|
491
|
-
...theme.fontFamilies === void 0 ? [] : Object.entries(theme.fontFamilies).map(([name, stack]) => declaration(`font-${name}`, stack)),
|
|
492
|
-
...theme.fontWeights === void 0 ? [] : Object.entries(theme.fontWeights).map(([name, weight]) => declaration(`font-weight-${name}`, numberText(weight))),
|
|
493
|
-
...theme.text === void 0 ? [] : Object.entries(theme.text).map(([name, token]) => declaration(`text-${name}`, `${numberText(token.sizePx)}px/${numberText(token.lineHeightPx)}px`)),
|
|
494
|
-
...theme.leading === void 0 ? [] : Object.entries(theme.leading).map(([name, ratio]) => declaration(`leading-${name}`, numberText(ratio))),
|
|
495
|
-
...(() => {
|
|
496
|
-
const trackingSource = theme.tracking ?? theme.trackingEm;
|
|
497
|
-
return trackingSource === void 0 ? [] : Object.entries(trackingSource).map(([name, em]) => declaration(`tracking-${name}`, `${numberText(em)}em`));
|
|
498
|
-
})(),
|
|
499
|
-
...theme.containerPx === void 0 ? [] : Object.entries(theme.containerPx).map(([name, px]) => declaration(`container-${name}`, `${numberText(px)}px`))
|
|
500
|
-
];
|
|
501
|
-
const dark = colorDeclarations(theme.colorsDark);
|
|
502
|
-
const blocks = [`@theme {\n${light.join("\n")}\n}`];
|
|
503
|
-
if (dark.length > 0) blocks.push(`@media (prefers-color-scheme: dark) {\n @theme {\n ${dark.join("\n ")}\n }\n}`);
|
|
504
|
-
return `${blocks.join("\n\n")}\n`;
|
|
505
|
-
};
|
|
506
|
-
//#endregion
|
|
507
|
-
export { parseThemeCss, printThemeCss };
|
|
508
|
-
|
|
509
|
-
//# sourceMappingURL=theme-css.js.map
|
|
1
|
+
import { a as brandThemeVariables, c as parseBrandCss, i as brandTailwindCss, l as printBrandCss, n as BRAND_FOREGROUND_VARIABLE, o as contrastForeground, r as brandForegroundDefaults, s as darkVariableName, t as BRAND_COLOR_VARIABLE } from "./theme-css-hFqcPSar.js";
|
|
2
|
+
export { BRAND_COLOR_VARIABLE, BRAND_FOREGROUND_VARIABLE, brandForegroundDefaults, brandTailwindCss, brandThemeVariables, contrastForeground, darkVariableName, parseBrandCss, printBrandCss };
|
package/dist/tsx-tracer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { buildLineMap } from "./diagnostics.js";
|
|
|
3
3
|
import { SML_TAGS, componentNameForSmlTag, smlTagForComponentName } from "./ast.js";
|
|
4
4
|
import { decodeEntities } from "./lexer.js";
|
|
5
5
|
import { parseSml } from "./parser.js";
|
|
6
|
-
import { r as applyOps } from "./ops-
|
|
6
|
+
import { r as applyOps } from "./ops-BvwqHl7V.js";
|
|
7
7
|
import { Result, TaggedError } from "better-result";
|
|
8
8
|
import { parse } from "@babel/parser";
|
|
9
9
|
//#region src/tsx-tracer.ts
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/internal/unsubscribe-link.ts
|
|
2
|
+
/**
|
|
3
|
+
* Marks the one link the serializer lets carry the unsubscribe placeholder as
|
|
4
|
+
* its href. Only `BrandFooter` sets it; nothing public exports it, so no
|
|
5
|
+
* author-written link can claim the exemption.
|
|
6
|
+
*/
|
|
7
|
+
const UNSUBSCRIBE_LINK = Symbol("samva.unsubscribe-link");
|
|
8
|
+
/**
|
|
9
|
+
* The per-recipient unsubscribe URL is not known at render: the send path
|
|
10
|
+
* substitutes each delivery's link for this placeholder, or removes the
|
|
11
|
+
* unsubscribe row when the send resolved no unsubscribe group.
|
|
12
|
+
*/
|
|
13
|
+
const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
|
|
14
|
+
/**
|
|
15
|
+
* Marks the elements `BrandFooter` writes the send path's footer markers on.
|
|
16
|
+
* The serializer emits `data-samva-footer` and `data-samva-unsubscribe` only
|
|
17
|
+
* for an element carrying it, so an author cannot claim a footer the send
|
|
18
|
+
* path would then not append.
|
|
19
|
+
*/
|
|
20
|
+
const BRAND_FOOTER_MARKER = Symbol("samva.brand-footer-marker");
|
|
21
|
+
/** The attributes only `BrandFooter` writes. */
|
|
22
|
+
const RESERVED_FOOTER_ATTRIBUTES = /* @__PURE__ */ new Set(["data-samva-footer", "data-samva-unsubscribe"]);
|
|
23
|
+
//#endregion
|
|
24
|
+
export { UNSUBSCRIBE_URL_PLACEHOLDER as i, RESERVED_FOOTER_ATTRIBUTES as n, UNSUBSCRIBE_LINK as r, BRAND_FOOTER_MARKER as t };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=unsubscribe-link-CoesnQDU.js.map
|