better-svelte-email 1.0.3 → 1.1.0-beta.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 (26) hide show
  1. package/README.md +1 -1
  2. package/dist/render/index.js +7 -6
  3. package/dist/render/utils/css/extract-rules-per-class.d.ts +2 -2
  4. package/dist/render/utils/css/extract-rules-per-class.js +13 -24
  5. package/dist/render/utils/css/get-custom-properties.d.ts +2 -2
  6. package/dist/render/utils/css/get-custom-properties.js +16 -31
  7. package/dist/render/utils/css/is-rule-inlinable.d.ts +1 -1
  8. package/dist/render/utils/css/is-rule-inlinable.js +30 -4
  9. package/dist/render/utils/css/make-inline-styles-for.d.ts +2 -2
  10. package/dist/render/utils/css/make-inline-styles-for.js +38 -41
  11. package/dist/render/utils/css/resolve-all-css-variables.d.ts +3 -3
  12. package/dist/render/utils/css/resolve-all-css-variables.js +107 -95
  13. package/dist/render/utils/css/resolve-calc-expressions.d.ts +2 -5
  14. package/dist/render/utils/css/resolve-calc-expressions.js +155 -118
  15. package/dist/render/utils/css/sanitize-declarations.d.ts +2 -2
  16. package/dist/render/utils/css/sanitize-declarations.js +226 -282
  17. package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +2 -2
  18. package/dist/render/utils/css/sanitize-non-inlinable-rules.js +14 -19
  19. package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -2
  20. package/dist/render/utils/css/sanitize-stylesheet.js +4 -4
  21. package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +1 -1
  22. package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +2 -2
  23. package/dist/render/utils/tailwindcss/setup-tailwind.js +3 -3
  24. package/package.json +5 -4
  25. package/dist/render/utils/css/unwrap-value.d.ts +0 -2
  26. package/dist/render/utils/css/unwrap-value.js +0 -6
@@ -1,42 +1,5 @@
1
- import { generate, List, parse, walk } from 'css-tree';
2
- function rgbNode(r, g, b, alpha) {
3
- const children = new List();
4
- children.appendData({
5
- type: 'Number',
6
- value: r.toFixed(0)
7
- });
8
- children.appendData({
9
- type: 'Operator',
10
- value: ','
11
- });
12
- children.appendData({
13
- type: 'Number',
14
- value: g.toFixed(0)
15
- });
16
- children.appendData({
17
- type: 'Operator',
18
- value: ','
19
- });
20
- children.appendData({
21
- type: 'Number',
22
- value: b.toFixed(0)
23
- });
24
- if (alpha !== 1 && alpha !== undefined) {
25
- children.appendData({
26
- type: 'Operator',
27
- value: ','
28
- });
29
- children.appendData({
30
- type: 'Number',
31
- value: alpha.toString()
32
- });
33
- }
34
- return {
35
- type: 'Function',
36
- name: 'rgb',
37
- children
38
- };
39
- }
1
+ import valueParser, {} from 'postcss-value-parser';
2
+ // Color conversion constants
40
3
  const LAB_TO_LMS = {
41
4
  l: [0.3963377773761749, 0.2158037573099136],
42
5
  m: [-0.1055613458156586, -0.0638541728258133],
@@ -65,7 +28,7 @@ function oklchToOklab(oklch) {
65
28
  b: oklch.c * Math.sin((oklch.h / 180) * Math.PI)
66
29
  };
67
30
  }
68
- /** Convert oklab to RGB */
31
+ /** Convert oklch to RGB */
69
32
  function oklchToRgb(oklch) {
70
33
  const oklab = oklchToOklab(oklch);
71
34
  const l = (oklab.l + LAB_TO_LMS.l[0] * oklab.a + LAB_TO_LMS.l[1] * oklab.b) ** 3;
@@ -80,31 +43,155 @@ function oklchToRgb(oklch) {
80
43
  b: clamp(b, 0, 255)
81
44
  };
82
45
  }
83
- function separteShorthandDeclaration(shorthandToReplace, [start, end]) {
84
- shorthandToReplace.property = start;
85
- const values = shorthandToReplace.value.type === 'Value'
86
- ? shorthandToReplace.value.children
87
- .toArray()
88
- .filter((child) => child.type === 'Dimension' || child.type === 'Number' || child.type === 'Percentage')
89
- : [shorthandToReplace.value];
90
- let endValue = shorthandToReplace.value;
91
- if (values.length === 2) {
92
- endValue = {
93
- type: 'Value',
94
- children: new List().fromArray([values[1]])
46
+ function formatRgb(r, g, b, a) {
47
+ const rInt = Math.round(r);
48
+ const gInt = Math.round(g);
49
+ const bInt = Math.round(b);
50
+ if (a !== undefined && a !== 1) {
51
+ return `rgb(${rInt}, ${gInt}, ${bInt}, ${a})`;
52
+ }
53
+ return `rgb(${rInt}, ${gInt}, ${bInt})`;
54
+ }
55
+ function hexToRgb(hex) {
56
+ hex = hex.replace('#', '');
57
+ if (hex.length === 3) {
58
+ return {
59
+ r: parseInt(hex[0] + hex[0], 16),
60
+ g: parseInt(hex[1] + hex[1], 16),
61
+ b: parseInt(hex[2] + hex[2], 16)
62
+ };
63
+ }
64
+ if (hex.length === 4) {
65
+ return {
66
+ r: parseInt(hex[0] + hex[0], 16),
67
+ g: parseInt(hex[1] + hex[1], 16),
68
+ b: parseInt(hex[2] + hex[2], 16),
69
+ a: parseInt(hex[3] + hex[3], 16) / 255
70
+ };
71
+ }
72
+ if (hex.length === 5) {
73
+ return {
74
+ r: parseInt(hex.slice(0, 2), 16),
75
+ g: parseInt(hex[2] + hex[2], 16),
76
+ b: parseInt(hex[3] + hex[3], 16),
77
+ a: parseInt(hex[4] + hex[4], 16) / 255
78
+ };
79
+ }
80
+ if (hex.length === 6) {
81
+ return {
82
+ r: parseInt(hex.slice(0, 2), 16),
83
+ g: parseInt(hex.slice(2, 4), 16),
84
+ b: parseInt(hex.slice(4, 6), 16)
95
85
  };
96
- shorthandToReplace.value = {
97
- type: 'Value',
98
- children: new List().fromArray([values[0]])
86
+ }
87
+ if (hex.length === 7) {
88
+ return {
89
+ r: parseInt(hex.slice(0, 2), 16),
90
+ g: parseInt(hex.slice(2, 4), 16),
91
+ b: parseInt(hex.slice(4, 6), 16),
92
+ a: parseInt(hex[6] + hex[6], 16) / 255
99
93
  };
100
94
  }
95
+ // 8 character hex
101
96
  return {
102
- type: 'Declaration',
103
- property: end,
104
- value: endValue,
105
- important: shorthandToReplace.important
97
+ r: parseInt(hex.slice(0, 2), 16),
98
+ g: parseInt(hex.slice(2, 4), 16),
99
+ b: parseInt(hex.slice(4, 6), 16),
100
+ a: parseInt(hex.slice(6, 8), 16) / 255
106
101
  };
107
102
  }
103
+ function parseOklchValues(nodes) {
104
+ const result = {};
105
+ for (const node of nodes) {
106
+ if (node.type === 'word') {
107
+ const numMatch = node.value.match(/^(-?[\d.]+)(%|deg)?$/i);
108
+ if (numMatch) {
109
+ const value = parseFloat(numMatch[1]);
110
+ const unit = numMatch[2]?.toLowerCase();
111
+ if (unit === '%') {
112
+ if (result.l === undefined) {
113
+ result.l = value / 100;
114
+ }
115
+ else if (result.a === undefined) {
116
+ result.a = value / 100;
117
+ }
118
+ }
119
+ else if (unit === 'deg') {
120
+ if (result.h === undefined) {
121
+ result.h = value;
122
+ }
123
+ }
124
+ else {
125
+ if (result.l === undefined) {
126
+ result.l = value;
127
+ }
128
+ else if (result.c === undefined) {
129
+ result.c = value;
130
+ }
131
+ else if (result.h === undefined) {
132
+ result.h = value;
133
+ }
134
+ else if (result.a === undefined) {
135
+ result.a = value;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return result;
142
+ }
143
+ function parseRgbValues(nodes) {
144
+ const result = {};
145
+ for (const node of nodes) {
146
+ if (node.type === 'word') {
147
+ const numMatch = node.value.match(/^(-?[\d.]+)(%)?$/);
148
+ if (numMatch) {
149
+ const value = parseFloat(numMatch[1]);
150
+ const isPercent = numMatch[2] === '%';
151
+ if (result.r === undefined) {
152
+ result.r = isPercent ? (value * 255) / 100 : value;
153
+ }
154
+ else if (result.g === undefined) {
155
+ result.g = isPercent ? (value * 255) / 100 : value;
156
+ }
157
+ else if (result.b === undefined) {
158
+ result.b = isPercent ? (value * 255) / 100 : value;
159
+ }
160
+ else if (result.a === undefined) {
161
+ result.a = isPercent ? value / 100 : value;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return result;
167
+ }
168
+ function transformColorMix(node) {
169
+ // We're expecting the structure to be something like:
170
+ // color-mix(in oklab, rgb(...) X%, transparent)
171
+ // Check if it ends with transparent
172
+ const lastNode = node.nodes[node.nodes.length - 1];
173
+ if (lastNode?.type !== 'word' || lastNode.value !== 'transparent') {
174
+ return null;
175
+ }
176
+ // Find the rgb function and percentage
177
+ let rgbFunc = null;
178
+ let percentage = null;
179
+ for (const child of node.nodes) {
180
+ if (child.type === 'function' && child.value === 'rgb') {
181
+ rgbFunc = child;
182
+ }
183
+ if (child.type === 'word' && child.value.endsWith('%')) {
184
+ percentage = parseFloat(child.value) / 100;
185
+ }
186
+ }
187
+ if (rgbFunc && percentage !== null) {
188
+ const rgbValues = parseRgbValues(rgbFunc.nodes);
189
+ if (rgbValues.r !== undefined && rgbValues.g !== undefined && rgbValues.b !== undefined) {
190
+ return formatRgb(rgbValues.r, rgbValues.g, rgbValues.b, percentage);
191
+ }
192
+ }
193
+ return null;
194
+ }
108
195
  /**
109
196
  * Meant to do all the things necessary, in a per-declaration basis, to have the best email client
110
197
  * support possible.
@@ -118,237 +205,94 @@ function separteShorthandDeclaration(shorthandToReplace, [start, end]) {
118
205
  * - convert `margin-inline` into `margin-left` and `margin-right`;
119
206
  * - convert `margin-block` into `margin-top` and `margin-bottom`.
120
207
  */
121
- export function sanitizeDeclarations(nodeContainingDeclarations) {
122
- walk(nodeContainingDeclarations, {
123
- visit: 'Declaration',
124
- enter(declaration, item, list) {
125
- if (declaration.value.type === 'Raw') {
126
- declaration.value = parse(declaration.value.value, {
127
- context: 'value'
128
- });
129
- }
130
- if (/border-radius\s*:\s*calc\s*\(\s*infinity\s*\*\s*1px\s*\)/i.test(generate(declaration))) {
131
- declaration.value = parse('9999px', { context: 'value' });
132
- }
133
- walk(declaration, {
134
- visit: 'Function',
135
- enter(func, funcParentListItem) {
136
- const children = func.children.toArray();
137
- if (func.name === 'oklch') {
138
- let l;
139
- let c;
140
- let h;
141
- let a;
142
- for (const child of children) {
143
- if (child.type === 'Number') {
144
- if (l === undefined) {
145
- l = Number.parseFloat(child.value);
146
- continue;
147
- }
148
- if (c === undefined) {
149
- c = Number.parseFloat(child.value);
150
- continue;
151
- }
152
- if (h === undefined) {
153
- h = Number.parseFloat(child.value);
154
- continue;
155
- }
156
- if (a === undefined) {
157
- a = Number.parseFloat(child.value);
158
- continue;
159
- }
160
- }
161
- if (child.type === 'Dimension' && child.unit === 'deg') {
162
- if (h === undefined) {
163
- h = Number.parseFloat(child.value);
164
- continue;
165
- }
166
- }
167
- if (child.type === 'Percentage') {
168
- if (l === undefined) {
169
- l = Number.parseFloat(child.value) / 100;
170
- continue;
171
- }
172
- if (a === undefined) {
173
- a = Number.parseFloat(child.value) / 100;
174
- }
175
- }
176
- }
177
- if (l === undefined || c === undefined || h === undefined) {
178
- throw new Error('Could not determine the parameters of an oklch() function.', {
179
- cause: declaration
180
- });
181
- }
182
- const rgb = oklchToRgb({
183
- l,
184
- c,
185
- h
208
+ export function sanitizeDeclarations(root) {
209
+ root.walkDecls((decl) => {
210
+ // Handle infinity calc for border-radius
211
+ if (decl.prop === 'border-radius' && /calc\s*\(\s*infinity\s*\*\s*1px\s*\)/i.test(decl.value)) {
212
+ decl.value = '9999px';
213
+ }
214
+ // Handle shorthand properties
215
+ if (decl.prop === 'padding-inline') {
216
+ const values = decl.value.split(/\s+/).filter(Boolean);
217
+ decl.prop = 'padding-left';
218
+ decl.value = values[0];
219
+ decl.cloneAfter({ prop: 'padding-right', value: values[1] || values[0] });
220
+ }
221
+ if (decl.prop === 'padding-block') {
222
+ const values = decl.value.split(/\s+/).filter(Boolean);
223
+ decl.prop = 'padding-top';
224
+ decl.value = values[0];
225
+ decl.cloneAfter({ prop: 'padding-bottom', value: values[1] || values[0] });
226
+ }
227
+ if (decl.prop === 'margin-inline') {
228
+ const values = decl.value.split(/\s+/).filter(Boolean);
229
+ decl.prop = 'margin-left';
230
+ decl.value = values[0];
231
+ decl.cloneAfter({ prop: 'margin-right', value: values[1] || values[0] });
232
+ }
233
+ if (decl.prop === 'margin-block') {
234
+ const values = decl.value.split(/\s+/).filter(Boolean);
235
+ decl.prop = 'margin-top';
236
+ decl.value = values[0];
237
+ decl.cloneAfter({ prop: 'margin-bottom', value: values[1] || values[0] });
238
+ }
239
+ // Parse and transform color values
240
+ if (decl.value.includes('oklch(') ||
241
+ decl.value.includes('rgb(') ||
242
+ decl.value.includes('#') ||
243
+ decl.value.includes('color-mix(')) {
244
+ const parsed = valueParser(decl.value);
245
+ parsed.walk((node) => {
246
+ // Convert oklch to rgb
247
+ if (node.type === 'function' && node.value === 'oklch') {
248
+ const oklchValues = parseOklchValues(node.nodes);
249
+ if (oklchValues.l === undefined ||
250
+ oklchValues.c === undefined ||
251
+ oklchValues.h === undefined) {
252
+ throw new Error('Could not determine the parameters of an oklch() function.', {
253
+ cause: decl
186
254
  });
187
- funcParentListItem.data = rgbNode(rgb.r, rgb.g, rgb.b, a);
188
- }
189
- if (func.name === 'rgb') {
190
- let r;
191
- let g;
192
- let b;
193
- let a;
194
- for (const child of children) {
195
- if (child.type === 'Number') {
196
- if (r === undefined) {
197
- r = Number.parseFloat(child.value);
198
- continue;
199
- }
200
- if (g === undefined) {
201
- g = Number.parseFloat(child.value);
202
- continue;
203
- }
204
- if (b === undefined) {
205
- b = Number.parseFloat(child.value);
206
- continue;
207
- }
208
- if (a === undefined) {
209
- a = Number.parseFloat(child.value);
210
- continue;
211
- }
212
- }
213
- if (child.type === 'Percentage') {
214
- if (r === undefined) {
215
- r = (Number.parseFloat(child.value) * 255) / 100;
216
- continue;
217
- }
218
- if (g === undefined) {
219
- g = (Number.parseFloat(child.value) * 255) / 100;
220
- continue;
221
- }
222
- if (b === undefined) {
223
- b = (Number.parseFloat(child.value) * 255) / 100;
224
- continue;
225
- }
226
- if (a === undefined) {
227
- a = Number.parseFloat(child.value) / 100;
228
- }
229
- }
230
- }
231
- if (r === undefined || g === undefined || b === undefined) {
232
- throw new Error('Could not determine the parameters of an rgb() function.', {
233
- cause: declaration
234
- });
235
- }
236
- if (a === undefined || a === 1) {
237
- funcParentListItem.data = rgbNode(r, g, b);
238
- }
239
- else {
240
- funcParentListItem.data = rgbNode(r, g, b, a);
241
- }
242
255
  }
256
+ const rgb = oklchToRgb({
257
+ l: oklchValues.l,
258
+ c: oklchValues.c,
259
+ h: oklchValues.h
260
+ });
261
+ // Transform function node to word node
262
+ node.type = 'word';
263
+ node.value = formatRgb(rgb.r, rgb.g, rgb.b, oklchValues.a);
264
+ node.nodes = [];
243
265
  }
244
- });
245
- walk(declaration, {
246
- visit: 'Hash',
247
- enter(hash, hashParentListItem) {
248
- const hex = hash.value.trim();
249
- if (hex.length === 3) {
250
- const r = Number.parseInt(hex.charAt(0) + hex.charAt(0), 16);
251
- const g = Number.parseInt(hex.charAt(1) + hex.charAt(1), 16);
252
- const b = Number.parseInt(hex.charAt(2) + hex.charAt(2), 16);
253
- hashParentListItem.data = rgbNode(r, g, b);
254
- return;
255
- }
256
- if (hex.length === 4) {
257
- const r = Number.parseInt(hex.charAt(0) + hex.charAt(0), 16);
258
- const g = Number.parseInt(hex.charAt(1) + hex.charAt(1), 16);
259
- const b = Number.parseInt(hex.charAt(2) + hex.charAt(2), 16);
260
- const a = Number.parseInt(hex.charAt(3) + hex.charAt(3), 16) / 255;
261
- hashParentListItem.data = rgbNode(r, g, b, a);
262
- return;
263
- }
264
- if (hex.length === 5) {
265
- const r = Number.parseInt(hex.slice(0, 2), 16);
266
- const g = Number.parseInt(hex.charAt(2) + hex.charAt(2), 16);
267
- const b = Number.parseInt(hex.charAt(3) + hex.charAt(3), 16);
268
- const a = Number.parseInt(hex.charAt(4) + hex.charAt(4), 16) / 255;
269
- hashParentListItem.data = rgbNode(r, g, b, a);
270
- return;
271
- }
272
- if (hex.length === 6) {
273
- const r = Number.parseInt(hex.slice(0, 2), 16);
274
- const g = Number.parseInt(hex.slice(2, 4), 16);
275
- const b = Number.parseInt(hex.slice(4, 6), 16);
276
- hashParentListItem.data = rgbNode(r, g, b);
277
- return;
278
- }
279
- if (hex.length === 7) {
280
- const r = Number.parseInt(hex.slice(0, 2), 16);
281
- const g = Number.parseInt(hex.slice(2, 4), 16);
282
- const b = Number.parseInt(hex.slice(4, 6), 16);
283
- const a = Number.parseInt(hex.charAt(6) + hex.charAt(6), 16) / 255;
284
- hashParentListItem.data = rgbNode(r, g, b, a);
285
- return;
266
+ // Convert space-based rgb to comma-based
267
+ if (node.type === 'function' && node.value === 'rgb') {
268
+ const rgbValues = parseRgbValues(node.nodes);
269
+ if (rgbValues.r === undefined || rgbValues.g === undefined || rgbValues.b === undefined) {
270
+ throw new Error('Could not determine the parameters of an rgb() function.', {
271
+ cause: decl
272
+ });
286
273
  }
287
- const r = Number.parseInt(hex.slice(0, 2), 16);
288
- const g = Number.parseInt(hex.slice(2, 4), 16);
289
- const b = Number.parseInt(hex.slice(4, 6), 16);
290
- const a = Number.parseInt(hex.slice(6, 8), 16) / 255;
291
- hashParentListItem.data = rgbNode(r, g, b, a);
274
+ // Transform function node to word node
275
+ node.type = 'word';
276
+ node.value = formatRgb(rgbValues.r, rgbValues.g, rgbValues.b, rgbValues.a);
277
+ node.nodes = [];
292
278
  }
293
- });
294
- walk(declaration, {
295
- visit: 'Function',
296
- enter(func, parentListItem) {
297
- if (func.name === 'color-mix') {
298
- const children = func.children.toArray();
299
- // We're expecting the children here to be something like:
300
- // Identifier (in)
301
- // Identifier (oklab)
302
- // Operator (,)
303
- // FunctionNode (rgb(...))
304
- // Node (opacity)
305
- // Operator (,)
306
- // Identifier (transparent)
307
- const color = children[3];
308
- const opacity = children[4];
309
- if (func.children.last?.type === 'Identifier' &&
310
- func.children.last.name === 'transparent' &&
311
- color?.type === 'Function' &&
312
- color?.name === 'rgb' &&
313
- opacity) {
314
- color.children.appendData({
315
- type: 'Operator',
316
- value: ','
317
- });
318
- color.children.appendData(opacity);
319
- parentListItem.data = color;
320
- }
279
+ // Handle color-mix with transparent
280
+ if (node.type === 'function' && node.value === 'color-mix') {
281
+ const result = transformColorMix(node);
282
+ if (result) {
283
+ // Transform function node to word node
284
+ node.type = 'word';
285
+ node.value = result;
286
+ node.nodes = [];
321
287
  }
322
288
  }
289
+ // Convert hex to rgb
290
+ if (node.type === 'word' && node.value.startsWith('#')) {
291
+ const rgb = hexToRgb(node.value);
292
+ node.value = formatRgb(rgb.r, rgb.g, rgb.b, rgb.a);
293
+ }
323
294
  });
324
- if (declaration.property === 'padding-inline') {
325
- const paddingRight = separteShorthandDeclaration(declaration, [
326
- 'padding-left',
327
- 'padding-right'
328
- ]);
329
- list.insertData(paddingRight, item);
330
- }
331
- if (declaration.property === 'padding-block') {
332
- const paddingBottom = separteShorthandDeclaration(declaration, [
333
- 'padding-top',
334
- 'padding-bottom'
335
- ]);
336
- list.insertData(paddingBottom, item);
337
- }
338
- if (declaration.property === 'margin-inline') {
339
- const marginRight = separteShorthandDeclaration(declaration, [
340
- 'margin-left',
341
- 'margin-right'
342
- ]);
343
- list.insertData(marginRight, item);
344
- }
345
- if (declaration.property === 'margin-block') {
346
- const paddingBottom = separteShorthandDeclaration(declaration, [
347
- 'margin-top',
348
- 'margin-bottom'
349
- ]);
350
- list.insertData(paddingBottom, item);
351
- }
295
+ decl.value = valueParser.stringify(parsed.nodes);
352
296
  }
353
297
  });
354
298
  }
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import type { Root } from 'postcss';
2
2
  /**
3
3
  * This function goes through a few steps to ensure the best email client support and
4
4
  * to ensure that media queries and pseudo classes are applied correctly alongside
@@ -8,4 +8,4 @@ import { type CssNode } from 'css-tree';
8
8
  * 1. Converts all declarations in all rules into important ones
9
9
  * 2. Sanitizes class selectors of all non-inlinable rules
10
10
  */
11
- export declare function sanitizeNonInlinableRules(node: CssNode): void;
11
+ export declare function sanitizeNonInlinableRules(root: Root): void;
@@ -1,4 +1,3 @@
1
- import { string, walk } from 'css-tree';
2
1
  import { sanitizeClassName } from '../compatibility/sanitize-class-name.js';
3
2
  import { isRuleInlinable } from './is-rule-inlinable.js';
4
3
  /**
@@ -10,24 +9,20 @@ import { isRuleInlinable } from './is-rule-inlinable.js';
10
9
  * 1. Converts all declarations in all rules into important ones
11
10
  * 2. Sanitizes class selectors of all non-inlinable rules
12
11
  */
13
- export function sanitizeNonInlinableRules(node) {
14
- walk(node, {
15
- visit: 'Rule',
16
- enter(rule) {
17
- if (!isRuleInlinable(rule)) {
18
- walk(rule.prelude, (node) => {
19
- if (node.type === 'ClassSelector') {
20
- const unescapedClassName = string.decode(node.name);
21
- node.name = sanitizeClassName(unescapedClassName);
22
- }
23
- });
24
- walk(rule, {
25
- visit: 'Declaration',
26
- enter(declaration) {
27
- declaration.important = true;
28
- }
29
- });
30
- }
12
+ export function sanitizeNonInlinableRules(root) {
13
+ root.walkRules((rule) => {
14
+ if (!isRuleInlinable(rule)) {
15
+ // Sanitize class names in selector
16
+ // The regex matches class names including escaped characters (like \: or \/)
17
+ // Note: \\. must come FIRST in the alternation to properly match escapes
18
+ rule.selector = rule.selector.replace(/\.((?:\\.|[^\s.:>+~[#,])+)/g, (match, className) => {
19
+ const unescaped = className.replace(/\\(.)/g, '$1');
20
+ return '.' + sanitizeClassName(unescaped);
21
+ });
22
+ // Make all declarations important
23
+ rule.walkDecls((decl) => {
24
+ decl.important = true;
25
+ });
31
26
  }
32
27
  });
33
28
  }
@@ -1,2 +1,2 @@
1
- import type { StyleSheet } from 'css-tree';
2
- export declare function sanitizeStyleSheet(styleSheet: StyleSheet): void;
1
+ import type { Root } from 'postcss';
2
+ export declare function sanitizeStyleSheet(root: Root): void;
@@ -1,8 +1,8 @@
1
1
  import { resolveAllCssVariables } from './resolve-all-css-variables.js';
2
2
  import { resolveCalcExpressions } from './resolve-calc-expressions.js';
3
3
  import { sanitizeDeclarations } from './sanitize-declarations.js';
4
- export function sanitizeStyleSheet(styleSheet) {
5
- resolveAllCssVariables(styleSheet);
6
- resolveCalcExpressions(styleSheet);
7
- sanitizeDeclarations(styleSheet);
4
+ export function sanitizeStyleSheet(root) {
5
+ resolveAllCssVariables(root);
6
+ resolveCalcExpressions(root);
7
+ sanitizeDeclarations(root);
8
8
  }
@@ -1,4 +1,4 @@
1
- import type { Rule } from 'css-tree';
1
+ import type { Rule } from 'postcss';
2
2
  import type { CustomProperties } from '../css/get-custom-properties.js';
3
3
  import type { AST } from '../../index.js';
4
4
  export declare function addInlinedStylesToElement(element: AST.Element, inlinableRules: Map<string, Rule>, nonInlinableRules: Map<string, Rule>, customProperties: CustomProperties, unknownClasses: string[]): AST.Element;
@@ -1,7 +1,7 @@
1
- import { type StyleSheet } from 'css-tree';
1
+ import { type Root } from 'postcss';
2
2
  import type { TailwindConfig } from '../../index.js';
3
3
  export type TailwindSetup = Awaited<ReturnType<typeof setupTailwind>>;
4
4
  export declare function setupTailwind(config: TailwindConfig): Promise<{
5
5
  addUtilities: (candidates: string[]) => void;
6
- getStyleSheet: () => StyleSheet;
6
+ getStyleSheet: () => Root;
7
7
  }>;
@@ -1,4 +1,4 @@
1
- import { parse } from 'css-tree';
1
+ import postcss, {} from 'postcss';
2
2
  import { compile } from 'tailwindcss';
3
3
  import indexCss from './tailwind-stylesheets/index.js';
4
4
  import preflightCss from './tailwind-stylesheets/preflight.js';
@@ -60,8 +60,8 @@ export async function setupTailwind(config) {
60
60
  addUtilities: function addUtilities(candidates) {
61
61
  css = compiler.build(candidates);
62
62
  },
63
- getStyleSheet: function getCss() {
64
- return parse(css);
63
+ getStyleSheet: function getStyleSheet() {
64
+ return postcss.parse(css);
65
65
  }
66
66
  };
67
67
  }