better-svelte-email 0.3.6 → 1.0.0-beta.1

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 (66) hide show
  1. package/README.md +1 -0
  2. package/dist/components/Body.svelte +7 -3
  3. package/dist/components/Button.svelte +10 -12
  4. package/dist/components/Button.svelte.d.ts +2 -2
  5. package/dist/index.d.ts +2 -6
  6. package/dist/index.js +3 -5
  7. package/dist/preprocessor/index.d.ts +19 -0
  8. package/dist/preprocessor/index.js +19 -0
  9. package/dist/preview/EmailPreview.svelte +539 -196
  10. package/dist/preview/EmailPreview.svelte.d.ts +11 -1
  11. package/dist/preview/EmailTreeNode.svelte +255 -0
  12. package/dist/preview/EmailTreeNode.svelte.d.ts +10 -0
  13. package/dist/preview/Favicon.svelte +106 -0
  14. package/dist/preview/Favicon.svelte.d.ts +5 -0
  15. package/dist/preview/email-tree.d.ts +13 -0
  16. package/dist/preview/email-tree.js +61 -0
  17. package/dist/preview/index.d.ts +35 -5
  18. package/dist/preview/index.js +87 -37
  19. package/dist/preview/theme.css +42 -0
  20. package/dist/render/index.d.ts +66 -0
  21. package/dist/render/index.js +138 -0
  22. package/dist/render/utils/compatibility/sanitize-class-name.d.ts +7 -0
  23. package/dist/render/utils/compatibility/sanitize-class-name.js +35 -0
  24. package/dist/render/utils/css/extract-rules-per-class.d.ts +5 -0
  25. package/dist/render/utils/css/extract-rules-per-class.js +37 -0
  26. package/dist/render/utils/css/get-custom-properties.d.ts +8 -0
  27. package/dist/render/utils/css/get-custom-properties.js +37 -0
  28. package/dist/render/utils/css/is-rule-inlinable.d.ts +2 -0
  29. package/dist/render/utils/css/is-rule-inlinable.js +6 -0
  30. package/dist/render/utils/css/make-inline-styles-for.d.ts +3 -0
  31. package/dist/render/utils/css/make-inline-styles-for.js +57 -0
  32. package/dist/render/utils/css/resolve-all-css-variables.d.ts +8 -0
  33. package/dist/render/utils/css/resolve-all-css-variables.js +123 -0
  34. package/dist/render/utils/css/resolve-calc-expressions.d.ts +5 -0
  35. package/dist/render/utils/css/resolve-calc-expressions.js +126 -0
  36. package/dist/render/utils/css/sanitize-declarations.d.ts +15 -0
  37. package/dist/render/utils/css/sanitize-declarations.js +354 -0
  38. package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +11 -0
  39. package/dist/render/utils/css/sanitize-non-inlinable-rules.js +33 -0
  40. package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -0
  41. package/dist/render/utils/css/sanitize-stylesheet.js +8 -0
  42. package/dist/render/utils/css/unwrap-value.d.ts +2 -0
  43. package/dist/render/utils/css/unwrap-value.js +6 -0
  44. package/dist/render/utils/html/is-valid-node.d.ts +2 -0
  45. package/dist/render/utils/html/is-valid-node.js +3 -0
  46. package/dist/render/utils/html/remove-attributes-functions.d.ts +2 -0
  47. package/dist/render/utils/html/remove-attributes-functions.js +10 -0
  48. package/dist/render/utils/html/walk.d.ts +15 -0
  49. package/dist/render/utils/html/walk.js +36 -0
  50. package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +4 -0
  51. package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.js +61 -0
  52. package/dist/render/utils/tailwindcss/pixel-based-preset.d.ts +2 -0
  53. package/dist/render/utils/tailwindcss/pixel-based-preset.js +58 -0
  54. package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +7 -0
  55. package/dist/render/utils/tailwindcss/setup-tailwind.js +67 -0
  56. package/dist/render/utils/tailwindcss/tailwind-stylesheets/index.d.ts +2 -0
  57. package/dist/render/utils/tailwindcss/tailwind-stylesheets/index.js +899 -0
  58. package/dist/render/utils/tailwindcss/tailwind-stylesheets/preflight.d.ts +2 -0
  59. package/dist/render/utils/tailwindcss/tailwind-stylesheets/preflight.js +396 -0
  60. package/dist/render/utils/tailwindcss/tailwind-stylesheets/theme.d.ts +2 -0
  61. package/dist/render/utils/tailwindcss/tailwind-stylesheets/theme.js +465 -0
  62. package/dist/render/utils/tailwindcss/tailwind-stylesheets/utilities.d.ts +2 -0
  63. package/dist/render/utils/tailwindcss/tailwind-stylesheets/utilities.js +4 -0
  64. package/dist/utils/index.d.ts +2 -1
  65. package/dist/utils/index.js +13 -10
  66. package/package.json +39 -20
@@ -0,0 +1,123 @@
1
+ import { generate, parse, walk } from 'css-tree';
2
+ function doSelectorsIntersect(first, second) {
3
+ const firstStringified = generate(first);
4
+ const secondStringified = generate(second);
5
+ if (firstStringified === secondStringified) {
6
+ return true;
7
+ }
8
+ let hasSomeUniversal = false;
9
+ const walker = (node, _parentListItem, parentList) => {
10
+ if (hasSomeUniversal)
11
+ return;
12
+ if (node.type === 'PseudoClassSelector' && node.name === 'root') {
13
+ hasSomeUniversal = true;
14
+ }
15
+ if (node.type === 'TypeSelector' && node.name === '*' && parentList.size === 1) {
16
+ hasSomeUniversal = true;
17
+ }
18
+ };
19
+ walk(first, walker);
20
+ walk(second, walker);
21
+ if (hasSomeUniversal) {
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ export function resolveAllCssVariables(node) {
27
+ const variableDefinitions = new Set();
28
+ const variableUses = new Set();
29
+ const path = [];
30
+ walk(node, {
31
+ leave() {
32
+ path.shift();
33
+ },
34
+ enter(node) {
35
+ if (node.type === 'Declaration') {
36
+ const declaration = node;
37
+ // Ignores @layer (properties) { ... } to avoid variable resolution conflicts
38
+ if (path.some((ancestor) => ancestor.type === 'Atrule' &&
39
+ ancestor.name === 'layer' &&
40
+ ancestor.prelude !== null &&
41
+ generate(ancestor.prelude).includes('properties'))) {
42
+ path.unshift(node);
43
+ return;
44
+ }
45
+ if (/--[\S]+/.test(declaration.property)) {
46
+ variableDefinitions.add({
47
+ declaration,
48
+ path: [...path],
49
+ variableName: declaration.property,
50
+ definition: generate(declaration.value)
51
+ });
52
+ }
53
+ else {
54
+ function parseVariableUsesFrom(node) {
55
+ walk(node, {
56
+ visit: 'Function',
57
+ enter(funcNode) {
58
+ if (funcNode.name === 'var') {
59
+ const children = funcNode.children.toArray();
60
+ const name = generate(children[0]);
61
+ const fallback =
62
+ // The second argument should be an "," Operator Node,
63
+ // such that the actual fallback is only in the third argument
64
+ children[2] ? generate(children[2]) : undefined;
65
+ variableUses.add({
66
+ declaration,
67
+ path: [...path],
68
+ fallback,
69
+ variableName: name,
70
+ raw: generate(funcNode)
71
+ });
72
+ if (fallback?.includes('var(')) {
73
+ const parsedFallback = parse(fallback, {
74
+ context: 'value'
75
+ });
76
+ parseVariableUsesFrom(parsedFallback);
77
+ }
78
+ }
79
+ }
80
+ });
81
+ }
82
+ parseVariableUsesFrom(declaration.value);
83
+ }
84
+ }
85
+ path.unshift(node);
86
+ }
87
+ });
88
+ for (const use of variableUses) {
89
+ let hasReplaced = false;
90
+ for (const definition of variableDefinitions) {
91
+ if (use.variableName !== definition.variableName) {
92
+ continue;
93
+ }
94
+ if (use.path[0]?.type === 'Block' &&
95
+ use.path[1]?.type === 'Atrule' &&
96
+ use.path[2]?.type === 'Block' &&
97
+ use.path[3]?.type === 'Rule' &&
98
+ definition.path[0].type === 'Block' &&
99
+ definition.path[1].type === 'Rule' &&
100
+ doSelectorsIntersect(use.path[3].prelude, definition.path[1].prelude)) {
101
+ use.declaration.value = parse(generate(use.declaration.value).replaceAll(use.raw, definition.definition), {
102
+ context: 'value'
103
+ });
104
+ hasReplaced = true;
105
+ break;
106
+ }
107
+ if (use.path[0]?.type === 'Block' &&
108
+ use.path[1]?.type === 'Rule' &&
109
+ definition.path[0]?.type === 'Block' &&
110
+ definition.path[1]?.type === 'Rule' &&
111
+ doSelectorsIntersect(use.path[1].prelude, definition.path[1].prelude)) {
112
+ use.declaration.value = parse(generate(use.declaration.value).replaceAll(use.raw, definition.definition), {
113
+ context: 'value'
114
+ });
115
+ hasReplaced = true;
116
+ break;
117
+ }
118
+ }
119
+ if (!hasReplaced && use.fallback) {
120
+ use.declaration.value = parse(generate(use.declaration.value).replaceAll(use.raw, use.fallback), { context: 'value' });
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,5 @@
1
+ import { type CssNode } from 'css-tree';
2
+ /**
3
+ * Intentionally only resolves `*` and `/` operations without dealing with parenthesis, because this is the only thing required to run Tailwind v4
4
+ */
5
+ export declare function resolveCalcExpressions(node: CssNode): void;
@@ -0,0 +1,126 @@
1
+ import { walk } from 'css-tree';
2
+ /**
3
+ * Intentionally only resolves `*` and `/` operations without dealing with parenthesis, because this is the only thing required to run Tailwind v4
4
+ */
5
+ export function resolveCalcExpressions(node) {
6
+ walk(node, {
7
+ visit: 'Function',
8
+ enter(func, funcListItem) {
9
+ if (func.name === 'calc') {
10
+ /*
11
+ [
12
+ { type: 'Dimension', loc: null, value: '0.25', unit: 'rem' },
13
+ { type: 'Operator', loc: null, value: '*' },
14
+ { type: 'Number', loc: null, value: '2' }
15
+ { type: 'Percentage', loc: null, value: '2' }
16
+ ]
17
+ */
18
+ func.children.forEach((child, item) => {
19
+ const left = item.prev;
20
+ const right = item.next;
21
+ if (left &&
22
+ right &&
23
+ child.type === 'Operator' &&
24
+ (left.data.type === 'Dimension' ||
25
+ left.data.type === 'Number' ||
26
+ left.data.type === 'Percentage') &&
27
+ (right.data.type === 'Dimension' ||
28
+ right.data.type === 'Number' ||
29
+ right.data.type === 'Percentage')) {
30
+ if (child.value === '*' || child.value === '/') {
31
+ const value = (() => {
32
+ if (child.value === '*') {
33
+ return String(Number.parseFloat(left.data.value) * Number.parseFloat(right.data.value));
34
+ }
35
+ if (right.data.value === '0') {
36
+ return '0';
37
+ }
38
+ return String(Number.parseFloat(left.data.value) / Number.parseFloat(right.data.value));
39
+ })();
40
+ if (left.data.type === 'Dimension' && right.data.type === 'Number') {
41
+ item.data = {
42
+ type: 'Dimension',
43
+ unit: left.data.unit,
44
+ value
45
+ };
46
+ func.children.remove(left);
47
+ func.children.remove(right);
48
+ }
49
+ else if (left.data.type === 'Number' && right.data.type === 'Dimension') {
50
+ item.data = {
51
+ type: 'Dimension',
52
+ unit: right.data.unit,
53
+ value
54
+ };
55
+ func.children.remove(left);
56
+ func.children.remove(right);
57
+ }
58
+ else if (left.data.type === 'Number' && right.data.type === 'Number') {
59
+ item.data = {
60
+ type: 'Number',
61
+ value
62
+ };
63
+ func.children.remove(left);
64
+ func.children.remove(right);
65
+ }
66
+ else if (left.data.type === 'Dimension' &&
67
+ right.data.type === 'Dimension' &&
68
+ left.data.unit === right.data.unit) {
69
+ if (child.value === '/') {
70
+ item.data = {
71
+ type: 'Number',
72
+ value
73
+ };
74
+ }
75
+ else {
76
+ item.data = {
77
+ type: 'Dimension',
78
+ unit: left.data.unit,
79
+ value
80
+ };
81
+ }
82
+ func.children.remove(left);
83
+ func.children.remove(right);
84
+ }
85
+ else if (left.data.type === 'Percentage' && right.data.type === 'Number') {
86
+ item.data = {
87
+ type: 'Percentage',
88
+ value
89
+ };
90
+ func.children.remove(left);
91
+ func.children.remove(right);
92
+ }
93
+ else if (left.data.type === 'Number' && right.data.type === 'Percentage') {
94
+ item.data = {
95
+ type: 'Percentage',
96
+ value
97
+ };
98
+ func.children.remove(left);
99
+ func.children.remove(right);
100
+ }
101
+ else if (left.data.type === 'Percentage' && right.data.type === 'Percentage') {
102
+ if (child.value === '/') {
103
+ item.data = {
104
+ type: 'Number',
105
+ value
106
+ };
107
+ }
108
+ else {
109
+ item.data = {
110
+ type: 'Percentage',
111
+ value
112
+ };
113
+ }
114
+ func.children.remove(left);
115
+ func.children.remove(right);
116
+ }
117
+ }
118
+ }
119
+ });
120
+ if (func.children.size === 1 && func.children.first) {
121
+ funcListItem.data = func.children.first;
122
+ }
123
+ }
124
+ }
125
+ });
126
+ }
@@ -0,0 +1,15 @@
1
+ import { type CssNode } from 'css-tree';
2
+ /**
3
+ * Meant to do all the things necessary, in a per-declaration basis, to have the best email client
4
+ * support possible.
5
+ *
6
+ * Here's the transformations it does so far:
7
+ * - convert all `rgb` with space-based syntax into a comma based one;
8
+ * - convert all `oklch` values into `rgb`;
9
+ * - convert all hex values into `rgb`;
10
+ * - convert `padding-inline` into `padding-left` and `padding-right`;
11
+ * - convert `padding-block` into `padding-top` and `padding-bottom`;
12
+ * - convert `margin-inline` into `margin-left` and `margin-right`;
13
+ * - convert `margin-block` into `margin-top` and `margin-bottom`.
14
+ */
15
+ export declare function sanitizeDeclarations(nodeContainingDeclarations: CssNode): void;
@@ -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,2 @@
1
+ import type { StyleSheet } from 'css-tree';
2
+ export declare function sanitizeStyleSheet(styleSheet: StyleSheet): void;