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.
- package/README.md +1 -1
- package/dist/render/index.js +7 -6
- package/dist/render/utils/css/extract-rules-per-class.d.ts +2 -2
- package/dist/render/utils/css/extract-rules-per-class.js +13 -24
- package/dist/render/utils/css/get-custom-properties.d.ts +2 -2
- package/dist/render/utils/css/get-custom-properties.js +16 -31
- package/dist/render/utils/css/is-rule-inlinable.d.ts +1 -1
- package/dist/render/utils/css/is-rule-inlinable.js +30 -4
- package/dist/render/utils/css/make-inline-styles-for.d.ts +2 -2
- package/dist/render/utils/css/make-inline-styles-for.js +38 -41
- package/dist/render/utils/css/resolve-all-css-variables.d.ts +3 -3
- package/dist/render/utils/css/resolve-all-css-variables.js +107 -95
- package/dist/render/utils/css/resolve-calc-expressions.d.ts +2 -5
- package/dist/render/utils/css/resolve-calc-expressions.js +155 -118
- package/dist/render/utils/css/sanitize-declarations.d.ts +2 -2
- package/dist/render/utils/css/sanitize-declarations.js +226 -282
- package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +2 -2
- package/dist/render/utils/css/sanitize-non-inlinable-rules.js +14 -19
- package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -2
- package/dist/render/utils/css/sanitize-stylesheet.js +4 -4
- package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +1 -1
- package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +2 -2
- package/dist/render/utils/tailwindcss/setup-tailwind.js +3 -3
- package/package.json +5 -4
- package/dist/render/utils/css/unwrap-value.d.ts +0 -2
- package/dist/render/utils/css/unwrap-value.js +0 -6
|
@@ -1,126 +1,163 @@
|
|
|
1
|
-
import
|
|
1
|
+
import valueParser from 'postcss-value-parser';
|
|
2
|
+
function parseValue(str) {
|
|
3
|
+
const match = str.match(/^(-?[\d.]+)(%|[a-z]+)?$/i);
|
|
4
|
+
if (match) {
|
|
5
|
+
const value = parseFloat(match[1]);
|
|
6
|
+
const unit = match[2] || '';
|
|
7
|
+
return {
|
|
8
|
+
value,
|
|
9
|
+
unit,
|
|
10
|
+
type: unit === '%' ? 'percentage' : unit ? 'dimension' : 'number'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function formatValue(parsed) {
|
|
16
|
+
return `${parsed.value}${parsed.unit}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Splits a calc expression string into tokens (values and operators)
|
|
20
|
+
* Handles both space-separated and non-space-separated expressions
|
|
21
|
+
*/
|
|
22
|
+
function tokenizeCalcExpression(expr) {
|
|
23
|
+
const tokens = [];
|
|
24
|
+
let current = '';
|
|
25
|
+
for (let i = 0; i < expr.length; i++) {
|
|
26
|
+
const char = expr[i];
|
|
27
|
+
if (char === '*' || char === '/') {
|
|
28
|
+
if (current.trim()) {
|
|
29
|
+
tokens.push(current.trim());
|
|
30
|
+
}
|
|
31
|
+
tokens.push(char);
|
|
32
|
+
current = '';
|
|
33
|
+
}
|
|
34
|
+
else if (char === '+' || char === '-') {
|
|
35
|
+
// Only treat as operator if not at start and previous char wasn't an operator
|
|
36
|
+
// (to handle negative numbers and units like 1e-5)
|
|
37
|
+
if (current.trim() && !/[eE]$/.test(current.trim())) {
|
|
38
|
+
tokens.push(current.trim());
|
|
39
|
+
tokens.push(char);
|
|
40
|
+
current = '';
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
current += char;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (char === ' ') {
|
|
47
|
+
if (current.trim()) {
|
|
48
|
+
// Check if next non-space char is an operator
|
|
49
|
+
let nextNonSpace = i + 1;
|
|
50
|
+
while (nextNonSpace < expr.length && expr[nextNonSpace] === ' ') {
|
|
51
|
+
nextNonSpace++;
|
|
52
|
+
}
|
|
53
|
+
const nextChar = expr[nextNonSpace];
|
|
54
|
+
if (nextChar !== '*' && nextChar !== '/' && nextChar !== '+' && nextChar !== '-') {
|
|
55
|
+
tokens.push(current.trim());
|
|
56
|
+
current = '';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
current += char;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (current.trim()) {
|
|
65
|
+
tokens.push(current.trim());
|
|
66
|
+
}
|
|
67
|
+
return tokens;
|
|
68
|
+
}
|
|
2
69
|
/**
|
|
3
|
-
* Intentionally only resolves `*` and `/` operations without dealing with parenthesis,
|
|
70
|
+
* Intentionally only resolves `*` and `/` operations without dealing with parenthesis,
|
|
71
|
+
* because this is the only thing required to run Tailwind v4
|
|
4
72
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
}
|
|
73
|
+
function evaluateCalcExpression(expr) {
|
|
74
|
+
const tokens = tokenizeCalcExpression(expr);
|
|
75
|
+
if (tokens.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
if (tokens.length === 1) {
|
|
78
|
+
const parsed = parseValue(tokens[0]);
|
|
79
|
+
return parsed ? formatValue(parsed) : null;
|
|
80
|
+
}
|
|
81
|
+
// Process * and / operations (left to right)
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < tokens.length) {
|
|
84
|
+
const token = tokens[i];
|
|
85
|
+
if (token === '*' || token === '/') {
|
|
86
|
+
const left = parseValue(tokens[i - 1]);
|
|
87
|
+
const right = parseValue(tokens[i + 1]);
|
|
88
|
+
if (left && right) {
|
|
89
|
+
let resultValue;
|
|
90
|
+
if (token === '*') {
|
|
91
|
+
resultValue = left.value * right.value;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
if (right.value === 0)
|
|
95
|
+
resultValue = 0;
|
|
96
|
+
else
|
|
97
|
+
resultValue = left.value / right.value;
|
|
98
|
+
}
|
|
99
|
+
// Determine result type
|
|
100
|
+
let resultUnit = '';
|
|
101
|
+
let resultType = 'number';
|
|
102
|
+
if (left.type === 'dimension' && right.type === 'number') {
|
|
103
|
+
resultUnit = left.unit;
|
|
104
|
+
resultType = 'dimension';
|
|
105
|
+
}
|
|
106
|
+
else if (left.type === 'number' && right.type === 'dimension') {
|
|
107
|
+
resultUnit = right.unit;
|
|
108
|
+
resultType = 'dimension';
|
|
109
|
+
}
|
|
110
|
+
else if (left.type === 'dimension' && right.type === 'dimension') {
|
|
111
|
+
if (token === '/') {
|
|
112
|
+
resultType = 'number';
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
resultUnit = left.unit;
|
|
116
|
+
resultType = 'dimension';
|
|
118
117
|
}
|
|
119
|
-
});
|
|
120
|
-
if (func.children.size === 1 && func.children.first) {
|
|
121
|
-
funcListItem.data = func.children.first;
|
|
122
118
|
}
|
|
119
|
+
else if (left.type === 'percentage' || right.type === 'percentage') {
|
|
120
|
+
if (token === '/' && left.type === 'percentage' && right.type === 'percentage') {
|
|
121
|
+
resultType = 'number';
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
resultUnit = '%';
|
|
125
|
+
resultType = 'percentage';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Replace the three tokens with the result
|
|
129
|
+
const result = formatValue({ value: resultValue, unit: resultUnit, type: resultType });
|
|
130
|
+
tokens.splice(i - 1, 3, result);
|
|
131
|
+
i = Math.max(0, i - 1); // Go back to check for more operations
|
|
132
|
+
continue;
|
|
123
133
|
}
|
|
124
134
|
}
|
|
135
|
+
i++;
|
|
136
|
+
}
|
|
137
|
+
if (tokens.length === 1) {
|
|
138
|
+
return tokens[0];
|
|
139
|
+
}
|
|
140
|
+
// If we still have multiple tokens, we couldn't fully evaluate
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
export function resolveCalcExpressions(root) {
|
|
144
|
+
root.walkDecls((decl) => {
|
|
145
|
+
if (!decl.value.includes('calc('))
|
|
146
|
+
return;
|
|
147
|
+
const parsed = valueParser(decl.value);
|
|
148
|
+
parsed.walk((node) => {
|
|
149
|
+
if (node.type === 'function' && node.value === 'calc') {
|
|
150
|
+
// Get the inner content of calc()
|
|
151
|
+
const innerContent = valueParser.stringify(node.nodes);
|
|
152
|
+
const result = evaluateCalcExpression(innerContent);
|
|
153
|
+
if (result) {
|
|
154
|
+
// Replace the function with the result
|
|
155
|
+
node.type = 'word';
|
|
156
|
+
node.value = result;
|
|
157
|
+
node.nodes = [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
decl.value = valueParser.stringify(parsed.nodes);
|
|
125
162
|
});
|
|
126
163
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Root } from 'postcss';
|
|
2
2
|
/**
|
|
3
3
|
* Meant to do all the things necessary, in a per-declaration basis, to have the best email client
|
|
4
4
|
* support possible.
|
|
@@ -12,4 +12,4 @@ import { type CssNode } from 'css-tree';
|
|
|
12
12
|
* - convert `margin-inline` into `margin-left` and `margin-right`;
|
|
13
13
|
* - convert `margin-block` into `margin-top` and `margin-bottom`.
|
|
14
14
|
*/
|
|
15
|
-
export declare function sanitizeDeclarations(
|
|
15
|
+
export declare function sanitizeDeclarations(root: Root): void;
|