@maizzle/framework 6.0.0-rc.15 → 6.0.0-rc.16

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 (45) hide show
  1. package/README.md +3 -3
  2. package/dist/components/Body.vue +9 -2
  3. package/dist/components/Button.vue +13 -8
  4. package/dist/components/Column.vue +22 -8
  5. package/dist/components/Container.vue +9 -5
  6. package/dist/components/Html.vue +7 -2
  7. package/dist/components/Layout.vue +30 -15
  8. package/dist/components/Overlap.vue +8 -3
  9. package/dist/components/Raw.vue +28 -0
  10. package/dist/components/Row.vue +22 -11
  11. package/dist/components/Section.vue +9 -5
  12. package/dist/components/Spacer.vue +9 -4
  13. package/dist/components/utils.d.mts +11 -1
  14. package/dist/components/utils.d.mts.map +1 -1
  15. package/dist/components/utils.mjs +11 -1
  16. package/dist/components/utils.mjs.map +1 -1
  17. package/dist/components/utils.ts +12 -0
  18. package/dist/composables/useOutlookFallback.d.mts +21 -0
  19. package/dist/composables/useOutlookFallback.d.mts.map +1 -0
  20. package/dist/composables/useOutlookFallback.mjs +30 -0
  21. package/dist/composables/useOutlookFallback.mjs.map +1 -0
  22. package/dist/index.d.mts +2 -1
  23. package/dist/index.mjs +2 -1
  24. package/dist/render/createRenderer.d.mts.map +1 -1
  25. package/dist/render/createRenderer.mjs +7 -75
  26. package/dist/render/createRenderer.mjs.map +1 -1
  27. package/dist/render/plugins/codeBlockExtract.d.mts +14 -0
  28. package/dist/render/plugins/codeBlockExtract.d.mts.map +1 -0
  29. package/dist/render/plugins/codeBlockExtract.mjs +34 -0
  30. package/dist/render/plugins/codeBlockExtract.mjs.map +1 -0
  31. package/dist/render/plugins/markdownExtract.d.mts +12 -0
  32. package/dist/render/plugins/markdownExtract.d.mts.map +1 -0
  33. package/dist/render/plugins/markdownExtract.mjs +50 -0
  34. package/dist/render/plugins/markdownExtract.mjs.map +1 -0
  35. package/dist/render/plugins/rawExtract.d.mts +14 -0
  36. package/dist/render/plugins/rawExtract.d.mts.map +1 -0
  37. package/dist/render/plugins/rawExtract.mjs +34 -0
  38. package/dist/render/plugins/rawExtract.mjs.map +1 -0
  39. package/dist/server/ui/App.vue +47 -2
  40. package/dist/server/ui/pages/Preview.vue +32 -3
  41. package/dist/transformers/columnWidth.d.mts +9 -9
  42. package/dist/transformers/columnWidth.d.mts.map +1 -1
  43. package/dist/transformers/columnWidth.mjs +422 -41
  44. package/dist/transformers/columnWidth.mjs.map +1 -1
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  </picture>
8
8
  </a>
9
9
  </p>
10
- <p>Quickly build HTML emails with Tailwind CSS</p>
10
+ <p>The modern email development framework</p>
11
11
  <div>
12
12
 
13
13
  [![Version][npm-version-shield]][npm]
@@ -20,9 +20,9 @@
20
20
 
21
21
  ## About
22
22
 
23
- > **Note:** This repository contains the core code of the Maizzle framework. If you want to build HTML emails using Maizzle, visit the [Starter repository](https://github.com/maizzle/maizzle).
23
+ > **Note:** This repository contains the core code of the Maizzle framework. If you want to start building HTML emails using Maizzle, visit the [Starter repository](https://github.com/maizzle/maizzle).
24
24
 
25
- Maizzle is a framework that helps you quickly build HTML emails with [Tailwind CSS](https://tailwindcss.com/).
25
+ Maizzle is a Vite-powered framework for building HTML emails with Vue and Tailwind CSS.
26
26
 
27
27
  ## Documentation
28
28
 
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { createStaticVNode, inject, useAttrs, useSlots } from 'vue'
3
3
  import type { PropType } from 'vue'
4
+ import { outlookFallbackProp } from './utils.ts'
5
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
4
6
 
5
7
  defineOptions({ inheritAttrs: false })
6
8
 
@@ -65,9 +67,12 @@ const props = defineProps({
65
67
  ariaLabel: {
66
68
  type: String,
67
69
  default: undefined
68
- }
70
+ },
71
+ outlookFallback: outlookFallbackProp,
69
72
  })
70
73
 
74
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
75
+
71
76
  const htmlLang = inject<string>('htmlLang', 'en')
72
77
 
73
78
  const render = () => {
@@ -78,10 +83,12 @@ const render = () => {
78
83
  const lang = props.xmlLang ?? htmlLang
79
84
 
80
85
  const parts = [
81
- `xml:lang="${lang}"`,
82
86
  `dir="${props.dir}"`,
83
87
  'style="margin: 0; padding: 0; width: 100%; word-break: break-word;"',
84
88
  ]
89
+ if (outlookFallback) {
90
+ parts.unshift(`xml:lang="${lang}"`)
91
+ }
85
92
 
86
93
  if (extraAttrs) {
87
94
  parts.push(extraAttrs)
@@ -3,6 +3,8 @@ import { computed, useAttrs } from 'vue'
3
3
  import type { PropType } from 'vue'
4
4
  import { twMerge } from 'tailwind-merge'
5
5
  import Outlook from './Outlook.vue'
6
+ import { outlookFallbackProp } from './utils.ts'
7
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
6
8
 
7
9
  defineOptions({ inheritAttrs: false })
8
10
 
@@ -103,9 +105,12 @@ const props = defineProps({
103
105
  iconClass: {
104
106
  type: String,
105
107
  default: ''
106
- }
108
+ },
109
+ outlookFallback: outlookFallbackProp,
107
110
  })
108
111
 
112
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
113
+
109
114
  const parsedIconWidth = computed(() => parseInt(String(props.iconWidth), 10))
110
115
 
111
116
  const alignClass = computed(() => props.align ? ({
@@ -171,21 +176,21 @@ const mergedClass = computed(() => twMerge(defaultClasses.value, attrs.class as
171
176
  :class="mergedClass"
172
177
  >
173
178
  <template v-if="!isLink">
174
- <Outlook><i class="mso-font-width-[150%]" :style="`mso-text-raise: ${msoPb};`" hidden>&emsp;</i></Outlook>
179
+ <Outlook v-if="outlookFallback"><i class="mso-font-width-[150%]" :style="`mso-text-raise: ${msoPb};`" hidden>&emsp;</i></Outlook>
175
180
  <template v-if="icon && iconPosition === 'left'">
176
- <span :style="`mso-text-raise: ${msoPt}`">
181
+ <span :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined">
177
182
  <img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
178
183
  </span>
179
- <Outlook><i class="mso-font-width-[30%]" hidden>&emsp;&#8203;</i></Outlook>
184
+ <Outlook v-if="outlookFallback"><i class="mso-font-width-[30%]" hidden>&emsp;&#8203;</i></Outlook>
180
185
  </template>
181
- <span :class="icon ? (iconPosition === 'right' ? 'mr-2' : 'ml-2') : ''" :style="`mso-text-raise: ${msoPt}`"><slot /></span>
186
+ <span :class="icon ? (iconPosition === 'right' ? 'mr-2' : 'ml-2') : ''" :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined"><slot /></span>
182
187
  <template v-if="icon && iconPosition === 'right'">
183
- <Outlook><i class="mso-font-width-[30%]" hidden>&emsp;&#8203;</i></Outlook>
184
- <span :style="`mso-text-raise: ${msoPt}`">
188
+ <Outlook v-if="outlookFallback"><i class="mso-font-width-[30%]" hidden>&emsp;&#8203;</i></Outlook>
189
+ <span :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined">
185
190
  <img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
186
191
  </span>
187
192
  </template>
188
- <Outlook><i class="mso-font-width-[150%]" hidden>&emsp;&#8203;</i></Outlook>
193
+ <Outlook v-if="outlookFallback"><i class="mso-font-width-[150%]" hidden>&emsp;&#8203;</i></Outlook>
189
194
  </template>
190
195
  <template v-else>
191
196
  <slot />
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, createStaticVNode, inject, useAttrs } from 'vue'
3
3
  import type { ComputedRef } from 'vue'
4
- import { nextId, normalizeToPixels } from './utils.ts'
4
+ import { twMerge } from 'tailwind-merge'
5
+ import { nextId, normalizeToPixels, outlookFallbackProp } from './utils.ts'
6
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
5
7
 
6
8
  defineOptions({ inheritAttrs: false })
7
9
 
@@ -29,9 +31,12 @@ const props = defineProps({
29
31
  msoStyle: {
30
32
  type: String,
31
33
  default: undefined
32
- }
34
+ },
35
+ outlookFallback: outlookFallbackProp,
33
36
  })
34
37
 
38
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
39
+
35
40
  const columnCount = inject<ComputedRef<number> | null>('columnCount', null)
36
41
 
37
42
  const count = computed(() => columnCount?.value ?? 2)
@@ -49,9 +54,17 @@ const msoWidth = computed(() => {
49
54
  return `__MAIZZLE_COLW_${colId}__`
50
55
  })
51
56
 
52
- const styles = computed(() =>
53
- `display: inline-block; min-width: ${minWidth.value}; font-size: 16px; vertical-align: top;`
54
- )
57
+ /**
58
+ * Baseline display/typography lives in classes not inline `:style` —
59
+ * so the user can override any of them via tailwind utilities. Inline
60
+ * `display: inline-block` would silently shadow a class like
61
+ * `inline-table` during CSS inlining; routing both through twMerge lets
62
+ * the user's utility cleanly replace ours instead of being dropped.
63
+ */
64
+ const baseClass = 'inline-block align-top text-base'
65
+ const mergedClass = computed(() => twMerge(baseClass, (attrs.class as string) ?? ''))
66
+
67
+ const styles = computed(() => `min-width: ${minWidth.value};`)
55
68
 
56
69
  const tdStyle = computed(() => {
57
70
  const parts = [`width: ${msoWidth.value}`, 'vertical-align: top']
@@ -71,14 +84,15 @@ const MsoAfter = () => createStaticVNode(
71
84
  </script>
72
85
 
73
86
  <template>
74
- <MsoBefore />
87
+ <MsoBefore v-if="outlookFallback" />
75
88
  <div
76
- v-bind="attrs"
89
+ v-bind="{ ...attrs, class: undefined }"
90
+ :class="mergedClass"
77
91
  :style="styles"
78
92
  :data-maizzle-cw-id="colId"
79
93
  :data-maizzle-cw-count="useMarker ? count : null"
80
94
  >
81
95
  <slot />
82
96
  </div>
83
- <MsoAfter />
97
+ <MsoAfter v-if="outlookFallback" />
84
98
  </template>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, provide, createStaticVNode, useAttrs } from 'vue'
3
3
  import { twMerge } from 'tailwind-merge'
4
- import { hasWidthUtility, nextId, normalizeToPixels } from './utils.ts'
4
+ import { hasWidthUtility, nextId, normalizeToPixels, outlookFallbackProp } from './utils.ts'
5
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
5
6
 
6
7
  defineOptions({ inheritAttrs: false })
7
8
 
@@ -33,12 +34,15 @@ const props = defineProps({
33
34
  msoWidth: {
34
35
  type: [String, Number],
35
36
  default: null
36
- }
37
+ },
38
+ outlookFallback: outlookFallbackProp,
37
39
  })
38
40
 
41
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
42
+
39
43
  provide('containerWidth', computed(() => props.width))
40
44
 
41
- const useMarker = props.width == null && props.msoWidth == null
45
+ const useMarker = outlookFallback && props.width == null && props.msoWidth == null
42
46
  const msoId = useMarker ? nextId('c') : null
43
47
 
44
48
  const styles = computed(() => {
@@ -77,7 +81,7 @@ const MsoAfter = () => createStaticVNode(
77
81
  </script>
78
82
 
79
83
  <template>
80
- <MsoBefore />
84
+ <MsoBefore v-if="outlookFallback" />
81
85
  <div
82
86
  v-bind="{ ...attrs, class: undefined }"
83
87
  :class="mergedClass"
@@ -87,5 +91,5 @@ const MsoAfter = () => createStaticVNode(
87
91
  >
88
92
  <slot />
89
93
  </div>
90
- <MsoAfter />
94
+ <MsoAfter v-if="outlookFallback" />
91
95
  </template>
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { createStaticVNode, provide, useAttrs, useSlots } from 'vue'
3
3
  import type { PropType } from 'vue'
4
+ import { outlookFallbackProp } from './utils.ts'
5
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
4
6
 
5
7
  defineOptions({ inheritAttrs: false })
6
8
 
@@ -65,9 +67,12 @@ const props = defineProps({
65
67
  xmlns: {
66
68
  type: [Boolean, String],
67
69
  default: true
68
- }
70
+ },
71
+ outlookFallback: outlookFallbackProp,
69
72
  })
70
73
 
74
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
75
+
71
76
  provide('htmlLang', props.lang)
72
77
 
73
78
  const render = () => {
@@ -80,7 +85,7 @@ const render = () => {
80
85
  `dir="${props.dir}"`,
81
86
  ]
82
87
 
83
- if (props.xmlns !== false && props.xmlns !== 'false') {
88
+ if (outlookFallback && props.xmlns !== false && props.xmlns !== 'false') {
84
89
  parts.push(
85
90
  'xmlns:v="urn:schemas-microsoft-com:vml"',
86
91
  'xmlns:o="urn:schemas-microsoft-com:office:office"',
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, useAttrs, createStaticVNode, type PropType } from 'vue'
3
3
  import { twMerge } from 'tailwind-merge'
4
+ import { outlookFallbackProp } from './utils.ts'
5
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
4
6
 
5
7
  defineOptions({ inheritAttrs: false })
6
8
 
@@ -52,27 +54,20 @@ const props = defineProps({
52
54
  ariaLabel: {
53
55
  type: String,
54
56
  default: undefined
55
- }
57
+ },
58
+ outlookFallback: outlookFallbackProp,
56
59
  })
57
60
 
61
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
62
+
58
63
  const attrs = useAttrs()
59
64
  const bodyMergedClass = computed(() => twMerge('m-0 p-0 size-full [word-break:break-word]', props.bodyClass))
60
65
  const articleMergedClass = computed(() => twMerge('[font-size:max(16px,1rem)] font-inter', attrs.class as string))
61
66
 
62
67
  const EmptyHead = () => createStaticVNode('<head></head>', 1)
63
- </script>
64
68
 
65
- <template>
66
- <html :lang="lang" :dir="dir" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
67
- <EmptyHead v-if="props.doubleHead === true || props.doubleHead === 'true'" />
68
- <head>
69
- <meta charset="utf-8">
70
- <meta name="x-apple-disable-message-reformatting">
71
- <meta name="viewport" content="width=device-width, initial-scale=1">
72
- <meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
73
- <meta name="color-scheme" content="light dark">
74
- <meta name="supported-color-schemes" content="light dark">
75
- <!--[if mso]>
69
+ const MsoHead = () => createStaticVNode(
70
+ `<!--[if mso]>
76
71
  <noscript>
77
72
  <xml>
78
73
  <o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
@@ -84,7 +79,27 @@ const EmptyHead = () => createStaticVNode('<head></head>', 1)
84
79
  td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
85
80
  .mso-break-all {word-break: break-all;}
86
81
  </style>
87
- <![endif]-->
82
+ <![endif]-->`,
83
+ 1
84
+ )
85
+
86
+ const htmlXmlns = computed(() => outlookFallback ? {
87
+ 'xmlns:v': 'urn:schemas-microsoft-com:vml',
88
+ 'xmlns:o': 'urn:schemas-microsoft-com:office:office',
89
+ } : {})
90
+ </script>
91
+
92
+ <template>
93
+ <html :lang="lang" :dir="dir" v-bind="htmlXmlns">
94
+ <EmptyHead v-if="props.doubleHead === true || props.doubleHead === 'true'" />
95
+ <head>
96
+ <meta charset="utf-8">
97
+ <meta name="x-apple-disable-message-reformatting">
98
+ <meta name="viewport" content="width=device-width, initial-scale=1">
99
+ <meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
100
+ <meta name="color-scheme" content="light dark">
101
+ <meta name="supported-color-schemes" content="light dark">
102
+ <MsoHead v-if="outlookFallback" />
88
103
  <link rel="preconnect" href="https://fonts.googleapis.com">
89
104
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
90
105
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
@@ -96,7 +111,7 @@ const EmptyHead = () => createStaticVNode('<head></head>', 1)
96
111
  }
97
112
  </style>
98
113
  </head>
99
- <body :xml:lang="lang" :class="bodyMergedClass">
114
+ <body :xml:lang="outlookFallback ? lang : null" :class="bodyMergedClass">
100
115
  <div
101
116
  role="article"
102
117
  aria-roledescription="email"
@@ -6,8 +6,10 @@ import {
6
6
  hasWidthInStyle,
7
7
  hasWidthUtility,
8
8
  nextId,
9
- normalizeToPixels
9
+ normalizeToPixels,
10
+ outlookFallbackProp
10
11
  } from './utils.ts'
12
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
11
13
 
12
14
  defineOptions({ inheritAttrs: false })
13
15
 
@@ -60,8 +62,11 @@ const props = defineProps({
60
62
  type: String,
61
63
  default: '0,-60px,0,0'
62
64
  },
65
+ outlookFallback: outlookFallbackProp,
63
66
  })
64
67
 
68
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
69
+
65
70
  const userStyle = computed(() => {
66
71
  const s = attrs.style
67
72
  if (!s) return ''
@@ -132,9 +137,9 @@ const VmlAfter = () => createStaticVNode('<!--[if mso]></v:textbox></v:rect><![e
132
137
  <table style="max-height: 0; position: relative; opacity: 0.999;">
133
138
  <tr>
134
139
  <td :style="tdStyle">
135
- <VmlBefore />
140
+ <VmlBefore v-if="outlookFallback" />
136
141
  <slot name="overlay" />
137
- <VmlAfter />
142
+ <VmlAfter v-if="outlookFallback" />
138
143
  </td>
139
144
  </tr>
140
145
  </table>
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { createStaticVNode } from 'vue'
3
+
4
+ export default {
5
+ inheritAttrs: false,
6
+ props: {
7
+ /**
8
+ * Raw content to emit verbatim.
9
+ *
10
+ * Auto-populated from slot content by
11
+ * the `maizzle:raw-extract` Vite plugin
12
+ * before Vue compiles the template,
13
+ * so `{{ }}` and other Vue/ESP
14
+ * syntax pass through untouched.
15
+ */
16
+ content: {
17
+ type: String,
18
+ default: '',
19
+ },
20
+ },
21
+ setup(props) {
22
+ if (!props.content) {
23
+ return () => createStaticVNode('', 0)
24
+ }
25
+ return () => createStaticVNode(props.content, 1)
26
+ },
27
+ }
28
+ </script>
@@ -5,8 +5,10 @@ const warnedLocations = new Set<string>()
5
5
  <script setup lang="ts">
6
6
  import { Comment, Text, computed, createStaticVNode, provide, useAttrs, useSlots, Fragment } from 'vue'
7
7
  import type { VNode } from 'vue'
8
+ import { twMerge } from 'tailwind-merge'
8
9
  import Column from './Column.vue'
9
- import { hasWidthInStyle, hasWidthUtility, normalizeToPixels } from './utils.ts'
10
+ import { hasWidthInStyle, hasWidthUtility, normalizeToPixels, outlookFallbackProp } from './utils.ts'
11
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
10
12
 
11
13
  defineOptions({ inheritAttrs: false })
12
14
 
@@ -35,9 +37,12 @@ const props = defineProps({
35
37
  cols: {
36
38
  type: Number,
37
39
  default: null
38
- }
40
+ },
41
+ outlookFallback: outlookFallbackProp,
39
42
  })
40
43
 
44
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
45
+
41
46
  const slots = useSlots()
42
47
 
43
48
  function countChildren(vnodes: VNode[]): number {
@@ -118,15 +123,20 @@ const colWidthSource = computed(() => {
118
123
  })
119
124
 
120
125
  const restAttrs = computed(() => {
121
- const { style: _, 'data-maizzle-loc': __, ...rest } = attrs
126
+ const { style: _, class: __, 'data-maizzle-loc': ___, ...rest } = attrs
122
127
  return rest
123
128
  })
124
129
 
125
- const divStyle = computed(() => {
126
- const parts: string[] = ['font-size: 0;']
127
- if (userStyle.value) parts.push(userStyle.value)
128
- return parts.join(' ')
129
- })
130
+ /**
131
+ * `font-size: 0;` removes the whitespace gap between inline-block
132
+ * children. Lives in a class so users can override (e.g. via a custom
133
+ * `text-*`) and twMerge resolves the conflict cleanly instead of the
134
+ * inline declaration silently shadowing the user's class.
135
+ */
136
+ const baseClass = 'text-0'
137
+ const mergedClass = computed(() => twMerge(baseClass, (attrs.class as string) ?? ''))
138
+
139
+ const divStyle = computed(() => userStyle.value || undefined)
130
140
 
131
141
  const MsoBefore = () => createStaticVNode(
132
142
  '<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: 100%"><tr><![endif]-->',
@@ -139,7 +149,7 @@ const MsoAfter = () => createStaticVNode(
139
149
  )
140
150
 
141
151
  const initialChildren = slots.default?.() ?? []
142
- if (hasMeaningfulContent(initialChildren) && !hasColumnChild(initialChildren)) {
152
+ if (outlookFallback && hasMeaningfulContent(initialChildren) && !hasColumnChild(initialChildren)) {
143
153
  const loc = (attrs['data-maizzle-loc'] as string | undefined) ?? '<unknown location>'
144
154
  if (!warnedLocations.has(loc)) {
145
155
  warnedLocations.add(loc)
@@ -150,13 +160,14 @@ if (hasMeaningfulContent(initialChildren) && !hasColumnChild(initialChildren)) {
150
160
  </script>
151
161
 
152
162
  <template>
153
- <MsoBefore />
163
+ <MsoBefore v-if="outlookFallback" />
154
164
  <div
155
165
  v-bind="restAttrs"
166
+ :class="mergedClass"
156
167
  :style="divStyle"
157
168
  :data-maizzle-cw="colWidthSource"
158
169
  >
159
170
  <slot />
160
171
  </div>
161
- <MsoAfter />
172
+ <MsoAfter v-if="outlookFallback" />
162
173
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, createStaticVNode, useAttrs } from 'vue'
3
- import { hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels } from './utils.ts'
3
+ import { hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels, outlookFallbackProp } from './utils.ts'
4
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
4
5
 
5
6
  defineOptions({ inheritAttrs: false })
6
7
 
@@ -31,9 +32,12 @@ const props = defineProps({
31
32
  msoStyle: {
32
33
  type: String,
33
34
  default: undefined
34
- }
35
+ },
36
+ outlookFallback: outlookFallbackProp,
35
37
  })
36
38
 
39
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
40
+
37
41
  const userStyle = computed(() => {
38
42
  const s = attrs.style
39
43
  if (!s) return ''
@@ -47,7 +51,7 @@ const userHasWidth = computed(() => {
47
51
  return hasWidthUtility(cls) || hasWidthInStyle(userStyle.value)
48
52
  })
49
53
 
50
- const useMarker = props.width == null && userHasWidth.value
54
+ const useMarker = outlookFallback && props.width == null && userHasWidth.value
51
55
  const msoId = useMarker ? nextId('s') : null
52
56
 
53
57
  const divStyle = computed(() => {
@@ -96,7 +100,7 @@ const MsoAfter = () => createStaticVNode(
96
100
  </script>
97
101
 
98
102
  <template>
99
- <MsoBefore />
103
+ <MsoBefore v-if="outlookFallback" />
100
104
  <div
101
105
  v-bind="restAttrs"
102
106
  :style="divStyle"
@@ -106,5 +110,5 @@ const MsoAfter = () => createStaticVNode(
106
110
  >
107
111
  <slot />
108
112
  </div>
109
- <MsoAfter />
113
+ <MsoAfter v-if="outlookFallback" />
110
114
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
- import { normalizeToPixels } from './utils.ts'
3
+ import { normalizeToPixels, outlookFallbackProp } from './utils.ts'
4
+ import { useOutlookFallback } from '../composables/useOutlookFallback'
4
5
 
5
6
  const props = defineProps({
6
7
  /** The type of spacer. */
@@ -22,9 +23,12 @@ const props = defineProps({
22
23
  msoHeight: {
23
24
  type: [String, Number],
24
25
  default: null
25
- }
26
+ },
27
+ outlookFallback: outlookFallbackProp,
26
28
  })
27
29
 
30
+ const outlookFallback = useOutlookFallback(props.outlookFallback)
31
+
28
32
  function parsePixelValue(value: string | number): number {
29
33
  if (typeof value === 'number') return value
30
34
  return Number.parseFloat(value) || 0
@@ -37,7 +41,7 @@ const verticalStyles = computed(() => {
37
41
  s.push(`line-height: ${normalizeToPixels(props.height)};`)
38
42
  }
39
43
 
40
- if (props.msoHeight) {
44
+ if (outlookFallback && props.msoHeight) {
41
45
  s.push(`mso-line-height-alt: ${normalizeToPixels(props.msoHeight)};`)
42
46
  }
43
47
 
@@ -45,7 +49,8 @@ const verticalStyles = computed(() => {
45
49
  })
46
50
 
47
51
  const horizontalStyles = computed(() => {
48
- return `display:inline-block; width: ${normalizeToPixels(props.width)}; font-size: 16px;${msoFontWidth.value}`
52
+ const mso = outlookFallback ? msoFontWidth.value : ''
53
+ return `display:inline-block; width: ${normalizeToPixels(props.width)}; font-size: 16px;${mso}`
49
54
  })
50
55
 
51
56
  const msoFontWidth = computed(() => {
@@ -13,6 +13,16 @@ declare function hasWidthUtility(classStr: string): boolean;
13
13
  declare function hasWidthInStyle(styleStr: string): boolean;
14
14
  declare function hasHeightUtility(classStr: string): boolean;
15
15
  declare function hasHeightInStyle(styleStr: string): boolean;
16
+ /**
17
+ * Shared prop for components that emit MSO/VML fallback markup. The
18
+ * `null` default acts as the "unset" sentinel — `useOutlookFallback`
19
+ * treats `null` as inherit-from-ancestor (root default `true`),
20
+ * letting users override per-component without losing inheritance.
21
+ */
22
+ declare const outlookFallbackProp: {
23
+ readonly type: BooleanConstructor;
24
+ readonly default: null;
25
+ };
16
26
  //#endregion
17
- export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels };
27
+ export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels, outlookFallbackProp };
18
28
  //# sourceMappingURL=utils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../../src/components/utils.ts"],"mappings":";iBAAgB,iBAAA,CAAkB,KAAA;AAAlC;;;;;AAiBA;;;AAjBA,iBAiBgB,MAAA,CAAO,MAAA;AAAA,iBAKP,eAAA,CAAgB,QAAA;AAAA,iBAQhB,eAAA,CAAgB,QAAA;AAAA,iBAIhB,gBAAA,CAAiB,QAAA;AAAA,iBAQjB,gBAAA,CAAiB,QAAA"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../../src/components/utils.ts"],"mappings":";iBAAgB,iBAAA,CAAkB,KAAA;AAAlC;;;;;AAiBA;;;AAjBA,iBAiBgB,MAAA,CAAO,MAAA;AAAA,iBAKP,eAAA,CAAgB,QAAA;AAAA,iBAQhB,eAAA,CAAgB,QAAA;AAAA,iBAIhB,gBAAA,CAAiB,QAAA;AAAA,iBAQjB,gBAAA,CAAiB,QAAA;;;AAZjC;;;;cAsBa,mBAAA;EAAA,eAGH,kBAAA;EAAA"}
@@ -34,7 +34,17 @@ function hasHeightUtility(classStr) {
34
34
  function hasHeightInStyle(styleStr) {
35
35
  return /(?:^|;\s*)(?:max-height|height)\s*:/i.test(styleStr);
36
36
  }
37
+ /**
38
+ * Shared prop for components that emit MSO/VML fallback markup. The
39
+ * `null` default acts as the "unset" sentinel — `useOutlookFallback`
40
+ * treats `null` as inherit-from-ancestor (root default `true`),
41
+ * letting users override per-component without losing inheritance.
42
+ */
43
+ const outlookFallbackProp = {
44
+ type: Boolean,
45
+ default: null
46
+ };
37
47
 
38
48
  //#endregion
39
- export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels };
49
+ export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels, outlookFallbackProp };
40
50
  //# sourceMappingURL=utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../src/components/utils.ts"],"sourcesContent":["export function normalizeToPixels(value: string | number): string {\n if (typeof value === 'number' || Number.isFinite(Number(value))) {\n return `${value}px`\n }\n return value\n}\n\nconst counters: Record<string, number> = {}\n\n/**\n * Module-scoped sequential ID generator. Used by components to mint\n * unique marker ids (e.g. `c1`, `c2`) for the post-render transformer.\n *\n * Must live here (not inside `<script setup>`) because Vue compiles\n * `<script setup>` into the component's `setup()` function — any\n * `let counter = 0` there resets per instance, causing id collisions.\n */\nexport function nextId(prefix: string): string {\n counters[prefix] = (counters[prefix] ?? 0) + 1\n return `${prefix}${counters[prefix]}`\n}\n\nexport function hasWidthUtility(classStr: string): boolean {\n return classStr.split(/\\s+/).some((c) => {\n const utility = c.split(':').pop() ?? ''\n const clean = utility.replace(/^!/, '')\n return /^(w-|max-w-|min-w-)/.test(clean)\n })\n}\n\nexport function hasWidthInStyle(styleStr: string): boolean {\n return /(?:^|;\\s*)(?:max-width|width)\\s*:/i.test(styleStr)\n}\n\nexport function hasHeightUtility(classStr: string): boolean {\n return classStr.split(/\\s+/).some((c) => {\n const utility = c.split(':').pop() ?? ''\n const clean = utility.replace(/^!/, '')\n return /^(h-|max-h-|min-h-)/.test(clean)\n })\n}\n\nexport function hasHeightInStyle(styleStr: string): boolean {\n return /(?:^|;\\s*)(?:max-height|height)\\s*:/i.test(styleStr)\n}\n"],"mappings":";AAAA,SAAgB,kBAAkB,OAAgC;AAChE,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,CAAC,CAC7D,QAAO,GAAG,MAAM;AAElB,QAAO;;AAGT,MAAM,WAAmC,EAAE;;;;;;;;;AAU3C,SAAgB,OAAO,QAAwB;AAC7C,UAAS,WAAW,SAAS,WAAW,KAAK;AAC7C,QAAO,GAAG,SAAS,SAAS;;AAG9B,SAAgB,gBAAgB,UAA2B;AACzD,QAAO,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;EAEvC,MAAM,SADU,EAAE,MAAM,IAAI,CAAC,KAAK,IAAI,IAChB,QAAQ,MAAM,GAAG;AACvC,SAAO,sBAAsB,KAAK,MAAM;GACxC;;AAGJ,SAAgB,gBAAgB,UAA2B;AACzD,QAAO,qCAAqC,KAAK,SAAS;;AAG5D,SAAgB,iBAAiB,UAA2B;AAC1D,QAAO,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;EAEvC,MAAM,SADU,EAAE,MAAM,IAAI,CAAC,KAAK,IAAI,IAChB,QAAQ,MAAM,GAAG;AACvC,SAAO,sBAAsB,KAAK,MAAM;GACxC;;AAGJ,SAAgB,iBAAiB,UAA2B;AAC1D,QAAO,uCAAuC,KAAK,SAAS"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../src/components/utils.ts"],"sourcesContent":["export function normalizeToPixels(value: string | number): string {\n if (typeof value === 'number' || Number.isFinite(Number(value))) {\n return `${value}px`\n }\n return value\n}\n\nconst counters: Record<string, number> = {}\n\n/**\n * Module-scoped sequential ID generator. Used by components to mint\n * unique marker ids (e.g. `c1`, `c2`) for the post-render transformer.\n *\n * Must live here (not inside `<script setup>`) because Vue compiles\n * `<script setup>` into the component's `setup()` function — any\n * `let counter = 0` there resets per instance, causing id collisions.\n */\nexport function nextId(prefix: string): string {\n counters[prefix] = (counters[prefix] ?? 0) + 1\n return `${prefix}${counters[prefix]}`\n}\n\nexport function hasWidthUtility(classStr: string): boolean {\n return classStr.split(/\\s+/).some((c) => {\n const utility = c.split(':').pop() ?? ''\n const clean = utility.replace(/^!/, '')\n return /^(w-|max-w-|min-w-)/.test(clean)\n })\n}\n\nexport function hasWidthInStyle(styleStr: string): boolean {\n return /(?:^|;\\s*)(?:max-width|width)\\s*:/i.test(styleStr)\n}\n\nexport function hasHeightUtility(classStr: string): boolean {\n return classStr.split(/\\s+/).some((c) => {\n const utility = c.split(':').pop() ?? ''\n const clean = utility.replace(/^!/, '')\n return /^(h-|max-h-|min-h-)/.test(clean)\n })\n}\n\nexport function hasHeightInStyle(styleStr: string): boolean {\n return /(?:^|;\\s*)(?:max-height|height)\\s*:/i.test(styleStr)\n}\n\n/**\n * Shared prop for components that emit MSO/VML fallback markup. The\n * `null` default acts as the \"unset\" sentinel — `useOutlookFallback`\n * treats `null` as inherit-from-ancestor (root default `true`),\n * letting users override per-component without losing inheritance.\n */\nexport const outlookFallbackProp = {\n type: Boolean,\n default: null,\n} as const\n\n"],"mappings":";AAAA,SAAgB,kBAAkB,OAAgC;AAChE,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,CAAC,CAC7D,QAAO,GAAG,MAAM;AAElB,QAAO;;AAGT,MAAM,WAAmC,EAAE;;;;;;;;;AAU3C,SAAgB,OAAO,QAAwB;AAC7C,UAAS,WAAW,SAAS,WAAW,KAAK;AAC7C,QAAO,GAAG,SAAS,SAAS;;AAG9B,SAAgB,gBAAgB,UAA2B;AACzD,QAAO,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;EAEvC,MAAM,SADU,EAAE,MAAM,IAAI,CAAC,KAAK,IAAI,IAChB,QAAQ,MAAM,GAAG;AACvC,SAAO,sBAAsB,KAAK,MAAM;GACxC;;AAGJ,SAAgB,gBAAgB,UAA2B;AACzD,QAAO,qCAAqC,KAAK,SAAS;;AAG5D,SAAgB,iBAAiB,UAA2B;AAC1D,QAAO,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;EAEvC,MAAM,SADU,EAAE,MAAM,IAAI,CAAC,KAAK,IAAI,IAChB,QAAQ,MAAM,GAAG;AACvC,SAAO,sBAAsB,KAAK,MAAM;GACxC;;AAGJ,SAAgB,iBAAiB,UAA2B;AAC1D,QAAO,uCAAuC,KAAK,SAAS;;;;;;;;AAS9D,MAAa,sBAAsB;CACjC,MAAM;CACN,SAAS;CACV"}
@@ -43,3 +43,15 @@ export function hasHeightUtility(classStr: string): boolean {
43
43
  export function hasHeightInStyle(styleStr: string): boolean {
44
44
  return /(?:^|;\s*)(?:max-height|height)\s*:/i.test(styleStr)
45
45
  }
46
+
47
+ /**
48
+ * Shared prop for components that emit MSO/VML fallback markup. The
49
+ * `null` default acts as the "unset" sentinel — `useOutlookFallback`
50
+ * treats `null` as inherit-from-ancestor (root default `true`),
51
+ * letting users override per-component without losing inheritance.
52
+ */
53
+ export const outlookFallbackProp = {
54
+ type: Boolean,
55
+ default: null,
56
+ } as const
57
+
@@ -0,0 +1,21 @@
1
+ //#region src/composables/useOutlookFallback.d.ts
2
+ /**
3
+ * Toggle whether descendants emit Outlook (MSO) and VML fallback markup.
4
+ *
5
+ * Call once in a Layout/template's `<script setup>` to disable for the
6
+ * whole tree:
7
+ * `useOutlookFallback(false)`
8
+ *
9
+ * Components inheriting `false` skip MSO ghost tables, VML rectangles,
10
+ * `xmlns:v`/`xmlns:o`, mso-specific CSS, and Button's `<Outlook>`
11
+ * spacers. Each MSO-aware component still accepts an `outlook-fallback`
12
+ * prop that overrides inheritance for its subtree.
13
+ *
14
+ * @param value Pass `true`/`false` to set; omit to just read the
15
+ * inherited value (defaults to `true` at the root).
16
+ * @returns The resolved boolean for the current component.
17
+ */
18
+ declare function useOutlookFallback(value?: boolean | null): boolean;
19
+ //#endregion
20
+ export { useOutlookFallback };
21
+ //# sourceMappingURL=useOutlookFallback.d.mts.map