@samva/markup 0.15.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENTS.md +42 -35
  2. package/CHANGELOG.md +92 -0
  3. package/README.md +10 -9
  4. package/dist/{assets-tISHaQNF.d.ts → assets-CS5Xa_mr.d.ts} +3 -3
  5. package/dist/{brand-nreeKWxF.js → brand-CIiEqkU7.js} +24 -31
  6. package/dist/{brand-plugin-CHhcei-j.d.ts → brand-plugin-Dj9SMpfS.d.ts} +3 -1
  7. package/dist/brand.d.ts +2 -2
  8. package/dist/brand.js +3 -3
  9. package/dist/compatibility.d.ts +76 -0
  10. package/dist/compatibility.js +807 -0
  11. package/dist/compiled-template-Bc4Dq9zy.d.ts +268 -0
  12. package/dist/compiler.d.ts +58 -225
  13. package/dist/compiler.js +261 -1297
  14. package/dist/{components-3XHjYD3i.js → components-C5850-Qg.js} +37 -29
  15. package/dist/css-B_aVKrDo.js +436 -0
  16. package/dist/css-color-DLM12Osu.js +315 -0
  17. package/dist/{diagnostic-codes-UXwOO3A-.js → diagnostic-codes-DZKb0Xcg.js} +4 -0
  18. package/dist/{diagnostics-CUYlQ_6x.d.ts → diagnostics-D4vSLQrO.d.ts} +1 -10
  19. package/dist/diagnostics.d.ts +6 -2
  20. package/dist/diagnostics.js +3 -3
  21. package/dist/edit.d.ts +10 -9
  22. package/dist/edit.js +12 -11
  23. package/dist/email/jsx-dev-runtime.d.ts +1 -1
  24. package/dist/email/jsx-dev-runtime.js +1 -1
  25. package/dist/email/jsx-runtime.d.ts +2 -2
  26. package/dist/email/jsx-runtime.js +2 -2
  27. package/dist/email.d.ts +1 -1
  28. package/dist/email.js +1 -1
  29. package/dist/html-DJUK2YJF.js +483 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/{jsx-runtime-DC7xG0aA.d.ts → jsx-runtime-DL8NVF1f.d.ts} +12 -6
  32. package/dist/{jsx-runtime-CBlexoeR.js → jsx-runtime-qL19u3Fy.js} +17 -10
  33. package/dist/preview-rNoJBEIY.d.ts +104 -0
  34. package/dist/{profile-AatO_SvP.js → profile-JTvgCD6X.js} +614 -24
  35. package/dist/{html-BvRFepeO.js → render-template-DwPDm3Hq.js} +155 -572
  36. package/dist/render.d.ts +40 -28
  37. package/dist/render.js +3 -47
  38. package/dist/selectors-CYWCoxZr.js +157 -0
  39. package/dist/sms.d.ts +1 -1
  40. package/dist/sms.js +1 -1
  41. package/dist/{template-B-UDXK8a.d.ts → template-DmNcoH4Q.d.ts} +2 -9
  42. package/dist/{theme-css-s9QwD5Ko.js → theme-css-P9F5S7Ic.js} +3 -316
  43. package/dist/whatsapp.d.ts +1 -1
  44. package/dist/whatsapp.js +1 -1
  45. package/generated/contract.json +7 -2
  46. package/generated/reference.md +3 -2
  47. package/package.json +5 -1
  48. package/skills/sml/SKILL.md +2 -2
  49. package/dist/ir-DNL6H7jA.js +0 -705
  50. package/dist/render-BdvewoQt.d.ts +0 -564
  51. package/dist/render-DpYouYYG.js +0 -258
@@ -1,5 +1,5 @@
1
- import { i as escapeText, r as escapeAttribute } from "./html-BvRFepeO.js";
2
- import { a as jsx, c as rawMarkup, l as reportEmailDiagnostic, n as Fragment, s as normalizeChildren, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
1
+ import { i as escapeText, r as escapeAttribute } from "./html-DJUK2YJF.js";
2
+ import { a as jsx, c as rawMarkup, l as withEmailDiagnostics, n as Fragment, s as normalizeChildren, t as EmailNode } from "./jsx-runtime-qL19u3Fy.js";
3
3
  //#region src/email/primitive-css.ts
4
4
  /** The classes the primitives put on the elements they generate. */
5
5
  const PRIMITIVE_CLASSES = {
@@ -58,10 +58,10 @@ const presentation = (table, row, rowProps = {}) => jsx("table", {
58
58
  });
59
59
  /** A primitive's own class, then the author's. */
60
60
  const classNames = (own, authored) => authored === void 0 || authored.trim() === "" ? own : `${own} ${authored}`;
61
- const optionalNumber = (component, name, value, bounds = {}) => {
61
+ const optionalNumber = (found, component, name, value, bounds = {}) => {
62
62
  if (value === void 0) return void 0;
63
63
  if (typeof value === "number" && Number.isFinite(value) && (bounds.min === void 0 || value >= bounds.min)) return value;
64
- reportEmailDiagnostic({
64
+ found.push({
65
65
  code: "invalid-number-prop",
66
66
  severity: "error",
67
67
  message: `<${component} ${name}> must be a finite number${bounds.min === void 0 ? "" : ` of at least ${bounds.min}`}.`
@@ -90,7 +90,8 @@ const preheaderNode = (text) => jsx("div", {
90
90
  * screen edge (`gutter`).
91
91
  */
92
92
  const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor = "var(--color-background)", gutter, style, children }) => {
93
- const safeGutter = optionalNumber("Email", "gutter", gutter, { min: 0 });
93
+ const found = [];
94
+ const safeGutter = optionalNumber(found, "Email", "gutter", gutter, { min: 0 });
94
95
  const cellStyle = {
95
96
  ...safeGutter === void 0 ? {} : {
96
97
  paddingLeft: `${safeGutter}px`,
@@ -98,7 +99,7 @@ const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor = "
98
99
  },
99
100
  ...style
100
101
  };
101
- return jsx("html", {
102
+ const document = jsx("html", {
102
103
  lang,
103
104
  dir,
104
105
  xmlns: "http://www.w3.org/1999/xhtml",
@@ -146,6 +147,7 @@ const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor = "
146
147
  }))]
147
148
  })]
148
149
  });
150
+ return withEmailDiagnostics(document, found);
149
151
  };
150
152
  /** A pixel width as `width: 100%` capped at it, so a fixed-width table still fits a phone. */
151
153
  const fluidWidth = (width) => {
@@ -234,9 +236,10 @@ const Columns = ({ className, align = "center", width = "100%", style, each, per
234
236
  width,
235
237
  ...style === void 0 ? {} : { style }
236
238
  };
237
- const safeGap = optionalNumber("Columns", "gap", gap, { min: 0 });
238
- if (each === void 0 || typeof children !== "function") return presentation(table, withGaps(normalizeChildren(children).map((cell) => withColumnLayout(cell, void 0, stack)), safeGap, stack), { style: { width: "100%" } });
239
- if (per !== void 0 && (!Number.isInteger(per) || per < 1)) reportEmailDiagnostic({
239
+ const found = [];
240
+ const safeGap = optionalNumber(found, "Columns", "gap", gap, { min: 0 });
241
+ if (each === void 0 || typeof children !== "function") return withEmailDiagnostics(presentation(table, withGaps(normalizeChildren(children).map((cell) => withColumnLayout(cell, void 0, stack)), safeGap, stack), { style: { width: "100%" } }), found);
242
+ if (per !== void 0 && (!Number.isInteger(per) || per < 1)) found.push({
240
243
  code: "invalid-columns-per",
241
244
  severity: "error",
242
245
  message: "<Columns per> must be a positive integer."
@@ -246,7 +249,8 @@ const Columns = ({ className, align = "center", width = "100%", style, each, per
246
249
  if (size === void 0) chunks.push(each);
247
250
  else for (let at = 0; at < each.length; at += size) chunks.push(each.slice(at, at + size));
248
251
  const share = equalColumnWidth(size ?? Math.max(each.length, 1));
249
- return jsx(Fragment, { children: chunks.map((chunk) => presentation(table, withGaps(chunk.flatMap((item, index) => normalizeChildren(children(item, index)).map((cell) => withColumnLayout(cell, share, stack))), safeGap, stack), { style: { width: "100%" } })) });
252
+ const rows = jsx(Fragment, { children: chunks.map((chunk) => presentation(table, withGaps(chunk.flatMap((item, index) => normalizeChildren(children(item, index)).map((cell) => withColumnLayout(cell, share, stack))), safeGap, stack), { style: { width: "100%" } })) });
253
+ return withEmailDiagnostics(rows, found);
250
254
  };
251
255
  /** One cell of a `Columns` row. */
252
256
  const Column = ({ className, align, valign, width, style, children }) => jsx("td", {
@@ -259,25 +263,27 @@ const Column = ({ className, align, valign, width, style, children }) => jsx("td
259
263
  });
260
264
  /** Average advance of a bold sans-serif glyph, as a fraction of the font size. */
261
265
  const BOLD_GLYPH_ADVANCE = .6;
262
- const invalidButtonNumber = (name) => reportEmailDiagnostic({
263
- code: "invalid-button-number",
264
- severity: "error",
265
- message: `<Button ${name}> must be a finite number.`
266
- });
267
- const buttonNumber = (name, value, fallback) => {
266
+ const invalidButtonNumber = (found, name) => {
267
+ found.push({
268
+ code: "invalid-button-number",
269
+ severity: "error",
270
+ message: `<Button ${name}> must be a finite number.`
271
+ });
272
+ };
273
+ const buttonNumber = (found, name, value, fallback) => {
268
274
  if (typeof value === "number" && Number.isFinite(value)) return value;
269
- invalidButtonNumber(name);
275
+ invalidButtonNumber(found, name);
270
276
  return fallback;
271
277
  };
272
- const optionalButtonNumber = (name, value) => {
278
+ const optionalButtonNumber = (found, name, value) => {
273
279
  if (value === void 0) return void 0;
274
280
  if (typeof value === "number" && Number.isFinite(value)) return value;
275
- invalidButtonNumber(name);
281
+ invalidButtonNumber(found, name);
276
282
  };
277
- const optionalPositiveButtonNumber = (name, value) => {
283
+ const optionalPositiveButtonNumber = (found, name, value) => {
278
284
  if (value === void 0) return void 0;
279
285
  if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
280
- reportEmailDiagnostic({
286
+ found.push({
281
287
  code: "invalid-button-number",
282
288
  severity: "error",
283
289
  message: `<Button ${name}> must be a positive finite number.`
@@ -291,15 +297,16 @@ const optionalPositiveButtonNumber = (name, value) => {
291
297
  * does not use the theme's primary fill.
292
298
  */
293
299
  const Button = ({ className, href, width, height, paddingX = 16, backgroundColor, color, borderRadius, borderColor, borderWidth = 0, fontSize = 16, fontFamily, align = "left", style, children }) => {
294
- const safeWidth = optionalButtonNumber("width", width);
295
- const safeHeight = optionalPositiveButtonNumber("height", height);
296
- const safePaddingX = buttonNumber("paddingX", paddingX, 16);
297
- const safeBorderRadius = optionalButtonNumber("borderRadius", borderRadius);
298
- const safeBorderWidth = buttonNumber("borderWidth", borderWidth, 0);
299
- const safeFontSize = buttonNumber("fontSize", fontSize, 16);
300
+ const found = [];
301
+ const safeWidth = optionalButtonNumber(found, "width", width);
302
+ const safeHeight = optionalPositiveButtonNumber(found, "height", height);
303
+ const safePaddingX = buttonNumber(found, "paddingX", paddingX, 16);
304
+ const safeBorderRadius = optionalButtonNumber(found, "borderRadius", borderRadius);
305
+ const safeBorderWidth = buttonNumber(found, "borderWidth", borderWidth, 0);
306
+ const safeFontSize = buttonNumber(found, "fontSize", fontSize, 16);
300
307
  const label = typeof children === "string" ? children : void 0;
301
308
  const fallback = safeHeight !== void 0 && label !== void 0;
302
- if (!fallback) reportEmailDiagnostic({
309
+ if (!fallback) found.push({
303
310
  code: "button-outlook-fallback",
304
311
  severity: "warning",
305
312
  message: "<Button> draws its classic Outlook shape from VML, which needs a height and a plain string label. Without both, classic Windows Outlook shows an unstyled link.",
@@ -310,7 +317,7 @@ const Button = ({ className, href, width, height, paddingX = 16, backgroundColor
310
317
  const fill = backgroundColor ?? "var(--color-primary)";
311
318
  const ink = color ?? "var(--color-primary-foreground)";
312
319
  const family = fontFamily ?? "var(--font-sans)";
313
- return presentation({
320
+ const button = presentation({
314
321
  width: "100%",
315
322
  style: { borderCollapse: "collapse" }
316
323
  }, jsx("td", {
@@ -342,6 +349,7 @@ const Button = ({ className, href, width, height, paddingX = 16, backgroundColor
342
349
  children
343
350
  })]
344
351
  }));
352
+ return withEmailDiagnostics(button, found);
345
353
  };
346
354
  /** Vertical space that survives clients which collapse margins. */
347
355
  const Spacer = ({ height }) => presentation({ width: "100%" }, jsx("td", {
@@ -0,0 +1,436 @@
1
+ import { t as downlevelColors } from "./css-color-DLM12Osu.js";
2
+ import { n as isDarkSchemeCondition } from "./dark-scheme-CbCwGDWH.js";
3
+ import { o as unquote } from "./selectors-CYWCoxZr.js";
4
+ import { generate, ident, parse, walk } from "css-tree";
5
+ //#region src/email/fonts.ts
6
+ const FONT_FILE = /\.woff2$/i;
7
+ const WOFF2_FORMAT = /^woff2(?:-variations)?$/i;
8
+ const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
9
+ const declarationValue = (declaration) => declaration.type === "Declaration" ? generate(declaration.value).trim() : "";
10
+ /**
11
+ * Validate one `@font-face` and rewrite its project-relative `src` URLs.
12
+ * Accepts only WOFF2: a relative URL must name a `.woff2` file the project has,
13
+ * and an `https:` URL stays an external reference. `local()` entries pass.
14
+ */
15
+ const compileFontFace = (node, origin, resolve) => {
16
+ const diagnostics = [];
17
+ const fail = (code, message) => diagnostics.push({
18
+ code,
19
+ severity: "error",
20
+ message: `${origin}: ${message}`,
21
+ origins: []
22
+ });
23
+ if (node.type !== "Atrule" || node.block === null || node.block.children === null) return {
24
+ rule: void 0,
25
+ diagnostics
26
+ };
27
+ let family;
28
+ let sources = 0;
29
+ for (const declaration of node.block.children) {
30
+ if (declaration.type !== "Declaration") continue;
31
+ const property = declaration.property.toLowerCase();
32
+ if (property === "font-family") family = unquote(declarationValue(declaration));
33
+ if (property !== "src") continue;
34
+ const authored = declarationValue(declaration);
35
+ let value;
36
+ try {
37
+ value = parse(authored, { context: "value" });
38
+ } catch {
39
+ fail("email-font-src", "@font-face src could not be parsed.");
40
+ continue;
41
+ }
42
+ walk(value, { enter: (part) => {
43
+ if (part.type === "Function" && part.name.toLowerCase() === "format") {
44
+ const format = unquote(generate(part).slice(7, -1));
45
+ if (!WOFF2_FORMAT.test(format)) fail("email-font-format", `@font-face format(${format}) is not supported. Email fonts are WOFF2 only; convert the file to .woff2.`);
46
+ return;
47
+ }
48
+ if (part.type !== "Url") return;
49
+ sources++;
50
+ const reference = part.value;
51
+ const path = reference.replace(/[?#].*$/, "");
52
+ if (/^https:\/\//i.test(reference)) {
53
+ if (!FONT_FILE.test(path) && !/format\(\s*['"]?woff2/i.test(generate(value))) fail("email-font-format", `@font-face src ${JSON.stringify(reference)} is not a WOFF2 file. Email fonts are WOFF2 only.`);
54
+ return;
55
+ }
56
+ if (SCHEME.test(reference) || reference.startsWith("//")) {
57
+ fail("email-font-src", `@font-face src ${JSON.stringify(reference)} must be a project .woff2 file or an https URL.`);
58
+ return;
59
+ }
60
+ if (!FONT_FILE.test(path)) {
61
+ fail("email-font-format", `@font-face src ${JSON.stringify(reference)} is not a .woff2 file. Email fonts are WOFF2 only; convert the file to .woff2.`);
62
+ return;
63
+ }
64
+ const url = resolve?.(reference);
65
+ if (url === void 0) {
66
+ fail("email-font-missing", `@font-face src ${JSON.stringify(reference)} names no file in the project. Paths resolve from the stylesheet's own directory.`);
67
+ return;
68
+ }
69
+ part.value = url;
70
+ } });
71
+ declaration.value = {
72
+ type: "Raw",
73
+ value: generate(value).replace(/\)(?=(?:format|tech)\()/gi, ") ")
74
+ };
75
+ }
76
+ if (family === void 0 || family === "") fail("email-font-family", "@font-face declares no font-family.");
77
+ if (sources === 0) fail("email-font-src", "@font-face declares no url() src.");
78
+ if (diagnostics.length > 0 || family === void 0) return {
79
+ rule: void 0,
80
+ diagnostics
81
+ };
82
+ return {
83
+ rule: {
84
+ family,
85
+ css: generate(node)
86
+ },
87
+ diagnostics
88
+ };
89
+ };
90
+ //#endregion
91
+ //#region src/email/css.ts
92
+ const UNLAYERED = Number.MAX_SAFE_INTEGER;
93
+ const CONDITIONAL_AT_RULES = /* @__PURE__ */ new Set([
94
+ "media",
95
+ "supports",
96
+ "container"
97
+ ]);
98
+ const HEAD_AT_RULES = /* @__PURE__ */ new Set([
99
+ "page",
100
+ "counter-style",
101
+ "keyframes"
102
+ ]);
103
+ const ROOT_SELECTORS = /* @__PURE__ */ new Set([
104
+ ":root",
105
+ ":host",
106
+ "html",
107
+ "*",
108
+ ":where(:root)"
109
+ ]);
110
+ const text = (node) => node === null || node === void 0 ? "" : generate(node);
111
+ /** The CSS specificity triple for one selector, ignoring the parts email clients drop. */
112
+ const specificityOf = (selector) => {
113
+ let id = 0;
114
+ let cls = 0;
115
+ let type = 0;
116
+ const visit = (node) => {
117
+ switch (node.type) {
118
+ case "IdSelector":
119
+ id += 1;
120
+ break;
121
+ case "ClassSelector":
122
+ case "AttributeSelector":
123
+ cls += 1;
124
+ break;
125
+ case "PseudoClassSelector":
126
+ if (node.name !== "where") cls += 1;
127
+ break;
128
+ case "TypeSelector":
129
+ if (node.name !== "*") type += 1;
130
+ break;
131
+ case "PseudoElementSelector": type += 1;
132
+ }
133
+ if ("children" in node && node.children !== null && node.children !== void 0) for (const child of node.children) visit(child);
134
+ };
135
+ visit(selector);
136
+ return [
137
+ id,
138
+ cls,
139
+ type
140
+ ];
141
+ };
142
+ const hasPseudo = (selector) => {
143
+ let found = false;
144
+ const visit = (node) => {
145
+ if (node.type === "PseudoClassSelector" || node.type === "PseudoElementSelector") found = true;
146
+ if ("children" in node && node.children !== null && node.children !== void 0) for (const child of node.children) visit(child);
147
+ };
148
+ visit(selector);
149
+ return found;
150
+ };
151
+ const COMBINATORS = {
152
+ " ": "descendant",
153
+ ">": "child",
154
+ "+": "adjacent",
155
+ "~": "sibling"
156
+ };
157
+ /** Compile a selector into the plain-data matcher the render lane uses. */
158
+ const compileSelector = (selector) => {
159
+ let ast;
160
+ try {
161
+ ast = parse(selector, {
162
+ context: "selector",
163
+ positions: false
164
+ });
165
+ } catch {
166
+ return;
167
+ }
168
+ if (ast.type !== "Selector" || ast.children === null) return void 0;
169
+ const parts = [];
170
+ let tag;
171
+ let id;
172
+ let classes = [];
173
+ let attributes = [];
174
+ let opaque = false;
175
+ const flush = (after) => {
176
+ parts.push({
177
+ compound: {
178
+ tag,
179
+ id,
180
+ classes,
181
+ attributes,
182
+ opaque
183
+ },
184
+ ...after === void 0 ? {} : { after }
185
+ });
186
+ tag = void 0;
187
+ id = void 0;
188
+ classes = [];
189
+ attributes = [];
190
+ opaque = false;
191
+ };
192
+ for (const node of ast.children) switch (node.type) {
193
+ case "TypeSelector":
194
+ if (node.name !== "*") tag = node.name.toLowerCase();
195
+ break;
196
+ case "IdSelector":
197
+ id = node.name;
198
+ break;
199
+ case "ClassSelector":
200
+ classes.push(ident.decode(node.name));
201
+ break;
202
+ case "AttributeSelector":
203
+ attributes.push({
204
+ name: node.name.name,
205
+ operator: node.matcher ?? void 0,
206
+ value: node.value === null || node.value === void 0 ? void 0 : node.value.type === "String" ? node.value.value : node.value.name
207
+ });
208
+ break;
209
+ case "Combinator":
210
+ flush(COMBINATORS[node.name.trim() === "" ? " " : node.name] ?? "descendant");
211
+ break;
212
+ default: opaque = true;
213
+ }
214
+ flush();
215
+ return { parts };
216
+ };
217
+ /** Substitute a nested rule's `&` against its parent selector. */
218
+ const nestSelector = (parent, child) => child.includes("&") ? child.replaceAll("&", parent) : `${parent} ${child}`;
219
+ /** Parse one stylesheet into rules, keeping layer, order and conditions. */
220
+ const parseStylesheet = (source, options = {}) => {
221
+ const rules = [];
222
+ const headAtRules = [];
223
+ const fontFaces = [];
224
+ const variables = {};
225
+ const darkVariables = {};
226
+ const conditionalVariables = /* @__PURE__ */ new Set();
227
+ const diagnostics = [];
228
+ const layers = /* @__PURE__ */ new Map();
229
+ const selectorFacts = /* @__PURE__ */ new Map();
230
+ let order = options.startOrder ?? 0;
231
+ /** Everything a rule needs from its selector, computed once per distinct string. */
232
+ const factsFor = (selector) => {
233
+ const cached = selectorFacts.get(selector);
234
+ if (cached !== void 0) return cached;
235
+ let facts;
236
+ try {
237
+ const node = parse(selector, {
238
+ context: "selector",
239
+ positions: false
240
+ });
241
+ const classes = /* @__PURE__ */ new Set();
242
+ walk(node, {
243
+ visit: "ClassSelector",
244
+ enter: (part) => {
245
+ classes.add(ident.decode(part.name));
246
+ }
247
+ });
248
+ facts = {
249
+ classes: [...classes],
250
+ specificity: specificityOf(node),
251
+ pseudo: hasPseudo(node),
252
+ matcher: compileSelector(selector)
253
+ };
254
+ } catch {
255
+ facts = {
256
+ classes: [],
257
+ specificity: [
258
+ 0,
259
+ 0,
260
+ 0
261
+ ],
262
+ pseudo: true,
263
+ matcher: void 0
264
+ };
265
+ }
266
+ selectorFacts.set(selector, facts);
267
+ return facts;
268
+ };
269
+ let ast;
270
+ try {
271
+ ast = parse(source, {
272
+ parseValue: false,
273
+ parseCustomProperty: false,
274
+ positions: false
275
+ });
276
+ } catch (cause) {
277
+ return {
278
+ rules: [],
279
+ headAtRules: [],
280
+ fontFaces: [],
281
+ variables: {},
282
+ darkVariables: {},
283
+ diagnostics: [{
284
+ code: "css-parse-error",
285
+ severity: "error",
286
+ message: `${options.origin ?? "stylesheet"} could not be parsed: ${String(cause)}`,
287
+ origins: []
288
+ }]
289
+ };
290
+ }
291
+ const declarationsOf = (block) => {
292
+ const output = [];
293
+ if (block === null || block.type !== "Block" || block.children === null) return output;
294
+ for (const child of block.children) {
295
+ if (child.type !== "Declaration") continue;
296
+ output.push({
297
+ property: child.property,
298
+ value: downlevelColors(text(child.value).trim().replace(/[\t\n\r\f]/g, " ")),
299
+ important: child.important === true
300
+ });
301
+ }
302
+ return output;
303
+ };
304
+ const emit = (selector, context, declarations) => {
305
+ if (declarations.length === 0) return;
306
+ const facts = factsFor(selector);
307
+ if (ROOT_SELECTORS.has(selector.trim())) for (const declaration of declarations) {
308
+ if (!declaration.property.startsWith("--")) continue;
309
+ if (context.conditions.length === 0) variables[declaration.property] = declaration.value;
310
+ else if (isDarkSchemeCondition(context.conditions)) darkVariables[declaration.property] = declaration.value;
311
+ else conditionalVariables.add(declaration.property);
312
+ }
313
+ rules.push({
314
+ selector,
315
+ classes: facts.classes,
316
+ specificity: facts.specificity,
317
+ declarations,
318
+ order: order++,
319
+ layer: context.layer,
320
+ conditions: context.conditions,
321
+ inlinable: context.conditions.length === 0 && !facts.pseudo,
322
+ matcher: facts.matcher
323
+ });
324
+ };
325
+ /**
326
+ * Declarations directly in a block belong to the enclosing selector, whether
327
+ * that block is the rule's own or a `@media` nested inside it — which is
328
+ * exactly the shape Tailwind v4 emits for a responsive utility.
329
+ */
330
+ const walkBlock = (block, context) => {
331
+ if (block === null || block.type !== "Block" || block.children === null) return;
332
+ const declarations = declarationsOf(block);
333
+ if (declarations.length > 0 && context.selector !== void 0) emit(context.selector, context, declarations);
334
+ for (const child of block.children) if (child.type !== "Declaration") visitNode(child, context);
335
+ };
336
+ const visitNode = (node, context) => {
337
+ if (node.type === "Rule") {
338
+ const prelude = node.prelude;
339
+ const selectors = prelude.type === "SelectorList" && prelude.children !== null ? [...prelude.children] : [prelude];
340
+ for (const selector of selectors) {
341
+ const own = text(selector).trim();
342
+ if (own === "") continue;
343
+ walkBlock(node.block, {
344
+ ...context,
345
+ selector: context.selector === void 0 ? own : nestSelector(context.selector, own)
346
+ });
347
+ }
348
+ return;
349
+ }
350
+ if (node.type !== "Atrule") return;
351
+ const name = node.name.toLowerCase();
352
+ if (name === "layer") {
353
+ const prelude = text(node.prelude).trim();
354
+ if (node.block === null) {
355
+ for (const declared of prelude.split(",").map((part) => part.trim())) if (declared !== "" && !layers.has(declared)) layers.set(declared, layers.size);
356
+ return;
357
+ }
358
+ const named = prelude.split(",")[0]?.trim() ?? "";
359
+ if (named !== "" && !layers.has(named)) layers.set(named, layers.size);
360
+ walkBlock(node.block, {
361
+ ...context,
362
+ layer: layers.get(named) ?? context.layer
363
+ });
364
+ return;
365
+ }
366
+ if (name === "theme" || name === "scope") {
367
+ const into = context.conditions.length === 0 ? variables : isDarkSchemeCondition(context.conditions) ? darkVariables : void 0;
368
+ for (const declaration of declarationsOf(node.block)) if (declaration.property.startsWith("--")) {
369
+ if (into === void 0) conditionalVariables.add(declaration.property);
370
+ else into[declaration.property] = declaration.value;
371
+ }
372
+ for (const child of node.block?.children ?? []) if (child.type !== "Declaration") visitNode(child, context);
373
+ return;
374
+ }
375
+ if (name === "property") {
376
+ const initial = declarationsOf(node.block).find((declaration) => declaration.property === "initial-value");
377
+ const property = text(node.prelude).trim();
378
+ if (initial !== void 0 && property.startsWith("--") && !(property in variables)) variables[property] = initial.value;
379
+ return;
380
+ }
381
+ if (CONDITIONAL_AT_RULES.has(name)) {
382
+ walkBlock(node.block, {
383
+ ...context,
384
+ conditions: [...context.conditions, `@${name} ${text(node.prelude).trim()}`]
385
+ });
386
+ return;
387
+ }
388
+ if (name === "font-face") {
389
+ const face = compileFontFace(node, options.origin ?? "stylesheet", options.resolveFontFile);
390
+ if (face.rule !== void 0) fontFaces.push(face.rule);
391
+ diagnostics.push(...face.diagnostics);
392
+ return;
393
+ }
394
+ if (HEAD_AT_RULES.has(name)) {
395
+ headAtRules.push(generate(node));
396
+ return;
397
+ }
398
+ if (name === "import") {
399
+ if (options.themeImports === true) return;
400
+ diagnostics.push({
401
+ code: "css-import-unresolved",
402
+ severity: "error",
403
+ message: `${options.origin ?? "stylesheet"} uses @import ${text(node.prelude).trim()}; import the stylesheet from the template module instead so the compiler can resolve it.`,
404
+ origins: []
405
+ });
406
+ return;
407
+ }
408
+ if (name === "charset" || name === "namespace") return;
409
+ diagnostics.push({
410
+ code: "css-unsupported-at-rule",
411
+ severity: "warning",
412
+ message: `@${name} is not applied to email output; remove it or move the declarations into a class rule.`,
413
+ origins: []
414
+ });
415
+ };
416
+ if (ast.type === "StyleSheet" && ast.children !== null) for (const child of ast.children) visitNode(child, {
417
+ conditions: [],
418
+ layer: UNLAYERED
419
+ });
420
+ for (const property of conditionalVariables) if (property in variables) diagnostics.push({
421
+ code: "css-conditional-variable",
422
+ severity: "warning",
423
+ message: `${property} is redefined inside a conditional at-rule. Inlined declarations take the unconditional value; keep the override on a class rule so the retained head rule can apply it.`,
424
+ origins: []
425
+ });
426
+ return {
427
+ rules,
428
+ headAtRules,
429
+ fontFaces,
430
+ variables,
431
+ darkVariables,
432
+ diagnostics
433
+ };
434
+ };
435
+ //#endregion
436
+ export { parseStylesheet as t };