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,126 +1,163 @@
1
- import { walk } from 'css-tree';
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, because this is the only thing required to run Tailwind v4
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
- 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
- }
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 { type CssNode } from 'css-tree';
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(nodeContainingDeclarations: CssNode): void;
15
+ export declare function sanitizeDeclarations(root: Root): void;