better-svelte-email 0.0.3 → 0.1.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.
@@ -1,9 +1,9 @@
1
- <script>
2
- import { styleToString } from '../utils/index.js';
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
3
 
4
- let { styleString = '', children, ...restProps } = $props();
4
+ let { children, ...restProps }: { children?: any } & HTMLAttributes<HTMLBodyElement> = $props();
5
5
  </script>
6
6
 
7
- <body {...restProps} style={styleString}>
7
+ <body {...restProps}>
8
8
  {@render children?.()}
9
9
  </body>
@@ -1,13 +1,7 @@
1
- export default Body;
2
- type Body = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Body: import("svelte").Component<{
7
- styleString?: string;
8
- children: any;
9
- } & Record<string, any>, {}, "">;
1
+ import type { HTMLAttributes } from 'svelte/elements';
10
2
  type $$ComponentProps = {
11
- styleString?: string;
12
- children: any;
13
- } & Record<string, any>;
3
+ children?: any;
4
+ } & HTMLAttributes<HTMLBodyElement>;
5
+ declare const Body: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Body = ReturnType<typeof Body>;
7
+ export default Body;
@@ -1,15 +1,22 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { styleToString, pxToPt } from '../utils/index.js';
3
+ import type { HTMLAttributes } from 'svelte/elements';
3
4
 
4
5
  let {
5
6
  href = '#',
6
7
  target = '_blank',
7
- styleString = '',
8
+ style = '',
8
9
  pX = 0,
9
10
  pY = 0,
10
11
  children,
11
12
  ...restProps
12
- } = $props();
13
+ }: {
14
+ href?: string;
15
+ target?: string;
16
+ pX?: number;
17
+ pY?: number;
18
+ children: any;
19
+ } & HTMLAttributes<HTMLAnchorElement> = $props();
13
20
 
14
21
  const y = pY * 2;
15
22
  const textRaise = pxToPt(y.toString());
@@ -34,7 +41,7 @@
34
41
  msoTextRaise: pY ? pxToPt(pY.toString()) : undefined
35
42
  });
36
43
 
37
- const finalStyle = buttonStyle + (styleString ? ';' + styleString : '');
44
+ const finalStyle = buttonStyle + (style ? ';' + style : '');
38
45
  </script>
39
46
 
40
47
  <a {...restProps} {href} {target} style={finalStyle}>
@@ -1,21 +1,11 @@
1
- export default Button;
2
- type Button = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Button: import("svelte").Component<{
7
- href?: string;
8
- target?: string;
9
- styleString?: string;
10
- pX?: number;
11
- pY?: number;
12
- children: any;
13
- } & Record<string, any>, {}, "">;
1
+ import type { HTMLAttributes } from 'svelte/elements';
14
2
  type $$ComponentProps = {
15
3
  href?: string;
16
4
  target?: string;
17
- styleString?: string;
18
5
  pX?: number;
19
6
  pY?: number;
20
7
  children: any;
21
- } & Record<string, any>;
8
+ } & HTMLAttributes<HTMLAnchorElement>;
9
+ declare const Button: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Button = ReturnType<typeof Button>;
11
+ export default Button;
@@ -1,11 +1,18 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { styleToString } from '../utils/index.js';
3
+ import type { HTMLAttributes } from 'svelte/elements';
3
4
 
4
- let { styleString = '', children, ...restProps } = $props();
5
+ let {
6
+ children,
7
+ style,
8
+ ...restProps
9
+ }: {
10
+ children: any;
11
+ } & HTMLAttributes<HTMLTableElement> = $props();
5
12
 
6
13
  // Default max-width for email containers (600px = 37.5em)
7
14
  const baseStyle = styleToString({ maxWidth: '37.5em' });
8
- const finalStyle = baseStyle + (styleString ? ';' + styleString : '');
15
+ const finalStyle = baseStyle + (style ? ';' + style : '');
9
16
  </script>
10
17
 
11
18
  <table
@@ -1,13 +1,7 @@
1
- export default Container;
2
- type Container = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Container: import("svelte").Component<{
7
- styleString?: string;
8
- children: any;
9
- } & Record<string, any>, {}, "">;
1
+ import type { HTMLAttributes } from 'svelte/elements';
10
2
  type $$ComponentProps = {
11
- styleString?: string;
12
3
  children: any;
13
- } & Record<string, any>;
4
+ } & HTMLAttributes<HTMLTableElement>;
5
+ declare const Container: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Container = ReturnType<typeof Container>;
7
+ export default Container;
@@ -0,0 +1,14 @@
1
+ <script lang="ts">
2
+ import { styleToString } from '../utils/index.js';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ let props: HTMLAttributes<HTMLHRElement> = $props();
6
+
7
+ const style = styleToString({
8
+ width: '100%',
9
+ border: 'none',
10
+ borderTop: '1px solid #eaeaea'
11
+ });
12
+ </script>
13
+
14
+ <hr {...props} style={style + (props.style ? ';' + props.style : '')} />
@@ -0,0 +1,4 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ declare const Hr: import("svelte").Component<HTMLAttributes<HTMLHRElement>, {}, "">;
3
+ type Hr = ReturnType<typeof Hr>;
4
+ export default Hr;
@@ -2,18 +2,18 @@
2
2
  interface Props {
3
3
  lang?: string;
4
4
  dir?: 'ltr' | 'rtl' | 'auto' | null | undefined;
5
- styleString?: string;
5
+ style?: string;
6
6
  children?: any;
7
7
  [key: string]: any;
8
8
  }
9
9
 
10
- let { lang = 'en', dir = 'ltr', styleString = '', children, ...restProps }: Props = $props();
10
+ let { lang = 'en', dir = 'ltr', children, ...restProps }: Props = $props();
11
11
 
12
12
  const doctype =
13
13
  '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
14
14
  </script>
15
15
 
16
16
  {@html doctype}
17
- <html {...restProps} id="__svelte-email" {lang} {dir} style={styleString}>
17
+ <html {...restProps} id="__svelte-email" {lang} {dir}>
18
18
  {@render children?.()}
19
19
  </html>
@@ -1,7 +1,7 @@
1
1
  interface Props {
2
2
  lang?: string;
3
3
  dir?: 'ltr' | 'rtl' | 'auto' | null | undefined;
4
- styleString?: string;
4
+ style?: string;
5
5
  children?: any;
6
6
  [key: string]: any;
7
7
  }
@@ -1,16 +1,15 @@
1
- <script>
2
- let { styleString = '', children, ...restProps } = $props();
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ let {
5
+ children,
6
+ ...restProps
7
+ }: {
8
+ children: any;
9
+ } & HTMLAttributes<HTMLTableElement> = $props();
3
10
  </script>
4
11
 
5
- <table
6
- width="100%"
7
- role="presentation"
8
- cellspacing="0"
9
- cellpadding="0"
10
- border="0"
11
- {...restProps}
12
- style={styleString}
13
- >
12
+ <table width="100%" role="presentation" cellspacing="0" cellpadding="0" border="0" {...restProps}>
14
13
  <tbody>
15
14
  <tr>
16
15
  <td>
@@ -1,13 +1,7 @@
1
- export default Section;
2
- type Section = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Section: import("svelte").Component<{
7
- styleString?: string;
8
- children: any;
9
- } & Record<string, any>, {}, "">;
1
+ import type { HTMLAttributes } from 'svelte/elements';
10
2
  type $$ComponentProps = {
11
- styleString?: string;
12
3
  children: any;
13
- } & Record<string, any>;
4
+ } & HTMLAttributes<HTMLTableElement>;
5
+ declare const Section: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Section = ReturnType<typeof Section>;
7
+ export default Section;
@@ -1,7 +1,17 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { styleToString } from '../utils/index.js';
3
+ import type { HTMLAttributes } from 'svelte/elements';
3
4
 
4
- let { as = 'p', styleString = '', children, ...restProps } = $props();
5
+ let {
6
+ as = 'p',
7
+ style = '',
8
+ children,
9
+ ...restProps
10
+ }: {
11
+ as?: string;
12
+ style?: string;
13
+ children: any;
14
+ } & HTMLAttributes<HTMLParagraphElement> = $props();
5
15
 
6
16
  // Default email-safe text styles
7
17
  const baseStyle = styleToString({
@@ -9,7 +19,7 @@
9
19
  lineHeight: '24px',
10
20
  margin: '16px 0'
11
21
  });
12
- const finalStyle = baseStyle + (styleString ? ';' + styleString : '');
22
+ const finalStyle = baseStyle + (style ? ';' + style : '');
13
23
  </script>
14
24
 
15
25
  <svelte:element this={as} {...restProps} style={finalStyle}>
@@ -1,15 +1,9 @@
1
- export default Text;
2
- type Text = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Text: import("svelte").Component<{
7
- as?: string;
8
- styleString?: string;
9
- children: any;
10
- } & Record<string, any>, {}, "">;
1
+ import type { HTMLAttributes } from 'svelte/elements';
11
2
  type $$ComponentProps = {
12
3
  as?: string;
13
- styleString?: string;
4
+ style?: string;
14
5
  children: any;
15
- } & Record<string, any>;
6
+ } & HTMLAttributes<HTMLParagraphElement>;
7
+ declare const Text: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type Text = ReturnType<typeof Text>;
9
+ export default Text;
@@ -1,7 +1,8 @@
1
- export { default as Html } from './Html.svelte';
2
- export { default as Head } from './Head.svelte';
3
1
  export { default as Body } from './Body.svelte';
2
+ export { default as Button } from './Button.svelte';
4
3
  export { default as Container } from './Container.svelte';
4
+ export { default as Head } from './Head.svelte';
5
+ export { default as Hr } from './Hr.svelte';
6
+ export { default as Html } from './Html.svelte';
5
7
  export { default as Section } from './Section.svelte';
6
8
  export { default as Text } from './Text.svelte';
7
- export { default as Button } from './Button.svelte';
@@ -1,9 +1,10 @@
1
1
  // Email Components for better-svelte-email
2
2
  // These components work with the preprocessor's styleString prop
3
- export { default as Html } from './Html.svelte';
4
- export { default as Head } from './Head.svelte';
5
3
  export { default as Body } from './Body.svelte';
4
+ export { default as Button } from './Button.svelte';
6
5
  export { default as Container } from './Container.svelte';
6
+ export { default as Head } from './Head.svelte';
7
+ export { default as Hr } from './Hr.svelte';
8
+ export { default as Html } from './Html.svelte';
7
9
  export { default as Section } from './Section.svelte';
8
10
  export { default as Text } from './Text.svelte';
9
- export { default as Button } from './Button.svelte';
@@ -10,25 +10,25 @@
10
10
  @media(min-width: 640px){.sm\:bg-green-600{background-color:rgb(22,163,74) !important}}
11
11
  </style>
12
12
  </Head>
13
- <Body styleString="background-color:rgb(243,244,246); font-family:ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;">
14
- <Container styleString="margin-left:auto;margin-right:auto; max-width:42rem; background-color:rgb(255,255,255); padding:2rem;">
13
+ <Body style="background-color:rgb(243,244,246); font-family:ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;">
14
+ <Container style="margin-left:auto;margin-right:auto; max-width:42rem; background-color:rgb(255,255,255); padding:2rem;">
15
15
  <!-- Header -->
16
- <Section styleString="margin-bottom:1.5rem; border-bottom-width:1px; border-color:rgb(229,231,235); padding-bottom:1.5rem;">
17
- <Text as="h1" styleString="margin-bottom:0.5rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(17,24,39);">better-svelte-email Demo</Text>
18
- <Text styleString="color:rgb(75,85,99);">
16
+ <Section style="margin-bottom:1.5rem; border-bottom-width:1px; border-color:rgb(229,231,235); padding-bottom:1.5rem;">
17
+ <Text as="h1" style="margin-bottom:0.5rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(17,24,39);">better-svelte-email Demo</Text>
18
+ <Text style="color:rgb(75,85,99);">
19
19
  This email was generated using Tailwind classes and transformed by the preprocessor!
20
20
  </Text>
21
21
  </Section>
22
22
 
23
23
  <!-- Main Content -->
24
- <Section styleString="margin-bottom:1.5rem;">
25
- <Text styleString="margin-bottom:1rem; font-size:1.125rem;line-height:1.75rem; color:rgb(31,41,55);">Hello {userName}! 👋</Text>
24
+ <Section style="margin-bottom:1.5rem;">
25
+ <Text style="margin-bottom:1rem; font-size:1.125rem;line-height:1.75rem; color:rgb(31,41,55);">Hello {userName}! 👋</Text>
26
26
 
27
- <Text styleString="margin-bottom:1rem; color:rgb(55,65,81);">{testMessage}</Text>
27
+ <Text style="margin-bottom:1rem; color:rgb(55,65,81);">{testMessage}</Text>
28
28
 
29
- <Container styleString="margin-bottom:1.5rem; border-radius:0.5rem; background-color:rgb(239,246,255); padding:1rem;">
30
- <Text styleString="margin-bottom:0.5rem; font-weight:600; color:rgb(30,58,138);">✨ Features Demonstrated:</Text>
31
- <ul styleString="list-style-type:disc; padding-left:1.25rem; color:rgb(30,64,175);">
29
+ <Container style="margin-bottom:1.5rem; border-radius:0.5rem; background-color:rgb(239,246,255); padding:1rem;">
30
+ <Text style="margin-bottom:0.5rem; font-weight:600; color:rgb(30,58,138);">✨ Features Demonstrated:</Text>
31
+ <ul style="list-style-type:disc; padding-left:1.25rem; color:rgb(30,64,175);">
32
32
  <li>Tailwind classes converted to inline styles</li>
33
33
  <li>Responsive design with media queries</li>
34
34
  <li>Email-safe CSS transformations</li>
@@ -37,17 +37,17 @@
37
37
  </Container>
38
38
 
39
39
  <!-- Buttons showcase -->
40
- <Section styleString="margin-bottom:1.5rem;">
40
+ <Section style="margin-bottom:1.5rem;">
41
41
  <Button
42
42
  href="https://github.com/Konixy/better-svelte-email"
43
- class="sm_bg_green_600" styleString="display:inline-block; border-radius:0.25rem; background-color:rgb(37,99,235); padding-left:1.5rem;padding-right:1.5rem; padding-top:0.75rem;padding-bottom:0.75rem; font-weight:600; color:rgb(255,255,255);"
43
+ class="sm_bg_green_600" style="display:inline-block; border-radius:0.25rem; background-color:rgb(37,99,235); padding-left:1.5rem;padding-right:1.5rem; padding-top:0.75rem;padding-bottom:0.75rem; font-weight:600; color:rgb(255,255,255);"
44
44
  >
45
45
  View on GitHub
46
46
  </Button>
47
47
 
48
48
  <Button
49
49
  href="https://svelte.dev"
50
- styleString="margin-left:0.75rem; display:inline-block; border-radius:0.25rem; border-width:1px; border-color:rgb(209,213,219); background-color:rgb(255,255,255); padding-left:1.5rem;padding-right:1.5rem; padding-top:0.75rem;padding-bottom:0.75rem; font-weight:600; color:rgb(55,65,81);"
50
+ style="margin-left:0.75rem; display:inline-block; border-radius:0.25rem; border-width:1px; border-color:rgb(209,213,219); background-color:rgb(255,255,255); padding-left:1.5rem;padding-right:1.5rem; padding-top:0.75rem;padding-bottom:0.75rem; font-weight:600; color:rgb(55,65,81);"
51
51
  >
52
52
  Learn Svelte 5
53
53
  </Button>
@@ -55,23 +55,23 @@
55
55
  </Section>
56
56
 
57
57
  <!-- Stats Grid -->
58
- <Section styleString="margin-bottom:1.5rem;">
58
+ <Section style="margin-bottom:1.5rem;">
59
59
  <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
60
60
  <tbody>
61
61
  <tr>
62
- <td styleString="border-radius:0.5rem; background-color:rgb(250,245,255); padding:1rem; text-align:center;" style="width: 33.33%;">
63
- <Text as="div" styleString="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(147,51,234);">52+</Text>
64
- <Text as="div" styleString="font-size:0.875rem;line-height:1.25rem; color:rgb(126,34,206);">Tests Passing</Text>
62
+ <td style="border-radius:0.5rem; background-color:rgb(250,245,255); padding:1rem; text-align:center;width: 33.33%;" >
63
+ <Text as="div" style="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(147,51,234);">52+</Text>
64
+ <Text as="div" style="font-size:0.875rem;line-height:1.25rem; color:rgb(126,34,206);">Tests Passing</Text>
65
65
  </td>
66
66
  <td style="width: 10px;"></td>
67
- <td styleString="border-radius:0.5rem; background-color:rgb(240,253,244); padding:1rem; text-align:center;" style="width: 33.33%;">
68
- <Text as="div" styleString="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(22,163,74);">100%</Text>
69
- <Text as="div" styleString="font-size:0.875rem;line-height:1.25rem; color:rgb(21,128,61);">TypeScript</Text>
67
+ <td style="border-radius:0.5rem; background-color:rgb(240,253,244); padding:1rem; text-align:center;width: 33.33%;" >
68
+ <Text as="div" style="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(22,163,74);">100%</Text>
69
+ <Text as="div" style="font-size:0.875rem;line-height:1.25rem; color:rgb(21,128,61);">TypeScript</Text>
70
70
  </td>
71
71
  <td style="width: 10px;"></td>
72
- <td styleString="border-radius:0.5rem; background-color:rgb(255,247,237); padding:1rem; text-align:center;" style="width: 33.33%;">
73
- <Text as="div" styleString="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(234,88,12);">0ms</Text>
74
- <Text as="div" styleString="font-size:0.875rem;line-height:1.25rem; color:rgb(194,65,12);">Runtime Cost</Text>
72
+ <td style="border-radius:0.5rem; background-color:rgb(255,247,237); padding:1rem; text-align:center;width: 33.33%;" >
73
+ <Text as="div" style="margin-bottom:0.25rem; font-size:1.875rem;line-height:2.25rem; font-weight:700; color:rgb(234,88,12);">0ms</Text>
74
+ <Text as="div" style="font-size:0.875rem;line-height:1.25rem; color:rgb(194,65,12);">Runtime Cost</Text>
75
75
  </td>
76
76
  </tr>
77
77
  </tbody>
@@ -79,11 +79,11 @@
79
79
  </Section>
80
80
 
81
81
  <!-- Code Example -->
82
- <Container styleString="margin-bottom:1.5rem; border-radius:0.5rem; background-color:rgb(249,250,251); padding:1rem;">
83
- <Text styleString="margin-bottom:0.5rem; font-size:0.875rem;line-height:1.25rem; font-weight:600; color:rgb(55,65,81);">How it works:</Text>
82
+ <Container style="margin-bottom:1.5rem; border-radius:0.5rem; background-color:rgb(249,250,251); padding:1rem;">
83
+ <Text style="margin-bottom:0.5rem; font-size:0.875rem;line-height:1.25rem; font-weight:600; color:rgb(55,65,81);">How it works:</Text>
84
84
  <pre
85
- styleString="overflow-x:auto; border-radius:0.25rem; background-color:rgb(17,24,39); padding:0.75rem; font-size:0.75rem;line-height:1rem; color:rgb(243,244,246);"
86
- style="margin: 0;"><code
85
+ style="overflow-x:auto; border-radius:0.25rem; background-color:rgb(17,24,39); padding:0.75rem; font-size:0.75rem;line-height:1rem; color:rgb(243,244,246);margin: 0;"
86
+ ><code
87
87
  >// Input
88
88
  &lt;Button class="bg-blue-500 text-white p-4"&gt;
89
89
  Click Me
@@ -97,9 +97,9 @@
97
97
  </Container>
98
98
 
99
99
  <!-- Footer -->
100
- <Section styleString="border-top-width:1px; border-color:rgb(229,231,235); padding-top:1.5rem; text-align:center;">
101
- <Text styleString="margin-bottom:0.5rem; font-size:0.875rem;line-height:1.25rem; color:rgb(75,85,99);">Built with Svelte 5 & Tailwind CSS</Text>
102
- <Text styleString="font-size:0.75rem;line-height:1rem; color:rgb(107,114,128);">
100
+ <Section style="border-top-width:1px; border-color:rgb(229,231,235); padding-top:1.5rem; text-align:center;">
101
+ <Text style="margin-bottom:0.5rem; font-size:0.875rem;line-height:1.25rem; color:rgb(75,85,99);">Built with Svelte 5 & Tailwind CSS</Text>
102
+ <Text style="font-size:0.75rem;line-height:1rem; color:rgb(107,114,128);">
103
103
  You're receiving this email because you tested the better-svelte-email demo.
104
104
  </Text>
105
105
  </Section>
@@ -5,9 +5,13 @@
5
5
  <Html>
6
6
  <Head />
7
7
  <Body>
8
- <Container styleString="background-color:rgb(243,244,246); padding:2rem;">
9
- <Text styleString="font-size:1.125rem;line-height:1.75rem; font-weight:700; color:rgb(37,99,235);">Hello World</Text>
10
- <Button styleString="border-radius:0.25rem; background-color:rgb(59,130,246); padding-left:1rem;padding-right:1rem; padding-top:0.5rem;padding-bottom:0.5rem; color:rgb(255,255,255);" href="https://example.com">
8
+ <Container style="background-color:rgb(243,244,246);padding: 2rem;" >
9
+ <Text style="font-size:1.125rem;line-height:1.75rem; font-weight:700; color:rgb(37,99,235);">Hello World</Text>
10
+ <Button
11
+ style="border-radius:0.25rem; background-color:rgb(59,130,246); padding-left:1rem;padding-right:1rem; padding-top:0.5rem;padding-bottom:0.5rem; color:rgb(255,255,255);width: 33.33%"
12
+
13
+ href="https://example.com"
14
+ >
11
15
  Click Me
12
16
  </Button>
13
17
  </Container>
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export { Html, Head, Body, Container, Section, Text, Button } from './components/index.js';
1
+ export { Body, Button, Container, Head, Hr, Html, Section, Text } from './components/index.js';
2
2
  export { betterSvelteEmailPreprocessor } from './preprocessor/index.js';
3
3
  export type { PreprocessorOptions, ComponentTransform } from './preprocessor/index.js';
4
4
  export type { ClassAttribute, TransformResult, MediaQueryStyle } from './preprocessor/types.js';
5
- export { parseClassAttributes, findHeadComponent } from './preprocessor/parser.js';
5
+ export type { TailwindConfig } from 'tw-to-css';
6
+ export { parseAttributes as parseClassAttributes, findHeadComponent } from './preprocessor/parser.js';
6
7
  export { createTailwindConverter, transformTailwindClasses, generateMediaQueries, sanitizeClassName } from './preprocessor/transformer.js';
7
8
  export { injectMediaQueries } from './preprocessor/head-injector.js';
8
9
  export { styleToString, pxToPt } from './utils/index.js';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Export email components
2
- export { Html, Head, Body, Container, Section, Text, Button } from './components/index.js';
2
+ export { Body, Button, Container, Head, Hr, Html, Section, Text } from './components/index.js';
3
3
  // Export the preprocessor
4
4
  export { betterSvelteEmailPreprocessor } from './preprocessor/index.js';
5
5
  // Export individual functions for advanced usage
6
- export { parseClassAttributes, findHeadComponent } from './preprocessor/parser.js';
6
+ export { parseAttributes as parseClassAttributes, findHeadComponent } from './preprocessor/parser.js';
7
7
  export { createTailwindConverter, transformTailwindClasses, generateMediaQueries, sanitizeClassName } from './preprocessor/transformer.js';
8
8
  export { injectMediaQueries } from './preprocessor/head-injector.js';
9
9
  // Export utilities
@@ -1,5 +1,5 @@
1
1
  import MagicString from 'magic-string';
2
- import { parseClassAttributes } from './parser.js';
2
+ import { parseAttributes } from './parser.js';
3
3
  import { createTailwindConverter, transformTailwindClasses, generateMediaQueries, sanitizeClassName } from './transformer.js';
4
4
  import { injectMediaQueries } from './head-injector.js';
5
5
  /**
@@ -76,8 +76,8 @@ function processEmailComponent(source, _filename, tailwindConverter, tailwindCon
76
76
  let transformedCode = source;
77
77
  const allMediaQueries = [];
78
78
  // Step 1: Parse and find all class attributes
79
- const classAttributes = parseClassAttributes(source);
80
- if (classAttributes.length === 0) {
79
+ const attributes = parseAttributes(source);
80
+ if (attributes.length === 0) {
81
81
  // No classes to transform
82
82
  return {
83
83
  originalCode: source,
@@ -90,19 +90,19 @@ function processEmailComponent(source, _filename, tailwindConverter, tailwindCon
90
90
  // Step 2: Transform each class attribute
91
91
  const s = new MagicString(transformedCode);
92
92
  // Process in reverse order to maintain correct positions
93
- const sortedAttributes = [...classAttributes].sort((a, b) => b.start - a.start);
94
- for (const classAttr of sortedAttributes) {
95
- if (!classAttr.isStatic) {
93
+ const sortedAttributes = [...attributes].sort((a, b) => b.class.start - a.class.start);
94
+ for (const attr of sortedAttributes) {
95
+ if (!attr.class.isStatic) {
96
96
  // Skip dynamic classes for now
97
- warnings.push(`Dynamic class expression detected in ${classAttr.elementName}. ` +
97
+ warnings.push(`Dynamic class expression detected in ${attr.class.elementName}. ` +
98
98
  `Only static classes can be transformed at build time.`);
99
99
  continue;
100
100
  }
101
101
  // Transform the classes
102
- const transformed = transformTailwindClasses(classAttr.raw, tailwindConverter);
102
+ const transformed = transformTailwindClasses(attr.class.raw, tailwindConverter);
103
103
  // Collect warnings about invalid classes
104
104
  if (transformed.invalidClasses.length > 0) {
105
- warnings.push(`Invalid Tailwind classes in ${classAttr.elementName}: ${transformed.invalidClasses.join(', ')}`);
105
+ warnings.push(`Invalid Tailwind classes in ${attr.class.elementName}: ${transformed.invalidClasses.join(', ')}`);
106
106
  }
107
107
  // Generate media queries for responsive classes
108
108
  if (transformed.responsiveClasses.length > 0) {
@@ -110,9 +110,13 @@ function processEmailComponent(source, _filename, tailwindConverter, tailwindCon
110
110
  allMediaQueries.push(...mediaQueries);
111
111
  }
112
112
  // Build the new attribute value
113
- const newAttributes = buildNewAttributes(transformed.inlineStyles, transformed.responsiveClasses);
113
+ const newAttributes = buildNewAttributes(transformed.inlineStyles, transformed.responsiveClasses, attr.style?.raw);
114
+ // Remove the already existing style attribute if it exists
115
+ if (attr.style) {
116
+ removeStyleAttribute(s, attr.style);
117
+ }
114
118
  // Replace the class attribute with new attributes
115
- replaceClassAttribute(s, classAttr, newAttributes);
119
+ replaceClassAttribute(s, attr.class, newAttributes);
116
120
  }
117
121
  transformedCode = s.toString();
118
122
  // Step 3: Inject media queries into <Head>
@@ -136,7 +140,7 @@ function processEmailComponent(source, _filename, tailwindConverter, tailwindCon
136
140
  /**
137
141
  * Build new attribute string from transformation result
138
142
  */
139
- function buildNewAttributes(inlineStyles, responsiveClasses) {
143
+ function buildNewAttributes(inlineStyles, responsiveClasses, existingStyles) {
140
144
  const parts = [];
141
145
  // Add responsive classes if any
142
146
  if (responsiveClasses.length > 0) {
@@ -147,7 +151,8 @@ function buildNewAttributes(inlineStyles, responsiveClasses) {
147
151
  if (inlineStyles) {
148
152
  // Escape quotes in styles
149
153
  const escapedStyles = inlineStyles.replace(/"/g, '&quot;');
150
- parts.push(`styleString="${escapedStyles}"`);
154
+ const withExisting = escapedStyles + (existingStyles ? existingStyles : '');
155
+ parts.push(`style="${withExisting}"`);
151
156
  }
152
157
  return parts.join(' ');
153
158
  }
@@ -194,3 +199,9 @@ function replaceClassAttribute(s, classAttr, newAttributes) {
194
199
  s.remove(removeStart, removeEnd);
195
200
  }
196
201
  }
202
+ /**
203
+ * Remove style attribute with MagicString
204
+ */
205
+ function removeStyleAttribute(s, styleAttr) {
206
+ s.remove(styleAttr.start, styleAttr.end);
207
+ }
@@ -1,9 +1,12 @@
1
- import type { ClassAttribute } from './types.js';
1
+ import type { ClassAttribute, StyleAttribute } from './types.js';
2
2
  /**
3
3
  * Parse Svelte 5 source code and extract all class attributes
4
4
  * Reference: https://svelte.dev/docs/svelte/svelte-compiler#parse
5
5
  */
6
- export declare function parseClassAttributes(source: string): ClassAttribute[];
6
+ export declare function parseAttributes(source: string): {
7
+ class: ClassAttribute;
8
+ style?: StyleAttribute;
9
+ }[];
7
10
  /**
8
11
  * Find the <Head> component in Svelte 5 AST
9
12
  * Returns the position where we should inject styles
@@ -3,8 +3,8 @@ import { parse } from 'svelte/compiler';
3
3
  * Parse Svelte 5 source code and extract all class attributes
4
4
  * Reference: https://svelte.dev/docs/svelte/svelte-compiler#parse
5
5
  */
6
- export function parseClassAttributes(source) {
7
- const classAttributes = [];
6
+ export function parseAttributes(source) {
7
+ const attributes = [];
8
8
  try {
9
9
  // Parse the Svelte file into an AST
10
10
  // Svelte 5 parse returns a Root node with modern AST structure
@@ -12,7 +12,7 @@ export function parseClassAttributes(source) {
12
12
  // Walk the html fragment (template portion) of the AST
13
13
  if (ast.html && ast.html.children) {
14
14
  for (const child of ast.html.children) {
15
- walkNode(child, classAttributes, source);
15
+ walkNode(child, attributes, source);
16
16
  }
17
17
  }
18
18
  }
@@ -20,12 +20,12 @@ export function parseClassAttributes(source) {
20
20
  console.error('Failed to parse Svelte file:', error);
21
21
  throw error;
22
22
  }
23
- return classAttributes;
23
+ return attributes;
24
24
  }
25
25
  /**
26
26
  * Recursively walk Svelte 5 AST nodes to find class attributes
27
27
  */
28
- function walkNode(node, classAttributes, source) {
28
+ function walkNode(node, attributes, source) {
29
29
  if (!node)
30
30
  return;
31
31
  // Svelte 5 AST structure:
@@ -37,18 +37,33 @@ function walkNode(node, classAttributes, source) {
37
37
  node.type === 'SlotElement' ||
38
38
  node.type === 'Component') {
39
39
  const elementName = node.name || 'unknown';
40
- // Look for class attribute in Svelte 5 AST
40
+ // Look for class and style attribute in Svelte 5 AST
41
41
  const classAttr = node.attributes?.find((attr) => attr.type === 'Attribute' && attr.name === 'class');
42
+ const styleAttr = node.attributes?.find((attr) => attr.type === 'Attribute' && attr.name === 'style');
42
43
  if (classAttr && classAttr.value) {
43
44
  // Extract class value
44
- const extracted = extractClassValue(classAttr, source);
45
- if (extracted) {
46
- classAttributes.push({
47
- raw: extracted.value,
48
- start: extracted.start,
49
- end: extracted.end,
50
- elementName,
51
- isStatic: extracted.isStatic
45
+ const extractedClass = extractClassValue(classAttr, source);
46
+ let extractedStyle = null;
47
+ if (styleAttr && styleAttr.value) {
48
+ extractedStyle = extractStyleValue(styleAttr, source);
49
+ }
50
+ if (extractedClass) {
51
+ attributes.push({
52
+ class: {
53
+ raw: extractedClass.value,
54
+ start: extractedClass.start,
55
+ end: extractedClass.end,
56
+ elementName,
57
+ isStatic: extractedClass.isStatic
58
+ },
59
+ style: extractedStyle
60
+ ? {
61
+ raw: extractedStyle.value,
62
+ start: extractedStyle.start,
63
+ end: extractedStyle.end,
64
+ elementName
65
+ }
66
+ : undefined
52
67
  });
53
68
  }
54
69
  }
@@ -56,21 +71,21 @@ function walkNode(node, classAttributes, source) {
56
71
  // Recursively process children
57
72
  if (node.children) {
58
73
  for (const child of node.children) {
59
- walkNode(child, classAttributes, source);
74
+ walkNode(child, attributes, source);
60
75
  }
61
76
  }
62
77
  // Handle conditional blocks (#if, #each, etc.)
63
78
  if (node.consequent) {
64
79
  if (node.consequent.children) {
65
80
  for (const child of node.consequent.children) {
66
- walkNode(child, classAttributes, source);
81
+ walkNode(child, attributes, source);
67
82
  }
68
83
  }
69
84
  }
70
85
  if (node.alternate) {
71
86
  if (node.alternate.children) {
72
87
  for (const child of node.alternate.children) {
73
- walkNode(child, classAttributes, source);
88
+ walkNode(child, attributes, source);
74
89
  }
75
90
  }
76
91
  }
@@ -78,7 +93,7 @@ function walkNode(node, classAttributes, source) {
78
93
  if (node.body) {
79
94
  if (node.body.children) {
80
95
  for (const child of node.body.children) {
81
- walkNode(child, classAttributes, source);
96
+ walkNode(child, attributes, source);
82
97
  }
83
98
  }
84
99
  }
@@ -130,8 +145,8 @@ function extractClassValue(classAttr, source) {
130
145
  // Mixed content (both Text and ExpressionTag)
131
146
  // Extract only the static Text portions for partial transformation
132
147
  let combinedValue = '';
133
- let start = classAttr.value[0].start;
134
- let end = classAttr.value[classAttr.value.length - 1].end;
148
+ const start = classAttr.value[0].start;
149
+ const end = classAttr.value[classAttr.value.length - 1].end;
135
150
  let hasStaticContent = false;
136
151
  for (const part of classAttr.value) {
137
152
  if (part.type === 'Text' && part.data) {
@@ -150,6 +165,57 @@ function extractClassValue(classAttr, source) {
150
165
  }
151
166
  return null;
152
167
  }
168
+ /**
169
+ * Extract the actual style value from a Svelte 5 attribute node
170
+ */
171
+ function extractStyleValue(styleAttr, source) {
172
+ // Svelte 5 attribute value formats:
173
+ // 1. Static string: style="color: red;"
174
+ // → value: [{ type: 'Text', data: 'color: red;' }]
175
+ //
176
+ // 2. Expression: style={someVar}
177
+ // → value: [{ type: 'ExpressionTag', expression: {...} }]
178
+ //
179
+ // 3. Mixed: style="color: red; {dynamicStyle}"
180
+ // → value: [{ type: 'Text' }, { type: 'ExpressionTag' }]
181
+ if (!styleAttr.value || styleAttr.value.length === 0) {
182
+ return null;
183
+ }
184
+ // Check if entirely static (only Text nodes)
185
+ const hasOnlyText = styleAttr.value.every((v) => v.type === 'Text');
186
+ if (hasOnlyText) {
187
+ // Fully static - we can extract this
188
+ const textContent = styleAttr.value.map((v) => v.data || '').join('');
189
+ return {
190
+ value: textContent,
191
+ start: styleAttr.start,
192
+ end: styleAttr.end
193
+ };
194
+ }
195
+ // Check if entirely dynamic (only ExpressionTag or MustacheTag)
196
+ const hasOnlyExpression = styleAttr.value.length === 1 &&
197
+ (styleAttr.value[0].type === 'ExpressionTag' || styleAttr.value[0].type === 'MustacheTag');
198
+ if (hasOnlyExpression) {
199
+ // Fully dynamic - extract the expression code
200
+ const exprNode = styleAttr.value[0];
201
+ const expressionCode = source.substring(exprNode.start, exprNode.end);
202
+ return {
203
+ value: expressionCode,
204
+ start: exprNode.start,
205
+ end: exprNode.end
206
+ };
207
+ }
208
+ // Mixed content (both Text and ExpressionTag)
209
+ // Extract the full content including dynamic parts
210
+ const start = styleAttr.value[0].start;
211
+ const end = styleAttr.value[styleAttr.value.length - 1].end;
212
+ const fullContent = source.substring(start, end);
213
+ return {
214
+ value: fullContent,
215
+ start: styleAttr.start,
216
+ end: styleAttr.end
217
+ };
218
+ }
153
219
  /**
154
220
  * Find the <Head> component in Svelte 5 AST
155
221
  * Returns the position where we should inject styles
@@ -167,7 +233,7 @@ export function findHeadComponent(source) {
167
233
  }
168
234
  return { found: false, insertPosition: null };
169
235
  }
170
- catch (error) {
236
+ catch {
171
237
  return { found: false, insertPosition: null };
172
238
  }
173
239
  }
@@ -43,6 +43,27 @@ export interface ClassAttribute {
43
43
  */
44
44
  isStatic: boolean;
45
45
  }
46
+ /**
47
+ * Represents a style attribute found in the AST
48
+ */
49
+ export interface StyleAttribute {
50
+ /**
51
+ * Raw style string (e.g., "background-color: red;")
52
+ */
53
+ raw: string;
54
+ /**
55
+ * Start position in source code
56
+ */
57
+ start: number;
58
+ /**
59
+ * End position in source code
60
+ */
61
+ end: number;
62
+ /**
63
+ * Parent element/component name
64
+ */
65
+ elementName: string;
66
+ }
46
67
  /**
47
68
  * Result of transforming Tailwind classes
48
69
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-svelte-email",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "author": "Anatole",
5
5
  "repository": {
6
6
  "type": "git",