@maizzle/framework 6.0.0-rc.12 → 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/build.mjs +4 -1
- package/dist/build.mjs.map +1 -1
- 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 +28 -12
- package/dist/serve.mjs.map +1 -1
- package/dist/server/compatibility.d.mts +54 -2
- package/dist/server/compatibility.d.mts.map +1 -1
- package/dist/server/compatibility.mjs +890 -76
- package/dist/server/compatibility.mjs.map +1 -1
- package/dist/server/linter.d.mts +15 -2
- package/dist/server/linter.d.mts.map +1 -1
- package/dist/server/linter.mjs +194 -43
- package/dist/server/linter.mjs.map +1 -1
- package/dist/server/sfc-utils.d.mts +18 -0
- package/dist/server/sfc-utils.d.mts.map +1 -0
- package/dist/server/sfc-utils.mjs +184 -0
- package/dist/server/sfc-utils.mjs.map +1 -0
- package/dist/server/ui/App.vue +27 -50
- package/dist/server/ui/components/SidebarClose.vue +12 -0
- package/dist/server/ui/components/ui/command/Command.vue +1 -0
- package/dist/server/ui/components/ui/input/Input.vue +1 -1
- package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +1 -1
- package/dist/server/ui/components/ui/tags-input/TagsInputInput.vue +1 -1
- package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
- package/dist/server/ui/pages/Preview.vue +215 -156
- package/dist/transformers/addAttributes.mjs +10 -6
- package/dist/transformers/addAttributes.mjs.map +1 -1
- 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/inlineCSS.mjs +2 -2
- package/dist/transformers/inlineCSS.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/purgeCSS.mjs +1 -1
- package/dist/transformers/purgeCSS.mjs.map +1 -1
- package/dist/transformers/tailwindcss.d.mts.map +1 -1
- package/dist/transformers/tailwindcss.mjs +6 -16
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/types/config.d.mts +42 -2
- package/dist/types/config.d.mts.map +1 -1
- package/dist/types/index.d.mts +2 -2
- 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 -3
- package/dist/_virtual/_rolldown/runtime.mjs +0 -32
- package/dist/node_modules/picomatch/index.mjs +0 -13
- package/dist/node_modules/picomatch/index.mjs.map +0 -1
- package/dist/node_modules/picomatch/lib/constants.mjs +0 -174
- package/dist/node_modules/picomatch/lib/constants.mjs.map +0 -1
- package/dist/node_modules/picomatch/lib/parse.mjs +0 -1067
- package/dist/node_modules/picomatch/lib/parse.mjs.map +0 -1
- package/dist/node_modules/picomatch/lib/picomatch.mjs +0 -304
- package/dist/node_modules/picomatch/lib/picomatch.mjs.map +0 -1
- package/dist/node_modules/picomatch/lib/scan.mjs +0 -296
- package/dist/node_modules/picomatch/lib/scan.mjs.map +0 -1
- package/dist/node_modules/picomatch/lib/utils.mjs +0 -53
- package/dist/node_modules/picomatch/lib/utils.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, createStaticVNode, useAttrs } from 'vue'
|
|
3
|
-
import { normalizeToPixels } from './utils.ts'
|
|
3
|
+
import { hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels } from './utils.ts'
|
|
4
4
|
|
|
5
5
|
defineOptions({ inheritAttrs: false })
|
|
6
6
|
|
|
@@ -12,11 +12,14 @@ const props = defineProps({
|
|
|
12
12
|
*
|
|
13
13
|
* Applied as `max-width` on the div and as `width` on the MSO table.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* When not set, the MSO table width is auto-derived from a width
|
|
16
|
+
* utility class (e.g. `max-w-md`) or inline style (`max-width`/
|
|
17
|
+
* `width`) on the component, after CSS inlining. Falls back to
|
|
18
|
+
* `100%` when no width source is provided.
|
|
16
19
|
*/
|
|
17
20
|
width: {
|
|
18
21
|
type: [String, Number],
|
|
19
|
-
default:
|
|
22
|
+
default: null
|
|
20
23
|
},
|
|
21
24
|
/**
|
|
22
25
|
* Inline CSS applied only to the MSO `<td>` element.
|
|
@@ -31,8 +34,6 @@ const props = defineProps({
|
|
|
31
34
|
}
|
|
32
35
|
})
|
|
33
36
|
|
|
34
|
-
const hasCustomWidth = computed(() => props.width !== '100%')
|
|
35
|
-
|
|
36
37
|
const userStyle = computed(() => {
|
|
37
38
|
const s = attrs.style
|
|
38
39
|
if (!s) return ''
|
|
@@ -41,9 +42,17 @@ const userStyle = computed(() => {
|
|
|
41
42
|
: String(s)
|
|
42
43
|
})
|
|
43
44
|
|
|
45
|
+
const userHasWidth = computed(() => {
|
|
46
|
+
const cls = (attrs.class as string) ?? ''
|
|
47
|
+
return hasWidthUtility(cls) || hasWidthInStyle(userStyle.value)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const useMarker = props.width == null && userHasWidth.value
|
|
51
|
+
const msoId = useMarker ? nextId('s') : null
|
|
52
|
+
|
|
44
53
|
const divStyle = computed(() => {
|
|
45
54
|
const parts: string[] = []
|
|
46
|
-
if (
|
|
55
|
+
if (props.width != null) parts.push(`max-width: ${normalizeToPixels(props.width)}`)
|
|
47
56
|
if (userStyle.value) parts.push(userStyle.value)
|
|
48
57
|
return parts.length ? parts.join('; ') : undefined
|
|
49
58
|
})
|
|
@@ -60,10 +69,22 @@ const tdStyles = computed(() => {
|
|
|
60
69
|
return parts.length ? parts.join('; ') : ''
|
|
61
70
|
})
|
|
62
71
|
|
|
72
|
+
const msoWidth = computed(() => {
|
|
73
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
74
|
+
if (useMarker) return `__MAIZZLE_MSOW_${msoId}__`
|
|
75
|
+
return '100%'
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const colWidthSource = computed(() => {
|
|
79
|
+
if (props.width != null) return normalizeToPixels(props.width)
|
|
80
|
+
if (userHasWidth.value) return ''
|
|
81
|
+
return null
|
|
82
|
+
})
|
|
83
|
+
|
|
63
84
|
const MsoBefore = () => {
|
|
64
85
|
const tdStyle = tdStyles.value ? ` style="${tdStyles.value}"` : ''
|
|
65
86
|
return createStaticVNode(
|
|
66
|
-
`<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: ${
|
|
87
|
+
`<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: ${msoWidth.value}"><tr><td${tdStyle}><![endif]-->`,
|
|
67
88
|
1
|
|
68
89
|
)
|
|
69
90
|
}
|
|
@@ -76,7 +97,13 @@ const MsoAfter = () => createStaticVNode(
|
|
|
76
97
|
|
|
77
98
|
<template>
|
|
78
99
|
<MsoBefore />
|
|
79
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
v-bind="restAttrs"
|
|
102
|
+
:style="divStyle"
|
|
103
|
+
:data-maizzle-msow-id="msoId"
|
|
104
|
+
:data-maizzle-msow-fallback="useMarker ? '100%' : null"
|
|
105
|
+
:data-maizzle-cw="colWidthSource"
|
|
106
|
+
>
|
|
80
107
|
<slot />
|
|
81
108
|
</div>
|
|
82
109
|
<MsoAfter />
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
//#region src/components/utils.d.ts
|
|
2
2
|
declare function normalizeToPixels(value: string | number): string;
|
|
3
|
+
/**
|
|
4
|
+
* Module-scoped sequential ID generator. Used by components to mint
|
|
5
|
+
* unique marker ids (e.g. `c1`, `c2`) for the post-render transformer.
|
|
6
|
+
*
|
|
7
|
+
* Must live here (not inside `<script setup>`) because Vue compiles
|
|
8
|
+
* `<script setup>` into the component's `setup()` function — any
|
|
9
|
+
* `let counter = 0` there resets per instance, causing id collisions.
|
|
10
|
+
*/
|
|
11
|
+
declare function nextId(prefix: string): string;
|
|
12
|
+
declare function hasWidthUtility(classStr: string): boolean;
|
|
13
|
+
declare function hasWidthInStyle(styleStr: string): boolean;
|
|
14
|
+
declare function hasHeightUtility(classStr: string): boolean;
|
|
15
|
+
declare function hasHeightInStyle(styleStr: string): boolean;
|
|
3
16
|
//#endregion
|
|
4
|
-
export { normalizeToPixels };
|
|
17
|
+
export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels };
|
|
5
18
|
//# 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"}
|
|
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"}
|
|
@@ -3,7 +3,38 @@ function normalizeToPixels(value) {
|
|
|
3
3
|
if (typeof value === "number" || Number.isFinite(Number(value))) return `${value}px`;
|
|
4
4
|
return value;
|
|
5
5
|
}
|
|
6
|
+
const counters = {};
|
|
7
|
+
/**
|
|
8
|
+
* Module-scoped sequential ID generator. Used by components to mint
|
|
9
|
+
* unique marker ids (e.g. `c1`, `c2`) for the post-render transformer.
|
|
10
|
+
*
|
|
11
|
+
* Must live here (not inside `<script setup>`) because Vue compiles
|
|
12
|
+
* `<script setup>` into the component's `setup()` function — any
|
|
13
|
+
* `let counter = 0` there resets per instance, causing id collisions.
|
|
14
|
+
*/
|
|
15
|
+
function nextId(prefix) {
|
|
16
|
+
counters[prefix] = (counters[prefix] ?? 0) + 1;
|
|
17
|
+
return `${prefix}${counters[prefix]}`;
|
|
18
|
+
}
|
|
19
|
+
function hasWidthUtility(classStr) {
|
|
20
|
+
return classStr.split(/\s+/).some((c) => {
|
|
21
|
+
const clean = (c.split(":").pop() ?? "").replace(/^!/, "");
|
|
22
|
+
return /^(w-|max-w-|min-w-)/.test(clean);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function hasWidthInStyle(styleStr) {
|
|
26
|
+
return /(?:^|;\s*)(?:max-width|width)\s*:/i.test(styleStr);
|
|
27
|
+
}
|
|
28
|
+
function hasHeightUtility(classStr) {
|
|
29
|
+
return classStr.split(/\s+/).some((c) => {
|
|
30
|
+
const clean = (c.split(":").pop() ?? "").replace(/^!/, "");
|
|
31
|
+
return /^(h-|max-h-|min-h-)/.test(clean);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function hasHeightInStyle(styleStr) {
|
|
35
|
+
return /(?:^|;\s*)(?:max-height|height)\s*:/i.test(styleStr);
|
|
36
|
+
}
|
|
6
37
|
|
|
7
38
|
//#endregion
|
|
8
|
-
export { normalizeToPixels };
|
|
39
|
+
export { hasHeightInStyle, hasHeightUtility, hasWidthInStyle, hasWidthUtility, nextId, normalizeToPixels };
|
|
9
40
|
//# 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"],"mappings":";AAAA,SAAgB,kBAAkB,OAAgC;AAChE,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,MAAM,CAAC,CAC7D,QAAO,GAAG,MAAM;AAElB,QAAO"}
|
|
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"}
|
package/dist/components/utils.ts
CHANGED
|
@@ -4,3 +4,42 @@ export function normalizeToPixels(value: string | number): string {
|
|
|
4
4
|
}
|
|
5
5
|
return value
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
const counters: Record<string, number> = {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Module-scoped sequential ID generator. Used by components to mint
|
|
12
|
+
* unique marker ids (e.g. `c1`, `c2`) for the post-render transformer.
|
|
13
|
+
*
|
|
14
|
+
* Must live here (not inside `<script setup>`) because Vue compiles
|
|
15
|
+
* `<script setup>` into the component's `setup()` function — any
|
|
16
|
+
* `let counter = 0` there resets per instance, causing id collisions.
|
|
17
|
+
*/
|
|
18
|
+
export function nextId(prefix: string): string {
|
|
19
|
+
counters[prefix] = (counters[prefix] ?? 0) + 1
|
|
20
|
+
return `${prefix}${counters[prefix]}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hasWidthUtility(classStr: string): boolean {
|
|
24
|
+
return classStr.split(/\s+/).some((c) => {
|
|
25
|
+
const utility = c.split(':').pop() ?? ''
|
|
26
|
+
const clean = utility.replace(/^!/, '')
|
|
27
|
+
return /^(w-|max-w-|min-w-)/.test(clean)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hasWidthInStyle(styleStr: string): boolean {
|
|
32
|
+
return /(?:^|;\s*)(?:max-width|width)\s*:/i.test(styleStr)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function hasHeightUtility(classStr: string): boolean {
|
|
36
|
+
return classStr.split(/\s+/).some((c) => {
|
|
37
|
+
const utility = c.split(':').pop() ?? ''
|
|
38
|
+
const clean = utility.replace(/^!/, '')
|
|
39
|
+
return /^(h-|max-h-|min-h-)/.test(clean)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasHeightInStyle(styleStr: string): boolean {
|
|
44
|
+
return /(?:^|;\s*)(?:max-height|height)\s*:/i.test(styleStr)
|
|
45
|
+
}
|
|
@@ -4,6 +4,12 @@ import { UsePlaintextOptions } from "./usePlaintext.mjs";
|
|
|
4
4
|
import { InjectionKey } from "vue";
|
|
5
5
|
|
|
6
6
|
//#region src/composables/renderContext.d.ts
|
|
7
|
+
interface FontRegistration {
|
|
8
|
+
family: string;
|
|
9
|
+
slug: string;
|
|
10
|
+
declaration: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
7
13
|
interface RenderContext {
|
|
8
14
|
doctype?: string;
|
|
9
15
|
preheader?: {
|
|
@@ -17,8 +23,9 @@ interface RenderContext {
|
|
|
17
23
|
handler: EventMap[EventName];
|
|
18
24
|
}>;
|
|
19
25
|
plaintext?: UsePlaintextOptions;
|
|
26
|
+
fonts?: FontRegistration[];
|
|
20
27
|
}
|
|
21
28
|
declare const RenderContextKey: InjectionKey<RenderContext>;
|
|
22
29
|
//#endregion
|
|
23
|
-
export { RenderContext, RenderContextKey };
|
|
30
|
+
export { FontRegistration, RenderContext, RenderContextKey };
|
|
24
31
|
//# sourceMappingURL=renderContext.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderContext.d.mts","names":[],"sources":["../../src/composables/renderContext.ts"],"mappings":";;;;;;UAKiB,aAAA;EACf,OAAA;EACA,SAAA;IAAc,IAAA;IAAc,WAAA;IAAqB,QAAA;EAAA;EACjD,SAAA,GAAY,aAAA;EACZ,gBAAA,EAAkB,KAAA;IAAQ,IAAA,EAAM,SAAA;IAAW,OAAA,EAAS,QAAA,CAAS,SAAA;EAAA;EAC7D,SAAA,GAAY,mBAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"renderContext.d.mts","names":[],"sources":["../../src/composables/renderContext.ts"],"mappings":";;;;;;UAKiB,gBAAA;EACf,MAAA;EACA,IAAA;EACA,WAAA;EACA,GAAA;AAAA;AAAA,UAGe,aAAA;EACf,OAAA;EACA,SAAA;IAAc,IAAA;IAAc,WAAA;IAAqB,QAAA;EAAA;EACjD,SAAA,GAAY,aAAA;EACZ,gBAAA,EAAkB,KAAA;IAAQ,IAAA,EAAM,SAAA;IAAW,OAAA,EAAS,QAAA,CAAS,SAAA;EAAA;EAC7D,SAAA,GAAY,mBAAA;EACZ,KAAA,GAAQ,gBAAA;AAAA;AAAA,cAGG,gBAAA,EAAkB,YAAA,CAAa,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderContext.mjs","names":[],"sources":["../../src/composables/renderContext.ts"],"sourcesContent":["import type { InjectionKey } from 'vue'\nimport type { MaizzleConfig } from '../types/index.ts'\nimport type { EventName, EventMap } from '../events/index.ts'\nimport type { UsePlaintextOptions } from './usePlaintext.ts'\n\nexport interface RenderContext {\n doctype?: string\n preheader?: { text: string; fillerCount: number; shyCount: number }\n sfcConfig?: MaizzleConfig\n sfcEventHandlers: Array<{ name: EventName; handler: EventMap[EventName] }>\n plaintext?: UsePlaintextOptions\n}\n\nexport const RenderContextKey: InjectionKey<RenderContext> = Symbol('RenderContext')\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"renderContext.mjs","names":[],"sources":["../../src/composables/renderContext.ts"],"sourcesContent":["import type { InjectionKey } from 'vue'\nimport type { MaizzleConfig } from '../types/index.ts'\nimport type { EventName, EventMap } from '../events/index.ts'\nimport type { UsePlaintextOptions } from './usePlaintext.ts'\n\nexport interface FontRegistration {\n family: string\n slug: string\n declaration: string\n url: string\n}\n\nexport interface RenderContext {\n doctype?: string\n preheader?: { text: string; fillerCount: number; shyCount: number }\n sfcConfig?: MaizzleConfig\n sfcEventHandlers: Array<{ name: EventName; handler: EventMap[EventName] }>\n plaintext?: UsePlaintextOptions\n fonts?: FontRegistration[]\n}\n\nexport const RenderContextKey: InjectionKey<RenderContext> = Symbol('RenderContext')\n"],"mappings":";AAqBA,MAAa,mBAAgD,OAAO,gBAAgB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/composables/useFont.d.ts
|
|
2
|
+
type FontProvider = 'google' | 'bunny';
|
|
3
|
+
interface UseFontOptions {
|
|
4
|
+
/**
|
|
5
|
+
* A single font family name, e.g. `"Roboto"` or `"Open Sans"`.
|
|
6
|
+
*
|
|
7
|
+
* For fallback fonts, use the `fallback` option instead of a
|
|
8
|
+
* comma-separated list here.
|
|
9
|
+
*/
|
|
10
|
+
family: string;
|
|
11
|
+
/** CSS fallback list appended to the `font-family` declaration. */
|
|
12
|
+
fallback?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Font provider used to build the stylesheet URL when `url` is omitted.
|
|
15
|
+
* Bunny Fonts is a drop-in, privacy-friendly Google Fonts mirror.
|
|
16
|
+
* @default 'google'
|
|
17
|
+
*/
|
|
18
|
+
provider?: FontProvider;
|
|
19
|
+
/**
|
|
20
|
+
* Stylesheet URL. When provided, used as-is for the `<link href>`.
|
|
21
|
+
* When omitted, a URL is built from `provider`, `family`, `weights`,
|
|
22
|
+
* `display` and `styles`.
|
|
23
|
+
*/
|
|
24
|
+
url?: string;
|
|
25
|
+
/** Font weights to load. Ignored when `url` is provided. */
|
|
26
|
+
weights?: number[];
|
|
27
|
+
/** `font-display` value. Ignored when `url` is provided. */
|
|
28
|
+
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
|
|
29
|
+
/** Font styles to load. Ignored when `url` is provided. */
|
|
30
|
+
styles?: Array<'normal' | 'italic'>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register a font for the current email template.
|
|
34
|
+
*
|
|
35
|
+
* Builds a Google Fonts stylesheet URL from `family`/`weights`/`display`/`styles`
|
|
36
|
+
* (or uses `url` as-is). The renderer injects a `<link>` tag into `<head>`
|
|
37
|
+
* and merges `--font-{slug}` declarations into the template's existing
|
|
38
|
+
* `@import "tailwindcss"` style block so a `font-{slug}` utility class
|
|
39
|
+
* is generated. If no Tailwind import is found, falls back to a `:root`
|
|
40
|
+
* declaration so the CSS variable is still available.
|
|
41
|
+
*
|
|
42
|
+
* Usage in SFC <script setup>:
|
|
43
|
+
* ```ts
|
|
44
|
+
* useFont({ family: 'Roboto', fallback: 'Verdana, sans-serif', weights: [400, 600] })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function useFont(options: UseFontOptions): void;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { FontProvider, UseFontOptions, useFont };
|
|
50
|
+
//# sourceMappingURL=useFont.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFont.d.mts","names":[],"sources":["../../src/composables/useFont.ts"],"mappings":";KA8CY,YAAA;AAAA,UAEK,cAAA;EAFO;;;;AAExB;;EAOE,MAAA;EAoBc;EAlBd,QAAA;EAAA;;;;;EAMA,QAAA,GAAW,YAAA;EAYX;;;;AA+CF;EArDE,GAAA;;EAEA,OAAA;EAmD6C;EAjD7C,OAAA;;EAEA,MAAA,GAAS,KAAA;AAAA;;;;;;;;;;;;;;;;iBA+CK,OAAA,CAAQ,OAAA,EAAS,cAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { RenderContextKey } from "./renderContext.mjs";
|
|
2
|
+
import { inject } from "vue";
|
|
3
|
+
|
|
4
|
+
//#region src/composables/useFont.ts
|
|
5
|
+
const FAMILY_CATEGORIES = {
|
|
6
|
+
"Roboto": "sans",
|
|
7
|
+
"Open Sans": "sans",
|
|
8
|
+
"Inter": "sans",
|
|
9
|
+
"Lato": "sans",
|
|
10
|
+
"Montserrat": "sans",
|
|
11
|
+
"Merriweather": "serif",
|
|
12
|
+
"Playfair Display": "serif",
|
|
13
|
+
"Lora": "serif",
|
|
14
|
+
"PT Serif": "serif",
|
|
15
|
+
"Noto Serif": "serif",
|
|
16
|
+
"Oswald": "display",
|
|
17
|
+
"Bebas Neue": "display",
|
|
18
|
+
"Anton": "display",
|
|
19
|
+
"Lobster": "display",
|
|
20
|
+
"Pacifico": "display",
|
|
21
|
+
"Dancing Script": "handwriting",
|
|
22
|
+
"Caveat": "handwriting",
|
|
23
|
+
"Shadows Into Light": "handwriting",
|
|
24
|
+
"Satisfy": "handwriting",
|
|
25
|
+
"Great Vibes": "handwriting",
|
|
26
|
+
"Roboto Mono": "mono",
|
|
27
|
+
"Source Code Pro": "mono",
|
|
28
|
+
"JetBrains Mono": "mono",
|
|
29
|
+
"Fira Code": "mono",
|
|
30
|
+
"Inconsolata": "mono"
|
|
31
|
+
};
|
|
32
|
+
const DEFAULT_FALLBACKS = {
|
|
33
|
+
sans: "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
34
|
+
serif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif",
|
|
35
|
+
mono: "ui-monospace, Menlo, Consolas, monospace",
|
|
36
|
+
display: "Impact, \"Arial Black\", system-ui, sans-serif",
|
|
37
|
+
handwriting: "\"Segoe Script\", \"Brush Script MT\", cursive"
|
|
38
|
+
};
|
|
39
|
+
const PROVIDER_BASE_URL = {
|
|
40
|
+
google: "https://fonts.googleapis.com/css2",
|
|
41
|
+
bunny: "https://fonts.bunny.net/css2"
|
|
42
|
+
};
|
|
43
|
+
function slugify(family) {
|
|
44
|
+
return family.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
45
|
+
}
|
|
46
|
+
function buildProviderUrl(opts) {
|
|
47
|
+
const familyParam = opts.family.trim().replace(/\s+/g, "+");
|
|
48
|
+
const weights = [...opts.weights].sort((a, b) => a - b);
|
|
49
|
+
const hasItalic = opts.styles.includes("italic");
|
|
50
|
+
const hasNormal = opts.styles.includes("normal");
|
|
51
|
+
const axis = hasItalic ? `:ital,wght@${weights.flatMap((w) => [...hasNormal ? [`0,${w}`] : [], `1,${w}`]).join(";")}` : `:wght@${weights.join(";")}`;
|
|
52
|
+
return `${PROVIDER_BASE_URL[opts.provider]}?family=${familyParam}${axis}&display=${opts.display}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register a font for the current email template.
|
|
56
|
+
*
|
|
57
|
+
* Builds a Google Fonts stylesheet URL from `family`/`weights`/`display`/`styles`
|
|
58
|
+
* (or uses `url` as-is). The renderer injects a `<link>` tag into `<head>`
|
|
59
|
+
* and merges `--font-{slug}` declarations into the template's existing
|
|
60
|
+
* `@import "tailwindcss"` style block so a `font-{slug}` utility class
|
|
61
|
+
* is generated. If no Tailwind import is found, falls back to a `:root`
|
|
62
|
+
* declaration so the CSS variable is still available.
|
|
63
|
+
*
|
|
64
|
+
* Usage in SFC <script setup>:
|
|
65
|
+
* ```ts
|
|
66
|
+
* useFont({ family: 'Roboto', fallback: 'Verdana, sans-serif', weights: [400, 600] })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
function useFont(options) {
|
|
70
|
+
const ctx = inject(RenderContextKey);
|
|
71
|
+
if (!ctx) return;
|
|
72
|
+
ctx.fonts = ctx.fonts ?? [];
|
|
73
|
+
if (ctx.fonts.some((f) => f.family === options.family)) return;
|
|
74
|
+
const url = options.url ?? buildProviderUrl({
|
|
75
|
+
family: options.family,
|
|
76
|
+
provider: options.provider ?? "google",
|
|
77
|
+
weights: options.weights ?? [400],
|
|
78
|
+
display: options.display ?? "swap",
|
|
79
|
+
styles: options.styles ?? ["normal"]
|
|
80
|
+
});
|
|
81
|
+
const fallback = options.fallback ?? DEFAULT_FALLBACKS[FAMILY_CATEGORIES[options.family] ?? "sans"];
|
|
82
|
+
const declaration = `${/\s/.test(options.family) ? `"${options.family}"` : options.family}, ${fallback}`;
|
|
83
|
+
ctx.fonts.push({
|
|
84
|
+
family: options.family,
|
|
85
|
+
slug: slugify(options.family),
|
|
86
|
+
declaration,
|
|
87
|
+
url
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { useFont };
|
|
93
|
+
//# sourceMappingURL=useFont.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFont.mjs","names":[],"sources":["../../src/composables/useFont.ts"],"sourcesContent":["import { inject } from 'vue'\nimport { RenderContextKey } from './renderContext.ts'\n\ntype FontCategory = 'sans' | 'serif' | 'mono' | 'display' | 'handwriting'\n\nconst FAMILY_CATEGORIES: Record<string, FontCategory> = {\n // Sans-serif\n 'Roboto': 'sans',\n 'Open Sans': 'sans',\n 'Inter': 'sans',\n 'Lato': 'sans',\n 'Montserrat': 'sans',\n // Serif\n 'Merriweather': 'serif',\n 'Playfair Display': 'serif',\n 'Lora': 'serif',\n 'PT Serif': 'serif',\n 'Noto Serif': 'serif',\n // Display\n 'Oswald': 'display',\n 'Bebas Neue': 'display',\n 'Anton': 'display',\n 'Lobster': 'display',\n 'Pacifico': 'display',\n // Handwriting\n 'Dancing Script': 'handwriting',\n 'Caveat': 'handwriting',\n 'Shadows Into Light': 'handwriting',\n 'Satisfy': 'handwriting',\n 'Great Vibes': 'handwriting',\n // Monospace\n 'Roboto Mono': 'mono',\n 'Source Code Pro': 'mono',\n 'JetBrains Mono': 'mono',\n 'Fira Code': 'mono',\n 'Inconsolata': 'mono',\n}\n\nconst DEFAULT_FALLBACKS: Record<FontCategory, string> = {\n sans: 'ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif',\n serif: 'ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif',\n mono: 'ui-monospace, Menlo, Consolas, monospace',\n display: 'Impact, \"Arial Black\", system-ui, sans-serif',\n handwriting: '\"Segoe Script\", \"Brush Script MT\", cursive',\n}\n\nexport type FontProvider = 'google' | 'bunny'\n\nexport interface UseFontOptions {\n /**\n * A single font family name, e.g. `\"Roboto\"` or `\"Open Sans\"`.\n *\n * For fallback fonts, use the `fallback` option instead of a\n * comma-separated list here.\n */\n family: string\n /** CSS fallback list appended to the `font-family` declaration. */\n fallback?: string\n /**\n * Font provider used to build the stylesheet URL when `url` is omitted.\n * Bunny Fonts is a drop-in, privacy-friendly Google Fonts mirror.\n * @default 'google'\n */\n provider?: FontProvider\n /**\n * Stylesheet URL. When provided, used as-is for the `<link href>`.\n * When omitted, a URL is built from `provider`, `family`, `weights`,\n * `display` and `styles`.\n */\n url?: string\n /** Font weights to load. Ignored when `url` is provided. */\n weights?: number[]\n /** `font-display` value. Ignored when `url` is provided. */\n display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional'\n /** Font styles to load. Ignored when `url` is provided. */\n styles?: Array<'normal' | 'italic'>\n}\n\nconst PROVIDER_BASE_URL: Record<FontProvider, string> = {\n google: 'https://fonts.googleapis.com/css2',\n bunny: 'https://fonts.bunny.net/css2',\n}\n\nfunction slugify(family: string): string {\n return family\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^a-z0-9-]/g, '')\n}\n\nfunction buildProviderUrl(opts: Required<Omit<UseFontOptions, 'url' | 'fallback'>>): string {\n const familyParam = opts.family.trim().replace(/\\s+/g, '+')\n const weights = [...opts.weights].sort((a, b) => a - b)\n const hasItalic = opts.styles.includes('italic')\n const hasNormal = opts.styles.includes('normal')\n\n const axis = hasItalic\n ? `:ital,wght@${weights.flatMap(w => [\n ...(hasNormal ? [`0,${w}`] : []),\n `1,${w}`,\n ]).join(';')}`\n : `:wght@${weights.join(';')}`\n\n return `${PROVIDER_BASE_URL[opts.provider]}?family=${familyParam}${axis}&display=${opts.display}`\n}\n\n/**\n * Register a font for the current email template.\n *\n * Builds a Google Fonts stylesheet URL from `family`/`weights`/`display`/`styles`\n * (or uses `url` as-is). The renderer injects a `<link>` tag into `<head>`\n * and merges `--font-{slug}` declarations into the template's existing\n * `@import \"tailwindcss\"` style block so a `font-{slug}` utility class\n * is generated. If no Tailwind import is found, falls back to a `:root`\n * declaration so the CSS variable is still available.\n *\n * Usage in SFC <script setup>:\n * ```ts\n * useFont({ family: 'Roboto', fallback: 'Verdana, sans-serif', weights: [400, 600] })\n * ```\n */\nexport function useFont(options: UseFontOptions): void {\n const ctx = inject(RenderContextKey)\n if (!ctx) return\n\n ctx.fonts = ctx.fonts ?? []\n if (ctx.fonts.some(f => f.family === options.family)) return\n\n const url = options.url ?? buildProviderUrl({\n family: options.family,\n provider: options.provider ?? 'google',\n weights: options.weights ?? [400],\n display: options.display ?? 'swap',\n styles: options.styles ?? ['normal'],\n })\n\n const fallback = options.fallback\n ?? DEFAULT_FALLBACKS[FAMILY_CATEGORIES[options.family] ?? 'sans']\n const quoted = /\\s/.test(options.family) ? `\"${options.family}\"` : options.family\n const declaration = `${quoted}, ${fallback}`\n\n ctx.fonts.push({\n family: options.family,\n slug: slugify(options.family),\n declaration,\n url,\n })\n}\n"],"mappings":";;;;AAKA,MAAM,oBAAkD;CAEtD,UAAU;CACV,aAAa;CACb,SAAS;CACT,QAAQ;CACR,cAAc;CAEd,gBAAgB;CAChB,oBAAoB;CACpB,QAAQ;CACR,YAAY;CACZ,cAAc;CAEd,UAAU;CACV,cAAc;CACd,SAAS;CACT,WAAW;CACX,YAAY;CAEZ,kBAAkB;CAClB,UAAU;CACV,sBAAsB;CACtB,WAAW;CACX,eAAe;CAEf,eAAe;CACf,mBAAmB;CACnB,kBAAkB;CAClB,aAAa;CACb,eAAe;CAChB;AAED,MAAM,oBAAkD;CACtD,MAAM;CACN,OAAO;CACP,MAAM;CACN,SAAS;CACT,aAAa;CACd;AAkCD,MAAM,oBAAkD;CACtD,QAAQ;CACR,OAAO;CACR;AAED,SAAS,QAAQ,QAAwB;AACvC,QAAO,OACJ,MAAM,CACN,aAAa,CACb,QAAQ,QAAQ,IAAI,CACpB,QAAQ,eAAe,GAAG;;AAG/B,SAAS,iBAAiB,MAAkE;CAC1F,MAAM,cAAc,KAAK,OAAO,MAAM,CAAC,QAAQ,QAAQ,IAAI;CAC3D,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;CACvD,MAAM,YAAY,KAAK,OAAO,SAAS,SAAS;CAChD,MAAM,YAAY,KAAK,OAAO,SAAS,SAAS;CAEhD,MAAM,OAAO,YACT,cAAc,QAAQ,SAAQ,MAAK,CACjC,GAAI,YAAY,CAAC,KAAK,IAAI,GAAG,EAAE,EAC/B,KAAK,IACN,CAAC,CAAC,KAAK,IAAI,KACZ,SAAS,QAAQ,KAAK,IAAI;AAE9B,QAAO,GAAG,kBAAkB,KAAK,UAAU,UAAU,cAAc,KAAK,WAAW,KAAK;;;;;;;;;;;;;;;;;AAkB1F,SAAgB,QAAQ,SAA+B;CACrD,MAAM,MAAM,OAAO,iBAAiB;AACpC,KAAI,CAAC,IAAK;AAEV,KAAI,QAAQ,IAAI,SAAS,EAAE;AAC3B,KAAI,IAAI,MAAM,MAAK,MAAK,EAAE,WAAW,QAAQ,OAAO,CAAE;CAEtD,MAAM,MAAM,QAAQ,OAAO,iBAAiB;EAC1C,QAAQ,QAAQ;EAChB,UAAU,QAAQ,YAAY;EAC9B,SAAS,QAAQ,WAAW,CAAC,IAAI;EACjC,SAAS,QAAQ,WAAW;EAC5B,QAAQ,QAAQ,UAAU,CAAC,SAAS;EACrC,CAAC;CAEF,MAAM,WAAW,QAAQ,YACpB,kBAAkB,kBAAkB,QAAQ,WAAW;CAE5D,MAAM,cAAc,GADL,KAAK,KAAK,QAAQ,OAAO,GAAG,IAAI,QAAQ,OAAO,KAAK,QAAQ,OAC7C,IAAI;AAElC,KAAI,MAAM,KAAK;EACb,QAAQ,QAAQ;EAChB,MAAM,QAAQ,QAAQ,OAAO;EAC7B;EACA;EACD,CAAC"}
|
package/dist/index.d.mts
CHANGED
|
@@ -5,6 +5,7 @@ import { usePlaintext } from "./composables/usePlaintext.mjs";
|
|
|
5
5
|
import { useConfig } from "./composables/useConfig.mjs";
|
|
6
6
|
import { useDoctype } from "./composables/useDoctype.mjs";
|
|
7
7
|
import { useEvent } from "./composables/useEvent.mjs";
|
|
8
|
+
import { useFont } from "./composables/useFont.mjs";
|
|
8
9
|
import { resolveConfig } from "./config/index.mjs";
|
|
9
10
|
import { maizzle } from "./plugin.mjs";
|
|
10
11
|
import { CreateRendererOptions, RenderedTemplate, Renderer, createRenderer } from "./render/createRenderer.mjs";
|
|
@@ -28,4 +29,4 @@ import { replaceStrings } from "./transformers/replaceStrings.mjs";
|
|
|
28
29
|
import { format } from "./transformers/format.mjs";
|
|
29
30
|
import { minify } from "./transformers/minify.mjs";
|
|
30
31
|
import { useHead } from "@unhead/vue";
|
|
31
|
-
export { type AttributesConfig, type CreateRendererOptions, type CssConfig, type EntitiesConfig, type FilterFunction, type FiltersConfig, type HtmlConfig, type MaizzleConfig, type RenderOptions, type RenderResult, type RenderedTemplate, type Renderer, type UrlConfig, type UrlQuery, type UrlQueryOptions, addAttributes, attributeToStyle, base, build, createPlaintext, createRenderer, defineConfig, entities, filters, format, inlineCSS, inlineLink, maizzle, minify, removeAttributes, purgeCSS as removeUnusedCSS, render, replaceStrings, resolveConfig, safeClassNames, serve, shorthandCSS, sixHex, urlQuery, useConfig, useDoctype, useEvent, useHead, usePlaintext };
|
|
32
|
+
export { type AttributesConfig, type CreateRendererOptions, type CssConfig, type EntitiesConfig, type FilterFunction, type FiltersConfig, type HtmlConfig, type MaizzleConfig, type RenderOptions, type RenderResult, type RenderedTemplate, type Renderer, type UrlConfig, type UrlQuery, type UrlQueryOptions, addAttributes, attributeToStyle, base, build, createPlaintext, createRenderer, defineConfig, entities, filters, format, inlineCSS, inlineLink, maizzle, minify, removeAttributes, purgeCSS as removeUnusedCSS, render, replaceStrings, resolveConfig, safeClassNames, serve, shorthandCSS, sixHex, urlQuery, useConfig, useDoctype, useEvent, useFont, useHead, usePlaintext };
|
package/dist/index.mjs
CHANGED
|
@@ -25,7 +25,8 @@ import { render } from "./render/index.mjs";
|
|
|
25
25
|
import { serve } from "./serve.mjs";
|
|
26
26
|
import { useDoctype } from "./composables/useDoctype.mjs";
|
|
27
27
|
import { useEvent } from "./composables/useEvent.mjs";
|
|
28
|
+
import { useFont } from "./composables/useFont.mjs";
|
|
28
29
|
import { usePlaintext } from "./composables/usePlaintext.mjs";
|
|
29
30
|
import { useHead } from "@unhead/vue";
|
|
30
31
|
|
|
31
|
-
export { addAttributes, attributeToStyle, base, build, createPlaintext, createRenderer, defineConfig, entities, filters, format, inlineCSS, inlineLink, maizzle, minify, removeAttributes, purgeCSS as removeUnusedCSS, render, replaceStrings, resolveConfig, safeClassNames, serve, shorthandCSS, sixHex, urlQuery, useConfig, useDoctype, useEvent, useHead, usePlaintext };
|
|
32
|
+
export { addAttributes, attributeToStyle, base, build, createPlaintext, createRenderer, defineConfig, entities, filters, format, inlineCSS, inlineLink, maizzle, minify, removeAttributes, purgeCSS as removeUnusedCSS, render, replaceStrings, resolveConfig, safeClassNames, serve, shorthandCSS, sixHex, urlQuery, useConfig, useDoctype, useEvent, useFont, useHead, usePlaintext };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from "postcss";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/postcss/quoteFontFamilies.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Re-quote multi-word font-family identifiers that lightningcss "optimised"
|
|
6
|
+
* by removing quotes. CSS allows space-separated identifiers as a family
|
|
7
|
+
* name, but Google Fonts (and most style guides) prescribe quoted form.
|
|
8
|
+
*/
|
|
9
|
+
declare function quoteFontFamilies(): Plugin;
|
|
10
|
+
declare const postcss = true;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { postcss, quoteFontFamilies };
|
|
13
|
+
//# sourceMappingURL=quoteFontFamilies.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quoteFontFamilies.d.mts","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"mappings":";;;;;AA+CA;;;iBAAgB,iBAAA,CAAA,GAAqB,MAAA;AAAA,cA6BxB,OAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//#region src/plugins/postcss/quoteFontFamilies.ts
|
|
2
|
+
const GENERIC_KEYWORDS = new Set([
|
|
3
|
+
"serif",
|
|
4
|
+
"sans-serif",
|
|
5
|
+
"monospace",
|
|
6
|
+
"cursive",
|
|
7
|
+
"fantasy",
|
|
8
|
+
"system-ui",
|
|
9
|
+
"ui-serif",
|
|
10
|
+
"ui-sans-serif",
|
|
11
|
+
"ui-monospace",
|
|
12
|
+
"ui-rounded",
|
|
13
|
+
"emoji",
|
|
14
|
+
"math",
|
|
15
|
+
"fangsong",
|
|
16
|
+
"inherit",
|
|
17
|
+
"initial",
|
|
18
|
+
"unset",
|
|
19
|
+
"revert",
|
|
20
|
+
"revert-layer"
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Split a font-family value on top-level commas only, preserving quoted
|
|
24
|
+
* strings and parenthesised groups like `var(...)`.
|
|
25
|
+
*/
|
|
26
|
+
function splitFamilies(value) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let quote = null;
|
|
30
|
+
let start = 0;
|
|
31
|
+
for (let i = 0; i < value.length; i++) {
|
|
32
|
+
const ch = value[i];
|
|
33
|
+
if (quote) {
|
|
34
|
+
if (ch === "\\") {
|
|
35
|
+
i++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === quote) quote = null;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === "\"" || ch === "'") {
|
|
42
|
+
quote = ch;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === "(") depth++;
|
|
46
|
+
else if (ch === ")") depth--;
|
|
47
|
+
else if (ch === "," && depth === 0) {
|
|
48
|
+
parts.push(value.slice(start, i).trim());
|
|
49
|
+
start = i + 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
parts.push(value.slice(start).trim());
|
|
53
|
+
return parts.filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Re-quote multi-word font-family identifiers that lightningcss "optimised"
|
|
57
|
+
* by removing quotes. CSS allows space-separated identifiers as a family
|
|
58
|
+
* name, but Google Fonts (and most style guides) prescribe quoted form.
|
|
59
|
+
*/
|
|
60
|
+
function quoteFontFamilies() {
|
|
61
|
+
return {
|
|
62
|
+
postcssPlugin: "quote-font-families",
|
|
63
|
+
Declaration: { "font-family": (decl) => {
|
|
64
|
+
const value = decl.value;
|
|
65
|
+
if (!value || !/\s/.test(value)) return;
|
|
66
|
+
const families = splitFamilies(value);
|
|
67
|
+
let changed = false;
|
|
68
|
+
const fixed = families.map((token) => {
|
|
69
|
+
if (token.startsWith("\"") || token.startsWith("'")) return token;
|
|
70
|
+
if (token.startsWith("var(")) return token;
|
|
71
|
+
if (!token.includes(" ")) return token;
|
|
72
|
+
if (GENERIC_KEYWORDS.has(token.toLowerCase())) return token;
|
|
73
|
+
changed = true;
|
|
74
|
+
return `"${token}"`;
|
|
75
|
+
});
|
|
76
|
+
if (changed) decl.value = fixed.join(", ");
|
|
77
|
+
} }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const postcss = true;
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
export { postcss, quoteFontFamilies };
|
|
84
|
+
//# sourceMappingURL=quoteFontFamilies.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quoteFontFamilies.mjs","names":[],"sources":["../../../src/plugins/postcss/quoteFontFamilies.ts"],"sourcesContent":["import type { Plugin } from 'postcss'\n\nconst GENERIC_KEYWORDS = new Set([\n 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',\n 'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded',\n 'emoji', 'math', 'fangsong',\n 'inherit', 'initial', 'unset', 'revert', 'revert-layer',\n])\n\n/**\n * Split a font-family value on top-level commas only, preserving quoted\n * strings and parenthesised groups like `var(...)`.\n */\nfunction splitFamilies(value: string): string[] {\n const parts: string[] = []\n let depth = 0\n let quote: string | null = null\n let start = 0\n\n for (let i = 0; i < value.length; i++) {\n const ch = value[i]\n if (quote) {\n if (ch === '\\\\') { i++; continue }\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(value.slice(start, i).trim())\n start = i + 1\n }\n }\n\n parts.push(value.slice(start).trim())\n return parts.filter(Boolean)\n}\n\n/**\n * Re-quote multi-word font-family identifiers that lightningcss \"optimised\"\n * by removing quotes. CSS allows space-separated identifiers as a family\n * name, but Google Fonts (and most style guides) prescribe quoted form.\n */\nexport function quoteFontFamilies(): Plugin {\n return {\n postcssPlugin: 'quote-font-families',\n Declaration: {\n 'font-family': (decl) => {\n const value = decl.value\n if (!value || !/\\s/.test(value)) return\n\n const families = splitFamilies(value)\n let changed = false\n\n const fixed = families.map((token) => {\n if (token.startsWith('\"') || token.startsWith(\"'\")) return token\n if (token.startsWith('var(')) return token\n if (!token.includes(' ')) return token\n if (GENERIC_KEYWORDS.has(token.toLowerCase())) return token\n\n changed = true\n return `\"${token}\"`\n })\n\n if (changed) {\n decl.value = fixed.join(', ')\n }\n },\n },\n }\n}\n\nexport const postcss = true\n"],"mappings":";AAEA,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CAAS;CAAc;CAAa;CAAW;CAC/C;CAAa;CAAY;CAAiB;CAAgB;CAC1D;CAAS;CAAQ;CACjB;CAAW;CAAW;CAAS;CAAU;CAC1C,CAAC;;;;;AAMF,SAAS,cAAc,OAAyB;CAC9C,MAAM,QAAkB,EAAE;CAC1B,IAAI,QAAQ;CACZ,IAAI,QAAuB;CAC3B,IAAI,QAAQ;AAEZ,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,KAAK,MAAM;AACjB,MAAI,OAAO;AACT,OAAI,OAAO,MAAM;AAAE;AAAK;;AACxB,OAAI,OAAO,MAAO,SAAQ;AAC1B;;AAEF,MAAI,OAAO,QAAO,OAAO,KAAK;AAC5B,WAAQ;AACR;;AAEF,MAAI,OAAO,IAAK;WACP,OAAO,IAAK;WACZ,OAAO,OAAO,UAAU,GAAG;AAClC,SAAM,KAAK,MAAM,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC;AACxC,WAAQ,IAAI;;;AAIhB,OAAM,KAAK,MAAM,MAAM,MAAM,CAAC,MAAM,CAAC;AACrC,QAAO,MAAM,OAAO,QAAQ;;;;;;;AAQ9B,SAAgB,oBAA4B;AAC1C,QAAO;EACL,eAAe;EACf,aAAa,EACX,gBAAgB,SAAS;GACvB,MAAM,QAAQ,KAAK;AACnB,OAAI,CAAC,SAAS,CAAC,KAAK,KAAK,MAAM,CAAE;GAEjC,MAAM,WAAW,cAAc,MAAM;GACrC,IAAI,UAAU;GAEd,MAAM,QAAQ,SAAS,KAAK,UAAU;AACpC,QAAI,MAAM,WAAW,KAAI,IAAI,MAAM,WAAW,IAAI,CAAE,QAAO;AAC3D,QAAI,MAAM,WAAW,OAAO,CAAE,QAAO;AACrC,QAAI,CAAC,MAAM,SAAS,IAAI,CAAE,QAAO;AACjC,QAAI,iBAAiB,IAAI,MAAM,aAAa,CAAC,CAAE,QAAO;AAEtD,cAAU;AACV,WAAO,IAAI,MAAM;KACjB;AAEF,OAAI,QACF,MAAK,QAAQ,MAAM,KAAK,KAAK;KAGlC;EACF;;AAGH,MAAa,UAAU"}
|
|
@@ -236,10 +236,12 @@ async function createRenderer(options = {}) {
|
|
|
236
236
|
if (bodyAttrs) html = html.replace(/<body([^>]*)>/, `<body$1 ${bodyAttrs}>`);
|
|
237
237
|
if (bodyTagsOpen) html = html.replace(/<body([^>]*)>/, `<body$1>\n${bodyTagsOpen}`);
|
|
238
238
|
if (bodyTags) html = html.replace("</body>", `${bodyTags}\n</body>`);
|
|
239
|
-
|
|
239
|
+
const hasTeleports = ssrContext.teleports && Object.keys(ssrContext.teleports).length > 0;
|
|
240
|
+
const hasFonts = (renderContext.fonts?.length ?? 0) > 0;
|
|
241
|
+
if (hasTeleports || hasFonts) {
|
|
240
242
|
const { parse: parseDom, serialize: serializeDom, walk } = await import("../utils/ast/index.mjs");
|
|
241
243
|
let dom = parseDom(html);
|
|
242
|
-
for (const [rawTarget, content] of Object.entries(ssrContext.teleports)) {
|
|
244
|
+
if (hasTeleports) for (const [rawTarget, content] of Object.entries(ssrContext.teleports)) {
|
|
243
245
|
if (!content) continue;
|
|
244
246
|
const prepend = rawTarget.endsWith(":start");
|
|
245
247
|
const target = prepend ? rawTarget.slice(0, -6) : rawTarget;
|
|
@@ -253,6 +255,10 @@ async function createRenderer(options = {}) {
|
|
|
253
255
|
}
|
|
254
256
|
});
|
|
255
257
|
}
|
|
258
|
+
if (hasFonts) {
|
|
259
|
+
const { injectFonts } = await import("./injectFonts.mjs");
|
|
260
|
+
injectFonts(dom, renderContext.fonts, parseDom, walk);
|
|
261
|
+
}
|
|
256
262
|
html = serializeDom(dom);
|
|
257
263
|
}
|
|
258
264
|
if (renderContext.preheader) {
|