@maizzle/framework 6.0.0-rc.6 → 6.0.0-rc.8
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.vue +105 -36
- package/dist/components/Button.vue +4 -1
- package/dist/components/CodeBlock.vue +11 -18
- package/dist/components/CodeInline.vue +6 -1
- package/dist/components/Column.vue +30 -5
- package/dist/components/Container.vue +10 -2
- package/dist/components/Divider.vue +28 -0
- package/dist/components/Head.vue +22 -0
- package/dist/components/Heading.vue +28 -0
- package/dist/components/Html.vue +98 -47
- package/dist/components/Layout.vue +93 -0
- package/dist/components/Link.vue +26 -0
- package/dist/components/Markdown.vue +83 -0
- package/dist/components/Outlook.vue +36 -0
- package/dist/components/Overlap.vue +25 -5
- package/dist/components/{Preview.vue → Preheader.vue} +1 -1
- package/dist/components/Row.vue +16 -5
- package/dist/components/Section.vue +83 -0
- package/dist/components/Text.vue +29 -0
- package/dist/components/Vml.vue +165 -13
- package/dist/plugins/postcss/tailwindCleanup.mjs +22 -13
- package/dist/plugins/postcss/tailwindCleanup.mjs.map +1 -1
- package/dist/render/createRenderer.d.mts +2 -3
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +67 -4
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +84 -4
- package/dist/serve.mjs.map +1 -1
- package/dist/server/compatibility.d.mts +1 -2
- package/dist/server/compatibility.d.mts.map +1 -1
- package/dist/server/compatibility.mjs +30 -16
- package/dist/server/compatibility.mjs.map +1 -1
- package/dist/server/email.d.mts +17 -0
- package/dist/server/email.d.mts.map +1 -0
- package/dist/server/email.mjs +41 -0
- package/dist/server/email.mjs.map +1 -0
- package/dist/server/linter.d.mts +1 -2
- package/dist/server/linter.d.mts.map +1 -1
- package/dist/server/linter.mjs +60 -71
- package/dist/server/linter.mjs.map +1 -1
- package/dist/server/ui/App.vue +205 -69
- package/dist/server/ui/components/ui/checkbox/Checkbox.vue +35 -0
- package/dist/server/ui/components/ui/checkbox/index.ts +1 -0
- package/dist/server/ui/components/ui/command/CommandDialog.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandInput.vue +19 -1
- package/dist/server/ui/components/ui/command/CommandItem.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandList.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandShortcut.vue +1 -1
- package/dist/server/ui/components/ui/dialog/DialogOverlay.vue +9 -1
- package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/dist/server/ui/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
- package/dist/server/ui/components/ui/sheet/SheetOverlay.vue +9 -1
- package/dist/server/ui/components/ui/sidebar/Sidebar.vue +8 -1
- package/dist/server/ui/components/ui/sidebar/SidebarProvider.vue +1 -1
- package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +5 -4
- package/dist/server/ui/components/ui/tags-input/TagsInput.vue +26 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputInput.vue +17 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItem.vue +19 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +22 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItemText.vue +17 -0
- package/dist/server/ui/components/ui/tags-input/index.ts +5 -0
- package/dist/server/ui/components/ui/toggle/index.ts +3 -3
- package/dist/server/ui/components/ui/toggle-group/ToggleGroup.vue +1 -1
- package/dist/server/ui/components/ui/toggle-group/ToggleGroupItem.vue +2 -2
- package/dist/server/ui/main.css +20 -20
- package/dist/server/ui/pages/Home.vue +12 -5
- package/dist/server/ui/pages/Preview.vue +495 -211
- package/dist/transformers/inlineCSS.d.mts +1 -14
- package/dist/transformers/inlineCSS.d.mts.map +1 -1
- package/dist/transformers/inlineCSS.mjs +25 -34
- package/dist/transformers/inlineCSS.mjs.map +1 -1
- package/dist/transformers/purgeCSS.d.mts.map +1 -1
- package/dist/transformers/purgeCSS.mjs +67 -1
- package/dist/transformers/purgeCSS.mjs.map +1 -1
- package/dist/transformers/tailwindcss.mjs +3 -7
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/types/config.d.mts +47 -29
- package/dist/types/config.d.mts.map +1 -1
- package/dist/types/index.d.mts +2 -2
- package/package.json +7 -3
- package/dist/server/ui/components/ui/resizable/ResizableHandle.vue +0 -30
- package/dist/server/ui/components/ui/resizable/ResizablePanel.vue +0 -21
- package/dist/server/ui/components/ui/resizable/ResizablePanelGroup.vue +0 -25
- package/dist/server/ui/components/ui/resizable/index.ts +0 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineOptions({ inheritAttrs: false })
|
|
5
|
+
|
|
6
|
+
defineProps({
|
|
7
|
+
/**
|
|
8
|
+
* Classes to add to the `<body>` tag.
|
|
9
|
+
*/
|
|
10
|
+
bodyClass: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: ''
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* Language code for the `lang` and `xml:lang` attributes.
|
|
16
|
+
*
|
|
17
|
+
* @default 'en'
|
|
18
|
+
*/
|
|
19
|
+
lang: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: 'en'
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Text direction.
|
|
25
|
+
*
|
|
26
|
+
* @default 'ltr'
|
|
27
|
+
*/
|
|
28
|
+
dir: {
|
|
29
|
+
type: String as PropType<'ltr' | 'rtl'>,
|
|
30
|
+
default: 'ltr'
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* Accessible label for the email article wrapper.
|
|
34
|
+
*
|
|
35
|
+
* Used as the `aria-label` on the inner `<div role="article">`.
|
|
36
|
+
*
|
|
37
|
+
* @example 'Order confirmation'
|
|
38
|
+
*/
|
|
39
|
+
ariaLabel: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: undefined
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<html :lang="lang" :dir="dir" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
48
|
+
<head>
|
|
49
|
+
<meta charset="utf-8">
|
|
50
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
51
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
52
|
+
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
|
|
53
|
+
<meta name="color-scheme" content="light dark">
|
|
54
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
55
|
+
<!--[if mso]>
|
|
56
|
+
<noscript>
|
|
57
|
+
<xml>
|
|
58
|
+
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
59
|
+
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
60
|
+
</o:OfficeDocumentSettings>
|
|
61
|
+
</xml>
|
|
62
|
+
</noscript>
|
|
63
|
+
<style>
|
|
64
|
+
td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
|
|
65
|
+
.mso-break-all {word-break: break-all;}
|
|
66
|
+
</style>
|
|
67
|
+
<![endif]-->
|
|
68
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
69
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
|
|
70
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
|
|
71
|
+
<style>
|
|
72
|
+
@import "@maizzle/tailwindcss";
|
|
73
|
+
|
|
74
|
+
img {
|
|
75
|
+
@apply max-w-full align-middle;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
</style>
|
|
79
|
+
</head>
|
|
80
|
+
<body :xml:lang="lang" :class="['m-0 p-0 size-full [word-break:break-word]', bodyClass]">
|
|
81
|
+
<div
|
|
82
|
+
role="article"
|
|
83
|
+
aria-roledescription="email"
|
|
84
|
+
:aria-label="ariaLabel"
|
|
85
|
+
:lang="lang"
|
|
86
|
+
:dir="dir"
|
|
87
|
+
style="font-size: medium;"
|
|
88
|
+
:class="['[font-size:max(16px,1rem)]', $attrs.class]">
|
|
89
|
+
<slot />
|
|
90
|
+
</div>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
|
93
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
defineProps({
|
|
8
|
+
/**
|
|
9
|
+
* The URL the link points to.
|
|
10
|
+
*/
|
|
11
|
+
href: {
|
|
12
|
+
type: String,
|
|
13
|
+
required: true,
|
|
14
|
+
validator: (v: string) => v.trim().length > 0,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const attrs = useAttrs()
|
|
19
|
+
const mergedClass = computed(() => twMerge('no-underline', attrs.class as string))
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<a :href="href" v-bind="$attrs" :class="mergedClass">
|
|
24
|
+
<slot />
|
|
25
|
+
</a>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode, type PropType } from 'vue'
|
|
3
|
+
import { createMarkdownExit } from 'markdown-exit'
|
|
4
|
+
import { codeToHtml, type BundledTheme } from 'shiki'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
props: {
|
|
8
|
+
/** Markdown content string. Overrides slot content. */
|
|
9
|
+
content: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: ''
|
|
12
|
+
},
|
|
13
|
+
/** Path to a markdown file to render. Resolved at build time. */
|
|
14
|
+
src: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ''
|
|
17
|
+
},
|
|
18
|
+
/** Shiki theme for fenced code blocks. @default 'github-dark-high-contrast' */
|
|
19
|
+
shikiTheme: {
|
|
20
|
+
type: String as PropType<BundledTheme>,
|
|
21
|
+
default: 'github-dark-high-contrast'
|
|
22
|
+
},
|
|
23
|
+
/** Wrap output in a div element. @default false */
|
|
24
|
+
wrapper: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: false
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
inheritAttrs: false,
|
|
30
|
+
async setup(props, { slots, attrs }) {
|
|
31
|
+
let source = props.content
|
|
32
|
+
|
|
33
|
+
if (!source) {
|
|
34
|
+
const slotContent = slots.default?.()
|
|
35
|
+
source = slotContent
|
|
36
|
+
?.map((vnode: any) => (typeof vnode.children === 'string' ? vnode.children : ''))
|
|
37
|
+
.join('') ?? ''
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
source = source.trim()
|
|
41
|
+
|
|
42
|
+
if (!source) {
|
|
43
|
+
return () => createStaticVNode('', 0)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const md = createMarkdownExit({
|
|
47
|
+
html: true,
|
|
48
|
+
linkify: true,
|
|
49
|
+
typographer: true,
|
|
50
|
+
highlight: async (code, lang) => {
|
|
51
|
+
try {
|
|
52
|
+
return await codeToHtml(code, { lang, theme: props.shikiTheme })
|
|
53
|
+
} catch {
|
|
54
|
+
return ''
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const wrapPre = (html: string) =>
|
|
60
|
+
`<table class="w-full"><tr><td class="max-w-0 mso-padding-alt-4">${html}</td></tr></table>\n`
|
|
61
|
+
|
|
62
|
+
const defaultFence = md.renderer.rules.fence!
|
|
63
|
+
md.renderer.rules.fence = (...args) => {
|
|
64
|
+
const result = defaultFence(...args)
|
|
65
|
+
if (typeof result === 'string') return wrapPre(result)
|
|
66
|
+
return result.then(wrapPre)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const defaultCodeBlock = md.renderer.rules.code_block!
|
|
70
|
+
md.renderer.rules.code_block = (...args) => wrapPre(defaultCodeBlock(...args) as string)
|
|
71
|
+
|
|
72
|
+
let html = await md.renderAsync(source)
|
|
73
|
+
|
|
74
|
+
if (props.wrapper) {
|
|
75
|
+
const classes = attrs.class ? ` class="${attrs.class}"` : ''
|
|
76
|
+
const style = attrs.style ? ` style="${attrs.style}"` : ''
|
|
77
|
+
html = `<div${classes}${style}>${html}</div>`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return () => createStaticVNode(html, 1)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
@@ -22,11 +22,47 @@ const parseList = (value: string) =>
|
|
|
22
22
|
export default {
|
|
23
23
|
name: 'Outlook',
|
|
24
24
|
props: {
|
|
25
|
+
/**
|
|
26
|
+
* Render content only in the specified Outlook version(s).
|
|
27
|
+
*
|
|
28
|
+
* Comma-separated list of years.
|
|
29
|
+
*
|
|
30
|
+
* @example '2013'
|
|
31
|
+
* @example '2013,2016'
|
|
32
|
+
*/
|
|
25
33
|
only: String,
|
|
34
|
+
/**
|
|
35
|
+
* Render content in all Outlook versions except the specified one(s).
|
|
36
|
+
*
|
|
37
|
+
* Comma-separated list of years.
|
|
38
|
+
*
|
|
39
|
+
* @example '2007'
|
|
40
|
+
* @example '2007,2010'
|
|
41
|
+
*/
|
|
26
42
|
not: String,
|
|
43
|
+
/**
|
|
44
|
+
* Render content in Outlook versions lower than the specified year.
|
|
45
|
+
*
|
|
46
|
+
* @example '2013'
|
|
47
|
+
*/
|
|
27
48
|
lt: String,
|
|
49
|
+
/**
|
|
50
|
+
* Render content in Outlook versions lower than or equal to the specified year.
|
|
51
|
+
*
|
|
52
|
+
* @example '2013'
|
|
53
|
+
*/
|
|
28
54
|
lte: String,
|
|
55
|
+
/**
|
|
56
|
+
* Render content in Outlook versions greater than the specified year.
|
|
57
|
+
*
|
|
58
|
+
* @example '2010'
|
|
59
|
+
*/
|
|
29
60
|
gt: String,
|
|
61
|
+
/**
|
|
62
|
+
* Render content in Outlook versions greater than or equal to the specified year.
|
|
63
|
+
*
|
|
64
|
+
* @example '2010'
|
|
65
|
+
*/
|
|
30
66
|
gte: String
|
|
31
67
|
},
|
|
32
68
|
setup(props, { slots }) {
|
|
@@ -7,22 +7,42 @@ defineOptions({ inheritAttrs: false })
|
|
|
7
7
|
const attrs = useAttrs()
|
|
8
8
|
|
|
9
9
|
const props = defineProps({
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Max height of the background (default slot) content.
|
|
12
|
+
*
|
|
13
|
+
* This constrains the visible area of the background layer.
|
|
14
|
+
*/
|
|
11
15
|
height: {
|
|
12
16
|
type: [String, Number],
|
|
13
17
|
required: true
|
|
14
18
|
},
|
|
15
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Width of the overlay table and VML rectangle.
|
|
21
|
+
*
|
|
22
|
+
* Should match the width of the content being overlapped.
|
|
23
|
+
*/
|
|
16
24
|
width: {
|
|
17
25
|
type: [String, Number],
|
|
18
26
|
required: true
|
|
19
27
|
},
|
|
20
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Height of the VML rectangle in Outlook.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to the `height` prop value. Use this to fine-tune
|
|
32
|
+
* the overlay height specifically for Outlook rendering.
|
|
33
|
+
*/
|
|
21
34
|
msoHeight: {
|
|
22
35
|
type: [String, Number],
|
|
23
36
|
default: null
|
|
24
37
|
},
|
|
25
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* VML textbox inset for Outlook positioning.
|
|
40
|
+
*
|
|
41
|
+
* Controls the offset of the overlay content as `top,right,bottom,left`.
|
|
42
|
+
* Use negative values to shift content upward into the background area.
|
|
43
|
+
*
|
|
44
|
+
* @default '0,-60px,0,0'
|
|
45
|
+
*/
|
|
26
46
|
msoInset: {
|
|
27
47
|
type: String,
|
|
28
48
|
default: '0,-60px,0,0'
|
|
@@ -37,7 +57,7 @@ const vmlOpen = computed(() => {
|
|
|
37
57
|
const w = normalizeToPixels(props.width)
|
|
38
58
|
const h = normalizeToPixels(props.msoHeight ?? props.height)
|
|
39
59
|
|
|
40
|
-
return `<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" stroked="f" filled="f" style="width
|
|
60
|
+
return `<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" stroked="f" filled="f" style="width: ${w}; height: ${h};"><v:textbox inset="${props.msoInset}"><![endif]-->`
|
|
41
61
|
})
|
|
42
62
|
|
|
43
63
|
const VmlBefore = () => createStaticVNode(vmlOpen.value, 1)
|
|
@@ -15,6 +15,6 @@ defineProps({
|
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
17
|
<Teleport to="body:start">
|
|
18
|
-
<div style="display:none"><slot /><template v-for="i in fillerCount" :key="'f'+i"> ͏ </template><template v-for="i in shyCount" :key="'s'+i">­ </template> </div>
|
|
18
|
+
<div style="display: none"><slot /><template v-for="i in fillerCount" :key="'f'+i"> ͏ </template><template v-for="i in shyCount" :key="'s'+i">­ </template> </div>
|
|
19
19
|
</Teleport>
|
|
20
20
|
</template>
|
package/dist/components/Row.vue
CHANGED
|
@@ -7,12 +7,23 @@ defineOptions({ inheritAttrs: false })
|
|
|
7
7
|
const attrs = useAttrs()
|
|
8
8
|
|
|
9
9
|
const props = defineProps({
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Override the inherited container width.
|
|
12
|
+
*
|
|
13
|
+
* Used to calculate column widths. Inherited from the
|
|
14
|
+
* parent `Container` by default.
|
|
15
|
+
*/
|
|
11
16
|
width: {
|
|
12
17
|
type: [String, Number],
|
|
13
18
|
default: null
|
|
14
19
|
},
|
|
15
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Override the auto-detected column count.
|
|
22
|
+
*
|
|
23
|
+
* By default, the number of direct child elements is used.
|
|
24
|
+
* Set this when the auto-detection doesn't match your layout
|
|
25
|
+
* (e.g. when using `v-if` or `v-for`).
|
|
26
|
+
*/
|
|
16
27
|
cols: {
|
|
17
28
|
type: Number,
|
|
18
29
|
default: null
|
|
@@ -48,13 +59,13 @@ const rowWidth = computed(() => props.width ?? containerWidth?.value ?? '37.5em'
|
|
|
48
59
|
|
|
49
60
|
function divideValue(value: string | number, divisor: number): string {
|
|
50
61
|
if (typeof value === 'number') {
|
|
51
|
-
return `${value / divisor}px`
|
|
62
|
+
return `${parseFloat((value / divisor).toFixed(2))}px`
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
const num = Number.parseFloat(value)
|
|
55
66
|
const unit = value.replace(String(num), '') || 'px'
|
|
56
67
|
|
|
57
|
-
return `${num / divisor}${unit}`
|
|
68
|
+
return `${parseFloat((num / divisor).toFixed(2))}${unit}`
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
provide('columnMinWidth', computed(() => divideValue(rowWidth.value, columnCount.value)))
|
|
@@ -73,7 +84,7 @@ const MsoAfter = () => createStaticVNode(
|
|
|
73
84
|
|
|
74
85
|
<template>
|
|
75
86
|
<MsoBefore />
|
|
76
|
-
<div v-bind="attrs" style="
|
|
87
|
+
<div v-bind="attrs" style="font-size: 0;">
|
|
77
88
|
<slot />
|
|
78
89
|
</div>
|
|
79
90
|
<MsoAfter />
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, createStaticVNode, useAttrs } from 'vue'
|
|
3
|
+
import { normalizeToPixels } from './utils.ts'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
const attrs = useAttrs()
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
/**
|
|
11
|
+
* Width of the section.
|
|
12
|
+
*
|
|
13
|
+
* Applied as `max-width` on the div and as `width` on the MSO table.
|
|
14
|
+
*
|
|
15
|
+
* @default '100%'
|
|
16
|
+
*/
|
|
17
|
+
width: {
|
|
18
|
+
type: [String, Number],
|
|
19
|
+
default: '100%'
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Inline CSS applied only to the MSO `<td>` element.
|
|
23
|
+
*
|
|
24
|
+
* Use for Outlook-specific styling that shouldn't affect other clients.
|
|
25
|
+
*
|
|
26
|
+
* @example 'padding: 10px 20px'
|
|
27
|
+
*/
|
|
28
|
+
msoStyle: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: undefined
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const hasCustomWidth = computed(() => props.width !== '100%')
|
|
35
|
+
|
|
36
|
+
const userStyle = computed(() => {
|
|
37
|
+
const s = attrs.style
|
|
38
|
+
if (!s) return ''
|
|
39
|
+
return typeof s === 'object'
|
|
40
|
+
? Object.entries(s).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`).join('; ')
|
|
41
|
+
: String(s)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const divStyle = computed(() => {
|
|
45
|
+
const parts: string[] = []
|
|
46
|
+
if (hasCustomWidth.value) parts.push(`max-width: ${normalizeToPixels(props.width)}`)
|
|
47
|
+
if (userStyle.value) parts.push(userStyle.value)
|
|
48
|
+
return parts.length ? parts.join('; ') : undefined
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const restAttrs = computed(() => {
|
|
52
|
+
const { style: _, ...rest } = attrs
|
|
53
|
+
return rest
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const tdStyles = computed(() => {
|
|
57
|
+
const parts: string[] = []
|
|
58
|
+
if (userStyle.value) parts.push(userStyle.value)
|
|
59
|
+
if (props.msoStyle) parts.push(props.msoStyle)
|
|
60
|
+
return parts.length ? parts.join('; ') : ''
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const MsoBefore = () => {
|
|
64
|
+
const tdStyle = tdStyles.value ? ` style="${tdStyles.value}"` : ''
|
|
65
|
+
return createStaticVNode(
|
|
66
|
+
`<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: ${normalizeToPixels(props.width)}"><tr><td${tdStyle}><![endif]-->`,
|
|
67
|
+
1
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MsoAfter = () => createStaticVNode(
|
|
72
|
+
'<!--[if mso]></td></tr></table><![endif]-->',
|
|
73
|
+
1
|
|
74
|
+
)
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<MsoBefore />
|
|
79
|
+
<div v-bind="restAttrs" :style="divStyle">
|
|
80
|
+
<slot />
|
|
81
|
+
</div>
|
|
82
|
+
<MsoAfter />
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type PropType, computed, useAttrs } from 'vue'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
/**
|
|
9
|
+
* The HTML element to render.
|
|
10
|
+
* @default 'p'
|
|
11
|
+
*/
|
|
12
|
+
as: {
|
|
13
|
+
type: String as PropType<'p' | 'span'>,
|
|
14
|
+
default: 'p',
|
|
15
|
+
validator: (v: string) => ['p', 'span'].includes(v),
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const attrs = useAttrs()
|
|
20
|
+
|
|
21
|
+
const defaultClass = computed(() => props.as === 'span' ? 'text-base' : 'm-0 my-4 text-base')
|
|
22
|
+
const mergedClass = computed(() => twMerge(defaultClass.value, attrs.class as string))
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<component :is="props.as" v-bind="$attrs" :class="mergedClass">
|
|
27
|
+
<slot />
|
|
28
|
+
</component>
|
|
29
|
+
</template>
|