@samva/markup 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/AGENTS.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +41 -0
  4. package/dist/ast-DAhnAvQN.d.ts +120 -0
  5. package/dist/ast.d.ts +2 -0
  6. package/dist/ast.js +159 -0
  7. package/dist/ast.js.map +1 -0
  8. package/dist/candidate.d.ts +29 -0
  9. package/dist/candidate.js +88 -0
  10. package/dist/candidate.js.map +1 -0
  11. package/dist/checks-CW0nFj5n.js +58922 -0
  12. package/dist/checks-CW0nFj5n.js.map +1 -0
  13. package/dist/checks.d.ts +26 -0
  14. package/dist/checks.js +2 -0
  15. package/dist/classes.d.ts +27 -0
  16. package/dist/classes.js +184 -0
  17. package/dist/classes.js.map +1 -0
  18. package/dist/compile-sms.d.ts +28 -0
  19. package/dist/compile-sms.js +44 -0
  20. package/dist/compile-sms.js.map +1 -0
  21. package/dist/compile-whatsapp.d.ts +56 -0
  22. package/dist/compile-whatsapp.js +186 -0
  23. package/dist/compile-whatsapp.js.map +1 -0
  24. package/dist/compile.d.ts +93 -0
  25. package/dist/compile.js +938 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/components.d.ts +289 -0
  28. package/dist/components.js +106 -0
  29. package/dist/components.js.map +1 -0
  30. package/dist/conditionals.d.ts +10 -0
  31. package/dist/conditionals.js +42 -0
  32. package/dist/conditionals.js.map +1 -0
  33. package/dist/diagnostics-oveE-laV.d.ts +79 -0
  34. package/dist/diagnostics.d.ts +2 -0
  35. package/dist/diagnostics.js +62 -0
  36. package/dist/diagnostics.js.map +1 -0
  37. package/dist/emit.d.ts +50 -0
  38. package/dist/emit.js +151 -0
  39. package/dist/emit.js.map +1 -0
  40. package/dist/footer.d.ts +66 -0
  41. package/dist/footer.js +90 -0
  42. package/dist/footer.js.map +1 -0
  43. package/dist/interpolate.d.ts +27 -0
  44. package/dist/interpolate.js +61 -0
  45. package/dist/interpolate.js.map +1 -0
  46. package/dist/jsx-dev-runtime.d.ts +10 -0
  47. package/dist/jsx-dev-runtime.js +7 -0
  48. package/dist/jsx-dev-runtime.js.map +1 -0
  49. package/dist/jsx-runtime-fSxcvlx4.d.ts +57 -0
  50. package/dist/jsx-runtime.d.ts +2 -0
  51. package/dist/jsx-runtime.js +102 -0
  52. package/dist/jsx-runtime.js.map +1 -0
  53. package/dist/lexer.d.ts +60 -0
  54. package/dist/lexer.js +269 -0
  55. package/dist/lexer.js.map +1 -0
  56. package/dist/model.d.ts +39 -0
  57. package/dist/model.js +90 -0
  58. package/dist/model.js.map +1 -0
  59. package/dist/ops.d.ts +208 -0
  60. package/dist/ops.js +546 -0
  61. package/dist/ops.js.map +1 -0
  62. package/dist/parser-yzjUOGhc.d.ts +387 -0
  63. package/dist/parser.d.ts +2 -0
  64. package/dist/parser.js +821 -0
  65. package/dist/parser.js.map +1 -0
  66. package/dist/predicate-DTtVtLK7.d.ts +49 -0
  67. package/dist/predicate.d.ts +2 -0
  68. package/dist/predicate.js +417 -0
  69. package/dist/predicate.js.map +1 -0
  70. package/dist/presets/category-mapping.d.ts +13 -0
  71. package/dist/presets/category-mapping.js +49 -0
  72. package/dist/presets/category-mapping.js.map +1 -0
  73. package/dist/presets/public-gallery.d.ts +490 -0
  74. package/dist/presets/public-gallery.js +563 -0
  75. package/dist/presets/public-gallery.js.map +1 -0
  76. package/dist/presets/public-handoff.d.ts +68 -0
  77. package/dist/presets/public-handoff.js +105 -0
  78. package/dist/presets/public-handoff.js.map +1 -0
  79. package/dist/presets/public-preview-artifacts.d.ts +41 -0
  80. package/dist/presets/public-preview-artifacts.js +109 -0
  81. package/dist/presets/public-preview-artifacts.js.map +1 -0
  82. package/dist/presets/public-preview.d.ts +1168 -0
  83. package/dist/presets/public-preview.js +1158 -0
  84. package/dist/presets/public-preview.js.map +1 -0
  85. package/dist/presets/sources.d.ts +7 -0
  86. package/dist/presets/sources.js +54 -0
  87. package/dist/presets/sources.js.map +1 -0
  88. package/dist/presets.d.ts +350 -0
  89. package/dist/presets.js +1251 -0
  90. package/dist/presets.js.map +1 -0
  91. package/dist/printer.d.ts +6 -0
  92. package/dist/printer.js +122 -0
  93. package/dist/printer.js.map +1 -0
  94. package/dist/registry.d.ts +42 -0
  95. package/dist/registry.js +646 -0
  96. package/dist/registry.js.map +1 -0
  97. package/dist/render-template.d.ts +42 -0
  98. package/dist/render-template.js +74 -0
  99. package/dist/render-template.js.map +1 -0
  100. package/dist/sms-segments-y4FpPilJ.d.ts +13 -0
  101. package/dist/sms-segments.d.ts +2 -0
  102. package/dist/sms-segments.js +46 -0
  103. package/dist/sms-segments.js.map +1 -0
  104. package/dist/theme-6ApjjiBo.d.ts +116 -0
  105. package/dist/theme-css.d.ts +24 -0
  106. package/dist/theme-css.js +509 -0
  107. package/dist/theme-css.js.map +1 -0
  108. package/dist/theme.d.ts +2 -0
  109. package/dist/theme.js +95 -0
  110. package/dist/theme.js.map +1 -0
  111. package/dist/tokens.d.ts +6 -0
  112. package/dist/tokens.js +417 -0
  113. package/dist/tokens.js.map +1 -0
  114. package/dist/variables-3NGLpNTB.d.ts +77 -0
  115. package/dist/variables.d.ts +2 -0
  116. package/dist/variables.js +141 -0
  117. package/dist/variables.js.map +1 -0
  118. package/dist/version-BITr58kk.js +96 -0
  119. package/dist/version-BITr58kk.js.map +1 -0
  120. package/generated/sml-agent-contract.json +917 -0
  121. package/generated/sml-email-agent-contract.json +790 -0
  122. package/generated/sml-email-language-reference.md +160 -0
  123. package/generated/sml-language-reference.md +220 -0
  124. package/package.json +203 -0
@@ -0,0 +1,509 @@
1
+ import { Result } from "effect";
2
+ //#region src/theme-css.ts
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.fail(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.succeed({
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-css.js","names":[],"sources":["../src/theme-css.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport type { Diagnostic, Span } from \"./diagnostics\";\nimport type { TextToken, TokenOverrides } from \"./theme\";\n\n// `@theme` CSS authoring: literal Tailwind v4 syntax parsed by a closed-grammar\n// pure-TS parser (Worker + browser safe, no CSS library). The grammar is:\n//\n// file = { trivia } block { block } { trivia }\n// block = theme-block | dark-block\n// theme-block = \"@theme\" \"{\" { declaration } \"}\"\n// dark-block = \"@media\" \"(\" \"prefers-color-scheme\" \":\" \"dark\" \")\"\n// \"{\" theme-block { theme-block } \"}\" (colors only)\n// declaration = \"--\" namespace \"-\" name \":\" value \";\"\n// trivia = whitespace | \"/*\" ... \"*/\"\n//\n// Namespace mapping (Tailwind v4 names → ParsedTheme fields): --color-* →\n// colors (hex only, normalized to 6-digit lowercase), --radius-* → radiusPx,\n// --font-* → fontFamilies, --font-weight-* → fontWeights, --text-* → text\n// (\"16px\" or \"16px/24px\"; a bare size defaults line-height to round(1.5×size),\n// the leading-normal ratio), --leading-* → leading, --spacing → spacingPx,\n// --container-* → containerPx, --tracking-* → tracking (em numbers).\n//\n// Closed means closed: unknown namespaces, malformed values, duplicate\n// declarations, non-color namespaces in the dark block, and unbalanced braces\n// are all typed diagnostics with spans — collected exhaustively, never\n// fail-fast, so agents fix a list at a time. Structural scanning is\n// charCode-level like lexer.ts; leaf value validation uses the same small-regex\n// style as registry.ts.\n\n/**\n * `TokenOverrides` plus `tracking`. The compiler's `TokenOverrides` names its\n * letter-spacing slot `trackingEm`; the `@theme` CSS namespace and the persisted\n * theme schema both call it `tracking`, so the parser surfaces it under that\n * name and apps/api maps `tracking → trackingEm` at the compile call sites.\n */\nexport interface ParsedTheme extends TokenOverrides {\n /** tracking-{name} → letter-spacing in em (negative allowed). */\n readonly tracking?: Readonly<Record<string, number>> | undefined;\n}\n\nconst CHAR_TAB = 9;\nconst CHAR_LINE_FEED = 10;\nconst CHAR_CARRIAGE_RETURN = 13;\nconst CHAR_SPACE = 32;\nconst CHAR_STAR = 42;\nconst CHAR_HYPHEN = 45;\nconst CHAR_SLASH = 47;\nconst CHAR_COLON = 58;\nconst CHAR_SEMICOLON = 59;\nconst CHAR_AT = 64;\nconst CHAR_OPEN_PAREN = 40;\nconst CHAR_CLOSE_PAREN = 41;\nconst CHAR_OPEN_BRACE = 123;\nconst CHAR_CLOSE_BRACE = 125;\nconst CHAR_0 = 48;\nconst CHAR_9 = 57;\nconst CHAR_UPPER_A = 65;\nconst CHAR_UPPER_Z = 90;\nconst CHAR_LOWER_A = 97;\nconst CHAR_LOWER_Z = 122;\n\nconst isWhitespace = (code: number): boolean =>\n code === CHAR_SPACE ||\n code === CHAR_TAB ||\n code === CHAR_LINE_FEED ||\n code === CHAR_CARRIAGE_RETURN;\n\nconst isIdentChar = (code: number): boolean =>\n (code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z) ||\n (code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z) ||\n (code >= CHAR_0 && code <= CHAR_9) ||\n code === CHAR_HYPHEN;\n\n// ── Leaf value validators (registry.ts regex style) ──────────────────────────\n\nconst HEX_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n// Number form: `12`, `12.5`, or bare leading-dot `.5` — hand-authored themes\n// and editor completions often omit the leading zero, so accept both (the\n// alternation requires at least one digit; bare \"px\"/\"em\" never match).\nconst NUM = String.raw`(?:\\d+(?:\\.\\d+)?|\\.\\d+)`;\nconst PX_PATTERN = new RegExp(`^(${NUM})px$`);\nconst TEXT_PATTERN = new RegExp(`^(${NUM})px(?:\\\\s*/\\\\s*(${NUM})px)?$`);\nconst UNITLESS_PATTERN = new RegExp(`^${NUM}$`);\nconst EM_PATTERN = new RegExp(`^(-?${NUM})em$`);\nconst INTEGER_PATTERN = /^\\d+$/;\n\n/** #rgb/#rrggbb → 6-digit lowercase, or null. */\nconst normalizeHex = (value: string): string | null => {\n const match = HEX_PATTERN.exec(value);\n if (match === null) return null;\n const digits = match[1]!.toLowerCase();\n if (digits.length === 6) return `#${digits}`;\n return `#${digits[0]!}${digits[0]!}${digits[1]!}${digits[1]!}${digits[2]!}${digits[2]!}`;\n};\n\nconst pxNumber = (value: string): number | null => {\n const match = PX_PATTERN.exec(value);\n return match === null ? null : Number.parseFloat(match[1]!);\n};\n\n// ── Parser ────────────────────────────────────────────────────────────────────\n\ntype Scope = \"light\" | \"dark\";\n\ninterface Accumulator {\n readonly colors: Record<string, string>;\n readonly colorsDark: Record<string, string>;\n readonly text: Record<string, TextToken>;\n readonly leading: Record<string, number>;\n readonly radiusPx: Record<string, number>;\n readonly fontWeights: Record<string, number>;\n readonly fontFamilies: Record<string, string>;\n readonly containerPx: Record<string, number>;\n readonly tracking: Record<string, number>;\n spacingPx: number | undefined;\n}\n\nconst NAMESPACE_SUMMARY =\n \"supported namespaces: --color-*, --radius-*, --font-*, --font-weight-*, --text-*, --leading-*, --spacing, --container-*, --tracking-*\";\n\nexport const parseThemeCss = (\n css: string,\n): Result.Result<ParsedTheme, ReadonlyArray<Diagnostic>> => {\n const length = css.length;\n let position = 0;\n const diagnostics: Array<Diagnostic> = [];\n const seen = new Set<string>();\n const accumulator: Accumulator = {\n colors: {},\n colorsDark: {},\n text: {},\n leading: {},\n radiusPx: {},\n fontWeights: {},\n fontFamilies: {},\n containerPx: {},\n tracking: {},\n spacingPx: undefined,\n };\n let themeBlockCount = 0;\n\n const report = (code: string, message: string, span: Span): void => {\n diagnostics.push({ code, message, span });\n };\n\n const skipTrivia = (): void => {\n while (position < length) {\n const code = css.charCodeAt(position);\n if (isWhitespace(code)) {\n position++;\n continue;\n }\n if (code === CHAR_SLASH && css.charCodeAt(position + 1) === CHAR_STAR) {\n const start = position;\n const close = css.indexOf(\"*/\", position + 2);\n if (close === -1) {\n report(\"theme-unterminated-comment\", \"unterminated /* comment — add */\", {\n start,\n end: length,\n });\n position = length;\n return;\n }\n position = close + 2;\n continue;\n }\n return;\n }\n };\n\n /** `@word` at the current position, not followed by more ident chars. */\n const atKeyword = (word: string): boolean =>\n css.startsWith(word, position) && !isIdentChar(css.charCodeAt(position + word.length));\n\n /** Skips a balanced `{ ... }` region (position on or before the `{`). */\n const skipBalancedBlock = (): void => {\n while (position < length && css.charCodeAt(position) !== CHAR_OPEN_BRACE) position++;\n let depth = 0;\n while (position < length) {\n const code = css.charCodeAt(position);\n position++;\n if (code === CHAR_OPEN_BRACE) depth++;\n else if (code === CHAR_CLOSE_BRACE) {\n depth--;\n if (depth === 0) return;\n }\n }\n };\n\n /** Declaration-level recovery: past the next `;`, or up to the block's `}`. */\n const skipToDeclarationBoundary = (): void => {\n while (position < length) {\n const code = css.charCodeAt(position);\n if (code === CHAR_SEMICOLON) {\n position++;\n return;\n }\n if (code === CHAR_CLOSE_BRACE) return;\n position++;\n }\n };\n\n const record = <Value>(\n target: Record<string, Value>,\n scope: Scope,\n field: string,\n name: string,\n value: Value,\n propertySpan: Span,\n property: string,\n ): void => {\n const key = `${scope}:${field}:${name}`;\n if (seen.has(key)) {\n report(\n \"theme-duplicate-declaration\",\n `duplicate declaration --${property} — declare each token once`,\n propertySpan,\n );\n return;\n }\n seen.add(key);\n target[name] = value;\n };\n\n const parseDeclaration = (scope: Scope): void => {\n const propertyStart = position;\n position += 2; // consume --\n while (position < length && isIdentChar(css.charCodeAt(position))) position++;\n const property = css.slice(propertyStart + 2, position);\n const propertySpan: Span = { start: propertyStart, end: position };\n\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_COLON) {\n report(\"theme-expected-declaration\", `expected : after --${property}`, {\n start: position,\n end: Math.min(position + 1, length),\n });\n skipToDeclarationBoundary();\n return;\n }\n position++;\n skipTrivia();\n\n const valueStart = position;\n while (position < length) {\n const code = css.charCodeAt(position);\n if (code === CHAR_SEMICOLON || code === CHAR_CLOSE_BRACE || code === CHAR_OPEN_BRACE) break;\n position++;\n }\n let valueEnd = position;\n while (valueEnd > valueStart && isWhitespace(css.charCodeAt(valueEnd - 1))) valueEnd--;\n const value = css.slice(valueStart, valueEnd).replace(/\\s+/g, \" \");\n const valueSpan: Span = { start: valueStart, end: Math.max(valueEnd, valueStart + 1) };\n\n if (css.charCodeAt(position) === CHAR_SEMICOLON) {\n position++;\n } else {\n report(\"theme-missing-semicolon\", `missing ; after --${property} declaration`, propertySpan);\n }\n\n routeDeclaration(scope, property, propertySpan, value, valueSpan);\n };\n\n const invalidValue = (property: string, value: string, expected: string, span: Span): void => {\n report(\"theme-invalid-value\", `invalid value \"${value}\" for --${property} — ${expected}`, span);\n };\n\n const routeDeclaration = (\n scope: Scope,\n property: string,\n propertySpan: Span,\n value: string,\n valueSpan: Span,\n ): void => {\n const named = (prefix: string): string | null => {\n if (!property.startsWith(prefix)) return null;\n const name = property.slice(prefix.length);\n if (name.length === 0) {\n report(\n \"theme-invalid-value\",\n `--${property} is missing a token name — write --${prefix}<name>`,\n propertySpan,\n );\n return null;\n }\n if (name.includes(\"--\")) {\n const hint =\n prefix === \"text-\"\n ? \" — use the size/line-height shorthand instead: --text-xl: 20px/28px\"\n : \" — token names use single hyphens\";\n report(\"theme-invalid-value\", `malformed token name in --${property}${hint}`, propertySpan);\n return null;\n }\n return name;\n };\n\n if (value.length === 0) {\n invalidValue(property, value, \"the value is empty\", valueSpan);\n return;\n }\n\n if (property.startsWith(\"color-\")) {\n const name = named(\"color-\");\n if (name === null) return;\n const hex = normalizeHex(value);\n if (hex === null) {\n invalidValue(\n property,\n value,\n \"expected a hex color like #4f46e5 or #fff (named colors, oklch(), and rgb() are outside the closed grammar)\",\n valueSpan,\n );\n return;\n }\n if (scope === \"dark\") {\n record(accumulator.colorsDark, scope, \"colors\", name, hex, propertySpan, property);\n } else {\n record(accumulator.colors, scope, \"colors\", name, hex, propertySpan, property);\n }\n return;\n }\n\n if (scope === \"dark\") {\n report(\n \"theme-dark-non-color\",\n `--${property} is not allowed in the dark theme block — only --color-* tokens have dark counterparts`,\n propertySpan,\n );\n return;\n }\n\n if (property.startsWith(\"radius-\")) {\n const name = named(\"radius-\");\n if (name === null) return;\n const px = pxNumber(value);\n if (px === null) {\n invalidValue(property, value, 'expected a px length like \"12px\"', valueSpan);\n return;\n }\n record(accumulator.radiusPx, scope, \"radiusPx\", name, px, propertySpan, property);\n return;\n }\n\n if (property.startsWith(\"font-weight-\")) {\n const name = named(\"font-weight-\");\n if (name === null) return;\n const weight = INTEGER_PATTERN.test(value) ? Number.parseInt(value, 10) : null;\n if (weight === null || weight < 100 || weight > 900) {\n invalidValue(property, value, \"expected a font weight between 100 and 900\", valueSpan);\n return;\n }\n record(accumulator.fontWeights, scope, \"fontWeights\", name, weight, propertySpan, property);\n return;\n }\n\n if (property.startsWith(\"font-\")) {\n const name = named(\"font-\");\n if (name === null) return;\n if (value.includes(\"var(\") || value.includes(\"calc(\") || value.includes(\"oklch(\")) {\n invalidValue(\n property,\n value,\n \"expected a literal font stack — var()/calc() don't exist in compiled email\",\n valueSpan,\n );\n return;\n }\n // Canonicalize quoted font names to single quotes — the system-wide\n // convention (baseTokens.fontFamilies, the compiler's inline font-family\n // flatten) and safe inside a double-quoted style=\"\" attribute without\n // escaping to &quot;. A CSS formatter may write the source with double\n // quotes; we normalize here just like hex is lowercased and whitespace\n // collapsed, so re-parse and downstream emission stay canonical.\n const stack = value.replaceAll('\"', \"'\");\n record(accumulator.fontFamilies, scope, \"fontFamilies\", name, stack, propertySpan, property);\n return;\n }\n\n if (property.startsWith(\"text-\")) {\n const name = named(\"text-\");\n if (name === null) return;\n const match = TEXT_PATTERN.exec(value);\n if (match === null) {\n invalidValue(\n property,\n value,\n 'expected \"16px\" or \"16px/24px\" (font-size/line-height)',\n valueSpan,\n );\n return;\n }\n const sizePx = Number.parseFloat(match[1]!);\n const lineHeightPx =\n match[2] === undefined ? Math.round(sizePx * 1.5) : Number.parseFloat(match[2]);\n record(\n accumulator.text,\n scope,\n \"text\",\n name,\n { sizePx, lineHeightPx },\n propertySpan,\n property,\n );\n return;\n }\n\n if (property.startsWith(\"leading-\")) {\n const name = named(\"leading-\");\n if (name === null) return;\n if (!UNITLESS_PATTERN.test(value) || Number.parseFloat(value) <= 0) {\n invalidValue(\n property,\n value,\n 'expected a unitless line-height ratio like \"1.5\"',\n valueSpan,\n );\n return;\n }\n record(\n accumulator.leading,\n scope,\n \"leading\",\n name,\n Number.parseFloat(value),\n propertySpan,\n property,\n );\n return;\n }\n\n if (property.startsWith(\"tracking-\")) {\n const name = named(\"tracking-\");\n if (name === null) return;\n const match = EM_PATTERN.exec(value);\n if (match === null) {\n invalidValue(\n property,\n value,\n 'expected an em value like \"0.025em\" or \"-0.05em\"',\n valueSpan,\n );\n return;\n }\n record(\n accumulator.tracking,\n scope,\n \"tracking\",\n name,\n Number.parseFloat(match[1]!),\n propertySpan,\n property,\n );\n return;\n }\n\n if (property.startsWith(\"container-\")) {\n const name = named(\"container-\");\n if (name === null) return;\n const px = pxNumber(value);\n if (px === null) {\n invalidValue(property, value, 'expected a px length like \"600px\"', valueSpan);\n return;\n }\n record(accumulator.containerPx, scope, \"containerPx\", name, px, propertySpan, property);\n return;\n }\n\n if (property === \"spacing\") {\n const px = pxNumber(value);\n if (px === null || px <= 0) {\n invalidValue(property, value, 'expected a positive px length like \"4px\"', valueSpan);\n return;\n }\n const key = \"light:spacingPx\";\n if (seen.has(key)) {\n report(\n \"theme-duplicate-declaration\",\n \"duplicate declaration --spacing — declare each token once\",\n propertySpan,\n );\n return;\n }\n seen.add(key);\n accumulator.spacingPx = px;\n return;\n }\n\n if (property.startsWith(\"spacing-\")) {\n report(\n \"theme-unknown-namespace\",\n `--${property} is not supported — only the bare --spacing multiplier is (spacing steps derive from it)`,\n propertySpan,\n );\n return;\n }\n\n report(\n \"theme-unknown-namespace\",\n `unknown namespace in --${property} — ${NAMESPACE_SUMMARY}`,\n propertySpan,\n );\n };\n\n /** Position after the `@theme` keyword; parses `{ declarations }`. */\n const parseThemeBlock = (scope: Scope): void => {\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_OPEN_BRACE) {\n report(\"theme-expected-block\", \"expected { after @theme\", {\n start: position,\n end: Math.min(position + 1, length),\n });\n return;\n }\n const openBrace = position;\n position++;\n themeBlockCount++;\n while (true) {\n skipTrivia();\n if (position >= length) {\n report(\"theme-unbalanced-braces\", \"unclosed @theme block — add }\", {\n start: openBrace,\n end: openBrace + 1,\n });\n return;\n }\n const code = css.charCodeAt(position);\n if (code === CHAR_CLOSE_BRACE) {\n position++;\n return;\n }\n if (code === CHAR_HYPHEN && css.charCodeAt(position + 1) === CHAR_HYPHEN) {\n parseDeclaration(scope);\n continue;\n }\n report(\"theme-expected-declaration\", \"expected a --<namespace>-<name> declaration\", {\n start: position,\n end: Math.min(position + 1, length),\n });\n skipToDeclarationBoundary();\n }\n };\n\n /** Position after the `@media` keyword; parses the dark wrapper block. */\n const parseMediaBlock = (): void => {\n const mediaStart = position - \"@media\".length;\n const preludeError = (): void => {\n report(\n \"theme-unsupported-media\",\n \"only @media (prefers-color-scheme: dark) is supported — the dark theme wrapper\",\n { start: mediaStart, end: Math.min(position + 1, length) },\n );\n skipBalancedBlock();\n };\n\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_OPEN_PAREN) return preludeError();\n position++;\n skipTrivia();\n if (!css.startsWith(\"prefers-color-scheme\", position)) return preludeError();\n position += \"prefers-color-scheme\".length;\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_COLON) return preludeError();\n position++;\n skipTrivia();\n if (!atKeyword(\"dark\")) return preludeError();\n position += \"dark\".length;\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_CLOSE_PAREN) return preludeError();\n position++;\n skipTrivia();\n if (css.charCodeAt(position) !== CHAR_OPEN_BRACE) {\n report(\"theme-expected-block\", \"expected { after the dark media query\", {\n start: position,\n end: Math.min(position + 1, length),\n });\n return;\n }\n const openBrace = position;\n position++;\n\n let sawThemeBlock = false;\n while (true) {\n skipTrivia();\n if (position >= length) {\n report(\"theme-unbalanced-braces\", \"unclosed @media block — add }\", {\n start: openBrace,\n end: openBrace + 1,\n });\n return;\n }\n const code = css.charCodeAt(position);\n if (code === CHAR_CLOSE_BRACE) {\n position++;\n break;\n }\n if (code === CHAR_AT && atKeyword(\"@theme\")) {\n position += \"@theme\".length;\n parseThemeBlock(\"dark\");\n sawThemeBlock = true;\n continue;\n }\n report(\"theme-expected-block\", \"expected an @theme block inside the dark media query\", {\n start: position,\n end: Math.min(position + 1, length),\n });\n skipBalancedBlock();\n }\n if (!sawThemeBlock) {\n report(\"theme-expected-block\", \"the dark media query contains no @theme block\", {\n start: openBrace,\n end: openBrace + 1,\n });\n }\n };\n\n while (position < length) {\n skipTrivia();\n if (position >= length) break;\n if (atKeyword(\"@theme\")) {\n position += \"@theme\".length;\n parseThemeBlock(\"light\");\n continue;\n }\n if (atKeyword(\"@media\")) {\n position += \"@media\".length;\n parseMediaBlock();\n continue;\n }\n report(\n \"theme-unexpected-content\",\n \"expected @theme or @media (prefers-color-scheme: dark) at the top level\",\n { start: position, end: Math.min(position + 1, length) },\n );\n // Recover past one balanced block (a stray rule) or to the next @.\n const next = position;\n while (position < length) {\n const code = css.charCodeAt(position);\n if (code === CHAR_AT && position > next) break;\n if (code === CHAR_OPEN_BRACE) {\n skipBalancedBlock();\n break;\n }\n position++;\n }\n }\n\n if (themeBlockCount === 0 && diagnostics.length === 0) {\n report(\n \"theme-missing-block\",\n \"no @theme block found — write @theme { --color-brand: #4f46e5; }\",\n {\n start: 0,\n end: Math.min(1, length),\n },\n );\n }\n\n if (diagnostics.length > 0) return Result.fail(diagnostics);\n\n const nonEmpty = <Value>(\n entries: Readonly<Record<string, Value>>,\n ): Readonly<Record<string, Value>> | undefined =>\n Object.keys(entries).length > 0 ? entries : undefined;\n\n const colors = nonEmpty(accumulator.colors);\n const colorsDark = nonEmpty(accumulator.colorsDark);\n const text = nonEmpty(accumulator.text);\n const leading = nonEmpty(accumulator.leading);\n const radiusPx = nonEmpty(accumulator.radiusPx);\n const fontWeights = nonEmpty(accumulator.fontWeights);\n const fontFamilies = nonEmpty(accumulator.fontFamilies);\n const containerPx = nonEmpty(accumulator.containerPx);\n const tracking = nonEmpty(accumulator.tracking);\n\n return Result.succeed({\n colors,\n colorsDark,\n spacingPx: accumulator.spacingPx,\n text,\n leading,\n radiusPx,\n fontWeights,\n fontFamilies,\n containerPx,\n tracking,\n });\n};\n\n// ── Printer (inverse of parseThemeCss) ────────────────────────────────────────\n// Emits Tailwind-v4 `@theme` CSS from a ParsedTheme. Round-trips through\n// parseThemeCss: `parseThemeCss(printThemeCss(t))` yields back `t` for any\n// ParsedTheme parseThemeCss can produce. Values are printed in the exact forms\n// the leaf validators accept (6-digit lowercase hex, `<n>px`, `<size>px/<lh>px`,\n// unitless leading, `<n>em` tracking) so nothing is re-normalized on re-parse.\n\n/** Trim a JS number to a stable decimal string (no exponent, no trailing zeros). */\nconst numberText = (value: number): string => {\n const text = String(value);\n if (!text.includes(\"e\") && !text.includes(\"E\")) return text;\n // |value| below ~5e-7 or at/above 1e21 stringifies in exponent form, which\n // the value patterns reject — expand to plain decimals and trim the zeros so\n // the printed theme always re-parses (the round-trip guarantee).\n const expanded = value.toLocaleString(\"en-US\", {\n useGrouping: false,\n maximumFractionDigits: 20,\n });\n if (!expanded.includes(\".\")) return expanded;\n return expanded.replace(/0+$/, \"\").replace(/\\.$/, \"\");\n};\n\nconst declaration = (property: string, value: string): string => ` --${property}: ${value};`;\n\nconst colorDeclarations = (colors: Readonly<Record<string, string>> | undefined): string[] =>\n colors === undefined\n ? []\n : Object.entries(colors).map(([name, hex]) => declaration(`color-${name}`, hex));\n\n/**\n * Serialize a ParsedTheme to `@theme` CSS. Light tokens go in the top-level\n * `@theme` block; `colorsDark` (colors-only, matching the parser) goes in the\n * `@media (prefers-color-scheme: dark) { @theme { … } }` wrapper.\n */\nexport const printThemeCss = (theme: ParsedTheme): string => {\n const light: string[] = [\n ...colorDeclarations(theme.colors),\n ...(theme.spacingPx !== undefined\n ? [declaration(\"spacing\", `${numberText(theme.spacingPx)}px`)]\n : []),\n ...(theme.radiusPx === undefined\n ? []\n : Object.entries(theme.radiusPx).map(([name, px]) =>\n declaration(`radius-${name}`, `${numberText(px)}px`),\n )),\n ...(theme.fontFamilies === undefined\n ? []\n : Object.entries(theme.fontFamilies).map(([name, stack]) =>\n declaration(`font-${name}`, stack),\n )),\n ...(theme.fontWeights === undefined\n ? []\n : Object.entries(theme.fontWeights).map(([name, weight]) =>\n declaration(`font-weight-${name}`, numberText(weight)),\n )),\n ...(theme.text === undefined\n ? []\n : Object.entries(theme.text).map(([name, token]) =>\n declaration(\n `text-${name}`,\n `${numberText(token.sizePx)}px/${numberText(token.lineHeightPx)}px`,\n ),\n )),\n ...(theme.leading === undefined\n ? []\n : Object.entries(theme.leading).map(([name, ratio]) =>\n declaration(`leading-${name}`, numberText(ratio)),\n )),\n // ParsedTheme surfaces letter-spacing under `tracking` (the `@theme` CSS and\n // persisted-schema name); the compiler's own `trackingEm` alias is folded in\n // so either input shape prints once, never twice.\n ...(() => {\n const trackingSource = theme.tracking ?? theme.trackingEm;\n return trackingSource === undefined\n ? []\n : Object.entries(trackingSource).map(([name, em]) =>\n declaration(`tracking-${name}`, `${numberText(em)}em`),\n );\n })(),\n ...(theme.containerPx === undefined\n ? []\n : Object.entries(theme.containerPx).map(([name, px]) =>\n declaration(`container-${name}`, `${numberText(px)}px`),\n )),\n ];\n\n const dark = colorDeclarations(theme.colorsDark);\n\n const blocks: string[] = [`@theme {\\n${light.join(\"\\n\")}\\n}`];\n if (dark.length > 0) {\n blocks.push(\n `@media (prefers-color-scheme: dark) {\\n @theme {\\n ${dark.join(\"\\n \")}\\n }\\n}`,\n );\n }\n return `${blocks.join(\"\\n\\n\")}\\n`;\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { a as ThemeLayers, c as escapeCssString, i as TextToken, l as flattenFontTokens, n as FontFaceSet, o as TokenOverrides, r as FontToken, s as TokenTable, t as FontFace, u as resolveTokenTable } from "./theme-6ApjjiBo.js";
2
+ export { FontFace, FontFaceSet, FontToken, TextToken, ThemeLayers, TokenOverrides, TokenTable, escapeCssString, flattenFontTokens, resolveTokenTable };