@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.
Files changed (86) hide show
  1. package/dist/components/Body.vue +105 -36
  2. package/dist/components/Button.vue +4 -1
  3. package/dist/components/CodeBlock.vue +11 -18
  4. package/dist/components/CodeInline.vue +6 -1
  5. package/dist/components/Column.vue +30 -5
  6. package/dist/components/Container.vue +10 -2
  7. package/dist/components/Divider.vue +28 -0
  8. package/dist/components/Head.vue +22 -0
  9. package/dist/components/Heading.vue +28 -0
  10. package/dist/components/Html.vue +98 -47
  11. package/dist/components/Layout.vue +93 -0
  12. package/dist/components/Link.vue +26 -0
  13. package/dist/components/Markdown.vue +83 -0
  14. package/dist/components/Outlook.vue +36 -0
  15. package/dist/components/Overlap.vue +25 -5
  16. package/dist/components/{Preview.vue → Preheader.vue} +1 -1
  17. package/dist/components/Row.vue +16 -5
  18. package/dist/components/Section.vue +83 -0
  19. package/dist/components/Text.vue +29 -0
  20. package/dist/components/Vml.vue +165 -13
  21. package/dist/plugins/postcss/tailwindCleanup.mjs +22 -13
  22. package/dist/plugins/postcss/tailwindCleanup.mjs.map +1 -1
  23. package/dist/render/createRenderer.d.mts +2 -3
  24. package/dist/render/createRenderer.d.mts.map +1 -1
  25. package/dist/render/createRenderer.mjs +67 -4
  26. package/dist/render/createRenderer.mjs.map +1 -1
  27. package/dist/serve.d.mts.map +1 -1
  28. package/dist/serve.mjs +84 -4
  29. package/dist/serve.mjs.map +1 -1
  30. package/dist/server/compatibility.d.mts +1 -2
  31. package/dist/server/compatibility.d.mts.map +1 -1
  32. package/dist/server/compatibility.mjs +30 -16
  33. package/dist/server/compatibility.mjs.map +1 -1
  34. package/dist/server/email.d.mts +17 -0
  35. package/dist/server/email.d.mts.map +1 -0
  36. package/dist/server/email.mjs +41 -0
  37. package/dist/server/email.mjs.map +1 -0
  38. package/dist/server/linter.d.mts +1 -2
  39. package/dist/server/linter.d.mts.map +1 -1
  40. package/dist/server/linter.mjs +60 -71
  41. package/dist/server/linter.mjs.map +1 -1
  42. package/dist/server/ui/App.vue +205 -69
  43. package/dist/server/ui/components/ui/checkbox/Checkbox.vue +35 -0
  44. package/dist/server/ui/components/ui/checkbox/index.ts +1 -0
  45. package/dist/server/ui/components/ui/command/CommandDialog.vue +1 -1
  46. package/dist/server/ui/components/ui/command/CommandInput.vue +19 -1
  47. package/dist/server/ui/components/ui/command/CommandItem.vue +1 -1
  48. package/dist/server/ui/components/ui/command/CommandList.vue +1 -1
  49. package/dist/server/ui/components/ui/command/CommandShortcut.vue +1 -1
  50. package/dist/server/ui/components/ui/dialog/DialogOverlay.vue +9 -1
  51. package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  52. package/dist/server/ui/components/ui/scroll-area/ScrollBar.vue +1 -1
  53. package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
  54. package/dist/server/ui/components/ui/sheet/SheetOverlay.vue +9 -1
  55. package/dist/server/ui/components/ui/sidebar/Sidebar.vue +8 -1
  56. package/dist/server/ui/components/ui/sidebar/SidebarProvider.vue +1 -1
  57. package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +5 -4
  58. package/dist/server/ui/components/ui/tags-input/TagsInput.vue +26 -0
  59. package/dist/server/ui/components/ui/tags-input/TagsInputInput.vue +17 -0
  60. package/dist/server/ui/components/ui/tags-input/TagsInputItem.vue +19 -0
  61. package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +22 -0
  62. package/dist/server/ui/components/ui/tags-input/TagsInputItemText.vue +17 -0
  63. package/dist/server/ui/components/ui/tags-input/index.ts +5 -0
  64. package/dist/server/ui/components/ui/toggle/index.ts +3 -3
  65. package/dist/server/ui/components/ui/toggle-group/ToggleGroup.vue +1 -1
  66. package/dist/server/ui/components/ui/toggle-group/ToggleGroupItem.vue +2 -2
  67. package/dist/server/ui/main.css +20 -20
  68. package/dist/server/ui/pages/Home.vue +12 -5
  69. package/dist/server/ui/pages/Preview.vue +495 -211
  70. package/dist/transformers/inlineCSS.d.mts +1 -14
  71. package/dist/transformers/inlineCSS.d.mts.map +1 -1
  72. package/dist/transformers/inlineCSS.mjs +25 -34
  73. package/dist/transformers/inlineCSS.mjs.map +1 -1
  74. package/dist/transformers/purgeCSS.d.mts.map +1 -1
  75. package/dist/transformers/purgeCSS.mjs +67 -1
  76. package/dist/transformers/purgeCSS.mjs.map +1 -1
  77. package/dist/transformers/tailwindcss.mjs +3 -7
  78. package/dist/transformers/tailwindcss.mjs.map +1 -1
  79. package/dist/types/config.d.mts +47 -29
  80. package/dist/types/config.d.mts.map +1 -1
  81. package/dist/types/index.d.mts +2 -2
  82. package/package.json +7 -3
  83. package/dist/server/ui/components/ui/resizable/ResizableHandle.vue +0 -30
  84. package/dist/server/ui/components/ui/resizable/ResizablePanel.vue +0 -21
  85. package/dist/server/ui/components/ui/resizable/ResizablePanelGroup.vue +0 -25
  86. package/dist/server/ui/components/ui/resizable/index.ts +0 -3
@@ -1,42 +1,111 @@
1
- <script lang="ts">
2
- import { createStaticVNode } from 'vue'
1
+ <script setup lang="ts">
2
+ import { createStaticVNode, inject, useAttrs, useSlots } from 'vue'
3
3
  import type { PropType } from 'vue'
4
4
 
5
- export default {
6
- name: 'Body',
7
- inheritAttrs: false,
8
- props: {
9
- xmlLang: {
10
- type: String,
11
- default: 'en'
12
- },
13
- dir: {
14
- type: String as PropType<'ltr' | 'rtl'>,
15
- default: 'ltr'
16
- }
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ const attrs = useAttrs()
8
+ const slots = useSlots()
9
+
10
+ const props = defineProps({
11
+ /**
12
+ * Language code for the `xml:lang` attribute on `<body>`.
13
+ *
14
+ * Inherited from the parent `Html` component's `lang` prop by default.
15
+ *
16
+ * @example 'fr'
17
+ */
18
+ xmlLang: {
19
+ type: String as PropType<
20
+ | 'af' | 'ar' | 'az'
21
+ | 'be' | 'bg' | 'bs'
22
+ | 'ca' | 'cs' | 'cy'
23
+ | 'da' | 'de' | 'dv'
24
+ | 'el' | 'en' | 'es' | 'et' | 'eu'
25
+ | 'fa' | 'fi' | 'fo' | 'fr'
26
+ | 'gl' | 'gu'
27
+ | 'he' | 'hi' | 'hr' | 'hu' | 'hy'
28
+ | 'id' | 'is' | 'it'
29
+ | 'ja'
30
+ | 'ka' | 'kk' | 'kn' | 'ko' | 'ky'
31
+ | 'lt' | 'lv'
32
+ | 'mk' | 'mn' | 'mr' | 'ms' | 'mt'
33
+ | 'nb' | 'nl' | 'nn' | 'no'
34
+ | 'pa' | 'pl' | 'pt'
35
+ | 'ro' | 'ru'
36
+ | 'sa' | 'se' | 'sk' | 'sl' | 'sq' | 'sr' | 'sv' | 'sw'
37
+ | 'ta' | 'te' | 'th' | 'tr' | 'tt'
38
+ | 'uk' | 'ur' | 'uz'
39
+ | 'vi'
40
+ | 'zh'
41
+ | (string & {})
42
+ >,
43
+ default: undefined
44
+ },
45
+ /**
46
+ * Text direction of the body.
47
+ *
48
+ * - `ltr` — left to right (default)
49
+ * - `rtl` — right to left
50
+ *
51
+ * @default 'ltr'
52
+ */
53
+ dir: {
54
+ type: String as PropType<'ltr' | 'rtl'>,
55
+ default: 'ltr'
17
56
  },
18
- setup(props, { slots, attrs }) {
19
- return () => {
20
- const extraAttrs = Object.entries(attrs)
21
- .map(([key, value]) => value === true ? key : `${key}="${value}"`)
22
- .join(' ')
23
-
24
- const parts = [
25
- `xml:lang="${props.xmlLang}"`,
26
- `dir="${props.dir}"`,
27
- 'style="margin: 0; padding: 0; width: 100%; word-break: break-word;"',
28
- ]
29
-
30
- if (extraAttrs) {
31
- parts.push(extraAttrs)
32
- }
33
-
34
- return [
35
- createStaticVNode(`<body ${parts.join(' ')}>`, 1),
36
- slots.default?.(),
37
- createStaticVNode('</body>', 1),
38
- ]
39
- }
57
+ /**
58
+ * Accessible label for the email article wrapper.
59
+ *
60
+ * Used as the `aria-label` on the inner `<div role="article">`.
61
+ * Helps screen readers identify the email content.
62
+ *
63
+ * @example 'Order confirmation'
64
+ */
65
+ ariaLabel: {
66
+ type: String,
67
+ default: undefined
68
+ }
69
+ })
70
+
71
+ const htmlLang = inject<string>('htmlLang', 'en')
72
+
73
+ const render = () => {
74
+ const extraAttrs = Object.entries(attrs)
75
+ .map(([key, value]) => value === true ? key : `${key}="${value}"`)
76
+ .join(' ')
77
+
78
+ const lang = props.xmlLang ?? htmlLang
79
+
80
+ const parts = [
81
+ `xml:lang="${lang}"`,
82
+ `dir="${props.dir}"`,
83
+ 'style="margin: 0; padding: 0; width: 100%; word-break: break-word;"',
84
+ ]
85
+
86
+ if (extraAttrs) {
87
+ parts.push(extraAttrs)
40
88
  }
89
+
90
+ const articleParts = [
91
+ 'role="article"',
92
+ 'aria-roledescription="email"',
93
+ props.ariaLabel ? `aria-label="${props.ariaLabel}"` : '',
94
+ `lang="${lang}"`,
95
+ `dir="${props.dir}"`,
96
+ 'style="font-size: medium; font-size: max(16px, 1rem)"',
97
+ ].filter(Boolean).join(' ')
98
+
99
+ return [
100
+ createStaticVNode(`<body ${parts.join(' ')}>`, 1),
101
+ createStaticVNode(`<div ${articleParts}>`, 1),
102
+ slots.default?.(),
103
+ createStaticVNode('</div>', 1),
104
+ createStaticVNode('</body>', 1),
105
+ ]
41
106
  }
42
107
  </script>
108
+
109
+ <template>
110
+ <render />
111
+ </template>
@@ -10,7 +10,10 @@ const attrs = useAttrs()
10
10
 
11
11
  const props = defineProps({
12
12
  /** The URL the button links to. */
13
- href: String,
13
+ href: {
14
+ type: String,
15
+ required: true
16
+ },
14
17
  /**
15
18
  * The button style variant.
16
19
  * - `solid` — filled background (default)
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { createStaticVNode } from 'vue'
3
- import { codeToHtml, getSingletonHighlighter } from 'shiki'
2
+ import { createStaticVNode, type PropType } from 'vue'
3
+ import { codeToHtml, getSingletonHighlighter, type BundledLanguage, type BundledTheme } from 'shiki'
4
4
 
5
5
  export default {
6
6
  props: {
@@ -9,33 +9,26 @@ export default {
9
9
  type: String,
10
10
  default: ''
11
11
  },
12
- /** Base64-encoded code, set by the Vite transform for slot content. */
13
- encodedCode: {
14
- type: String,
15
- default: ''
16
- },
17
12
  /** The language for syntax highlighting. @default 'html' */
18
- lang: {
19
- type: String,
13
+ language: {
14
+ type: String as PropType<BundledLanguage>,
20
15
  default: 'html'
21
16
  },
22
17
  /** The shiki theme to use. @default 'github-light' */
23
18
  theme: {
24
- type: String,
19
+ type: String as PropType<BundledTheme>,
25
20
  default: 'github-light'
26
21
  },
27
- /** CSS class for the wrapping table cell. @default 'max-w-0 mso-padding-alt-6' */
22
+ /** CSS class for the wrapping table cell. @default 'max-w-0 mso-padding-alt-4' */
28
23
  tdClass: {
29
24
  type: String,
30
- default: 'max-w-0 mso-padding-alt-6'
25
+ default: 'max-w-0 mso-padding-alt-4'
31
26
  }
32
27
  },
33
28
  inheritAttrs: false,
34
29
  async setup(props, { slots, attrs }) {
35
- // Prefer encodedCode (from Vite transform) → code prop → slot text
36
- let source = props.encodedCode
37
- ? Buffer.from(props.encodedCode, 'base64').toString('utf-8')
38
- : props.code
30
+ // Prefer code prop → slot text
31
+ let source = props.code
39
32
 
40
33
  if (!source) {
41
34
  const slotContent = slots.default?.()
@@ -51,7 +44,7 @@ export default {
51
44
  }
52
45
 
53
46
  const highlighted = await codeToHtml(source, {
54
- lang: props.lang,
47
+ lang: props.language,
55
48
  theme: props.theme,
56
49
  })
57
50
 
@@ -64,7 +57,7 @@ export default {
64
57
  .replace(/<\/code><\/pre>$/, '')
65
58
 
66
59
  const classes = ['font-mono', attrs.class].filter(Boolean).join(' ')
67
- const baseStyles = `background-color:${bg};padding:24px;overflow:auto;white-space:pre;word-wrap:normal;word-break:normal;word-spacing:normal`
60
+ const baseStyles = `background-color:${bg};padding:16px;overflow:auto;white-space:pre;word-wrap:normal;word-break:normal;word-spacing:normal`
68
61
  const styles = [baseStyles, attrs.style].filter(Boolean).join(';')
69
62
 
70
63
  const html = `<table class="w-full"><tr><td class="${props.tdClass}"><pre class="${classes}" style="${styles}"><code>${codeContent}</code></pre></td></tr></table>`
@@ -4,7 +4,12 @@ import { createStaticVNode } from 'vue'
4
4
  export default {
5
5
  inheritAttrs: false,
6
6
  props: {
7
- /** The inline code text. */
7
+ /**
8
+ * The inline code text to render.
9
+ *
10
+ * If not provided, the slot content is used instead.
11
+ * The text is HTML-escaped automatically.
12
+ */
8
13
  code: {
9
14
  type: String,
10
15
  default: ''
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, createStaticVNode, inject, useAttrs } from 'vue'
2
+ import { computed, createStaticVNode, inject, provide, useAttrs } from 'vue'
3
3
  import type { ComputedRef } from 'vue'
4
4
  import { normalizeToPixels } from './utils.ts'
5
5
 
@@ -8,10 +8,26 @@ defineOptions({ inheritAttrs: false })
8
8
  const attrs = useAttrs()
9
9
 
10
10
  const props = defineProps({
11
- /** Override the auto-computed min-width. */
11
+ /**
12
+ * Override the auto-computed column width.
13
+ *
14
+ * By default, the width is calculated from the parent `Row`
15
+ * by dividing its width by the column count.
16
+ */
12
17
  width: {
13
18
  type: [String, Number],
14
19
  default: null
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'
27
+ */
28
+ msoStyle: {
29
+ type: String,
30
+ default: undefined
15
31
  }
16
32
  })
17
33
 
@@ -26,10 +42,10 @@ const minWidth = computed(() => {
26
42
  // Fallback: divide container width by 2 if available
27
43
  if (containerWidth?.value) {
28
44
  const val = containerWidth.value
29
- if (typeof val === 'number') return `${val / 2}px`
45
+ if (typeof val === 'number') return `${parseFloat((val / 2).toFixed(2))}px`
30
46
  const num = Number.parseFloat(val)
31
47
  const unit = val.replace(String(num), '') || 'px'
32
- return `${num / 2}${unit}`
48
+ return `${parseFloat((num / 2).toFixed(2))}${unit}`
33
49
  }
34
50
 
35
51
  return '18.75em'
@@ -37,12 +53,21 @@ const minWidth = computed(() => {
37
53
 
38
54
  const msoWidth = computed(() => injectedMsoWidth?.value ?? '50%')
39
55
 
56
+ // Provide column width as containerWidth for nested Rows
57
+ provide('containerWidth', minWidth)
58
+
40
59
  const styles = computed(() => {
41
60
  return `display: inline-block; min-width: ${minWidth.value}; font-size: 16px; vertical-align: top;`
42
61
  })
43
62
 
63
+ const tdStyle = computed(() => {
64
+ const parts = ['vertical-align: top']
65
+ if (props.msoStyle) parts.push(props.msoStyle)
66
+ return parts.join('; ')
67
+ })
68
+
44
69
  const MsoBefore = () => createStaticVNode(
45
- `<!--[if mso]><td width="${msoWidth.value}" style="vertical-align:top"><![endif]-->`,
70
+ `<!--[if mso]><td width="${msoWidth.value}" style="${tdStyle.value}"><![endif]-->`,
46
71
  1
47
72
  )
48
73
 
@@ -7,7 +7,15 @@ defineOptions({ inheritAttrs: false })
7
7
  const attrs = useAttrs()
8
8
 
9
9
  const props = defineProps({
10
- /** Max width of the container. */
10
+ /**
11
+ * Max width of the container.
12
+ *
13
+ * Applied as `max-width` on the div and as `width` on the MSO table.
14
+ * Also provided to child `Row` and `Column` components for
15
+ * automatic column width calculation.
16
+ *
17
+ * @default '37.5em'
18
+ */
11
19
  width: {
12
20
  type: [String, Number],
13
21
  default: '37.5em'
@@ -21,7 +29,7 @@ const styles = computed(() => {
21
29
  })
22
30
 
23
31
  const MsoBefore = () => createStaticVNode(
24
- `<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width:${normalizeToPixels(props.width)}" align="center"><tr><td><![endif]-->`,
32
+ `<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: ${normalizeToPixels(props.width)}" align="center"><tr><td><![endif]-->`,
25
33
  1
26
34
  )
27
35
 
@@ -5,34 +5,62 @@ import { normalizeToPixels } from './utils.ts'
5
5
  const attrs = useAttrs()
6
6
 
7
7
  const props = defineProps({
8
+ /**
9
+ * Height (thickness) of the divider line.
10
+ *
11
+ * @default '1px'
12
+ */
8
13
  height: {
9
14
  type: [String, Number],
10
15
  default: '1px'
11
16
  },
17
+ /**
18
+ * Background color of the divider line.
19
+ *
20
+ * Defaults to `#cbd5e1` when no Tailwind `bg-*` class is used.
21
+ *
22
+ * @example '#e2e8f0'
23
+ */
12
24
  color: {
13
25
  type: String,
14
26
  default: null
15
27
  },
28
+ /**
29
+ * Vertical spacing (margin) above and below the divider.
30
+ *
31
+ * Overridden by `top` and `bottom` if set.
32
+ *
33
+ * @default '24px'
34
+ */
16
35
  spaceY: {
17
36
  type: [String, Number],
18
37
  default: '24px'
19
38
  },
39
+ /**
40
+ * Horizontal spacing (margin) on both sides of the divider.
41
+ *
42
+ * Overridden by `left` and `right` if set.
43
+ */
20
44
  spaceX: {
21
45
  type: [String, Number],
22
46
  default: null
23
47
  },
48
+ /** Margin above the divider. Overrides `spaceY` for the top side. */
24
49
  top: {
25
50
  type: [String, Number],
26
51
  default: null
27
52
  },
53
+ /** Margin below the divider. Overrides `spaceY` for the bottom side. */
28
54
  bottom: {
29
55
  type: [String, Number],
30
56
  default: null
31
57
  },
58
+ /** Margin to the left of the divider. Overrides `spaceX` for the left side. */
32
59
  left: {
33
60
  type: [String, Number],
34
61
  default: null
35
62
  },
63
+ /** Margin to the right of the divider. Overrides `spaceX` for the right side. */
36
64
  right: {
37
65
  type: [String, Number],
38
66
  default: null
@@ -1,4 +1,26 @@
1
+ <script setup lang="ts">
2
+ import { createStaticVNode } from 'vue'
3
+
4
+ const props = defineProps({
5
+ /**
6
+ * Render an empty `<head>` before the main head element.
7
+ *
8
+ * This is a workaround for Yahoo! Mail on Android, which
9
+ * strips styles from the first `<head>` element.
10
+ *
11
+ * @default false
12
+ */
13
+ double: {
14
+ type: [Boolean, String],
15
+ default: false
16
+ }
17
+ })
18
+
19
+ const EmptyHead = () => createStaticVNode('<head></head>', 1)
20
+ </script>
21
+
1
22
  <template>
23
+ <EmptyHead v-if="props.double === true || props.double === 'true'" />
2
24
  <head>
3
25
  <meta charset="utf-8">
4
26
  <meta name="x-apple-disable-message-reformatting">
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ import { computed, useAttrs } from 'vue'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ const props = defineProps({
8
+ /**
9
+ * The heading level (1-6), corresponding to `<h1>` through `<h6>`.
10
+ * @default 1
11
+ */
12
+ level: {
13
+ type: [String, Number],
14
+ default: 1,
15
+ validator: (v: string | number) => [1, 2, 3, 4, 5, 6].includes(Number(v)),
16
+ },
17
+ })
18
+
19
+ const attrs = useAttrs()
20
+ const tag = computed(() => `h${props.level}`)
21
+ const mergedClass = computed(() => twMerge('m-0', attrs.class as string))
22
+ </script>
23
+
24
+ <template>
25
+ <component :is="tag" v-bind="$attrs" :class="mergedClass">
26
+ <slot />
27
+ </component>
28
+ </template>
@@ -1,53 +1,104 @@
1
- <script lang="ts">
2
- import { createStaticVNode } from 'vue'
1
+ <script setup lang="ts">
2
+ import { createStaticVNode, provide, useAttrs, useSlots } from 'vue'
3
3
  import type { PropType } from 'vue'
4
4
 
5
- export default {
6
- name: 'Html',
7
- inheritAttrs: false,
8
- props: {
9
- lang: {
10
- type: String,
11
- default: 'en'
12
- },
13
- dir: {
14
- type: String as PropType<'ltr' | 'rtl'>,
15
- default: 'ltr'
16
- },
17
- xmlns: {
18
- type: String,
19
- default: null
20
- }
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ const attrs = useAttrs()
8
+ const slots = useSlots()
9
+
10
+ const props = defineProps({
11
+ /**
12
+ * Language code for the `lang` attribute on `<html>`.
13
+ *
14
+ * Also provided to the child `Body` component for `xml:lang`.
15
+ *
16
+ * @default 'en'
17
+ */
18
+ lang: {
19
+ type: String as PropType<
20
+ | 'af' | 'ar' | 'az'
21
+ | 'be' | 'bg' | 'bs'
22
+ | 'ca' | 'cs' | 'cy'
23
+ | 'da' | 'de' | 'dv'
24
+ | 'el' | 'en' | 'es' | 'et' | 'eu'
25
+ | 'fa' | 'fi' | 'fo' | 'fr'
26
+ | 'gl' | 'gu'
27
+ | 'he' | 'hi' | 'hr' | 'hu' | 'hy'
28
+ | 'id' | 'is' | 'it'
29
+ | 'ja'
30
+ | 'ka' | 'kk' | 'kn' | 'ko' | 'ky'
31
+ | 'lt' | 'lv'
32
+ | 'mk' | 'mn' | 'mr' | 'ms' | 'mt'
33
+ | 'nb' | 'nl' | 'nn' | 'no'
34
+ | 'pa' | 'pl' | 'pt'
35
+ | 'ro' | 'ru'
36
+ | 'sa' | 'se' | 'sk' | 'sl' | 'sq' | 'sr' | 'sv' | 'sw'
37
+ | 'ta' | 'te' | 'th' | 'tr' | 'tt'
38
+ | 'uk' | 'ur' | 'uz'
39
+ | 'vi'
40
+ | 'zh'
41
+ | (string & {})
42
+ >,
43
+ default: 'en'
44
+ },
45
+ /**
46
+ * Text direction of the document.
47
+ *
48
+ * - `ltr` — left to right (default)
49
+ * - `rtl` — right to left
50
+ *
51
+ * @default 'ltr'
52
+ */
53
+ dir: {
54
+ type: String as PropType<'ltr' | 'rtl'>,
55
+ default: 'ltr'
21
56
  },
22
- setup(props, { slots, attrs }) {
23
- return () => {
24
- const extraAttrs = Object.entries(attrs)
25
- .map(([key, value]) => value === true ? key : `${key}="${value}"`)
26
- .join(' ')
27
-
28
- const parts = [
29
- `lang="${props.lang}"`,
30
- `dir="${props.dir}"`,
31
- ]
32
-
33
- if (props.xmlns) {
34
- parts.push(
35
- `xmlns="${props.xmlns}"`,
36
- 'xmlns:v="urn:schemas-microsoft-com:vml"',
37
- 'xmlns:o="urn:schemas-microsoft-com:office:office"',
38
- )
39
- }
40
-
41
- if (extraAttrs) {
42
- parts.push(extraAttrs)
43
- }
44
-
45
- return [
46
- createStaticVNode(`<html ${parts.join(' ')}>`, 1),
47
- slots.default?.(),
48
- createStaticVNode('</html>', 1),
49
- ]
50
- }
57
+ /**
58
+ * Whether to include VML and Office XML namespace declarations.
59
+ *
60
+ * Required for Outlook VML support (background images, etc.).
61
+ * Set to `false` to omit the `xmlns:v` and `xmlns:o` attributes.
62
+ *
63
+ * @default true
64
+ */
65
+ xmlns: {
66
+ type: [Boolean, String],
67
+ default: true
68
+ }
69
+ })
70
+
71
+ provide('htmlLang', props.lang)
72
+
73
+ const render = () => {
74
+ const extraAttrs = Object.entries(attrs)
75
+ .map(([key, value]) => value === true ? key : `${key}="${value}"`)
76
+ .join(' ')
77
+
78
+ const parts = [
79
+ `lang="${props.lang}"`,
80
+ `dir="${props.dir}"`,
81
+ ]
82
+
83
+ if (props.xmlns !== false && props.xmlns !== 'false') {
84
+ parts.push(
85
+ 'xmlns:v="urn:schemas-microsoft-com:vml"',
86
+ 'xmlns:o="urn:schemas-microsoft-com:office:office"',
87
+ )
88
+ }
89
+
90
+ if (extraAttrs) {
91
+ parts.push(extraAttrs)
51
92
  }
93
+
94
+ return [
95
+ createStaticVNode(`<html ${parts.join(' ')}>`, 1),
96
+ slots.default?.(),
97
+ createStaticVNode('</html>', 1),
98
+ ]
52
99
  }
53
100
  </script>
101
+
102
+ <template>
103
+ <render />
104
+ </template>