better-svelte-email 0.3.5 → 1.0.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.
- package/dist/components/Body.svelte +7 -3
- package/dist/components/Button.svelte +10 -12
- package/dist/components/Button.svelte.d.ts +2 -2
- package/dist/index.d.ts +2 -6
- package/dist/index.js +3 -5
- package/dist/preprocessor/index.d.ts +19 -0
- package/dist/preprocessor/index.js +19 -0
- package/dist/preview/index.d.ts +34 -5
- package/dist/preview/index.js +91 -44
- package/dist/render/index.d.ts +66 -0
- package/dist/render/index.js +138 -0
- package/dist/render/utils/compatibility/sanitize-class-name.d.ts +7 -0
- package/dist/render/utils/compatibility/sanitize-class-name.js +35 -0
- package/dist/render/utils/css/extract-rules-per-class.d.ts +5 -0
- package/dist/render/utils/css/extract-rules-per-class.js +37 -0
- package/dist/render/utils/css/get-custom-properties.d.ts +8 -0
- package/dist/render/utils/css/get-custom-properties.js +37 -0
- package/dist/render/utils/css/is-rule-inlinable.d.ts +2 -0
- package/dist/render/utils/css/is-rule-inlinable.js +6 -0
- package/dist/render/utils/css/make-inline-styles-for.d.ts +3 -0
- package/dist/render/utils/css/make-inline-styles-for.js +57 -0
- package/dist/render/utils/css/resolve-all-css-variables.d.ts +8 -0
- package/dist/render/utils/css/resolve-all-css-variables.js +123 -0
- package/dist/render/utils/css/resolve-calc-expressions.d.ts +5 -0
- package/dist/render/utils/css/resolve-calc-expressions.js +126 -0
- package/dist/render/utils/css/sanitize-declarations.d.ts +15 -0
- package/dist/render/utils/css/sanitize-declarations.js +354 -0
- package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +11 -0
- package/dist/render/utils/css/sanitize-non-inlinable-rules.js +33 -0
- package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -0
- package/dist/render/utils/css/sanitize-stylesheet.js +8 -0
- package/dist/render/utils/css/unwrap-value.d.ts +2 -0
- package/dist/render/utils/css/unwrap-value.js +6 -0
- package/dist/render/utils/html/is-valid-node.d.ts +2 -0
- package/dist/render/utils/html/is-valid-node.js +3 -0
- package/dist/render/utils/html/remove-attributes-functions.d.ts +2 -0
- package/dist/render/utils/html/remove-attributes-functions.js +10 -0
- package/dist/render/utils/html/walk.d.ts +15 -0
- package/dist/render/utils/html/walk.js +36 -0
- package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +4 -0
- package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.js +61 -0
- package/dist/render/utils/tailwindcss/pixel-based-preset.d.ts +2 -0
- package/dist/render/utils/tailwindcss/pixel-based-preset.js +58 -0
- package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +7 -0
- package/dist/render/utils/tailwindcss/setup-tailwind.js +67 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/index.d.ts +2 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/index.js +899 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/preflight.d.ts +2 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/preflight.js +396 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/theme.d.ts +2 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/theme.js +465 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/utilities.d.ts +2 -0
- package/dist/render/utils/tailwindcss/tailwind-stylesheets/utilities.js +4 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +13 -10
- package/package.json +17 -2
|
@@ -0,0 +1,354 @@
|
|
|
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
|
+
}
|
|
40
|
+
const LAB_TO_LMS = {
|
|
41
|
+
l: [0.3963377773761749, 0.2158037573099136],
|
|
42
|
+
m: [-0.1055613458156586, -0.0638541728258133],
|
|
43
|
+
s: [-0.0894841775298119, -1.2914855480194092]
|
|
44
|
+
};
|
|
45
|
+
const LSM_TO_RGB = {
|
|
46
|
+
r: [4.0767416360759583, -3.3077115392580629, 0.2309699031821043],
|
|
47
|
+
g: [-1.2684379732850315, 2.6097573492876882, -0.341319376002657],
|
|
48
|
+
b: [-0.0041960761386756, -0.7034186179359362, 1.7076146940746117]
|
|
49
|
+
};
|
|
50
|
+
function lrgbToRgb(input) {
|
|
51
|
+
const absoluteNumber = Math.abs(input);
|
|
52
|
+
const sign = input < 0 ? -1 : 1;
|
|
53
|
+
if (absoluteNumber > 0.0031308) {
|
|
54
|
+
return sign * (absoluteNumber ** (1 / 2.4) * 1.055 - 0.055);
|
|
55
|
+
}
|
|
56
|
+
return input * 12.92;
|
|
57
|
+
}
|
|
58
|
+
function clamp(value, min, max) {
|
|
59
|
+
return Math.min(Math.max(value, min), max);
|
|
60
|
+
}
|
|
61
|
+
function oklchToOklab(oklch) {
|
|
62
|
+
return {
|
|
63
|
+
l: oklch.l,
|
|
64
|
+
a: oklch.c * Math.cos((oklch.h / 180) * Math.PI),
|
|
65
|
+
b: oklch.c * Math.sin((oklch.h / 180) * Math.PI)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Convert oklab to RGB */
|
|
69
|
+
function oklchToRgb(oklch) {
|
|
70
|
+
const oklab = oklchToOklab(oklch);
|
|
71
|
+
const l = (oklab.l + LAB_TO_LMS.l[0] * oklab.a + LAB_TO_LMS.l[1] * oklab.b) ** 3;
|
|
72
|
+
const m = (oklab.l + LAB_TO_LMS.m[0] * oklab.a + LAB_TO_LMS.m[1] * oklab.b) ** 3;
|
|
73
|
+
const s = (oklab.l + LAB_TO_LMS.s[0] * oklab.a + LAB_TO_LMS.s[1] * oklab.b) ** 3;
|
|
74
|
+
const r = 255 * lrgbToRgb(LSM_TO_RGB.r[0] * l + LSM_TO_RGB.r[1] * m + LSM_TO_RGB.r[2] * s);
|
|
75
|
+
const g = 255 * lrgbToRgb(LSM_TO_RGB.g[0] * l + LSM_TO_RGB.g[1] * m + LSM_TO_RGB.g[2] * s);
|
|
76
|
+
const b = 255 * lrgbToRgb(LSM_TO_RGB.b[0] * l + LSM_TO_RGB.b[1] * m + LSM_TO_RGB.b[2] * s);
|
|
77
|
+
return {
|
|
78
|
+
r: clamp(r, 0, 255),
|
|
79
|
+
g: clamp(g, 0, 255),
|
|
80
|
+
b: clamp(b, 0, 255)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
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]])
|
|
95
|
+
};
|
|
96
|
+
shorthandToReplace.value = {
|
|
97
|
+
type: 'Value',
|
|
98
|
+
children: new List().fromArray([values[0]])
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
type: 'Declaration',
|
|
103
|
+
property: end,
|
|
104
|
+
value: endValue,
|
|
105
|
+
important: shorthandToReplace.important
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Meant to do all the things necessary, in a per-declaration basis, to have the best email client
|
|
110
|
+
* support possible.
|
|
111
|
+
*
|
|
112
|
+
* Here's the transformations it does so far:
|
|
113
|
+
* - convert all `rgb` with space-based syntax into a comma based one;
|
|
114
|
+
* - convert all `oklch` values into `rgb`;
|
|
115
|
+
* - convert all hex values into `rgb`;
|
|
116
|
+
* - convert `padding-inline` into `padding-left` and `padding-right`;
|
|
117
|
+
* - convert `padding-block` into `padding-top` and `padding-bottom`;
|
|
118
|
+
* - convert `margin-inline` into `margin-left` and `margin-right`;
|
|
119
|
+
* - convert `margin-block` into `margin-top` and `margin-bottom`.
|
|
120
|
+
*/
|
|
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
|
|
186
|
+
});
|
|
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
|
+
}
|
|
243
|
+
}
|
|
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;
|
|
286
|
+
}
|
|
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);
|
|
292
|
+
}
|
|
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
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
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
|
+
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CssNode } from 'css-tree';
|
|
2
|
+
/**
|
|
3
|
+
* This function goes through a few steps to ensure the best email client support and
|
|
4
|
+
* to ensure that media queries and pseudo classes are applied correctly alongside
|
|
5
|
+
* the inline styles.
|
|
6
|
+
*
|
|
7
|
+
* What it does:
|
|
8
|
+
* 1. Converts all declarations in all rules into important ones
|
|
9
|
+
* 2. Sanitizes class selectors of all non-inlinable rules
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeNonInlinableRules(node: CssNode): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { string, walk } from 'css-tree';
|
|
2
|
+
import { sanitizeClassName } from '../compatibility/sanitize-class-name.js';
|
|
3
|
+
import { isRuleInlinable } from './is-rule-inlinable.js';
|
|
4
|
+
/**
|
|
5
|
+
* This function goes through a few steps to ensure the best email client support and
|
|
6
|
+
* to ensure that media queries and pseudo classes are applied correctly alongside
|
|
7
|
+
* the inline styles.
|
|
8
|
+
*
|
|
9
|
+
* What it does:
|
|
10
|
+
* 1. Converts all declarations in all rules into important ones
|
|
11
|
+
* 2. Sanitizes class selectors of all non-inlinable rules
|
|
12
|
+
*/
|
|
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
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { resolveAllCssVariables } from './resolve-all-css-variables.js';
|
|
2
|
+
import { resolveCalcExpressions } from './resolve-calc-expressions.js';
|
|
3
|
+
import { sanitizeDeclarations } from './sanitize-declarations.js';
|
|
4
|
+
export function sanitizeStyleSheet(styleSheet) {
|
|
5
|
+
resolveAllCssVariables(styleSheet);
|
|
6
|
+
resolveCalcExpressions(styleSheet);
|
|
7
|
+
sanitizeDeclarations(styleSheet);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isValidNode } from './is-valid-node.js';
|
|
2
|
+
import { walk } from './walk.js';
|
|
3
|
+
export function removeAttributesFunctions(ast) {
|
|
4
|
+
return walk(ast, (node) => {
|
|
5
|
+
if (isValidNode(node)) {
|
|
6
|
+
node.attrs = node.attrs?.filter((attr) => !['onload', 'onerror'].includes(attr.name)) ?? [];
|
|
7
|
+
}
|
|
8
|
+
return node;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DefaultTreeAdapterTypes as AST } from 'parse5';
|
|
2
|
+
type WalkableNode = AST.Document | AST.Element | AST.DocumentFragment | AST.Template;
|
|
3
|
+
type AnyNode = AST.ChildNode;
|
|
4
|
+
/**
|
|
5
|
+
* Walk through an HTML AST and transform nodes using a callback function.
|
|
6
|
+
*
|
|
7
|
+
* @param ast - The root node to start walking from
|
|
8
|
+
* @param callback - A function that receives each node and returns either:
|
|
9
|
+
* - The same node (no change)
|
|
10
|
+
* - A modified node (replace)
|
|
11
|
+
* - null (remove the node)
|
|
12
|
+
* @returns The transformed AST
|
|
13
|
+
*/
|
|
14
|
+
export declare function walk<T extends WalkableNode>(ast: T, callback: (node: AnyNode) => AnyNode | null): T;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk through an HTML AST and transform nodes using a callback function.
|
|
3
|
+
*
|
|
4
|
+
* @param ast - The root node to start walking from
|
|
5
|
+
* @param callback - A function that receives each node and returns either:
|
|
6
|
+
* - The same node (no change)
|
|
7
|
+
* - A modified node (replace)
|
|
8
|
+
* - null (remove the node)
|
|
9
|
+
* @returns The transformed AST
|
|
10
|
+
*/
|
|
11
|
+
export function walk(ast, callback) {
|
|
12
|
+
// Check if this node has childNodes
|
|
13
|
+
if (!('childNodes' in ast) || !ast.childNodes) {
|
|
14
|
+
return ast;
|
|
15
|
+
}
|
|
16
|
+
// Iterate backwards to handle splicing correctly
|
|
17
|
+
for (let i = ast.childNodes.length - 1; i >= 0; i--) {
|
|
18
|
+
const node = ast.childNodes[i];
|
|
19
|
+
const newNode = callback(node);
|
|
20
|
+
if (newNode === null) {
|
|
21
|
+
// Remove the node
|
|
22
|
+
ast.childNodes.splice(i, 1);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Replace the node if changed
|
|
26
|
+
ast.childNodes[i] = newNode;
|
|
27
|
+
// Recursively walk child nodes if they exist
|
|
28
|
+
if ('childNodes' in newNode &&
|
|
29
|
+
Array.isArray(newNode.childNodes) &&
|
|
30
|
+
newNode.childNodes.length > 0) {
|
|
31
|
+
walk(newNode, callback);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return ast;
|
|
36
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Rule } from 'css-tree';
|
|
2
|
+
import type { CustomProperties } from '../css/get-custom-properties.js';
|
|
3
|
+
import type { AST } from '../../index.js';
|
|
4
|
+
export declare function addInlinedStylesToElement(element: AST.Element, inlinableRules: Map<string, Rule>, nonInlinableRules: Map<string, Rule>, customProperties: CustomProperties, unknownClasses: string[]): AST.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { sanitizeClassName } from '../compatibility/sanitize-class-name.js';
|
|
2
|
+
import { makeInlineStylesFor } from '../css/make-inline-styles-for.js';
|
|
3
|
+
import { combineStyles } from '../../../utils/index.js';
|
|
4
|
+
export function addInlinedStylesToElement(element, inlinableRules, nonInlinableRules, customProperties, unknownClasses) {
|
|
5
|
+
const classAttr = element.attrs?.find((attr) => attr.name === 'class');
|
|
6
|
+
if (classAttr && classAttr.value) {
|
|
7
|
+
const classes = classAttr.value.split(/\s+/).filter(Boolean);
|
|
8
|
+
const residualClasses = [];
|
|
9
|
+
const rules = [];
|
|
10
|
+
for (const className of classes) {
|
|
11
|
+
const rule = inlinableRules.get(className);
|
|
12
|
+
if (rule) {
|
|
13
|
+
rules.push(rule);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
residualClasses.push(className);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const styles = makeInlineStylesFor(rules, customProperties);
|
|
20
|
+
const styleAttr = element.attrs?.find((attr) => attr.name === 'style');
|
|
21
|
+
const newStyles = combineStyles(styleAttr?.value ?? '', styles);
|
|
22
|
+
if (styleAttr) {
|
|
23
|
+
element.attrs = element.attrs?.map((attr) => {
|
|
24
|
+
if (attr.name === 'style') {
|
|
25
|
+
return { ...attr, value: newStyles };
|
|
26
|
+
}
|
|
27
|
+
return attr;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
element.attrs = [...element.attrs, { name: 'style', value: newStyles }];
|
|
32
|
+
}
|
|
33
|
+
if (residualClasses.length > 0) {
|
|
34
|
+
element.attrs = element.attrs?.map((attr) => {
|
|
35
|
+
if (attr.name === 'class') {
|
|
36
|
+
return {
|
|
37
|
+
...attr,
|
|
38
|
+
value: residualClasses
|
|
39
|
+
.map((className) => {
|
|
40
|
+
if (nonInlinableRules.has(className)) {
|
|
41
|
+
return sanitizeClassName(className);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (!unknownClasses.includes(className)) {
|
|
45
|
+
unknownClasses.push(className);
|
|
46
|
+
}
|
|
47
|
+
return className;
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
.join(' ')
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return attr;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
element.attrs = element.attrs?.filter((attr) => attr.name !== 'class');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return element;
|
|
61
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const pixelBasedPreset = {
|
|
2
|
+
theme: {
|
|
3
|
+
extend: {
|
|
4
|
+
fontSize: {
|
|
5
|
+
xs: ['12px', { lineHeight: '16px' }],
|
|
6
|
+
sm: ['14px', { lineHeight: '20px' }],
|
|
7
|
+
base: ['16px', { lineHeight: '24px' }],
|
|
8
|
+
lg: ['18px', { lineHeight: '28px' }],
|
|
9
|
+
xl: ['20px', { lineHeight: '28px' }],
|
|
10
|
+
'2xl': ['24px', { lineHeight: '32px' }],
|
|
11
|
+
'3xl': ['30px', { lineHeight: '36px' }],
|
|
12
|
+
'4xl': ['36px', { lineHeight: '36px' }],
|
|
13
|
+
'5xl': ['48px', { lineHeight: '1' }],
|
|
14
|
+
'6xl': ['60px', { lineHeight: '1' }],
|
|
15
|
+
'7xl': ['72px', { lineHeight: '1' }],
|
|
16
|
+
'8xl': ['96px', { lineHeight: '1' }],
|
|
17
|
+
'9xl': ['144px', { lineHeight: '1' }]
|
|
18
|
+
},
|
|
19
|
+
spacing: {
|
|
20
|
+
px: '1px',
|
|
21
|
+
0: '0',
|
|
22
|
+
0.5: '2px',
|
|
23
|
+
1: '4px',
|
|
24
|
+
1.5: '6px',
|
|
25
|
+
2: '8px',
|
|
26
|
+
2.5: '10px',
|
|
27
|
+
3: '12px',
|
|
28
|
+
3.5: '14px',
|
|
29
|
+
4: '16px',
|
|
30
|
+
5: '20px',
|
|
31
|
+
6: '24px',
|
|
32
|
+
7: '28px',
|
|
33
|
+
8: '32px',
|
|
34
|
+
9: '36px',
|
|
35
|
+
10: '40px',
|
|
36
|
+
11: '44px',
|
|
37
|
+
12: '48px',
|
|
38
|
+
14: '56px',
|
|
39
|
+
16: '64px',
|
|
40
|
+
20: '80px',
|
|
41
|
+
24: '96px',
|
|
42
|
+
28: '112px',
|
|
43
|
+
32: '128px',
|
|
44
|
+
36: '144px',
|
|
45
|
+
40: '160px',
|
|
46
|
+
44: '176px',
|
|
47
|
+
48: '192px',
|
|
48
|
+
52: '208px',
|
|
49
|
+
56: '224px',
|
|
50
|
+
60: '240px',
|
|
51
|
+
64: '256px',
|
|
52
|
+
72: '288px',
|
|
53
|
+
80: '320px',
|
|
54
|
+
96: '384px'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type StyleSheet } from 'css-tree';
|
|
2
|
+
import type { TailwindConfig } from '../../index.js';
|
|
3
|
+
export type TailwindSetup = Awaited<ReturnType<typeof setupTailwind>>;
|
|
4
|
+
export declare function setupTailwind(config: TailwindConfig): Promise<{
|
|
5
|
+
addUtilities: (candidates: string[]) => void;
|
|
6
|
+
getStyleSheet: () => StyleSheet;
|
|
7
|
+
}>;
|