@maizzle/framework 6.0.0-rc.13 → 6.0.0-rc.14
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/Button.vue +2 -2
- package/dist/components/CodeBlock.vue +2 -1
- package/dist/components/Column.vue +28 -22
- package/dist/components/Container.vue +47 -9
- package/dist/components/Font.vue +96 -0
- package/dist/components/Layout.vue +9 -4
- package/dist/components/Overlap.vue +75 -18
- package/dist/components/Row.vue +40 -19
- package/dist/components/Section.vue +35 -8
- package/dist/components/utils.d.mts +14 -1
- package/dist/components/utils.d.mts.map +1 -1
- package/dist/components/utils.mjs +32 -1
- package/dist/components/utils.mjs.map +1 -1
- package/dist/components/utils.ts +39 -0
- package/dist/composables/renderContext.d.mts +8 -1
- package/dist/composables/renderContext.d.mts.map +1 -1
- package/dist/composables/renderContext.mjs.map +1 -1
- package/dist/composables/useFont.d.mts +50 -0
- package/dist/composables/useFont.d.mts.map +1 -0
- package/dist/composables/useFont.mjs +93 -0
- package/dist/composables/useFont.mjs.map +1 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/plugins/postcss/quoteFontFamilies.d.mts +13 -0
- package/dist/plugins/postcss/quoteFontFamilies.d.mts.map +1 -0
- package/dist/plugins/postcss/quoteFontFamilies.mjs +84 -0
- package/dist/plugins/postcss/quoteFontFamilies.mjs.map +1 -0
- package/dist/render/createRenderer.mjs +8 -2
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/render/injectFonts.d.mts +15 -0
- package/dist/render/injectFonts.d.mts.map +1 -0
- package/dist/render/injectFonts.mjs +46 -0
- package/dist/render/injectFonts.mjs.map +1 -0
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +6 -2
- package/dist/serve.mjs.map +1 -1
- package/dist/server/ui/App.vue +25 -11
- package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
- package/dist/server/ui/pages/Preview.vue +24 -5
- package/dist/transformers/columnWidth.d.mts +31 -0
- package/dist/transformers/columnWidth.d.mts.map +1 -0
- package/dist/transformers/columnWidth.mjs +166 -0
- package/dist/transformers/columnWidth.mjs.map +1 -0
- package/dist/transformers/index.d.mts.map +1 -1
- package/dist/transformers/index.mjs +4 -0
- package/dist/transformers/index.mjs.map +1 -1
- package/dist/transformers/msoWidthFromClass.d.mts +19 -0
- package/dist/transformers/msoWidthFromClass.d.mts.map +1 -0
- package/dist/transformers/msoWidthFromClass.mjs +61 -0
- package/dist/transformers/msoWidthFromClass.mjs.map +1 -0
- package/dist/transformers/tailwindcss.d.mts.map +1 -1
- package/dist/transformers/tailwindcss.mjs +4 -12
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/utils/decodeStyleEntities.d.mts +15 -0
- package/dist/utils/decodeStyleEntities.d.mts.map +1 -0
- package/dist/utils/decodeStyleEntities.mjs +18 -0
- package/dist/utils/decodeStyleEntities.mjs.map +1 -0
- package/package.json +2 -1
|
@@ -174,7 +174,7 @@ const mergedClass = computed(() => twMerge(defaultClasses.value, attrs.class as
|
|
|
174
174
|
<Outlook><i class="mso-font-width-[150%]" :style="`mso-text-raise: ${msoPb};`" hidden> </i></Outlook>
|
|
175
175
|
<template v-if="icon && iconPosition === 'left'">
|
|
176
176
|
<span :style="`mso-text-raise: ${msoPt}`">
|
|
177
|
-
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`">
|
|
177
|
+
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
|
|
178
178
|
</span>
|
|
179
179
|
<Outlook><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
180
180
|
</template>
|
|
@@ -182,7 +182,7 @@ const mergedClass = computed(() => twMerge(defaultClasses.value, attrs.class as
|
|
|
182
182
|
<template v-if="icon && iconPosition === 'right'">
|
|
183
183
|
<Outlook><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
184
184
|
<span :style="`mso-text-raise: ${msoPt}`">
|
|
185
|
-
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`">
|
|
185
|
+
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
|
|
186
186
|
</span>
|
|
187
187
|
</template>
|
|
188
188
|
<Outlook><i class="mso-font-width-[150%]" hidden> ​</i></Outlook>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createStaticVNode, type PropType } from 'vue'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
4
|
import { codeToHtml, getSingletonHighlighter, type BundledLanguage, type BundledTheme } from 'shiki'
|
|
4
5
|
|
|
5
6
|
export default {
|
|
@@ -56,7 +57,7 @@ export default {
|
|
|
56
57
|
.replace(/^<pre[^>]*><code>/, '')
|
|
57
58
|
.replace(/<\/code><\/pre>$/, '')
|
|
58
59
|
|
|
59
|
-
const classes =
|
|
60
|
+
const classes = twMerge('font-mono', attrs.class as string)
|
|
60
61
|
const baseStyles = `background-color:${bg};padding:16px;overflow:auto;white-space:pre;word-wrap:normal;word-break:normal;word-spacing:normal`
|
|
61
62
|
const styles = [baseStyles, attrs.style].filter(Boolean).join(';')
|
|
62
63
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, createStaticVNode, inject,
|
|
2
|
+
import { computed, createStaticVNode, inject, useAttrs } from 'vue'
|
|
3
3
|
import type { ComputedRef } from 'vue'
|
|
4
|
-
import { normalizeToPixels } from './utils.ts'
|
|
4
|
+
import { nextId, normalizeToPixels } from './utils.ts'
|
|
5
5
|
|
|
6
6
|
defineOptions({ inheritAttrs: false })
|
|
7
7
|
|
|
@@ -11,8 +11,9 @@ const props = defineProps({
|
|
|
11
11
|
/**
|
|
12
12
|
* Override the auto-computed column width.
|
|
13
13
|
*
|
|
14
|
-
* By default, the width is calculated from the
|
|
15
|
-
*
|
|
14
|
+
* By default, the width is calculated from the nearest sized
|
|
15
|
+
* ancestor (`Container`, `Section`, `Row`, or outer `Column`)
|
|
16
|
+
* divided by the column count detected on the parent `Row`.
|
|
16
17
|
*/
|
|
17
18
|
width: {
|
|
18
19
|
type: [String, Number],
|
|
@@ -31,35 +32,35 @@ const props = defineProps({
|
|
|
31
32
|
}
|
|
32
33
|
})
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
-
const containerWidth = inject<ComputedRef<string | number> | null>('containerWidth', null)
|
|
36
|
-
const injectedMsoWidth = inject<ComputedRef<string> | null>('columnMsoWidth', null)
|
|
35
|
+
const columnCount = inject<ComputedRef<number> | null>('columnCount', null)
|
|
37
36
|
|
|
38
|
-
const
|
|
39
|
-
if (props.width) return normalizeToPixels(props.width)
|
|
40
|
-
|
|
41
|
-
return injectedMinWidth?.value ?? null
|
|
42
|
-
})
|
|
37
|
+
const count = computed(() => columnCount?.value ?? 2)
|
|
43
38
|
|
|
44
|
-
const
|
|
39
|
+
const useMarker = props.width == null
|
|
40
|
+
const colId = useMarker ? nextId('co') : null
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
const minWidth = computed(() => {
|
|
43
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
44
|
+
return `__MAIZZLE_COLW_${colId}__`
|
|
45
|
+
})
|
|
48
46
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return `${parts.join('; ')};`
|
|
47
|
+
const msoWidth = computed(() => {
|
|
48
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
49
|
+
return `__MAIZZLE_COLW_${colId}__`
|
|
53
50
|
})
|
|
54
51
|
|
|
52
|
+
const styles = computed(() =>
|
|
53
|
+
`display: inline-block; min-width: ${minWidth.value}; font-size: 16px; vertical-align: top;`
|
|
54
|
+
)
|
|
55
|
+
|
|
55
56
|
const tdStyle = computed(() => {
|
|
56
|
-
const parts = ['vertical-align: top']
|
|
57
|
+
const parts = [`width: ${msoWidth.value}`, 'vertical-align: top']
|
|
57
58
|
if (props.msoStyle) parts.push(props.msoStyle)
|
|
58
59
|
return parts.join('; ')
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
const MsoBefore = () => createStaticVNode(
|
|
62
|
-
`<!--[if mso]><td
|
|
63
|
+
`<!--[if mso]><td style="${tdStyle.value}"><![endif]-->`,
|
|
63
64
|
1
|
|
64
65
|
)
|
|
65
66
|
|
|
@@ -71,7 +72,12 @@ const MsoAfter = () => createStaticVNode(
|
|
|
71
72
|
|
|
72
73
|
<template>
|
|
73
74
|
<MsoBefore />
|
|
74
|
-
<div
|
|
75
|
+
<div
|
|
76
|
+
v-bind="attrs"
|
|
77
|
+
:style="styles"
|
|
78
|
+
:data-maizzle-cw-id="colId"
|
|
79
|
+
:data-maizzle-cw-count="useMarker ? count : null"
|
|
80
|
+
>
|
|
75
81
|
<slot />
|
|
76
82
|
</div>
|
|
77
83
|
<MsoAfter />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, provide, createStaticVNode, useAttrs } from 'vue'
|
|
3
|
-
import {
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import { hasWidthUtility, nextId, normalizeToPixels } from './utils.ts'
|
|
4
5
|
|
|
5
6
|
defineOptions({ inheritAttrs: false })
|
|
6
7
|
|
|
@@ -11,27 +12,58 @@ const props = defineProps({
|
|
|
11
12
|
* Max width of the container.
|
|
12
13
|
*
|
|
13
14
|
* Applied as `max-width` on the div and as `width` on the MSO table.
|
|
14
|
-
* Also
|
|
15
|
-
*
|
|
15
|
+
* Also used as the width source for descendant `Row`/`Column`
|
|
16
|
+
* components when computing column widths.
|
|
16
17
|
*
|
|
17
|
-
* When not set,
|
|
18
|
-
*
|
|
19
|
-
* The MSO table
|
|
18
|
+
* When not set, the div defaults to `w-150 mx-auto` (600px,
|
|
19
|
+
* centered) — overridable via Tailwind classes such as
|
|
20
|
+
* `w-[400px]` or `max-w-xl`. The MSO table width is auto-derived
|
|
21
|
+
* from the resolved width/max-width after CSS inlining, falling
|
|
22
|
+
* back to 600px when unresolvable.
|
|
20
23
|
*/
|
|
21
24
|
width: {
|
|
22
25
|
type: [String, Number],
|
|
23
26
|
default: null
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Override the Outlook (MSO) table width independently of the
|
|
30
|
+
* div's width. Highest priority — wins over `width` and any
|
|
31
|
+
* class-derived value.
|
|
32
|
+
*/
|
|
33
|
+
msoWidth: {
|
|
34
|
+
type: [String, Number],
|
|
35
|
+
default: null
|
|
24
36
|
}
|
|
25
37
|
})
|
|
26
38
|
|
|
27
39
|
provide('containerWidth', computed(() => props.width))
|
|
28
40
|
|
|
41
|
+
const useMarker = props.width == null && props.msoWidth == null
|
|
42
|
+
const msoId = useMarker ? nextId('c') : null
|
|
43
|
+
|
|
29
44
|
const styles = computed(() => {
|
|
30
|
-
if (
|
|
45
|
+
if (props.width == null) return undefined
|
|
31
46
|
return `max-width: ${normalizeToPixels(props.width)}; margin: 0 auto;`
|
|
32
47
|
})
|
|
33
48
|
|
|
34
|
-
const
|
|
49
|
+
const mergedClass = computed(() => {
|
|
50
|
+
if (props.width != null) return attrs.class as string | undefined
|
|
51
|
+
const userClass = (attrs.class as string) ?? ''
|
|
52
|
+
const defaultClass = hasWidthUtility(userClass)
|
|
53
|
+
? 'm-0 mx-auto'
|
|
54
|
+
: 'w-150 m-0 mx-auto'
|
|
55
|
+
return twMerge(defaultClass, userClass)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const msoWidth = computed(() => {
|
|
59
|
+
if (props.msoWidth != null) return normalizeToPixels(props.msoWidth)
|
|
60
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
61
|
+
return `__MAIZZLE_MSOW_${msoId}__`
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const colWidthSource = computed(() =>
|
|
65
|
+
props.width != null ? normalizeToPixels(props.width) : ''
|
|
66
|
+
)
|
|
35
67
|
|
|
36
68
|
const MsoBefore = () => createStaticVNode(
|
|
37
69
|
`<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: ${msoWidth.value}" align="center"><tr><td><![endif]-->`,
|
|
@@ -46,7 +78,13 @@ const MsoAfter = () => createStaticVNode(
|
|
|
46
78
|
|
|
47
79
|
<template>
|
|
48
80
|
<MsoBefore />
|
|
49
|
-
<div
|
|
81
|
+
<div
|
|
82
|
+
v-bind="{ ...attrs, class: undefined }"
|
|
83
|
+
:class="mergedClass"
|
|
84
|
+
:style="styles"
|
|
85
|
+
:data-maizzle-msow-id="msoId"
|
|
86
|
+
:data-maizzle-cw="colWidthSource"
|
|
87
|
+
>
|
|
50
88
|
<slot />
|
|
51
89
|
</div>
|
|
52
90
|
<MsoAfter />
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type PropType } from 'vue'
|
|
3
|
+
import { useFont } from '../composables/useFont'
|
|
4
|
+
|
|
5
|
+
type PopularGoogleFont =
|
|
6
|
+
// Sans-serif
|
|
7
|
+
| 'Roboto' | 'Open Sans' | 'Inter' | 'Lato' | 'Montserrat'
|
|
8
|
+
// Serif
|
|
9
|
+
| 'Merriweather' | 'Playfair Display' | 'Lora' | 'PT Serif' | 'Noto Serif'
|
|
10
|
+
// Display
|
|
11
|
+
| 'Oswald' | 'Bebas Neue' | 'Anton' | 'Lobster' | 'Pacifico'
|
|
12
|
+
// Handwriting
|
|
13
|
+
| 'Dancing Script' | 'Caveat' | 'Shadows Into Light' | 'Satisfy' | 'Great Vibes'
|
|
14
|
+
// Monospace
|
|
15
|
+
| 'Roboto Mono' | 'Source Code Pro' | 'JetBrains Mono' | 'Fira Code' | 'Inconsolata'
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
/**
|
|
19
|
+
* A single font family name, e.g. `"Roboto"` or `"Open Sans"`.
|
|
20
|
+
*
|
|
21
|
+
* For fallback fonts, use the `fallback` prop instead of a
|
|
22
|
+
* comma-separated list here. Popular Google Fonts are suggested
|
|
23
|
+
* in the IDE, but any string is accepted.
|
|
24
|
+
*
|
|
25
|
+
* @example "Open Sans"
|
|
26
|
+
*/
|
|
27
|
+
family: {
|
|
28
|
+
type: String as PropType<PopularGoogleFont | (string & {})>,
|
|
29
|
+
required: true,
|
|
30
|
+
validator: (v: string) => v.trim().length > 0,
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* CSS fallback list appended to the `font-family` declaration.
|
|
34
|
+
*
|
|
35
|
+
* @example "Verdana, sans-serif"
|
|
36
|
+
*/
|
|
37
|
+
fallback: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Font provider used to build the stylesheet URL when `url` is omitted.
|
|
43
|
+
* Bunny Fonts is a drop-in, privacy-friendly Google Fonts mirror.
|
|
44
|
+
*/
|
|
45
|
+
provider: {
|
|
46
|
+
type: String as PropType<'google' | 'bunny'>,
|
|
47
|
+
default: 'google',
|
|
48
|
+
validator: (v: string) => ['google', 'bunny'].includes(v),
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Stylesheet URL. When provided, used as-is for the `<link href>`.
|
|
52
|
+
* When omitted, a Google Fonts URL is built from `family`, `weights`,
|
|
53
|
+
* `display` and `styles`.
|
|
54
|
+
*/
|
|
55
|
+
url: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: '',
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Font weights to load. Ignored when `url` is provided.
|
|
61
|
+
*/
|
|
62
|
+
weights: {
|
|
63
|
+
type: Array as () => number[],
|
|
64
|
+
default: () => [400],
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* `font-display` value. Ignored when `url` is provided.
|
|
68
|
+
*/
|
|
69
|
+
display: {
|
|
70
|
+
type: String as PropType<'auto' | 'block' | 'swap' | 'fallback' | 'optional'>,
|
|
71
|
+
default: 'swap',
|
|
72
|
+
validator: (v: string) => ['auto', 'block', 'swap', 'fallback', 'optional'].includes(v),
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Font styles to load. Ignored when `url` is provided.
|
|
76
|
+
*
|
|
77
|
+
* @example ['normal', 'italic']
|
|
78
|
+
*/
|
|
79
|
+
styles: {
|
|
80
|
+
type: Array as () => Array<'normal' | 'italic'>,
|
|
81
|
+
default: () => ['normal'],
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
useFont({
|
|
86
|
+
family: props.family,
|
|
87
|
+
fallback: props.fallback || undefined,
|
|
88
|
+
provider: props.provider,
|
|
89
|
+
url: props.url || undefined,
|
|
90
|
+
weights: props.weights,
|
|
91
|
+
display: props.display,
|
|
92
|
+
styles: props.styles,
|
|
93
|
+
})
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<template></template>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { computed, useAttrs, type PropType } from 'vue'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
4
|
|
|
4
5
|
defineOptions({ inheritAttrs: false })
|
|
5
6
|
|
|
6
|
-
defineProps({
|
|
7
|
+
const props = defineProps({
|
|
7
8
|
/**
|
|
8
9
|
* Classes to add to the `<body>` tag.
|
|
9
10
|
*/
|
|
@@ -41,6 +42,10 @@ defineProps({
|
|
|
41
42
|
default: undefined
|
|
42
43
|
}
|
|
43
44
|
})
|
|
45
|
+
|
|
46
|
+
const attrs = useAttrs()
|
|
47
|
+
const bodyMergedClass = computed(() => twMerge('m-0 p-0 size-full [word-break:break-word]', props.bodyClass))
|
|
48
|
+
const articleMergedClass = computed(() => twMerge('[font-size:max(16px,1rem)] font-inter', attrs.class as string))
|
|
44
49
|
</script>
|
|
45
50
|
|
|
46
51
|
<template>
|
|
@@ -76,7 +81,7 @@ defineProps({
|
|
|
76
81
|
}
|
|
77
82
|
</style>
|
|
78
83
|
</head>
|
|
79
|
-
<body :xml:lang="lang" :class="
|
|
84
|
+
<body :xml:lang="lang" :class="bodyMergedClass">
|
|
80
85
|
<div
|
|
81
86
|
role="article"
|
|
82
87
|
aria-roledescription="email"
|
|
@@ -84,7 +89,7 @@ defineProps({
|
|
|
84
89
|
:lang="lang"
|
|
85
90
|
:dir="dir"
|
|
86
91
|
style="font-size: medium;"
|
|
87
|
-
:class="
|
|
92
|
+
:class="articleMergedClass"
|
|
88
93
|
>
|
|
89
94
|
<slot />
|
|
90
95
|
</div>
|
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
3
|
-
import {
|
|
2
|
+
import { computed, useAttrs, createStaticVNode } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
hasHeightInStyle,
|
|
5
|
+
hasHeightUtility,
|
|
6
|
+
hasWidthInStyle,
|
|
7
|
+
hasWidthUtility,
|
|
8
|
+
nextId,
|
|
9
|
+
normalizeToPixels
|
|
10
|
+
} from './utils.ts'
|
|
4
11
|
|
|
5
12
|
defineOptions({ inheritAttrs: false })
|
|
6
13
|
|
|
7
14
|
const attrs = useAttrs()
|
|
8
15
|
|
|
9
|
-
const containerWidth = inject('containerWidth', undefined)
|
|
10
|
-
|
|
11
16
|
const props = defineProps({
|
|
12
17
|
/**
|
|
13
18
|
* Max height of the background (default slot) content.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
20
|
+
* Applied as `max-height` on the background div and as `height`
|
|
21
|
+
* on the VML rectangle. When not set, the height is taken from
|
|
22
|
+
* a Tailwind utility (e.g. `h-50`, `max-h-[200px]`) or inline
|
|
23
|
+
* `height`/`max-height` style on the component, after CSS inlining.
|
|
16
24
|
*/
|
|
17
25
|
height: {
|
|
18
26
|
type: [String, Number],
|
|
19
|
-
|
|
27
|
+
default: null
|
|
20
28
|
},
|
|
21
29
|
/**
|
|
22
30
|
* Width of the overlay table and VML rectangle.
|
|
23
31
|
*
|
|
24
|
-
* When
|
|
32
|
+
* When not set, derived from a width utility class or inline
|
|
33
|
+
* style on the component itself, otherwise from the nearest
|
|
34
|
+
* sized ancestor (`Container`, `Section`, outer `Column`).
|
|
35
|
+
* Falls back to `100%` when no source is found.
|
|
25
36
|
*/
|
|
26
37
|
width: {
|
|
27
38
|
type: [String, Number],
|
|
28
|
-
default:
|
|
39
|
+
default: null
|
|
29
40
|
},
|
|
30
41
|
/**
|
|
31
42
|
* Height of the VML rectangle in Outlook.
|
|
32
43
|
*
|
|
33
|
-
* Defaults to the `height
|
|
34
|
-
*
|
|
44
|
+
* Defaults to the resolved `height`. Use this to fine-tune the
|
|
45
|
+
* overlay height specifically for Outlook rendering.
|
|
35
46
|
*/
|
|
36
47
|
msoHeight: {
|
|
37
48
|
type: [String, Number],
|
|
@@ -51,17 +62,56 @@ const props = defineProps({
|
|
|
51
62
|
},
|
|
52
63
|
})
|
|
53
64
|
|
|
54
|
-
const
|
|
65
|
+
const userStyle = computed(() => {
|
|
66
|
+
const s = attrs.style
|
|
67
|
+
if (!s) return ''
|
|
68
|
+
return typeof s === 'object'
|
|
69
|
+
? Object.entries(s).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`).join('; ')
|
|
70
|
+
: String(s)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const userClass = computed(() => (attrs.class as string) ?? '')
|
|
74
|
+
|
|
75
|
+
const userHasWidth = computed(() =>
|
|
76
|
+
hasWidthUtility(userClass.value) || hasWidthInStyle(userStyle.value)
|
|
77
|
+
)
|
|
78
|
+
const userHasHeight = computed(() =>
|
|
79
|
+
hasHeightUtility(userClass.value) || hasHeightInStyle(userStyle.value)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const useWidthMarker = props.width == null
|
|
83
|
+
const useHeightMarker = props.height == null && props.msoHeight == null
|
|
84
|
+
const id = (useWidthMarker || useHeightMarker) ? nextId('o') : null
|
|
85
|
+
|
|
86
|
+
const widthValue = computed(() =>
|
|
87
|
+
useWidthMarker ? `__MAIZZLE_COLW_${id}__` : normalizeToPixels(props.width)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const heightValue = computed(() => {
|
|
91
|
+
if (props.msoHeight != null) return normalizeToPixels(props.msoHeight)
|
|
92
|
+
if (props.height != null) return normalizeToPixels(props.height)
|
|
93
|
+
return `__MAIZZLE_OH_${id}__`
|
|
94
|
+
})
|
|
55
95
|
|
|
56
96
|
const backgroundStyles = computed(() => {
|
|
57
|
-
|
|
97
|
+
const parts: string[] = []
|
|
98
|
+
if (props.height != null) parts.push(`max-height: ${normalizeToPixels(props.height)}`)
|
|
99
|
+
parts.push('margin: 0 auto', 'text-align: center')
|
|
100
|
+
if (userStyle.value) parts.push(userStyle.value)
|
|
101
|
+
return parts.join('; ') + ';'
|
|
58
102
|
})
|
|
59
103
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
const tdStyle = computed(() =>
|
|
105
|
+
`width: ${widthValue.value}; max-width: 100%; vertical-align: top;`
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const vmlOpen = computed(() =>
|
|
109
|
+
`<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" stroked="f" filled="f" style="width: ${widthValue.value}; height: ${heightValue.value};"><v:textbox inset="${props.msoInset}"><![endif]-->`
|
|
110
|
+
)
|
|
63
111
|
|
|
64
|
-
|
|
112
|
+
const restAttrs = computed(() => {
|
|
113
|
+
const { style: _, ...rest } = attrs
|
|
114
|
+
return rest
|
|
65
115
|
})
|
|
66
116
|
|
|
67
117
|
const VmlBefore = () => createStaticVNode(vmlOpen.value, 1)
|
|
@@ -69,12 +119,19 @@ const VmlAfter = () => createStaticVNode('<!--[if mso]></v:textbox></v:rect><![e
|
|
|
69
119
|
</script>
|
|
70
120
|
|
|
71
121
|
<template>
|
|
72
|
-
<div
|
|
122
|
+
<div
|
|
123
|
+
v-bind="restAttrs"
|
|
124
|
+
:style="backgroundStyles"
|
|
125
|
+
:data-maizzle-cw-id="useWidthMarker ? id : null"
|
|
126
|
+
:data-maizzle-cw-count="useWidthMarker ? 1 : null"
|
|
127
|
+
:data-maizzle-cw-self="useWidthMarker && userHasWidth ? '' : null"
|
|
128
|
+
:data-maizzle-oh-id="useHeightMarker && userHasHeight ? id : null"
|
|
129
|
+
>
|
|
73
130
|
<slot />
|
|
74
131
|
</div>
|
|
75
132
|
<table style="max-height: 0; position: relative; opacity: 0.999;">
|
|
76
133
|
<tr>
|
|
77
|
-
<td :style="
|
|
134
|
+
<td :style="tdStyle">
|
|
78
135
|
<VmlBefore />
|
|
79
136
|
<slot name="overlay" />
|
|
80
137
|
<VmlAfter />
|
package/dist/components/Row.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Comment, computed, createStaticVNode,
|
|
3
|
-
import type {
|
|
2
|
+
import { Comment, computed, createStaticVNode, provide, useAttrs, useSlots, Fragment } from 'vue'
|
|
3
|
+
import type { VNode } from 'vue'
|
|
4
|
+
import { hasWidthInStyle, hasWidthUtility, normalizeToPixels } from './utils.ts'
|
|
4
5
|
|
|
5
6
|
defineOptions({ inheritAttrs: false })
|
|
6
7
|
|
|
@@ -8,10 +9,12 @@ const attrs = useAttrs()
|
|
|
8
9
|
|
|
9
10
|
const props = defineProps({
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Explicit row width.
|
|
12
13
|
*
|
|
13
|
-
* Used
|
|
14
|
-
*
|
|
14
|
+
* Used as the width source for column min-width calculation.
|
|
15
|
+
* When not set, the nearest sized ancestor (`Container`, `Section`,
|
|
16
|
+
* outer `Column`, or this row's own width class/inline style) is
|
|
17
|
+
* used instead.
|
|
15
18
|
*/
|
|
16
19
|
width: {
|
|
17
20
|
type: [String, Number],
|
|
@@ -53,26 +56,40 @@ const columnCount = computed(() => {
|
|
|
53
56
|
return countChildren(children) || 1
|
|
54
57
|
})
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
provide('columnCount', columnCount)
|
|
57
60
|
|
|
58
|
-
const
|
|
61
|
+
const userStyle = computed(() => {
|
|
62
|
+
const s = attrs.style
|
|
63
|
+
if (!s) return ''
|
|
64
|
+
return typeof s === 'object'
|
|
65
|
+
? Object.entries(s).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`).join('; ')
|
|
66
|
+
: String(s)
|
|
67
|
+
})
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
const userHasWidth = computed(() => {
|
|
70
|
+
const cls = (attrs.class as string) ?? ''
|
|
71
|
+
return hasWidthUtility(cls) || hasWidthInStyle(userStyle.value)
|
|
72
|
+
})
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
const colWidthSource = computed(() => {
|
|
75
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
76
|
+
if (userHasWidth.value) return ''
|
|
77
|
+
return null
|
|
78
|
+
})
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
}
|
|
80
|
+
const restAttrs = computed(() => {
|
|
81
|
+
const { style: _, ...rest } = attrs
|
|
82
|
+
return rest
|
|
83
|
+
})
|
|
70
84
|
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
const divStyle = computed(() => {
|
|
86
|
+
const parts: string[] = ['font-size: 0;']
|
|
87
|
+
if (userStyle.value) parts.push(userStyle.value)
|
|
88
|
+
return parts.join(' ')
|
|
89
|
+
})
|
|
73
90
|
|
|
74
91
|
const MsoBefore = () => createStaticVNode(
|
|
75
|
-
'<!--[if mso]><table role="none" cellpadding="0" cellspacing="0"
|
|
92
|
+
'<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: 100%"><tr><![endif]-->',
|
|
76
93
|
1
|
|
77
94
|
)
|
|
78
95
|
|
|
@@ -84,7 +101,11 @@ const MsoAfter = () => createStaticVNode(
|
|
|
84
101
|
|
|
85
102
|
<template>
|
|
86
103
|
<MsoBefore />
|
|
87
|
-
<div
|
|
104
|
+
<div
|
|
105
|
+
v-bind="restAttrs"
|
|
106
|
+
:style="divStyle"
|
|
107
|
+
:data-maizzle-cw="colWidthSource"
|
|
108
|
+
>
|
|
88
109
|
<slot />
|
|
89
110
|
</div>
|
|
90
111
|
<MsoAfter />
|